JAVASCRIPT   16

Chart.Line.js

Guest on 5th June 2021 05:37:26 PM

  1. (function(){
  2.       "use strict";
  3.  
  4.       var root = this,
  5.             Chart = root.Chart,
  6.             helpers = Chart.helpers;
  7.  
  8.       var defaultConfig = {
  9.  
  10.             ///Boolean - Whether grid lines are shown across the chart
  11.             scaleShowGridLines : true,
  12.  
  13.             //String - Colour of the grid lines
  14.             scaleGridLineColor : "rgba(0,0,0,.05)",
  15.  
  16.             //Number - Width of the grid lines
  17.             scaleGridLineWidth : 1,
  18.  
  19.             //Boolean - Whether to show horizontal lines (except X axis)
  20.             scaleShowHorizontalLines: true,
  21.  
  22.             //Boolean - Whether to show vertical lines (except Y axis)
  23.             scaleShowVerticalLines: true,
  24.  
  25.             //Boolean - Whether the line is curved between points
  26.             bezierCurve : true,
  27.  
  28.             //Number - Tension of the bezier curve between points
  29.             bezierCurveTension : 0.4,
  30.  
  31.             //Boolean - Whether to show a dot for each point
  32.             pointDot : true,
  33.  
  34.             //Number - Radius of each point dot in pixels
  35.             pointDotRadius : 4,
  36.  
  37.             //Number - Pixel width of point dot stroke
  38.             pointDotStrokeWidth : 1,
  39.  
  40.             //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
  41.             pointHitDetectionRadius : 20,
  42.  
  43.             //Boolean - Whether to show a stroke for datasets
  44.             datasetStroke : true,
  45.  
  46.             //Number - Pixel width of dataset stroke
  47.             datasetStrokeWidth : 2,
  48.  
  49.             //Boolean - Whether to fill the dataset with a colour
  50.             datasetFill : true,
  51.  
  52.             //String - A legend template
  53.             legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
  54.  
  55.       };
  56.  
  57.  
  58.       Chart.Type.extend({
  59.             name: "Line",
  60.             defaults : defaultConfig,
  61.             initialize:  function(data){
  62.                   //Declare the extension of the default point, to cater for the options passed in to the constructor
  63.                   this.PointClass = Chart.Point.extend({
  64.                         strokeWidth : this.options.pointDotStrokeWidth,
  65.                         radius : this.options.pointDotRadius,
  66.                         display: this.options.pointDot,
  67.                         hitDetectionRadius : this.options.pointHitDetectionRadius,
  68.                         ctx : this.chart.ctx,
  69.                         inRange : function(mouseX){
  70.                               return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2));
  71.                         }
  72.                   });
  73.  
  74.                   this.datasets = [];
  75.  
  76.                   //Set up tooltip events on the chart
  77.                   if (this.options.showTooltips){
  78.                         helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
  79.                               var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
  80.                               this.eachPoints(function(point){
  81.                                     point.restore(['fillColor', 'strokeColor']);
  82.                               });
  83.                               helpers.each(activePoints, function(activePoint){
  84.                                     activePoint.fillColor = activePoint.highlightFill;
  85.                                     activePoint.strokeColor = activePoint.highlightStroke;
  86.                               });
  87.                               this.showTooltip(activePoints);
  88.                         });
  89.                   }
  90.  
  91.                   //Iterate through each of the datasets, and build this into a property of the chart
  92.                   helpers.each(data.datasets,function(dataset){
  93.  
  94.                         var datasetObject = {
  95.                               label : dataset.label || null,
  96.                               fillColor : dataset.fillColor,
  97.                               strokeColor : dataset.strokeColor,
  98.                               pointColor : dataset.pointColor,
  99.                               pointStrokeColor : dataset.pointStrokeColor,
  100.                               points : []
  101.                         };
  102.  
  103.                         this.datasets.push(datasetObject);
  104.  
  105.  
  106.                         helpers.each(dataset.data,function(dataPoint,index){
  107.                               //Add a new point for each piece of data, passing any required data to draw.
  108.                               datasetObject.points.push(new this.PointClass({
  109.                                     value : dataPoint,
  110.                                     label : data.labels[index],
  111.                                     datasetLabel: dataset.label,
  112.                                     strokeColor : dataset.pointStrokeColor,
  113.                                     fillColor : dataset.pointColor,
  114.                                     highlightFill : dataset.pointHighlightFill || dataset.pointColor,
  115.                                     highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
  116.                               }));
  117.                         },this);
  118.  
  119.                         this.buildScale(data.labels);
  120.  
  121.  
  122.                         this.eachPoints(function(point, index){
  123.                               helpers.extend(point, {
  124.                                     x: this.scale.calculateX(index),
  125.                                     y: this.scale.endPoint
  126.                               });
  127.                               point.save();
  128.                         }, this);
  129.  
  130.                   },this);
  131.  
  132.  
  133.                   this.render();
  134.             },
  135.             update : function(){
  136.                   this.scale.update();
  137.                   // Reset any highlight colours before updating.
  138.                   helpers.each(this.activeElements, function(activeElement){
  139.                         activeElement.restore(['fillColor', 'strokeColor']);
  140.                   });
  141.                   this.eachPoints(function(point){
  142.                         point.save();
  143.                   });
  144.                   this.render();
  145.             },
  146.             eachPoints : function(callback){
  147.                   helpers.each(this.datasets,function(dataset){
  148.                         helpers.each(dataset.points,callback,this);
  149.                   },this);
  150.             },
  151.             getPointsAtEvent : function(e){
  152.                   var pointsArray = [],
  153.                         eventPosition = helpers.getRelativePosition(e);
  154.                   helpers.each(this.datasets,function(dataset){
  155.                         helpers.each(dataset.points,function(point){
  156.                               if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point);
  157.                         });
  158.                   },this);
  159.                   return pointsArray;
  160.             },
  161.             buildScale : function(labels){
  162.                   var self = this;
  163.  
  164.                   var dataTotal = function(){
  165.                         var values = [];
  166.                         self.eachPoints(function(point){
  167.                               values.push(point.value);
  168.                         });
  169.  
  170.                         return values;
  171.                   };
  172.  
  173.                   var scaleOptions = {
  174.                         templateString : this.options.scaleLabel,
  175.                         height : this.chart.height,
  176.                         width : this.chart.width,
  177.                         ctx : this.chart.ctx,
  178.                         textColor : this.options.scaleFontColor,
  179.                         fontSize : this.options.scaleFontSize,
  180.                         fontStyle : this.options.scaleFontStyle,
  181.                         fontFamily : this.options.scaleFontFamily,
  182.                         valuesCount : labels.length,
  183.                         beginAtZero : this.options.scaleBeginAtZero,
  184.                         integersOnly : this.options.scaleIntegersOnly,
  185.                         calculateYRange : function(currentHeight){
  186.                               var updatedRanges = helpers.calculateScaleRange(
  187.                                     dataTotal(),
  188.                                     currentHeight,
  189.                                     this.fontSize,
  190.                                     this.beginAtZero,
  191.                                     this.integersOnly
  192.                               );
  193.                               helpers.extend(this, updatedRanges);
  194.                         },
  195.                         xLabels : labels,
  196.                         font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
  197.                         lineWidth : this.options.scaleLineWidth,
  198.                         lineColor : this.options.scaleLineColor,
  199.                         showHorizontalLines : this.options.scaleShowHorizontalLines,
  200.                         showVerticalLines : this.options.scaleShowVerticalLines,
  201.                         gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
  202.                         gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
  203.                         padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth,
  204.                         showLabels : this.options.scaleShowLabels,
  205.                         display : this.options.showScale
  206.                   };
  207.  
  208.                   if (this.options.scaleOverride){
  209.                         helpers.extend(scaleOptions, {
  210.                               calculateYRange: helpers.noop,
  211.                               steps: this.options.scaleSteps,
  212.                               stepValue: this.options.scaleStepWidth,
  213.                               min: this.options.scaleStartValue,
  214.                               max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
  215.                         });
  216.                   }
  217.  
  218.  
  219.                   this.scale = new Chart.Scale(scaleOptions);
  220.             },
  221.             addData : function(valuesArray,label){
  222.                   //Map the values array for each of the datasets
  223.  
  224.                   helpers.each(valuesArray,function(value,datasetIndex){
  225.                         //Add a new point for each piece of data, passing any required data to draw.
  226.                         this.datasets[datasetIndex].points.push(new this.PointClass({
  227.                               value : value,
  228.                               label : label,
  229.                               x: this.scale.calculateX(this.scale.valuesCount+1),
  230.                               y: this.scale.endPoint,
  231.                               strokeColor : this.datasets[datasetIndex].pointStrokeColor,
  232.                               fillColor : this.datasets[datasetIndex].pointColor
  233.                         }));
  234.                   },this);
  235.  
  236.                   this.scale.addXLabel(label);
  237.                   //Then re-render the chart.
  238.                   this.update();
  239.             },
  240.             removeData : function(){
  241.                   this.scale.removeXLabel();
  242.                   //Then re-render the chart.
  243.                   helpers.each(this.datasets,function(dataset){
  244.                         dataset.points.shift();
  245.                   },this);
  246.                   this.update();
  247.             },
  248.             reflow : function(){
  249.                   var newScaleProps = helpers.extend({
  250.                         height : this.chart.height,
  251.                         width : this.chart.width
  252.                   });
  253.                   this.scale.update(newScaleProps);
  254.             },
  255.             draw : function(ease){
  256.                   var easingDecimal = ease || 1;
  257.                   this.clear();
  258.  
  259.                   var ctx = this.chart.ctx;
  260.  
  261.                   // Some helper methods for getting the next/prev points
  262.                   var hasValue = function(item){
  263.                         return item.value !== null;
  264.                   },
  265.                   nextPoint = function(point, collection, index){
  266.                         return helpers.findNextWhere(collection, hasValue, index) || point;
  267.                   },
  268.                   previousPoint = function(point, collection, index){
  269.                         return helpers.findPreviousWhere(collection, hasValue, index) || point;
  270.                   };
  271.  
  272.                   this.scale.draw(easingDecimal);
  273.  
  274.  
  275.                   helpers.each(this.datasets,function(dataset){
  276.                         var pointsWithValues = helpers.where(dataset.points, hasValue);
  277.  
  278.                         //Transition each point first so that the line and point drawing isn't out of sync
  279.                         //We can use this extra loop to calculate the control points of this dataset also in this loop
  280.  
  281.                         helpers.each(dataset.points, function(point, index){
  282.                               if (point.hasValue()){
  283.                                     point.transition({
  284.                                           y : this.scale.calculateY(point.value),
  285.                                           x : this.scale.calculateX(index)
  286.                                     }, easingDecimal);
  287.                               }
  288.                         },this);
  289.  
  290.  
  291.                         // Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point
  292.                         // This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed
  293.                         if (this.options.bezierCurve){
  294.                               helpers.each(pointsWithValues, function(point, index){
  295.                                     var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0;
  296.                                     point.controlPoints = helpers.splineCurve(
  297.                                           previousPoint(point, pointsWithValues, index),
  298.                                           point,
  299.                                           nextPoint(point, pointsWithValues, index),
  300.                                           tension
  301.                                     );
  302.  
  303.                                     // Prevent the bezier going outside of the bounds of the graph
  304.  
  305.                                     // Cap puter bezier handles to the upper/lower scale bounds
  306.                                     if (point.controlPoints.outer.y > this.scale.endPoint){
  307.                                           point.controlPoints.outer.y = this.scale.endPoint;
  308.                                     }
  309.                                     else if (point.controlPoints.outer.y < this.scale.startPoint){
  310.                                           point.controlPoints.outer.y = this.scale.startPoint;
  311.                                     }
  312.  
  313.                                     // Cap inner bezier handles to the upper/lower scale bounds
  314.                                     if (point.controlPoints.inner.y > this.scale.endPoint){
  315.                                           point.controlPoints.inner.y = this.scale.endPoint;
  316.                                     }
  317.                                     else if (point.controlPoints.inner.y < this.scale.startPoint){
  318.                                           point.controlPoints.inner.y = this.scale.startPoint;
  319.                                     }
  320.                               },this);
  321.                         }
  322.  
  323.  
  324.                         //Draw the line between all the points
  325.                         ctx.lineWidth = this.options.datasetStrokeWidth;
  326.                         ctx.strokeStyle = dataset.strokeColor;
  327.                         ctx.beginPath();
  328.  
  329.                         helpers.each(pointsWithValues, function(point, index){
  330.                               if (index === 0){
  331.                                     ctx.moveTo(point.x, point.y);
  332.                               }
  333.                               else{
  334.                                     if(this.options.bezierCurve){
  335.                                           var previous = previousPoint(point, pointsWithValues, index);
  336.  
  337.                                           ctx.bezierCurveTo(
  338.                                                 previous.controlPoints.outer.x,
  339.                                                 previous.controlPoints.outer.y,
  340.                                                 point.controlPoints.inner.x,
  341.                                                 point.controlPoints.inner.y,
  342.                                                 point.x,
  343.                                                 point.y
  344.                                           );
  345.                                     }
  346.                                     else{
  347.                                           ctx.lineTo(point.x,point.y);
  348.                                     }
  349.                               }
  350.                         }, this);
  351.  
  352.                         ctx.stroke();
  353.  
  354.                         if (this.options.datasetFill && pointsWithValues.length > 0){
  355.                               //Round off the line by going to the base of the chart, back to the start, then fill.
  356.                               ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint);
  357.                               ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint);
  358.                               ctx.fillStyle = dataset.fillColor;
  359.                               ctx.closePath();
  360.                               ctx.fill();
  361.                         }
  362.  
  363.                         //Now draw the points over the line
  364.                         //A little inefficient double looping, but better than the line
  365.                         //lagging behind the point positions
  366.                         helpers.each(pointsWithValues,function(point){
  367.                               point.draw();
  368.                         });
  369.                   },this);
  370.             }
  371.       });
  372.  
  373.  
  374. }).call(this);

Raw Paste


Login or Register to edit or fork this paste. It's free.