Added more updates
[soho-sigint.git] / client-freqwatch / Chart.js / src / Chart.Line.js
diff --git a/client-freqwatch/Chart.js/src/Chart.Line.js b/client-freqwatch/Chart.js/src/Chart.Line.js
new file mode 100644 (file)
index 0000000..2ca73db
--- /dev/null
@@ -0,0 +1,380 @@
+(function(){
+       "use strict";
+
+       var root = this,
+               Chart = root.Chart,
+               helpers = Chart.helpers;
+
+       var defaultConfig = {
+
+               ///Boolean - Whether grid lines are shown across the chart
+               scaleShowGridLines : true,
+
+               //String - Colour of the grid lines
+               scaleGridLineColor : "rgba(0,0,0,.05)",
+
+               //Number - Width of the grid lines
+               scaleGridLineWidth : 1,
+
+               //Boolean - Whether to show horizontal lines (except X axis)
+               scaleShowHorizontalLines: true,
+
+               //Boolean - Whether to show vertical lines (except Y axis)
+               scaleShowVerticalLines: true,
+
+               //Boolean - Whether the line is curved between points
+               bezierCurve : true,
+
+               //Number - Tension of the bezier curve between points
+               bezierCurveTension : 0.4,
+
+               //Boolean - Whether to show a dot for each point
+               pointDot : true,
+
+               //Number - Radius of each point dot in pixels
+               pointDotRadius : 4,
+
+               //Number - Pixel width of point dot stroke
+               pointDotStrokeWidth : 1,
+
+               //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
+               pointHitDetectionRadius : 20,
+
+               //Boolean - Whether to show a stroke for datasets
+               datasetStroke : true,
+
+               //Number - Pixel width of dataset stroke
+               datasetStrokeWidth : 2,
+
+               //Boolean - Whether to fill the dataset with a colour
+               datasetFill : true,
+
+               //String - A legend template
+               legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"><%if(datasets[i].label){%><%=datasets[i].label%><%}%></span></li><%}%></ul>",
+
+               //Boolean - Whether to horizontally center the label and point dot inside the grid
+               offsetGridLines : false
+
+       };
+
+
+       Chart.Type.extend({
+               name: "Line",
+               defaults : defaultConfig,
+               initialize:  function(data){
+                       //Declare the extension of the default point, to cater for the options passed in to the constructor
+                       this.PointClass = Chart.Point.extend({
+                               offsetGridLines : this.options.offsetGridLines,
+                               strokeWidth : this.options.pointDotStrokeWidth,
+                               radius : this.options.pointDotRadius,
+                               display: this.options.pointDot,
+                               hitDetectionRadius : this.options.pointHitDetectionRadius,
+                               ctx : this.chart.ctx,
+                               inRange : function(mouseX){
+                                       return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2));
+                               }
+                       });
+
+                       this.datasets = [];
+
+                       //Set up tooltip events on the chart
+                       if (this.options.showTooltips){
+                               helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
+                                       var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
+                                       this.eachPoints(function(point){
+                                               point.restore(['fillColor', 'strokeColor']);
+                                       });
+                                       helpers.each(activePoints, function(activePoint){
+                                               activePoint.fillColor = activePoint.highlightFill;
+                                               activePoint.strokeColor = activePoint.highlightStroke;
+                                       });
+                                       this.showTooltip(activePoints);
+                               });
+                       }
+
+                       //Iterate through each of the datasets, and build this into a property of the chart
+                       helpers.each(data.datasets,function(dataset){
+
+                               var datasetObject = {
+                                       label : dataset.label || null,
+                                       fillColor : dataset.fillColor,
+                                       strokeColor : dataset.strokeColor,
+                                       pointColor : dataset.pointColor,
+                                       pointStrokeColor : dataset.pointStrokeColor,
+                                       points : []
+                               };
+
+                               this.datasets.push(datasetObject);
+
+
+                               helpers.each(dataset.data,function(dataPoint,index){
+                                       //Add a new point for each piece of data, passing any required data to draw.
+                                       datasetObject.points.push(new this.PointClass({
+                                               value : dataPoint,
+                                               label : data.labels[index],
+                                               datasetLabel: dataset.label,
+                                               strokeColor : dataset.pointStrokeColor,
+                                               fillColor : dataset.pointColor,
+                                               highlightFill : dataset.pointHighlightFill || dataset.pointColor,
+                                               highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
+                                       }));
+                               },this);
+
+                               this.buildScale(data.labels);
+
+
+                               this.eachPoints(function(point, index){
+                                       helpers.extend(point, {
+                                               x: this.scale.calculateX(index),
+                                               y: this.scale.endPoint
+                                       });
+                                       point.save();
+                               }, this);
+
+                       },this);
+
+
+                       this.render();
+               },
+               update : function(){
+                       this.scale.update();
+                       // Reset any highlight colours before updating.
+                       helpers.each(this.activeElements, function(activeElement){
+                               activeElement.restore(['fillColor', 'strokeColor']);
+                       });
+                       this.eachPoints(function(point){
+                               point.save();
+                       });
+                       this.render();
+               },
+               eachPoints : function(callback){
+                       helpers.each(this.datasets,function(dataset){
+                               helpers.each(dataset.points,callback,this);
+                       },this);
+               },
+               getPointsAtEvent : function(e){
+                       var pointsArray = [],
+                               eventPosition = helpers.getRelativePosition(e);
+                       helpers.each(this.datasets,function(dataset){
+                               helpers.each(dataset.points,function(point){
+                                       if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point);
+                               });
+                       },this);
+                       return pointsArray;
+               },
+               buildScale : function(labels){
+                       var self = this;
+
+                       var dataTotal = function(){
+                               var values = [];
+                               self.eachPoints(function(point){
+                                       values.push(point.value);
+                               });
+
+                               return values;
+                       };
+
+                       var scaleOptions = {
+                               templateString : this.options.scaleLabel,
+                               height : this.chart.height,
+                               width : this.chart.width,
+                               ctx : this.chart.ctx,
+                               textColor : this.options.scaleFontColor,
+                               offsetGridLines : this.options.offsetGridLines,
+                               fontSize : this.options.scaleFontSize,
+                               fontStyle : this.options.scaleFontStyle,
+                               fontFamily : this.options.scaleFontFamily,
+                               valuesCount : labels.length,
+                               beginAtZero : this.options.scaleBeginAtZero,
+                               integersOnly : this.options.scaleIntegersOnly,
+                               calculateYRange : function(currentHeight){
+                                       var updatedRanges = helpers.calculateScaleRange(
+                                               dataTotal(),
+                                               currentHeight,
+                                               this.fontSize,
+                                               this.beginAtZero,
+                                               this.integersOnly
+                                       );
+                                       helpers.extend(this, updatedRanges);
+                               },
+                               xLabels : labels,
+                               font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
+                               lineWidth : this.options.scaleLineWidth,
+                               lineColor : this.options.scaleLineColor,
+                               showHorizontalLines : this.options.scaleShowHorizontalLines,
+                               showVerticalLines : this.options.scaleShowVerticalLines,
+                               gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
+                               gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
+                               padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth,
+                               showLabels : this.options.scaleShowLabels,
+                               display : this.options.showScale
+                       };
+
+                       if (this.options.scaleOverride){
+                               helpers.extend(scaleOptions, {
+                                       calculateYRange: helpers.noop,
+                                       steps: this.options.scaleSteps,
+                                       stepValue: this.options.scaleStepWidth,
+                                       min: this.options.scaleStartValue,
+                                       max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
+                               });
+                       }
+
+
+                       this.scale = new Chart.Scale(scaleOptions);
+               },
+               addData : function(valuesArray,label){
+                       //Map the values array for each of the datasets
+
+                       helpers.each(valuesArray,function(value,datasetIndex){
+                               //Add a new point for each piece of data, passing any required data to draw.
+                               this.datasets[datasetIndex].points.push(new this.PointClass({
+                                       value : value,
+                                       label : label,
+                                       datasetLabel: this.datasets[datasetIndex].label,
+                                       x: this.scale.calculateX(this.scale.valuesCount+1),
+                                       y: this.scale.endPoint,
+                                       strokeColor : this.datasets[datasetIndex].pointStrokeColor,
+                                       fillColor : this.datasets[datasetIndex].pointColor
+                               }));
+                       },this);
+
+                       this.scale.addXLabel(label);
+                       //Then re-render the chart.
+                       this.update();
+               },
+               removeData : function(){
+                       this.scale.removeXLabel();
+                       //Then re-render the chart.
+                       helpers.each(this.datasets,function(dataset){
+                               dataset.points.shift();
+                       },this);
+                       this.update();
+               },
+               reflow : function(){
+                       var newScaleProps = helpers.extend({
+                               height : this.chart.height,
+                               width : this.chart.width
+                       });
+                       this.scale.update(newScaleProps);
+               },
+               draw : function(ease){
+                       var easingDecimal = ease || 1;
+                       this.clear();
+
+                       var ctx = this.chart.ctx;
+
+                       // Some helper methods for getting the next/prev points
+                       var hasValue = function(item){
+                               return item.value !== null;
+                       },
+                       nextPoint = function(point, collection, index){
+                               return helpers.findNextWhere(collection, hasValue, index) || point;
+                       },
+                       previousPoint = function(point, collection, index){
+                               return helpers.findPreviousWhere(collection, hasValue, index) || point;
+                       };
+
+                       this.scale.draw(easingDecimal);
+
+
+                       helpers.each(this.datasets,function(dataset){
+                               var pointsWithValues = helpers.where(dataset.points, hasValue);
+
+                               //Transition each point first so that the line and point drawing isn't out of sync
+                               //We can use this extra loop to calculate the control points of this dataset also in this loop
+
+                               helpers.each(dataset.points, function(point, index){
+                                       if (point.hasValue()){
+                                               point.transition({
+                                                       y : this.scale.calculateY(point.value),
+                                                       x : this.scale.calculateX(index)
+                                               }, easingDecimal);
+                                       }
+                               },this);
+
+
+                               // Control points need to be calculated in a separate loop, because we need to know the current x/y of the point
+                               // This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed
+                               if (this.options.bezierCurve){
+                                       helpers.each(pointsWithValues, function(point, index){
+                                               var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0;
+                                               point.controlPoints = helpers.splineCurve(
+                                                       previousPoint(point, pointsWithValues, index),
+                                                       point,
+                                                       nextPoint(point, pointsWithValues, index),
+                                                       tension
+                                               );
+
+                                               // Prevent the bezier going outside of the bounds of the graph
+
+                                               // Cap puter bezier handles to the upper/lower scale bounds
+                                               if (point.controlPoints.outer.y > this.scale.endPoint){
+                                                       point.controlPoints.outer.y = this.scale.endPoint;
+                                               }
+                                               else if (point.controlPoints.outer.y < this.scale.startPoint){
+                                                       point.controlPoints.outer.y = this.scale.startPoint;
+                                               }
+
+                                               // Cap inner bezier handles to the upper/lower scale bounds
+                                               if (point.controlPoints.inner.y > this.scale.endPoint){
+                                                       point.controlPoints.inner.y = this.scale.endPoint;
+                                               }
+                                               else if (point.controlPoints.inner.y < this.scale.startPoint){
+                                                       point.controlPoints.inner.y = this.scale.startPoint;
+                                               }
+                                       },this);
+                               }
+
+
+                               //Draw the line between all the points
+                               ctx.lineWidth = this.options.datasetStrokeWidth;
+                               ctx.strokeStyle = dataset.strokeColor;
+                               ctx.beginPath();
+
+                               helpers.each(pointsWithValues, function(point, index){
+                                       if (index === 0){
+                                               ctx.moveTo(point.x, point.y);
+                                       }
+                                       else{
+                                               if(this.options.bezierCurve){
+                                                       var previous = previousPoint(point, pointsWithValues, index);
+
+                                                       ctx.bezierCurveTo(
+                                                               previous.controlPoints.outer.x,
+                                                               previous.controlPoints.outer.y,
+                                                               point.controlPoints.inner.x,
+                                                               point.controlPoints.inner.y,
+                                                               point.x,
+                                                               point.y
+                                                       );
+                                               }
+                                               else{
+                                                       ctx.lineTo(point.x,point.y);
+                                               }
+                                       }
+                               }, this);
+
+                               ctx.stroke();
+
+                               if (this.options.datasetFill && pointsWithValues.length > 0){
+                                       //Round off the line by going to the base of the chart, back to the start, then fill.
+                                       ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint);
+                                       ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint);
+                                       ctx.fillStyle = dataset.fillColor;
+                                       ctx.closePath();
+                                       ctx.fill();
+                               }
+
+                               //Now draw the points over the line
+                               //A little inefficient double looping, but better than the line
+                               //lagging behind the point positions
+                               helpers.each(pointsWithValues,function(point){
+                                       point.draw();
+                               });
+                       },this);
+               }
+       });
+
+
+}).call(this);