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