6 helpers = Chart.helpers;
10 ///Boolean - Whether grid lines are shown across the chart
11 scaleShowGridLines : true,
13 //String - Colour of the grid lines
14 scaleGridLineColor : "rgba(0,0,0,.05)",
16 //Number - Width of the grid lines
17 scaleGridLineWidth : 1,
19 //Boolean - Whether to show horizontal lines (except X axis)
20 scaleShowHorizontalLines: true,
22 //Boolean - Whether to show vertical lines (except Y axis)
23 scaleShowVerticalLines: true,
25 //Boolean - Whether the line is curved between points
28 //Number - Tension of the bezier curve between points
29 bezierCurveTension : 0.4,
31 //Boolean - Whether to show a dot for each point
34 //Number - Radius of each point dot in pixels
37 //Number - Pixel width of point dot stroke
38 pointDotStrokeWidth : 1,
40 //Number - amount extra to add to the radius to cater for hit detection outside the drawn point
41 pointHitDetectionRadius : 20,
43 //Boolean - Whether to show a stroke for datasets
46 //Number - Pixel width of dataset stroke
47 datasetStrokeWidth : 2,
49 //Boolean - Whether to fill the dataset with a colour
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>",
55 //Boolean - Whether to horizontally center the label and point dot inside the grid
56 offsetGridLines : false
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,
73 inRange : function(mouseX){
74 return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2));
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']);
87 helpers.each(activePoints, function(activePoint){
88 activePoint.fillColor = activePoint.highlightFill;
89 activePoint.strokeColor = activePoint.highlightStroke;
91 this.showTooltip(activePoints);
95 //Iterate through each of the datasets, and build this into a property of the chart
96 helpers.each(data.datasets,function(dataset){
99 label : dataset.label || null,
100 fillColor : dataset.fillColor,
101 strokeColor : dataset.strokeColor,
102 pointColor : dataset.pointColor,
103 pointStrokeColor : dataset.pointStrokeColor,
107 this.datasets.push(datasetObject);
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({
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
123 this.buildScale(data.labels);
126 this.eachPoints(function(point, index){
127 helpers.extend(point, {
128 x: this.scale.calculateX(index),
129 y: this.scale.endPoint
141 // Reset any highlight colours before updating.
142 helpers.each(this.activeElements, function(activeElement){
143 activeElement.restore(['fillColor', 'strokeColor']);
145 this.eachPoints(function(point){
150 eachPoints : function(callback){
151 helpers.each(this.datasets,function(dataset){
152 helpers.each(dataset.points,callback,this);
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);
165 buildScale : function(labels){
168 var dataTotal = function(){
170 self.eachPoints(function(point){
171 values.push(point.value);
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(
198 helpers.extend(this, updatedRanges);
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
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)
224 this.scale = new Chart.Scale(scaleOptions);
226 addData : function(valuesArray,label){
227 //Map the values array for each of the datasets
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({
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
242 this.scale.addXLabel(label);
243 //Then re-render the chart.
246 removeData : function(){
247 this.scale.removeXLabel();
248 //Then re-render the chart.
249 helpers.each(this.datasets,function(dataset){
250 dataset.points.shift();
255 var newScaleProps = helpers.extend({
256 height : this.chart.height,
257 width : this.chart.width
259 this.scale.update(newScaleProps);
261 draw : function(ease){
262 var easingDecimal = ease || 1;
265 var ctx = this.chart.ctx;
267 // Some helper methods for getting the next/prev points
268 var hasValue = function(item){
269 return item.value !== null;
271 nextPoint = function(point, collection, index){
272 return helpers.findNextWhere(collection, hasValue, index) || point;
274 previousPoint = function(point, collection, index){
275 return helpers.findPreviousWhere(collection, hasValue, index) || point;
278 this.scale.draw(easingDecimal);
281 helpers.each(this.datasets,function(dataset){
282 var pointsWithValues = helpers.where(dataset.points, hasValue);
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
287 helpers.each(dataset.points, function(point, index){
288 if (point.hasValue()){
290 y : this.scale.calculateY(point.value),
291 x : this.scale.calculateX(index)
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),
305 nextPoint(point, pointsWithValues, index),
309 // Prevent the bezier going outside of the bounds of the graph
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;
315 else if (point.controlPoints.outer.y < this.scale.startPoint){
316 point.controlPoints.outer.y = this.scale.startPoint;
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;
323 else if (point.controlPoints.inner.y < this.scale.startPoint){
324 point.controlPoints.inner.y = this.scale.startPoint;
330 //Draw the line between all the points
331 ctx.lineWidth = this.options.datasetStrokeWidth;
332 ctx.strokeStyle = dataset.strokeColor;
335 helpers.each(pointsWithValues, function(point, index){
337 ctx.moveTo(point.x, point.y);
340 if(this.options.bezierCurve){
341 var previous = previousPoint(point, pointsWithValues, index);
344 previous.controlPoints.outer.x,
345 previous.controlPoints.outer.y,
346 point.controlPoints.inner.x,
347 point.controlPoints.inner.y,
353 ctx.lineTo(point.x,point.y);
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;
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){