Passed
Branch develop (893f38)
by Hans Erik
07:18
created

build/media/libraries/redcore/media/redcore/lib/Chart-js/Chart.js   F

Complexity

Total Complexity 562
Complexity/F 1.89

Size

Lines of Code 3466
Function Count 298

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
wmc 562
c 0
b 0
f 0
dl 0
loc 3466
rs 2.4
cc 0
nc 0
mnd 6
bc 485
fnc 298
bpm 1.6275
cpm 1.8859
noi 74

1 Function

Rating   Name   Duplication   Size   Complexity  
A Chart.js ➔ ??? 0 16 1

How to fix   Complexity   

Complexity

Complex classes like build/media/libraries/redcore/media/redcore/lib/Chart-js/Chart.js often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
/*!
2
 * Chart.js
3
 * http://chartjs.org/
4
 * Version: 1.0.2
5
 *
6
 * Copyright 2015 Nick Downie
7
 * Released under the MIT license
8
 * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
9
 */
10
11
12
(function(){
13
14
	"use strict";
15
16
	//Declare root variable - window in the browser, global on the server
17
	var root = this,
18
		previous = root.Chart;
19
20
	//Occupy the global variable of Chart, and create a simple base class
21
	var Chart = function(context){
22
		var chart = this;
0 ignored issues
show
Unused Code introduced by
The variable chart seems to be never used. Consider removing it.
Loading history...
23
		this.canvas = context.canvas;
24
25
		this.ctx = context;
26
27
		//Variables global to the chart
28
		var computeDimension = function(element,dimension)
29
		{
30
			if (element['offset'+dimension])
31
			{
32
				return element['offset'+dimension];
33
			}
34
			else
35
			{
36
				return document.defaultView.getComputedStyle(element).getPropertyValue(dimension);
37
			}
38
		}
39
40
		var width = this.width = computeDimension(context.canvas,'Width');
41
		var height = this.height = computeDimension(context.canvas,'Height');
42
43
		// Firefox requires this to work correctly
44
		context.canvas.width  = width;
45
		context.canvas.height = height;
46
47
		var width = this.width = context.canvas.width;
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable width already seems to be declared on line 40. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
48
		var height = this.height = context.canvas.height;
0 ignored issues
show
Comprehensibility Naming Best Practice introduced by
The variable height already seems to be declared on line 41. Consider using another variable name or omitting the var keyword.

This check looks for variables that are declared in multiple lines. There may be several reasons for this.

In the simplest case the variable name was reused by mistake. This may lead to very hard to locate bugs.

If you want to reuse a variable for another purpose, consider declaring it at or near the top of your function and just assigning to it subsequently so it is always declared.

Loading history...
49
		this.aspectRatio = this.width / this.height;
50
		//High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
51
		helpers.retinaScale(this);
52
53
		return this;
54
	};
55
	//Globally expose the defaults to allow for user updating/changing
56
	Chart.defaults = {
57
		global: {
58
			// Boolean - Whether to animate the chart
59
			animation: true,
60
61
			// Number - Number of animation steps
62
			animationSteps: 60,
63
64
			// String - Animation easing effect
65
			animationEasing: "easeOutQuart",
66
67
			// Boolean - If we should show the scale at all
68
			showScale: true,
69
70
			// Boolean - If we want to override with a hard coded scale
71
			scaleOverride: false,
72
73
			// ** Required if scaleOverride is true **
74
			// Number - The number of steps in a hard coded scale
75
			scaleSteps: null,
76
			// Number - The value jump in the hard coded scale
77
			scaleStepWidth: null,
78
			// Number - The scale starting value
79
			scaleStartValue: null,
80
81
			// String - Colour of the scale line
82
			scaleLineColor: "rgba(0,0,0,.1)",
83
84
			// Number - Pixel width of the scale line
85
			scaleLineWidth: 1,
86
87
			// Boolean - Whether to show labels on the scale
88
			scaleShowLabels: true,
89
90
			// Interpolated JS string - can access value
91
			scaleLabel: "<%=value%>",
92
93
			// Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there
94
			scaleIntegersOnly: true,
95
96
			// Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
97
			scaleBeginAtZero: false,
98
99
			// String - Scale label font declaration for the scale label
100
			scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
101
102
			// Number - Scale label font size in pixels
103
			scaleFontSize: 12,
104
105
			// String - Scale label font weight style
106
			scaleFontStyle: "normal",
107
108
			// String - Scale label font colour
109
			scaleFontColor: "#666",
110
111
			// Boolean - whether or not the chart should be responsive and resize when the browser does.
112
			responsive: false,
113
114
			// Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
115
			maintainAspectRatio: true,
116
117
			// Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
118
			showTooltips: true,
119
120
			// Boolean - Determines whether to draw built-in tooltip or call custom tooltip function
121
			customTooltips: false,
122
123
			// Array - Array of string names to attach tooltip events
124
			tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],
125
126
			// String - Tooltip background colour
127
			tooltipFillColor: "rgba(0,0,0,0.8)",
128
129
			// String - Tooltip label font declaration for the scale label
130
			tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
131
132
			// Number - Tooltip label font size in pixels
133
			tooltipFontSize: 14,
134
135
			// String - Tooltip font weight style
136
			tooltipFontStyle: "normal",
137
138
			// String - Tooltip label font colour
139
			tooltipFontColor: "#fff",
140
141
			// String - Tooltip title font declaration for the scale label
142
			tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
143
144
			// Number - Tooltip title font size in pixels
145
			tooltipTitleFontSize: 14,
146
147
			// String - Tooltip title font weight style
148
			tooltipTitleFontStyle: "bold",
149
150
			// String - Tooltip title font colour
151
			tooltipTitleFontColor: "#fff",
152
153
			// Number - pixel width of padding around tooltip text
154
			tooltipYPadding: 6,
155
156
			// Number - pixel width of padding around tooltip text
157
			tooltipXPadding: 6,
158
159
			// Number - Size of the caret on the tooltip
160
			tooltipCaretSize: 8,
161
162
			// Number - Pixel radius of the tooltip border
163
			tooltipCornerRadius: 6,
164
165
			// Number - Pixel offset from point x to tooltip edge
166
			tooltipXOffset: 10,
167
168
			// String - Template string for single tooltips
169
			tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>",
170
171
			// String - Template string for single tooltips
172
			multiTooltipTemplate: "<%= value %>",
173
174
			// String - Colour behind the legend colour block
175
			multiTooltipKeyBackground: '#fff',
176
177
			// Function - Will fire on animation progression.
178
			onAnimationProgress: function(){},
179
180
			// Function - Will fire on animation completion.
181
			onAnimationComplete: function(){}
182
183
		}
184
	};
185
186
	//Create a dictionary of chart types, to allow for extension of existing types
187
	Chart.types = {};
188
189
	//Global Chart helpers object for utility methods and classes
190
	var helpers = Chart.helpers = {};
191
192
		//-- Basic js utility methods
193
	var each = helpers.each = function(loopable,callback,self){
194
			var additionalArgs = Array.prototype.slice.call(arguments, 3);
195
			// Check to see if null or undefined firstly.
196
			if (loopable){
197
				if (loopable.length === +loopable.length){
198
					var i;
199
					for (i=0; i<loopable.length; i++){
200
						callback.apply(self,[loopable[i], i].concat(additionalArgs));
201
					}
202
				}
203
				else{
204
					for (var item in loopable){
0 ignored issues
show
Complexity introduced by
A for in loop automatically includes the property of any prototype object, consider checking the key using hasOwnProperty.

When iterating over the keys of an object, this includes not only the keys of the object, but also keys contained in the prototype of that object. It is generally a best practice to check for these keys specifically:

var someObject;
for (var key in someObject) {
    if ( ! someObject.hasOwnProperty(key)) {
        continue; // Skip keys from the prototype.
    }

    doSomethingWith(key);
}
Loading history...
205
						callback.apply(self,[loopable[item],item].concat(additionalArgs));
206
					}
207
				}
208
			}
209
		},
210
		clone = helpers.clone = function(obj){
211
			var objClone = {};
212
			each(obj,function(value,key){
213
				if (obj.hasOwnProperty(key)) objClone[key] = value;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
214
			});
215
			return objClone;
216
		},
217
		extend = helpers.extend = function(base){
218
			each(Array.prototype.slice.call(arguments,1), function(extensionObject) {
219
				each(extensionObject,function(value,key){
220
					if (extensionObject.hasOwnProperty(key)) base[key] = value;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
221
				});
222
			});
223
			return base;
224
		},
225
		merge = helpers.merge = function(base,master){
0 ignored issues
show
Unused Code introduced by
The parameter base is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
Unused Code introduced by
The parameter master is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
226
			//Merge properties in left object over to a shallow clone of object right.
227
			var args = Array.prototype.slice.call(arguments,0);
228
			args.unshift({});
229
			return extend.apply(null, args);
230
		},
231
		indexOf = helpers.indexOf = function(arrayToSearch, item){
232
			if (Array.prototype.indexOf) {
233
				return arrayToSearch.indexOf(item);
234
			}
235
			else{
236
				for (var i = 0; i < arrayToSearch.length; i++) {
237
					if (arrayToSearch[i] === item) return i;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
238
				}
239
				return -1;
240
			}
241
		},
242
		where = helpers.where = function(collection, filterCallback){
0 ignored issues
show
Unused Code introduced by
The assignment to variable where seems to be never used. Consider removing it.
Loading history...
243
			var filtered = [];
244
245
			helpers.each(collection, function(item){
246
				if (filterCallback(item)){
247
					filtered.push(item);
248
				}
249
			});
250
251
			return filtered;
252
		},
253
		findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex){
0 ignored issues
show
Unused Code introduced by
The assignment to variable findNextWhere seems to be never used. Consider removing it.
Loading history...
254
			// Default to start of the array
255
			if (!startIndex){
256
				startIndex = -1;
257
			}
258
			for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
259
				var currentItem = arrayToSearch[i];
260
				if (filterCallback(currentItem)){
261
					return currentItem;
262
				}
263
			}
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
264
		},
265
		findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex){
0 ignored issues
show
Unused Code introduced by
The assignment to variable findPreviousWhere seems to be never used. Consider removing it.
Loading history...
266
			// Default to end of the array
267
			if (!startIndex){
268
				startIndex = arrayToSearch.length;
269
			}
270
			for (var i = startIndex - 1; i >= 0; i--) {
271
				var currentItem = arrayToSearch[i];
272
				if (filterCallback(currentItem)){
273
					return currentItem;
274
				}
275
			}
0 ignored issues
show
Best Practice introduced by
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
276
		},
277
		inherits = helpers.inherits = function(extensions){
278
			//Basic javascript inheritance based on the model created in Backbone.js
279
			var parent = this;
280
			var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function(){ return parent.apply(this, arguments); };
281
282
			var Surrogate = function(){ this.constructor = ChartElement;};
283
			Surrogate.prototype = parent.prototype;
284
			ChartElement.prototype = new Surrogate();
285
286
			ChartElement.extend = inherits;
287
288
			if (extensions) extend(ChartElement.prototype, extensions);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
289
290
			ChartElement.__super__ = parent.prototype;
291
292
			return ChartElement;
293
		},
294
		noop = helpers.noop = function(){},
295
		uid = helpers.uid = (function(){
296
			var id=0;
297
			return function(){
298
				return "chart-" + id++;
299
			};
300
		})(),
301
		warn = helpers.warn = function(str){
302
			//Method for warning of errors
303
			if (window.console && typeof window.console.warn == "function") console.warn(str);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
304
		},
305
		amd = helpers.amd = (typeof define == 'function' && define.amd),
0 ignored issues
show
Bug introduced by
The variable define seems to be never declared. If this is a global, consider adding a /** global: define */ comment.

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
306
		//-- Math methods
307
		isNumber = helpers.isNumber = function(n){
308
			return !isNaN(parseFloat(n)) && isFinite(n);
309
		},
310
		max = helpers.max = function(array){
311
			return Math.max.apply( Math, array );
312
		},
313
		min = helpers.min = function(array){
314
			return Math.min.apply( Math, array );
315
		},
316
		cap = helpers.cap = function(valueToCap,maxValue,minValue){
0 ignored issues
show
Unused Code introduced by
The assignment to variable cap seems to be never used. Consider removing it.
Loading history...
317
			if(isNumber(maxValue)) {
318
				if( valueToCap > maxValue ) {
319
					return maxValue;
320
				}
321
			}
322
			else if(isNumber(minValue)){
323
				if ( valueToCap < minValue ){
324
					return minValue;
325
				}
326
			}
327
			return valueToCap;
328
		},
329
		getDecimalPlaces = helpers.getDecimalPlaces = function(num){
330
			if (num%1!==0 && isNumber(num)){
331
				return num.toString().split(".")[1].length;
332
			}
333
			else {
334
				return 0;
335
			}
336
		},
337
		toRadians = helpers.radians = function(degrees){
338
			return degrees * (Math.PI/180);
339
		},
340
		// Gets the angle from vertical upright to the point about a centre.
341
		getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint){
342
			var distanceFromXCenter = anglePoint.x - centrePoint.x,
343
				distanceFromYCenter = anglePoint.y - centrePoint.y,
344
				radialDistanceFromCenter = Math.sqrt( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);
345
346
347
			var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter);
348
349
			//If the segment is in the top left quadrant, we need to add another rotation to the angle
350
			if (distanceFromXCenter < 0 && distanceFromYCenter < 0){
351
				angle += Math.PI*2;
352
			}
353
354
			return {
355
				angle: angle,
356
				distance: radialDistanceFromCenter
357
			};
358
		},
359
		aliasPixel = helpers.aliasPixel = function(pixelWidth){
360
			return (pixelWidth % 2 === 0) ? 0 : 0.5;
361
		},
362
		splineCurve = helpers.splineCurve = function(FirstPoint,MiddlePoint,AfterPoint,t){
0 ignored issues
show
Unused Code introduced by
The assignment to variable splineCurve seems to be never used. Consider removing it.
Loading history...
363
			//Props to Rob Spencer at scaled innovation for his post on splining between points
364
			//http://scaledinnovation.com/analytics/splines/aboutSplines.html
365
			var d01=Math.sqrt(Math.pow(MiddlePoint.x-FirstPoint.x,2)+Math.pow(MiddlePoint.y-FirstPoint.y,2)),
366
				d12=Math.sqrt(Math.pow(AfterPoint.x-MiddlePoint.x,2)+Math.pow(AfterPoint.y-MiddlePoint.y,2)),
367
				fa=t*d01/(d01+d12),// scaling factor for triangle Ta
368
				fb=t*d12/(d01+d12);
369
			return {
370
				inner : {
371
					x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x),
372
					y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y)
373
				},
374
				outer : {
375
					x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x),
376
					y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y)
377
				}
378
			};
379
		},
380
		calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val){
381
			return Math.floor(Math.log(val) / Math.LN10);
382
		},
383
		calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly){
0 ignored issues
show
Unused Code introduced by
The assignment to variable calculateScaleRange seems to be never used. Consider removing it.
Loading history...
384
385
			//Set a minimum step of two - a point at the top of the graph, and a point at the base
386
			var minSteps = 2,
387
				maxSteps = Math.floor(drawingSize/(textSize * 1.5)),
388
				skipFitting = (minSteps >= maxSteps);
389
390
			var maxValue = max(valuesArray),
391
				minValue = min(valuesArray);
392
393
			// We need some degree of seperation here to calculate the scales if all the values are the same
394
			// Adding/minusing 0.5 will give us a range of 1.
395
			if (maxValue === minValue){
396
				maxValue += 0.5;
397
				// So we don't end up with a graph with a negative start value if we've said always start from zero
398
				if (minValue >= 0.5 && !startFromZero){
399
					minValue -= 0.5;
400
				}
401
				else{
402
					// Make up a whole number above the values
403
					maxValue += 0.5;
404
				}
405
			}
406
407
			var	valueRange = Math.abs(maxValue - minValue),
408
				rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
409
				graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
410
				graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
411
				graphRange = graphMax - graphMin,
412
				stepValue = Math.pow(10, rangeOrderOfMagnitude),
413
				numberOfSteps = Math.round(graphRange / stepValue);
414
415
			//If we have more space on the graph we'll use it to give more definition to the data
416
			while((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {
417
				if(numberOfSteps > maxSteps){
418
					stepValue *=2;
419
					numberOfSteps = Math.round(graphRange/stepValue);
420
					// Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
421
					if (numberOfSteps % 1 !== 0){
422
						skipFitting = true;
423
					}
424
				}
425
				//We can fit in double the amount of scale points on the scale
426
				else{
427
					//If user has declared ints only, and the step value isn't a decimal
428
					if (integersOnly && rangeOrderOfMagnitude >= 0){
429
						//If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
430
						if(stepValue/2 % 1 === 0){
431
							stepValue /=2;
432
							numberOfSteps = Math.round(graphRange/stepValue);
433
						}
434
						//If it would make it a float break out of the loop
435
						else{
436
							break;
437
						}
438
					}
439
					//If the scale doesn't have to be an int, make the scale more granular anyway.
440
					else{
441
						stepValue /=2;
442
						numberOfSteps = Math.round(graphRange/stepValue);
443
					}
444
445
				}
446
			}
447
448
			if (skipFitting){
449
				numberOfSteps = minSteps;
450
				stepValue = graphRange / numberOfSteps;
451
			}
452
453
			return {
454
				steps : numberOfSteps,
455
				stepValue : stepValue,
456
				min : graphMin,
457
				max	: graphMin + (numberOfSteps * stepValue)
458
			};
459
460
		},
461
		/* jshint ignore:start */
462
		// Blows up jshint errors based on the new Function constructor
463
		//Templating methods
464
		//Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
465
		template = helpers.template = function(templateString, valuesObject){
466
467
			// If templateString is function rather than string-template - call the function for valuesObject
468
469
			if(templateString instanceof Function){
470
			 	return templateString(valuesObject);
471
		 	}
472
473
			var cache = {};
474
			function tmpl(str, data){
475
				// Figure out if we're getting a template, or if we need to
476
				// load the template - and be sure to cache the result.
477
				var fn = !/\W/.test(str) ?
478
				cache[str] = cache[str] :
479
480
				// Generate a reusable function that will serve as a template
481
				// generator (and which will be cached).
482
				new Function("obj",
0 ignored issues
show
Performance Best Practice introduced by
Using new Function() to create a function is slow and difficult to debug. Such functions do not create a closure. Consider using another way to define your function.
Loading history...
483
					"var p=[],print=function(){p.push.apply(p,arguments);};" +
484
485
					// Introduce the data as local variables using with(){}
486
					"with(obj){p.push('" +
487
488
					// Convert the template into pure JavaScript
489
					str
490
						.replace(/[\r\t\n]/g, " ")
491
						.split("<%").join("\t")
492
						.replace(/((^|%>)[^\t]*)'/g, "$1\r")
493
						.replace(/\t=(.*?)%>/g, "',$1,'")
494
						.split("\t").join("');")
495
						.split("%>").join("p.push('")
496
						.split("\r").join("\\'") +
497
					"');}return p.join('');"
498
				);
499
500
				// Provide some basic currying to the user
501
				return data ? fn( data ) : fn;
502
			}
503
			return tmpl(templateString,valuesObject);
504
		},
505
		/* jshint ignore:end */
506
		generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){
0 ignored issues
show
Unused Code introduced by
The assignment to variable generateLabels seems to be never used. Consider removing it.
Loading history...
507
			var labelsArray = new Array(numberOfSteps);
0 ignored issues
show
Coding Style Best Practice introduced by
Using the Array constructor is generally discouraged. Consider using an array literal instead.
Loading history...
508
			if (labelTemplateString){
0 ignored issues
show
Best Practice introduced by
If you intend to check if the variable labelTemplateString is declared in the current environment, consider using typeof labelTemplateString === "undefined" instead. This is safe if the variable is not actually declared.
Loading history...
509
				each(labelsArray,function(val,index){
510
					labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))});
511
				});
512
			}
513
			return labelsArray;
514
		},
515
		//--Animation methods
516
		//Easing functions adapted from Robert Penner's easing equations
517
		//http://www.robertpenner.com/easing/
518
		easingEffects = helpers.easingEffects = {
519
			linear: function (t) {
520
				return t;
521
			},
522
			easeInQuad: function (t) {
523
				return t * t;
524
			},
525
			easeOutQuad: function (t) {
526
				return -1 * t * (t - 2);
527
			},
528
			easeInOutQuad: function (t) {
529
				if ((t /= 1 / 2) < 1) return 1 / 2 * t * t;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
530
				return -1 / 2 * ((--t) * (t - 2) - 1);
531
			},
532
			easeInCubic: function (t) {
533
				return t * t * t;
534
			},
535
			easeOutCubic: function (t) {
536
				return 1 * ((t = t / 1 - 1) * t * t + 1);
537
			},
538
			easeInOutCubic: function (t) {
539
				if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
540
				return 1 / 2 * ((t -= 2) * t * t + 2);
541
			},
542
			easeInQuart: function (t) {
543
				return t * t * t * t;
544
			},
545
			easeOutQuart: function (t) {
546
				return -1 * ((t = t / 1 - 1) * t * t * t - 1);
547
			},
548
			easeInOutQuart: function (t) {
549
				if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
550
				return -1 / 2 * ((t -= 2) * t * t * t - 2);
551
			},
552
			easeInQuint: function (t) {
553
				return 1 * (t /= 1) * t * t * t * t;
554
			},
555
			easeOutQuint: function (t) {
556
				return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
557
			},
558
			easeInOutQuint: function (t) {
559
				if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
560
				return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
561
			},
562
			easeInSine: function (t) {
563
				return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
564
			},
565
			easeOutSine: function (t) {
566
				return 1 * Math.sin(t / 1 * (Math.PI / 2));
567
			},
568
			easeInOutSine: function (t) {
569
				return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
570
			},
571
			easeInExpo: function (t) {
572
				return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
573
			},
574
			easeOutExpo: function (t) {
575
				return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
576
			},
577
			easeInOutExpo: function (t) {
578
				if (t === 0) return 0;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
579
				if (t === 1) return 1;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
580
				if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1));
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
581
				return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
582
			},
583
			easeInCirc: function (t) {
584
				if (t >= 1) return t;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
585
				return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
586
			},
587
			easeOutCirc: function (t) {
588
				return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
589
			},
590
			easeInOutCirc: function (t) {
591
				if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
592
				return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
593
			},
594
			easeInElastic: function (t) {
595
				var s = 1.70158;
0 ignored issues
show
Unused Code introduced by
The assignment to variable s seems to be never used. Consider removing it.
Loading history...
596
				var p = 0;
597
				var a = 1;
598
				if (t === 0) return 0;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
599
				if ((t /= 1) == 1) return 1;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
600
				if (!p) p = 1 * 0.3;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
601
				if (a < Math.abs(1)) {
602
					a = 1;
603
					s = p / 4;
604
				} else s = p / (2 * Math.PI) * Math.asin(1 / a);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
605
				return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
606
			},
607
			easeOutElastic: function (t) {
608
				var s = 1.70158;
0 ignored issues
show
Unused Code introduced by
The assignment to variable s seems to be never used. Consider removing it.
Loading history...
609
				var p = 0;
610
				var a = 1;
611
				if (t === 0) return 0;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
612
				if ((t /= 1) == 1) return 1;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
613
				if (!p) p = 1 * 0.3;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
614
				if (a < Math.abs(1)) {
615
					a = 1;
616
					s = p / 4;
617
				} else s = p / (2 * Math.PI) * Math.asin(1 / a);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
618
				return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
619
			},
620
			easeInOutElastic: function (t) {
621
				var s = 1.70158;
0 ignored issues
show
Unused Code introduced by
The assignment to variable s seems to be never used. Consider removing it.
Loading history...
622
				var p = 0;
623
				var a = 1;
624
				if (t === 0) return 0;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
625
				if ((t /= 1 / 2) == 2) return 1;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
626
				if (!p) p = 1 * (0.3 * 1.5);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
627
				if (a < Math.abs(1)) {
628
					a = 1;
629
					s = p / 4;
630
				} else s = p / (2 * Math.PI) * Math.asin(1 / a);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
631
				if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
632
				return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
633
			},
634
			easeInBack: function (t) {
635
				var s = 1.70158;
636
				return 1 * (t /= 1) * t * ((s + 1) * t - s);
637
			},
638
			easeOutBack: function (t) {
639
				var s = 1.70158;
640
				return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
641
			},
642
			easeInOutBack: function (t) {
643
				var s = 1.70158;
644
				if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
645
				return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
646
			},
647
			easeInBounce: function (t) {
648
				return 1 - easingEffects.easeOutBounce(1 - t);
649
			},
650
			easeOutBounce: function (t) {
651
				if ((t /= 1) < (1 / 2.75)) {
652
					return 1 * (7.5625 * t * t);
653
				} else if (t < (2 / 2.75)) {
654
					return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
655
				} else if (t < (2.5 / 2.75)) {
656
					return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
657
				} else {
658
					return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
659
				}
660
			},
661
			easeInOutBounce: function (t) {
662
				if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5;
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
663
				return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
664
			}
665
		},
666
		//Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
667
		requestAnimFrame = helpers.requestAnimFrame = (function(){
668
			return window.requestAnimationFrame ||
669
				window.webkitRequestAnimationFrame ||
670
				window.mozRequestAnimationFrame ||
671
				window.oRequestAnimationFrame ||
672
				window.msRequestAnimationFrame ||
673
				function(callback) {
674
					return window.setTimeout(callback, 1000 / 60);
675
				};
676
		})(),
677
		cancelAnimFrame = helpers.cancelAnimFrame = (function(){
678
			return window.cancelAnimationFrame ||
679
				window.webkitCancelAnimationFrame ||
680
				window.mozCancelAnimationFrame ||
681
				window.oCancelAnimationFrame ||
682
				window.msCancelAnimationFrame ||
683
				function(callback) {
684
					return window.clearTimeout(callback, 1000 / 60);
685
				};
686
		})(),
687
		animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){
688
689
			var currentStep = 0,
690
				easingFunction = easingEffects[easingString] || easingEffects.linear;
691
692
			var animationFrame = function(){
693
				currentStep++;
694
				var stepDecimal = currentStep/totalSteps;
695
				var easeDecimal = easingFunction(stepDecimal);
696
697
				callback.call(chartInstance,easeDecimal,stepDecimal, currentStep);
698
				onProgress.call(chartInstance,easeDecimal,stepDecimal);
699
				if (currentStep < totalSteps){
700
					chartInstance.animationFrame = requestAnimFrame(animationFrame);
701
				} else{
702
					onComplete.apply(chartInstance);
703
				}
704
			};
705
			requestAnimFrame(animationFrame);
706
		},
707
		//-- DOM methods
708
		getRelativePosition = helpers.getRelativePosition = function(evt){
0 ignored issues
show
Unused Code introduced by
The assignment to variable getRelativePosition seems to be never used. Consider removing it.
Loading history...
709
			var mouseX, mouseY;
710
			var e = evt.originalEvent || evt,
711
				canvas = evt.currentTarget || evt.srcElement,
712
				boundingRect = canvas.getBoundingClientRect();
713
714
			if (e.touches){
715
				mouseX = e.touches[0].clientX - boundingRect.left;
716
				mouseY = e.touches[0].clientY - boundingRect.top;
717
718
			}
719
			else{
720
				mouseX = e.clientX - boundingRect.left;
721
				mouseY = e.clientY - boundingRect.top;
722
			}
723
724
			return {
725
				x : mouseX,
726
				y : mouseY
727
			};
728
729
		},
730
		addEvent = helpers.addEvent = function(node,eventType,method){
731
			if (node.addEventListener){
732
				node.addEventListener(eventType,method);
733
			} else if (node.attachEvent){
734
				node.attachEvent("on"+eventType, method);
735
			} else {
736
				node["on"+eventType] = method;
737
			}
738
		},
739
		removeEvent = helpers.removeEvent = function(node, eventType, handler){
740
			if (node.removeEventListener){
741
				node.removeEventListener(eventType, handler, false);
742
			} else if (node.detachEvent){
743
				node.detachEvent("on"+eventType,handler);
744
			} else{
745
				node["on" + eventType] = noop;
746
			}
747
		},
748
		bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){
0 ignored issues
show
Unused Code introduced by
The assignment to variable bindEvents seems to be never used. Consider removing it.
Loading history...
749
			// Create the events object if it's not already present
750
			if (!chartInstance.events) chartInstance.events = {};
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
751
752
			each(arrayOfEvents,function(eventName){
753
				chartInstance.events[eventName] = function(){
754
					handler.apply(chartInstance, arguments);
755
				};
756
				addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]);
757
			});
758
		},
759
		unbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) {
760
			each(arrayOfEvents, function(handler,eventName){
761
				removeEvent(chartInstance.chart.canvas, eventName, handler);
762
			});
763
		},
764
		getMaximumWidth = helpers.getMaximumWidth = function(domNode){
765
			var container = domNode.parentNode;
766
			// TODO = check cross browser stuff with this.
767
			return container.clientWidth;
768
		},
769
		getMaximumHeight = helpers.getMaximumHeight = function(domNode){
770
			var container = domNode.parentNode;
771
			// TODO = check cross browser stuff with this.
772
			return container.clientHeight;
773
		},
774
		getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support
0 ignored issues
show
Unused Code introduced by
The assignment to variable getMaximumSize seems to be never used. Consider removing it.
Loading history...
775
		retinaScale = helpers.retinaScale = function(chart){
776
			var ctx = chart.ctx,
777
				width = chart.canvas.width,
778
				height = chart.canvas.height;
779
780
			if (window.devicePixelRatio) {
781
				ctx.canvas.style.width = width + "px";
782
				ctx.canvas.style.height = height + "px";
783
				ctx.canvas.height = height * window.devicePixelRatio;
784
				ctx.canvas.width = width * window.devicePixelRatio;
785
				ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
786
			}
787
		},
788
		//-- Canvas methods
789
		clear = helpers.clear = function(chart){
790
			chart.ctx.clearRect(0,0,chart.width,chart.height);
791
		},
792
		fontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){
793
			return fontStyle + " " + pixelSize+"px " + fontFamily;
794
		},
795
		longestText = helpers.longestText = function(ctx,font,arrayOfStrings){
796
			ctx.font = font;
797
			var longest = 0;
798
			each(arrayOfStrings,function(string){
799
				var textWidth = ctx.measureText(string).width;
800
				longest = (textWidth > longest) ? textWidth : longest;
801
			});
802
			return longest;
803
		},
804
		drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){
805
			ctx.beginPath();
806
			ctx.moveTo(x + radius, y);
807
			ctx.lineTo(x + width - radius, y);
808
			ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
809
			ctx.lineTo(x + width, y + height - radius);
810
			ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
811
			ctx.lineTo(x + radius, y + height);
812
			ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
813
			ctx.lineTo(x, y + radius);
814
			ctx.quadraticCurveTo(x, y, x + radius, y);
815
			ctx.closePath();
816
		};
817
818
819
	//Store a reference to each instance - allowing us to globally resize chart instances on window resize.
820
	//Destroy method on the chart will remove the instance of the chart from this reference.
821
	Chart.instances = {};
822
823
	Chart.Type = function(data,options,chart){
824
		this.options = options;
825
		this.chart = chart;
826
		this.id = uid();
827
		//Add the chart instance to the global namespace
828
		Chart.instances[this.id] = this;
829
830
		// Initialize is always called when a chart type is created
831
		// By default it is a no op, but it should be extended
832
		if (options.responsive){
833
			this.resize();
834
		}
835
		this.initialize.call(this,data);
836
	};
837
838
	//Core methods that'll be a part of every chart type
839
	extend(Chart.Type.prototype,{
840
		initialize : function(){return this;},
841
		clear : function(){
842
			clear(this.chart);
843
			return this;
844
		},
845
		stop : function(){
846
			// Stops any current animation loop occuring
847
			cancelAnimFrame(this.animationFrame);
848
			return this;
849
		},
850
		resize : function(callback){
851
			this.stop();
852
			var canvas = this.chart.canvas,
853
				newWidth = getMaximumWidth(this.chart.canvas),
854
				newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);
855
856
			canvas.width = this.chart.width = newWidth;
857
			canvas.height = this.chart.height = newHeight;
858
859
			retinaScale(this.chart);
860
861
			if (typeof callback === "function"){
862
				callback.apply(this, Array.prototype.slice.call(arguments, 1));
863
			}
864
			return this;
865
		},
866
		reflow : noop,
867
		render : function(reflow){
868
			if (reflow){
869
				this.reflow();
870
			}
871
			if (this.options.animation && !reflow){
872
				helpers.animationLoop(
873
					this.draw,
874
					this.options.animationSteps,
875
					this.options.animationEasing,
876
					this.options.onAnimationProgress,
877
					this.options.onAnimationComplete,
878
					this
879
				);
880
			}
881
			else{
882
				this.draw();
883
				this.options.onAnimationComplete.call(this);
884
			}
885
			return this;
886
		},
887
		generateLegend : function(){
888
			return template(this.options.legendTemplate,this);
889
		},
890
		destroy : function(){
891
			this.clear();
892
			unbindEvents(this, this.events);
893
			var canvas = this.chart.canvas;
894
895
			// Reset canvas height/width attributes starts a fresh with the canvas context
896
			canvas.width = this.chart.width;
897
			canvas.height = this.chart.height;
898
899
			// < IE9 doesn't support removeProperty
900
			if (canvas.style.removeProperty) {
901
				canvas.style.removeProperty('width');
902
				canvas.style.removeProperty('height');
903
			} else {
904
				canvas.style.removeAttribute('width');
905
				canvas.style.removeAttribute('height');
906
			}
907
908
			delete Chart.instances[this.id];
909
		},
910
		showTooltip : function(ChartElements, forceRedraw){
911
			// Only redraw the chart if we've actually changed what we're hovering on.
912
			if (typeof this.activeElements === 'undefined') this.activeElements = [];
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
913
914
			var isChanged = (function(Elements){
915
				var changed = false;
916
917
				if (Elements.length !== this.activeElements.length){
918
					changed = true;
919
					return changed;
920
				}
921
922
				each(Elements, function(element, index){
923
					if (element !== this.activeElements[index]){
924
						changed = true;
925
					}
926
				}, this);
927
				return changed;
928
			}).call(this, ChartElements);
929
930
			if (!isChanged && !forceRedraw){
931
				return;
932
			}
933
			else{
934
				this.activeElements = ChartElements;
935
			}
936
			this.draw();
937
			if(this.options.customTooltips){
938
				this.options.customTooltips(false);
939
			}
940
			if (ChartElements.length > 0){
941
				// If we have multiple datasets, show a MultiTooltip for all of the data points at that index
942
				if (this.datasets && this.datasets.length > 1) {
943
					var dataArray,
944
						dataIndex;
945
946
					for (var i = this.datasets.length - 1; i >= 0; i--) {
947
						dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments;
948
						dataIndex = indexOf(dataArray, ChartElements[0]);
949
						if (dataIndex !== -1){
950
							break;
951
						}
952
					}
953
					var tooltipLabels = [],
954
						tooltipColors = [],
955
						medianPosition = (function(index) {
0 ignored issues
show
Unused Code introduced by
The parameter index is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
956
957
							// Get all the points at that particular index
958
							var Elements = [],
959
								dataCollection,
960
								xPositions = [],
961
								yPositions = [],
962
								xMax,
963
								yMax,
964
								xMin,
965
								yMin;
966
							helpers.each(this.datasets, function(dataset){
967
								dataCollection = dataset.points || dataset.bars || dataset.segments;
968
								if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){
0 ignored issues
show
Bug introduced by
The variable dataIndex seems to not be initialized for all possible execution paths.
Loading history...
969
									Elements.push(dataCollection[dataIndex]);
970
								}
971
							});
972
973
							helpers.each(Elements, function(element) {
974
								xPositions.push(element.x);
975
								yPositions.push(element.y);
976
977
978
								//Include any colour information about the element
979
								tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element));
980
								tooltipColors.push({
981
									fill: element._saved.fillColor || element.fillColor,
982
									stroke: element._saved.strokeColor || element.strokeColor
983
								});
984
985
							}, this);
986
987
							yMin = min(yPositions);
988
							yMax = max(yPositions);
989
990
							xMin = min(xPositions);
991
							xMax = max(xPositions);
992
993
							return {
994
								x: (xMin > this.chart.width/2) ? xMin : xMax,
995
								y: (yMin + yMax)/2
996
							};
997
						}).call(this, dataIndex);
0 ignored issues
show
Bug introduced by
The variable dataIndex seems to not be initialized for all possible execution paths. Are you sure call handles undefined variables?
Loading history...
998
999
					new Chart.MultiTooltip({
1000
						x: medianPosition.x,
1001
						y: medianPosition.y,
1002
						xPadding: this.options.tooltipXPadding,
1003
						yPadding: this.options.tooltipYPadding,
1004
						xOffset: this.options.tooltipXOffset,
1005
						fillColor: this.options.tooltipFillColor,
1006
						textColor: this.options.tooltipFontColor,
1007
						fontFamily: this.options.tooltipFontFamily,
1008
						fontStyle: this.options.tooltipFontStyle,
1009
						fontSize: this.options.tooltipFontSize,
1010
						titleTextColor: this.options.tooltipTitleFontColor,
1011
						titleFontFamily: this.options.tooltipTitleFontFamily,
1012
						titleFontStyle: this.options.tooltipTitleFontStyle,
1013
						titleFontSize: this.options.tooltipTitleFontSize,
1014
						cornerRadius: this.options.tooltipCornerRadius,
1015
						labels: tooltipLabels,
1016
						legendColors: tooltipColors,
1017
						legendColorBackground : this.options.multiTooltipKeyBackground,
1018
						title: ChartElements[0].label,
1019
						chart: this.chart,
1020
						ctx: this.chart.ctx,
1021
						custom: this.options.customTooltips
1022
					}).draw();
1023
1024
				} else {
1025
					each(ChartElements, function(Element) {
1026
						var tooltipPosition = Element.tooltipPosition();
1027
						new Chart.Tooltip({
1028
							x: Math.round(tooltipPosition.x),
1029
							y: Math.round(tooltipPosition.y),
1030
							xPadding: this.options.tooltipXPadding,
1031
							yPadding: this.options.tooltipYPadding,
1032
							fillColor: this.options.tooltipFillColor,
1033
							textColor: this.options.tooltipFontColor,
1034
							fontFamily: this.options.tooltipFontFamily,
1035
							fontStyle: this.options.tooltipFontStyle,
1036
							fontSize: this.options.tooltipFontSize,
1037
							caretHeight: this.options.tooltipCaretSize,
1038
							cornerRadius: this.options.tooltipCornerRadius,
1039
							text: template(this.options.tooltipTemplate, Element),
1040
							chart: this.chart,
1041
							custom: this.options.customTooltips
1042
						}).draw();
1043
					}, this);
1044
				}
1045
			}
1046
			return this;
1047
		},
1048
		toBase64Image : function(){
1049
			return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
1050
		}
1051
	});
1052
1053
	Chart.Type.extend = function(extensions){
1054
1055
		var parent = this;
1056
1057
		var ChartType = function(){
1058
			return parent.apply(this,arguments);
1059
		};
1060
1061
		//Copy the prototype object of the this class
1062
		ChartType.prototype = clone(parent.prototype);
1063
		//Now overwrite some of the properties in the base class with the new extensions
1064
		extend(ChartType.prototype, extensions);
1065
1066
		ChartType.extend = Chart.Type.extend;
1067
1068
		if (extensions.name || parent.prototype.name){
1069
1070
			var chartName = extensions.name || parent.prototype.name;
1071
			//Assign any potential default values of the new chart type
1072
1073
			//If none are defined, we'll use a clone of the chart type this is being extended from.
1074
			//I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart
1075
			//doesn't define some defaults of their own.
1076
1077
			var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};
1078
1079
			Chart.defaults[chartName] = extend(baseDefaults,extensions.defaults);
1080
1081
			Chart.types[chartName] = ChartType;
1082
1083
			//Register this new chart type in the Chart prototype
1084
			Chart.prototype[chartName] = function(data,options){
1085
				var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {});
1086
				return new ChartType(data,config,this);
1087
			};
1088
		} else{
1089
			warn("Name not provided for this chart, so it hasn't been registered");
1090
		}
1091
		return parent;
1092
	};
1093
1094
	Chart.Element = function(configuration){
1095
		extend(this,configuration);
1096
		this.initialize.apply(this,arguments);
1097
		this.save();
1098
	};
1099
	extend(Chart.Element.prototype,{
1100
		initialize : function(){},
1101
		restore : function(props){
1102
			if (!props){
1103
				extend(this,this._saved);
1104
			} else {
1105
				each(props,function(key){
1106
					this[key] = this._saved[key];
1107
				},this);
1108
			}
1109
			return this;
1110
		},
1111
		save : function(){
1112
			this._saved = clone(this);
1113
			delete this._saved._saved;
1114
			return this;
1115
		},
1116
		update : function(newProps){
1117
			each(newProps,function(value,key){
1118
				this._saved[key] = this[key];
1119
				this[key] = value;
1120
			},this);
1121
			return this;
1122
		},
1123
		transition : function(props,ease){
1124
			each(props,function(value,key){
1125
				this[key] = ((value - this._saved[key]) * ease) + this._saved[key];
1126
			},this);
1127
			return this;
1128
		},
1129
		tooltipPosition : function(){
1130
			return {
1131
				x : this.x,
1132
				y : this.y
1133
			};
1134
		},
1135
		hasValue: function(){
1136
			return isNumber(this.value);
1137
		}
1138
	});
1139
1140
	Chart.Element.extend = inherits;
1141
1142
1143
	Chart.Point = Chart.Element.extend({
1144
		display: true,
1145
		inRange: function(chartX,chartY){
1146
			var hitDetectionRange = this.hitDetectionRadius + this.radius;
1147
			return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2));
1148
		},
1149
		draw : function(){
1150
			if (this.display){
1151
				var ctx = this.ctx;
1152
				ctx.beginPath();
1153
1154
				ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
1155
				ctx.closePath();
1156
1157
				ctx.strokeStyle = this.strokeColor;
1158
				ctx.lineWidth = this.strokeWidth;
1159
1160
				ctx.fillStyle = this.fillColor;
1161
1162
				ctx.fill();
1163
				ctx.stroke();
1164
			}
1165
1166
1167
			//Quick debug for bezier curve splining
1168
			//Highlights control points and the line between them.
1169
			//Handy for dev - stripped in the min version.
1170
1171
			// ctx.save();
1172
			// ctx.fillStyle = "black";
1173
			// ctx.strokeStyle = "black"
1174
			// ctx.beginPath();
1175
			// ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2);
1176
			// ctx.fill();
1177
1178
			// ctx.beginPath();
1179
			// ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2);
1180
			// ctx.fill();
1181
1182
			// ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);
1183
			// ctx.lineTo(this.x, this.y);
1184
			// ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);
1185
			// ctx.stroke();
1186
1187
			// ctx.restore();
1188
1189
1190
1191
		}
1192
	});
1193
1194
	Chart.Arc = Chart.Element.extend({
1195
		inRange : function(chartX,chartY){
1196
1197
			var pointRelativePosition = helpers.getAngleFromPoint(this, {
1198
				x: chartX,
1199
				y: chartY
1200
			});
1201
1202
			//Check if within the range of the open/close angle
1203
			var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle),
1204
				withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius);
1205
1206
			return (betweenAngles && withinRadius);
1207
			//Ensure within the outside of the arc centre, but inside arc outer
1208
		},
1209
		tooltipPosition : function(){
1210
			var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2),
1211
				rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius;
1212
			return {
1213
				x : this.x + (Math.cos(centreAngle) * rangeFromCentre),
1214
				y : this.y + (Math.sin(centreAngle) * rangeFromCentre)
1215
			};
1216
		},
1217
		draw : function(animationPercent){
1218
1219
			var easingDecimal = animationPercent || 1;
0 ignored issues
show
Unused Code introduced by
The variable easingDecimal seems to be never used. Consider removing it.
Loading history...
1220
1221
			var ctx = this.ctx;
1222
1223
			ctx.beginPath();
1224
1225
			ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle);
1226
1227
			ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true);
1228
1229
			ctx.closePath();
1230
			ctx.strokeStyle = this.strokeColor;
1231
			ctx.lineWidth = this.strokeWidth;
1232
1233
			ctx.fillStyle = this.fillColor;
1234
1235
			ctx.fill();
1236
			ctx.lineJoin = 'bevel';
1237
1238
			if (this.showStroke){
1239
				ctx.stroke();
1240
			}
1241
		}
1242
	});
1243
1244
	Chart.Rectangle = Chart.Element.extend({
1245
		draw : function(){
1246
			var ctx = this.ctx,
1247
				halfWidth = this.width/2,
1248
				leftX = this.x - halfWidth,
1249
				rightX = this.x + halfWidth,
1250
				top = this.base - (this.base - this.y),
1251
				halfStroke = this.strokeWidth / 2;
1252
1253
			// Canvas doesn't allow us to stroke inside the width so we can
1254
			// adjust the sizes to fit if we're setting a stroke on the line
1255
			if (this.showStroke){
1256
				leftX += halfStroke;
1257
				rightX -= halfStroke;
1258
				top += halfStroke;
1259
			}
1260
1261
			ctx.beginPath();
1262
1263
			ctx.fillStyle = this.fillColor;
1264
			ctx.strokeStyle = this.strokeColor;
1265
			ctx.lineWidth = this.strokeWidth;
1266
1267
			// It'd be nice to keep this class totally generic to any rectangle
1268
			// and simply specify which border to miss out.
1269
			ctx.moveTo(leftX, this.base);
1270
			ctx.lineTo(leftX, top);
1271
			ctx.lineTo(rightX, top);
1272
			ctx.lineTo(rightX, this.base);
1273
			ctx.fill();
1274
			if (this.showStroke){
1275
				ctx.stroke();
1276
			}
1277
		},
1278
		height : function(){
1279
			return this.base - this.y;
1280
		},
1281
		inRange : function(chartX,chartY){
1282
			return (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base);
1283
		}
1284
	});
1285
1286
	Chart.Tooltip = Chart.Element.extend({
1287
		draw : function(){
1288
1289
			var ctx = this.chart.ctx;
1290
1291
			ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
1292
1293
			this.xAlign = "center";
1294
			this.yAlign = "above";
1295
1296
			//Distance between the actual element.y position and the start of the tooltip caret
1297
			var caretPadding = this.caretPadding = 2;
1298
1299
			var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding,
1300
				tooltipRectHeight = this.fontSize + 2*this.yPadding,
1301
				tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding;
1302
1303
			if (this.x + tooltipWidth/2 >this.chart.width){
1304
				this.xAlign = "left";
1305
			} else if (this.x - tooltipWidth/2 < 0){
1306
				this.xAlign = "right";
1307
			}
1308
1309
			if (this.y - tooltipHeight < 0){
1310
				this.yAlign = "below";
1311
			}
1312
1313
1314
			var tooltipX = this.x - tooltipWidth/2,
1315
				tooltipY = this.y - tooltipHeight;
1316
1317
			ctx.fillStyle = this.fillColor;
1318
1319
			// Custom Tooltips
1320
			if(this.custom){
1321
				this.custom(this);
1322
			}
1323
			else{
1324
				switch(this.yAlign)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
1325
				{
1326
				case "above":
1327
					//Draw a caret above the x/y
1328
					ctx.beginPath();
1329
					ctx.moveTo(this.x,this.y - caretPadding);
1330
					ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight));
1331
					ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight));
1332
					ctx.closePath();
1333
					ctx.fill();
1334
					break;
1335
				case "below":
1336
					tooltipY = this.y + caretPadding + this.caretHeight;
1337
					//Draw a caret below the x/y
1338
					ctx.beginPath();
1339
					ctx.moveTo(this.x, this.y + caretPadding);
1340
					ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight);
1341
					ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight);
1342
					ctx.closePath();
1343
					ctx.fill();
1344
					break;
1345
				}
1346
1347
				switch(this.xAlign)
0 ignored issues
show
Coding Style introduced by
As per coding-style, switch statements should have a default case.
Loading history...
1348
				{
1349
				case "left":
1350
					tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight);
1351
					break;
1352
				case "right":
1353
					tooltipX = this.x - (this.cornerRadius + this.caretHeight);
1354
					break;
1355
				}
1356
1357
				drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius);
1358
1359
				ctx.fill();
1360
1361
				ctx.fillStyle = this.textColor;
1362
				ctx.textAlign = "center";
1363
				ctx.textBaseline = "middle";
1364
				ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2);
1365
			}
1366
		}
1367
	});
1368
1369
	Chart.MultiTooltip = Chart.Element.extend({
1370
		initialize : function(){
1371
			this.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
1372
1373
			this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily);
1374
1375
			this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5;
1376
1377
			this.ctx.font = this.titleFont;
1378
1379
			var titleWidth = this.ctx.measureText(this.title).width,
1380
				//Label has a legend square as well so account for this.
1381
				labelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3,
1382
				longestTextWidth = max([labelWidth,titleWidth]);
1383
1384
			this.width = longestTextWidth + (this.xPadding*2);
1385
1386
1387
			var halfHeight = this.height/2;
1388
1389
			//Check to ensure the height will fit on the canvas
1390
			if (this.y - halfHeight < 0 ){
1391
				this.y = halfHeight;
1392
			} else if (this.y + halfHeight > this.chart.height){
1393
				this.y = this.chart.height - halfHeight;
1394
			}
1395
1396
			//Decide whether to align left or right based on position on canvas
1397
			if (this.x > this.chart.width/2){
1398
				this.x -= this.xOffset + this.width;
1399
			} else {
1400
				this.x += this.xOffset;
1401
			}
1402
1403
1404
		},
1405
		getLineHeight : function(index){
1406
			var baseLineHeight = this.y - (this.height/2) + this.yPadding,
1407
				afterTitleIndex = index-1;
1408
1409
			//If the index is zero, we're getting the title
1410
			if (index === 0){
1411
				return baseLineHeight + this.titleFontSize/2;
1412
			} else{
1413
				return baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5;
1414
			}
1415
1416
		},
1417
		draw : function(){
1418
			// Custom Tooltips
1419
			if(this.custom){
1420
				this.custom(this);
1421
			}
1422
			else{
1423
				drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius);
1424
				var ctx = this.ctx;
1425
				ctx.fillStyle = this.fillColor;
1426
				ctx.fill();
1427
				ctx.closePath();
1428
1429
				ctx.textAlign = "left";
1430
				ctx.textBaseline = "middle";
1431
				ctx.fillStyle = this.titleTextColor;
1432
				ctx.font = this.titleFont;
1433
1434
				ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0));
1435
1436
				ctx.font = this.font;
1437
				helpers.each(this.labels,function(label,index){
1438
					ctx.fillStyle = this.textColor;
1439
					ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));
1440
1441
					//A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
1442
					//ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1443
					//Instead we'll make a white filled block to put the legendColour palette over.
1444
1445
					ctx.fillStyle = this.legendColorBackground;
1446
					ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1447
1448
					ctx.fillStyle = this.legendColors[index].fill;
1449
					ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
1450
1451
1452
				},this);
1453
			}
1454
		}
1455
	});
1456
1457
	Chart.Scale = Chart.Element.extend({
1458
		initialize : function(){
1459
			this.fit();
1460
		},
1461
		buildYLabels : function(){
1462
			this.yLabels = [];
1463
1464
			var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
1465
1466
			for (var i=0; i<=this.steps; i++){
1467
				this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
1468
			}
1469
			this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0;
1470
		},
1471
		addXLabel : function(label){
1472
			this.xLabels.push(label);
1473
			this.valuesCount++;
1474
			this.fit();
1475
		},
1476
		removeXLabel : function(){
1477
			this.xLabels.shift();
1478
			this.valuesCount--;
1479
			this.fit();
1480
		},
1481
		// Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use
1482
		fit: function(){
1483
			// First we need the width of the yLabels, assuming the xLabels aren't rotated
1484
1485
			// To do that we need the base line at the top and base of the chart, assuming there is no x label rotation
1486
			this.startPoint = (this.display) ? this.fontSize : 0;
1487
			this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels
1488
1489
			// Apply padding settings to the start and end point.
1490
			this.startPoint += this.padding;
1491
			this.endPoint -= this.padding;
1492
1493
			// Cache the starting height, so can determine if we need to recalculate the scale yAxis
1494
			var cachedHeight = this.endPoint - this.startPoint,
1495
				cachedYLabelWidth;
1496
1497
			// Build the current yLabels so we have an idea of what size they'll be to start
1498
			/*
1499
			 *	This sets what is returned from calculateScaleRange as static properties of this class:
1500
			 *
1501
				this.steps;
1502
				this.stepValue;
1503
				this.min;
1504
				this.max;
1505
			 *
1506
			 */
1507
			this.calculateYRange(cachedHeight);
1508
1509
			// With these properties set we can now build the array of yLabels
1510
			// and also the width of the largest yLabel
1511
			this.buildYLabels();
1512
1513
			this.calculateXLabelRotation();
1514
1515
			while((cachedHeight > this.endPoint - this.startPoint)){
1516
				cachedHeight = this.endPoint - this.startPoint;
1517
				cachedYLabelWidth = this.yLabelWidth;
1518
1519
				this.calculateYRange(cachedHeight);
1520
				this.buildYLabels();
1521
1522
				// Only go through the xLabel loop again if the yLabel width has changed
1523
				if (cachedYLabelWidth < this.yLabelWidth){
1524
					this.calculateXLabelRotation();
1525
				}
1526
			}
1527
1528
		},
1529
		calculateXLabelRotation : function(){
1530
			//Get the width of each grid by calculating the difference
1531
			//between x offsets between 0 and 1.
1532
1533
			this.ctx.font = this.font;
1534
1535
			var firstWidth = this.ctx.measureText(this.xLabels[0]).width,
1536
				lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,
1537
				firstRotated,
1538
				lastRotated;
1539
1540
1541
			this.xScalePaddingRight = lastWidth/2 + 3;
1542
			this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10;
1543
1544
			this.xLabelRotation = 0;
1545
			if (this.display){
1546
				var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels),
1547
					cosRotation,
1548
					firstRotatedWidth;
0 ignored issues
show
Unused Code introduced by
The variable firstRotatedWidth seems to be never used. Consider removing it.
Loading history...
1549
				this.xLabelWidth = originalLabelWidth;
1550
				//Allow 3 pixels x2 padding either side for label readability
1551
				var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;
1552
1553
				//Max label rotate should be 90 - also act as a loop counter
1554
				while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){
1555
					cosRotation = Math.cos(toRadians(this.xLabelRotation));
1556
1557
					firstRotated = cosRotation * firstWidth;
1558
					lastRotated = cosRotation * lastWidth;
0 ignored issues
show
Unused Code introduced by
The variable lastRotated seems to be never used. Consider removing it.
Loading history...
1559
1560
					// We're right aligning the text now.
1561
					if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){
1562
						this.xScalePaddingLeft = firstRotated + this.fontSize / 2;
1563
					}
1564
					this.xScalePaddingRight = this.fontSize/2;
1565
1566
1567
					this.xLabelRotation++;
1568
					this.xLabelWidth = cosRotation * originalLabelWidth;
1569
1570
				}
1571
				if (this.xLabelRotation > 0){
1572
					this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3;
1573
				}
1574
			}
1575
			else{
1576
				this.xLabelWidth = 0;
1577
				this.xScalePaddingRight = this.padding;
1578
				this.xScalePaddingLeft = this.padding;
1579
			}
1580
1581
		},
1582
		// Needs to be overidden in each Chart type
1583
		// Otherwise we need to pass all the data into the scale class
1584
		calculateYRange: noop,
1585
		drawingArea: function(){
1586
			return this.startPoint - this.endPoint;
1587
		},
1588
		calculateY : function(value){
1589
			var scalingFactor = this.drawingArea() / (this.min - this.max);
1590
			return this.endPoint - (scalingFactor * (value - this.min));
1591
		},
1592
		calculateX : function(index){
1593
			var isRotated = (this.xLabelRotation > 0),
0 ignored issues
show
Unused Code introduced by
The variable isRotated seems to be never used. Consider removing it.
Loading history...
1594
				// innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
1595
				innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
1596
				valueWidth = innerWidth/Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1),
1597
				valueOffset = (valueWidth * index) + this.xScalePaddingLeft;
1598
1599
			if (this.offsetGridLines){
1600
				valueOffset += (valueWidth/2);
1601
			}
1602
1603
			return Math.round(valueOffset);
1604
		},
1605
		update : function(newProps){
1606
			helpers.extend(this, newProps);
1607
			this.fit();
1608
		},
1609
		draw : function(){
1610
			var ctx = this.ctx,
1611
				yLabelGap = (this.endPoint - this.startPoint) / this.steps,
1612
				xStart = Math.round(this.xScalePaddingLeft);
1613
			if (this.display){
1614
				ctx.fillStyle = this.textColor;
1615
				ctx.font = this.font;
1616
				each(this.yLabels,function(labelString,index){
1617
					var yLabelCenter = this.endPoint - (yLabelGap * index),
1618
						linePositionY = Math.round(yLabelCenter),
1619
						drawHorizontalLine = this.showHorizontalLines;
1620
1621
					ctx.textAlign = "right";
1622
					ctx.textBaseline = "middle";
1623
					if (this.showLabels){
1624
						ctx.fillText(labelString,xStart - 10,yLabelCenter);
1625
					}
1626
1627
					// This is X axis, so draw it
1628
					if (index === 0 && !drawHorizontalLine){
1629
						drawHorizontalLine = true;
1630
					}
1631
1632
					if (drawHorizontalLine){
1633
						ctx.beginPath();
1634
					}
1635
1636
					if (index > 0){
1637
						// This is a grid line in the centre, so drop that
1638
						ctx.lineWidth = this.gridLineWidth;
1639
						ctx.strokeStyle = this.gridLineColor;
1640
					} else {
1641
						// This is the first line on the scale
1642
						ctx.lineWidth = this.lineWidth;
1643
						ctx.strokeStyle = this.lineColor;
1644
					}
1645
1646
					linePositionY += helpers.aliasPixel(ctx.lineWidth);
1647
1648
					if(drawHorizontalLine){
1649
						ctx.moveTo(xStart, linePositionY);
1650
						ctx.lineTo(this.width, linePositionY);
1651
						ctx.stroke();
1652
						ctx.closePath();
1653
					}
1654
1655
					ctx.lineWidth = this.lineWidth;
1656
					ctx.strokeStyle = this.lineColor;
1657
					ctx.beginPath();
1658
					ctx.moveTo(xStart - 5, linePositionY);
1659
					ctx.lineTo(xStart, linePositionY);
1660
					ctx.stroke();
1661
					ctx.closePath();
1662
1663
				},this);
1664
1665
				each(this.xLabels,function(label,index){
1666
					var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
1667
						// Check to see if line/bar here and decide where to place the line
1668
						linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
1669
						isRotated = (this.xLabelRotation > 0),
1670
						drawVerticalLine = this.showVerticalLines;
1671
1672
					// This is Y axis, so draw it
1673
					if (index === 0 && !drawVerticalLine){
1674
						drawVerticalLine = true;
1675
					}
1676
1677
					if (drawVerticalLine){
1678
						ctx.beginPath();
1679
					}
1680
1681
					if (index > 0){
1682
						// This is a grid line in the centre, so drop that
1683
						ctx.lineWidth = this.gridLineWidth;
1684
						ctx.strokeStyle = this.gridLineColor;
1685
					} else {
1686
						// This is the first line on the scale
1687
						ctx.lineWidth = this.lineWidth;
1688
						ctx.strokeStyle = this.lineColor;
1689
					}
1690
1691
					if (drawVerticalLine){
1692
						ctx.moveTo(linePos,this.endPoint);
1693
						ctx.lineTo(linePos,this.startPoint - 3);
1694
						ctx.stroke();
1695
						ctx.closePath();
1696
					}
1697
1698
1699
					ctx.lineWidth = this.lineWidth;
1700
					ctx.strokeStyle = this.lineColor;
1701
1702
1703
					// Small lines at the bottom of the base grid line
1704
					ctx.beginPath();
1705
					ctx.moveTo(linePos,this.endPoint);
1706
					ctx.lineTo(linePos,this.endPoint + 5);
1707
					ctx.stroke();
1708
					ctx.closePath();
1709
1710
					ctx.save();
1711
					ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8);
1712
					ctx.rotate(toRadians(this.xLabelRotation)*-1);
1713
					ctx.font = this.font;
1714
					ctx.textAlign = (isRotated) ? "right" : "center";
1715
					ctx.textBaseline = (isRotated) ? "middle" : "top";
1716
					ctx.fillText(label, 0, 0);
1717
					ctx.restore();
1718
				},this);
1719
1720
			}
1721
		}
1722
1723
	});
1724
1725
	Chart.RadialScale = Chart.Element.extend({
1726
		initialize: function(){
1727
			this.size = min([this.height, this.width]);
1728
			this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
1729
		},
1730
		calculateCenterOffset: function(value){
1731
			// Take into account half font size + the yPadding of the top value
1732
			var scalingFactor = this.drawingArea / (this.max - this.min);
1733
1734
			return (value - this.min) * scalingFactor;
1735
		},
1736
		update : function(){
1737
			if (!this.lineArc){
1738
				this.setScaleSize();
1739
			} else {
1740
				this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
1741
			}
1742
			this.buildYLabels();
1743
		},
1744
		buildYLabels: function(){
1745
			this.yLabels = [];
1746
1747
			var stepDecimalPlaces = getDecimalPlaces(this.stepValue);
1748
1749
			for (var i=0; i<=this.steps; i++){
1750
				this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
1751
			}
1752
		},
1753
		getCircumference : function(){
1754
			return ((Math.PI*2) / this.valuesCount);
1755
		},
1756
		setScaleSize: function(){
1757
			/*
1758
			 * Right, this is really confusing and there is a lot of maths going on here
1759
			 * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
1760
			 *
1761
			 * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
1762
			 *
1763
			 * Solution:
1764
			 *
1765
			 * We assume the radius of the polygon is half the size of the canvas at first
1766
			 * at each index we check if the text overlaps.
1767
			 *
1768
			 * Where it does, we store that angle and that index.
1769
			 *
1770
			 * After finding the largest index and angle we calculate how much we need to remove
1771
			 * from the shape radius to move the point inwards by that x.
1772
			 *
1773
			 * We average the left and right distances to get the maximum shape radius that can fit in the box
1774
			 * along with labels.
1775
			 *
1776
			 * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
1777
			 * on each side, removing that from the size, halving it and adding the left x protrusion width.
1778
			 *
1779
			 * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
1780
			 * and position it in the most space efficient manner
1781
			 *
1782
			 * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
1783
			 */
1784
1785
1786
			// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
1787
			// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
1788
			var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]),
1789
				pointPosition,
1790
				i,
1791
				textWidth,
1792
				halfTextWidth,
1793
				furthestRight = this.width,
1794
				furthestRightIndex,
1795
				furthestRightAngle,
1796
				furthestLeft = 0,
1797
				furthestLeftIndex,
1798
				furthestLeftAngle,
1799
				xProtrusionLeft,
1800
				xProtrusionRight,
1801
				radiusReductionRight,
1802
				radiusReductionLeft,
1803
				maxWidthRadius;
0 ignored issues
show
Unused Code introduced by
The variable maxWidthRadius seems to be never used. Consider removing it.
Loading history...
1804
			this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
1805
			for (i=0;i<this.valuesCount;i++){
1806
				// 5px to space the text slightly out - similar to what we do in the draw function.
1807
				pointPosition = this.getPointPosition(i, largestPossibleRadius);
1808
				textWidth = this.ctx.measureText(template(this.templateString, { value: this.labels[i] })).width + 5;
1809
				if (i === 0 || i === this.valuesCount/2){
1810
					// If we're at index zero, or exactly the middle, we're at exactly the top/bottom
1811
					// of the radar chart, so text will be aligned centrally, so we'll half it and compare
1812
					// w/left and right text sizes
1813
					halfTextWidth = textWidth/2;
1814
					if (pointPosition.x + halfTextWidth > furthestRight) {
1815
						furthestRight = pointPosition.x + halfTextWidth;
1816
						furthestRightIndex = i;
1817
					}
1818
					if (pointPosition.x - halfTextWidth < furthestLeft) {
1819
						furthestLeft = pointPosition.x - halfTextWidth;
1820
						furthestLeftIndex = i;
1821
					}
1822
				}
1823
				else if (i < this.valuesCount/2) {
1824
					// Less than half the values means we'll left align the text
1825
					if (pointPosition.x + textWidth > furthestRight) {
1826
						furthestRight = pointPosition.x + textWidth;
1827
						furthestRightIndex = i;
1828
					}
1829
				}
1830
				else if (i > this.valuesCount/2){
1831
					// More than half the values means we'll right align the text
1832
					if (pointPosition.x - textWidth < furthestLeft) {
1833
						furthestLeft = pointPosition.x - textWidth;
1834
						furthestLeftIndex = i;
1835
					}
1836
				}
1837
			}
1838
1839
			xProtrusionLeft = furthestLeft;
1840
1841
			xProtrusionRight = Math.ceil(furthestRight - this.width);
1842
1843
			furthestRightAngle = this.getIndexAngle(furthestRightIndex);
0 ignored issues
show
Bug introduced by
The variable furthestRightIndex seems to not be initialized for all possible execution paths. Are you sure getIndexAngle handles undefined variables?
Loading history...
1844
1845
			furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
0 ignored issues
show
Bug introduced by
The variable furthestLeftIndex seems to not be initialized for all possible execution paths. Are you sure getIndexAngle handles undefined variables?
Loading history...
1846
1847
			radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2);
1848
1849
			radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2);
1850
1851
			// Ensure we actually need to reduce the size of the chart
1852
			radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
1853
			radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;
1854
1855
			this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2;
1856
1857
			//this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
1858
			this.setCenterPoint(radiusReductionLeft, radiusReductionRight);
1859
1860
		},
1861
		setCenterPoint: function(leftMovement, rightMovement){
1862
1863
			var maxRight = this.width - rightMovement - this.drawingArea,
1864
				maxLeft = leftMovement + this.drawingArea;
1865
1866
			this.xCenter = (maxLeft + maxRight)/2;
1867
			// Always vertically in the centre as the text height doesn't change
1868
			this.yCenter = (this.height/2);
1869
		},
1870
1871
		getIndexAngle : function(index){
1872
			var angleMultiplier = (Math.PI * 2) / this.valuesCount;
1873
			// Start from the top instead of right, so remove a quarter of the circle
1874
1875
			return index * angleMultiplier - (Math.PI/2);
1876
		},
1877
		getPointPosition : function(index, distanceFromCenter){
1878
			var thisAngle = this.getIndexAngle(index);
1879
			return {
1880
				x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
1881
				y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
1882
			};
1883
		},
1884
		draw: function(){
1885
			if (this.display){
1886
				var ctx = this.ctx;
1887
				each(this.yLabels, function(label, index){
1888
					// Don't draw a centre value
1889
					if (index > 0){
1890
						var yCenterOffset = index * (this.drawingArea/this.steps),
1891
							yHeight = this.yCenter - yCenterOffset,
1892
							pointPosition;
1893
1894
						// Draw circular lines around the scale
1895
						if (this.lineWidth > 0){
1896
							ctx.strokeStyle = this.lineColor;
1897
							ctx.lineWidth = this.lineWidth;
1898
1899
							if(this.lineArc){
1900
								ctx.beginPath();
1901
								ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2);
1902
								ctx.closePath();
1903
								ctx.stroke();
1904
							} else{
1905
								ctx.beginPath();
1906
								for (var i=0;i<this.valuesCount;i++)
1907
								{
1908
									pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue)));
1909
									if (i === 0){
1910
										ctx.moveTo(pointPosition.x, pointPosition.y);
1911
									} else {
1912
										ctx.lineTo(pointPosition.x, pointPosition.y);
1913
									}
1914
								}
1915
								ctx.closePath();
1916
								ctx.stroke();
1917
							}
1918
						}
1919
						if(this.showLabels){
1920
							ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
1921
							if (this.showLabelBackdrop){
1922
								var labelWidth = ctx.measureText(label).width;
1923
								ctx.fillStyle = this.backdropColor;
1924
								ctx.fillRect(
1925
									this.xCenter - labelWidth/2 - this.backdropPaddingX,
1926
									yHeight - this.fontSize/2 - this.backdropPaddingY,
1927
									labelWidth + this.backdropPaddingX*2,
1928
									this.fontSize + this.backdropPaddingY*2
1929
								);
1930
							}
1931
							ctx.textAlign = 'center';
1932
							ctx.textBaseline = "middle";
1933
							ctx.fillStyle = this.fontColor;
1934
							ctx.fillText(label, this.xCenter, yHeight);
1935
						}
1936
					}
1937
				}, this);
1938
1939
				if (!this.lineArc){
1940
					ctx.lineWidth = this.angleLineWidth;
1941
					ctx.strokeStyle = this.angleLineColor;
1942
					for (var i = this.valuesCount - 1; i >= 0; i--) {
1943
						if (this.angleLineWidth > 0){
1944
							var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max));
1945
							ctx.beginPath();
1946
							ctx.moveTo(this.xCenter, this.yCenter);
1947
							ctx.lineTo(outerPosition.x, outerPosition.y);
1948
							ctx.stroke();
1949
							ctx.closePath();
1950
						}
1951
						// Extra 3px out for some label spacing
1952
						var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
1953
						ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
1954
						ctx.fillStyle = this.pointLabelFontColor;
1955
1956
						var labelsCount = this.labels.length,
1957
							halfLabelsCount = this.labels.length/2,
1958
							quarterLabelsCount = halfLabelsCount/2,
1959
							upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
1960
							exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
1961
						if (i === 0){
1962
							ctx.textAlign = 'center';
1963
						} else if(i === halfLabelsCount){
1964
							ctx.textAlign = 'center';
1965
						} else if (i < halfLabelsCount){
1966
							ctx.textAlign = 'left';
1967
						} else {
1968
							ctx.textAlign = 'right';
1969
						}
1970
1971
						// Set the correct text baseline based on outer positioning
1972
						if (exactQuarter){
1973
							ctx.textBaseline = 'middle';
1974
						} else if (upperHalf){
1975
							ctx.textBaseline = 'bottom';
1976
						} else {
1977
							ctx.textBaseline = 'top';
1978
						}
1979
1980
						ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
1981
					}
1982
				}
1983
			}
1984
		}
1985
	});
1986
1987
	// Attach global event to resize each chart instance when the browser resizes
1988
	helpers.addEvent(window, "resize", (function(){
1989
		// Basic debounce of resize function so it doesn't hurt performance when resizing browser.
1990
		var timeout;
1991
		return function(){
1992
			clearTimeout(timeout);
1993
			timeout = setTimeout(function(){
1994
				each(Chart.instances,function(instance){
1995
					// If the responsive flag is set in the chart instance config
1996
					// Cascade the resize event down to the chart.
1997
					if (instance.options.responsive){
1998
						instance.resize(instance.render, true);
1999
					}
2000
				});
2001
			}, 50);
2002
		};
2003
	})());
2004
2005
2006
	if (amd) {
2007
		define(function(){
2008
			return Chart;
2009
		});
2010
	} else if (typeof module === 'object' && module.exports) {
2011
		module.exports = Chart;
2012
	}
2013
2014
	root.Chart = Chart;
2015
2016
	Chart.noConflict = function(){
2017
		root.Chart = previous;
2018
		return Chart;
2019
	};
2020
2021
}).call(this);
2022
2023
(function(){
2024
	"use strict";
2025
2026
	var root = this,
2027
		Chart = root.Chart,
2028
		helpers = Chart.helpers;
2029
2030
2031
	var defaultConfig = {
2032
		//Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
2033
		scaleBeginAtZero : true,
2034
2035
		//Boolean - Whether grid lines are shown across the chart
2036
		scaleShowGridLines : true,
2037
2038
		//String - Colour of the grid lines
2039
		scaleGridLineColor : "rgba(0,0,0,.05)",
2040
2041
		//Number - Width of the grid lines
2042
		scaleGridLineWidth : 1,
2043
2044
		//Boolean - Whether to show horizontal lines (except X axis)
2045
		scaleShowHorizontalLines: true,
2046
2047
		//Boolean - Whether to show vertical lines (except Y axis)
2048
		scaleShowVerticalLines: true,
2049
2050
		//Boolean - If there is a stroke on each bar
2051
		barShowStroke : true,
2052
2053
		//Number - Pixel width of the bar stroke
2054
		barStrokeWidth : 2,
2055
2056
		//Number - Spacing between each of the X value sets
2057
		barValueSpacing : 5,
2058
2059
		//Number - Spacing between data sets within X values
2060
		barDatasetSpacing : 1,
2061
2062
		//String - A legend template
2063
		legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].fillColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"
2064
2065
	};
2066
2067
2068
	Chart.Type.extend({
2069
		name: "Bar",
2070
		defaults : defaultConfig,
2071
		initialize:  function(data){
2072
2073
			//Expose options as a scope variable here so we can access it in the ScaleClass
2074
			var options = this.options;
2075
2076
			this.ScaleClass = Chart.Scale.extend({
2077
				offsetGridLines : true,
2078
				calculateBarX : function(datasetCount, datasetIndex, barIndex){
2079
					//Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar
2080
					var xWidth = this.calculateBaseWidth(),
2081
						xAbsolute = this.calculateX(barIndex) - (xWidth/2),
2082
						barWidth = this.calculateBarWidth(datasetCount);
2083
2084
					return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth/2;
2085
				},
2086
				calculateBaseWidth : function(){
2087
					return (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing);
2088
				},
2089
				calculateBarWidth : function(datasetCount){
2090
					//The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
2091
					var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing);
2092
2093
					return (baseWidth / datasetCount);
2094
				}
2095
			});
2096
2097
			this.datasets = [];
2098
2099
			//Set up tooltip events on the chart
2100
			if (this.options.showTooltips){
2101
				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
2102
					var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : [];
2103
2104
					this.eachBars(function(bar){
2105
						bar.restore(['fillColor', 'strokeColor']);
2106
					});
2107
					helpers.each(activeBars, function(activeBar){
2108
						activeBar.fillColor = activeBar.highlightFill;
2109
						activeBar.strokeColor = activeBar.highlightStroke;
2110
					});
2111
					this.showTooltip(activeBars);
2112
				});
2113
			}
2114
2115
			//Declare the extension of the default point, to cater for the options passed in to the constructor
2116
			this.BarClass = Chart.Rectangle.extend({
2117
				strokeWidth : this.options.barStrokeWidth,
2118
				showStroke : this.options.barShowStroke,
2119
				ctx : this.chart.ctx
2120
			});
2121
2122
			//Iterate through each of the datasets, and build this into a property of the chart
2123
			helpers.each(data.datasets,function(dataset,datasetIndex){
0 ignored issues
show
Unused Code introduced by
The parameter datasetIndex is not used and could be removed.

This check looks for parameters in functions that are not used in the function body and are not followed by other parameters which are used inside the function.

Loading history...
2124
2125
				var datasetObject = {
2126
					label : dataset.label || null,
2127
					fillColor : dataset.fillColor,
2128
					strokeColor : dataset.strokeColor,
2129
					bars : []
2130
				};
2131
2132
				this.datasets.push(datasetObject);
2133
2134
				helpers.each(dataset.data,function(dataPoint,index){
2135
					//Add a new point for each piece of data, passing any required data to draw.
2136
					datasetObject.bars.push(new this.BarClass({
2137
						value : dataPoint,
2138
						label : data.labels[index],
2139
						datasetLabel: dataset.label,
2140
						strokeColor : dataset.strokeColor,
2141
						fillColor : dataset.fillColor,
2142
						highlightFill : dataset.highlightFill || dataset.fillColor,
2143
						highlightStroke : dataset.highlightStroke || dataset.strokeColor
2144
					}));
2145
				},this);
2146
2147
			},this);
2148
2149
			this.buildScale(data.labels);
2150
2151
			this.BarClass.prototype.base = this.scale.endPoint;
2152
2153
			this.eachBars(function(bar, index, datasetIndex){
2154
				helpers.extend(bar, {
2155
					width : this.scale.calculateBarWidth(this.datasets.length),
2156
					x: this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
2157
					y: this.scale.endPoint
2158
				});
2159
				bar.save();
2160
			}, this);
2161
2162
			this.render();
2163
		},
2164
		update : function(){
2165
			this.scale.update();
2166
			// Reset any highlight colours before updating.
2167
			helpers.each(this.activeElements, function(activeElement){
2168
				activeElement.restore(['fillColor', 'strokeColor']);
2169
			});
2170
2171
			this.eachBars(function(bar){
2172
				bar.save();
2173
			});
2174
			this.render();
2175
		},
2176
		eachBars : function(callback){
2177
			helpers.each(this.datasets,function(dataset, datasetIndex){
2178
				helpers.each(dataset.bars, callback, this, datasetIndex);
2179
			},this);
2180
		},
2181
		getBarsAtEvent : function(e){
2182
			var barsArray = [],
2183
				eventPosition = helpers.getRelativePosition(e),
2184
				datasetIterator = function(dataset){
2185
					barsArray.push(dataset.bars[barIndex]);
2186
				},
2187
				barIndex;
2188
2189
			for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) {
2190
				for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) {
2191
					if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){
2192
						helpers.each(this.datasets, datasetIterator);
2193
						return barsArray;
2194
					}
2195
				}
2196
			}
2197
2198
			return barsArray;
2199
		},
2200
		buildScale : function(labels){
2201
			var self = this;
2202
2203
			var dataTotal = function(){
2204
				var values = [];
2205
				self.eachBars(function(bar){
2206
					values.push(bar.value);
2207
				});
2208
				return values;
2209
			};
2210
2211
			var scaleOptions = {
2212
				templateString : this.options.scaleLabel,
2213
				height : this.chart.height,
2214
				width : this.chart.width,
2215
				ctx : this.chart.ctx,
2216
				textColor : this.options.scaleFontColor,
2217
				fontSize : this.options.scaleFontSize,
2218
				fontStyle : this.options.scaleFontStyle,
2219
				fontFamily : this.options.scaleFontFamily,
2220
				valuesCount : labels.length,
2221
				beginAtZero : this.options.scaleBeginAtZero,
2222
				integersOnly : this.options.scaleIntegersOnly,
2223
				calculateYRange: function(currentHeight){
2224
					var updatedRanges = helpers.calculateScaleRange(
2225
						dataTotal(),
2226
						currentHeight,
2227
						this.fontSize,
2228
						this.beginAtZero,
2229
						this.integersOnly
2230
					);
2231
					helpers.extend(this, updatedRanges);
2232
				},
2233
				xLabels : labels,
2234
				font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
2235
				lineWidth : this.options.scaleLineWidth,
2236
				lineColor : this.options.scaleLineColor,
2237
				showHorizontalLines : this.options.scaleShowHorizontalLines,
2238
				showVerticalLines : this.options.scaleShowVerticalLines,
2239
				gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
2240
				gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
2241
				padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0,
2242
				showLabels : this.options.scaleShowLabels,
2243
				display : this.options.showScale
2244
			};
2245
2246
			if (this.options.scaleOverride){
2247
				helpers.extend(scaleOptions, {
2248
					calculateYRange: helpers.noop,
2249
					steps: this.options.scaleSteps,
2250
					stepValue: this.options.scaleStepWidth,
2251
					min: this.options.scaleStartValue,
2252
					max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
2253
				});
2254
			}
2255
2256
			this.scale = new this.ScaleClass(scaleOptions);
2257
		},
2258
		addData : function(valuesArray,label){
2259
			//Map the values array for each of the datasets
2260
			helpers.each(valuesArray,function(value,datasetIndex){
2261
				//Add a new point for each piece of data, passing any required data to draw.
2262
				this.datasets[datasetIndex].bars.push(new this.BarClass({
2263
					value : value,
2264
					label : label,
2265
					x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1),
2266
					y: this.scale.endPoint,
2267
					width : this.scale.calculateBarWidth(this.datasets.length),
2268
					base : this.scale.endPoint,
2269
					strokeColor : this.datasets[datasetIndex].strokeColor,
2270
					fillColor : this.datasets[datasetIndex].fillColor
2271
				}));
2272
			},this);
2273
2274
			this.scale.addXLabel(label);
2275
			//Then re-render the chart.
2276
			this.update();
2277
		},
2278
		removeData : function(){
2279
			this.scale.removeXLabel();
2280
			//Then re-render the chart.
2281
			helpers.each(this.datasets,function(dataset){
2282
				dataset.bars.shift();
2283
			},this);
2284
			this.update();
2285
		},
2286
		reflow : function(){
2287
			helpers.extend(this.BarClass.prototype,{
2288
				y: this.scale.endPoint,
2289
				base : this.scale.endPoint
2290
			});
2291
			var newScaleProps = helpers.extend({
2292
				height : this.chart.height,
2293
				width : this.chart.width
2294
			});
2295
			this.scale.update(newScaleProps);
2296
		},
2297
		draw : function(ease){
2298
			var easingDecimal = ease || 1;
2299
			this.clear();
2300
2301
			var ctx = this.chart.ctx;
0 ignored issues
show
Unused Code introduced by
The assignment to variable ctx seems to be never used. Consider removing it.
Loading history...
2302
2303
			this.scale.draw(easingDecimal);
2304
2305
			//Draw all the bars for each dataset
2306
			helpers.each(this.datasets,function(dataset,datasetIndex){
2307
				helpers.each(dataset.bars,function(bar,index){
2308
					if (bar.hasValue()){
2309
						bar.base = this.scale.endPoint;
2310
						//Transition then draw
2311
						bar.transition({
2312
							x : this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
2313
							y : this.scale.calculateY(bar.value),
2314
							width : this.scale.calculateBarWidth(this.datasets.length)
2315
						}, easingDecimal).draw();
2316
					}
2317
				},this);
2318
2319
			},this);
2320
		}
2321
	});
2322
2323
2324
}).call(this);
2325
2326
(function(){
2327
	"use strict";
2328
2329
	var root = this,
2330
		Chart = root.Chart,
2331
		//Cache a local reference to Chart.helpers
2332
		helpers = Chart.helpers;
2333
2334
	var defaultConfig = {
2335
		//Boolean - Whether we should show a stroke on each segment
2336
		segmentShowStroke : true,
2337
2338
		//String - The colour of each segment stroke
2339
		segmentStrokeColor : "#fff",
2340
2341
		//Number - The width of each segment stroke
2342
		segmentStrokeWidth : 2,
2343
2344
		//The percentage of the chart that we cut out of the middle.
2345
		percentageInnerCutout : 50,
2346
2347
		//Number - Amount of animation steps
2348
		animationSteps : 100,
2349
2350
		//String - Animation easing effect
2351
		animationEasing : "easeOutBounce",
2352
2353
		//Boolean - Whether we animate the rotation of the Doughnut
2354
		animateRotate : true,
2355
2356
		//Boolean - Whether we animate scaling the Doughnut from the centre
2357
		animateScale : false,
2358
2359
		//String - A legend template
2360
		legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"
2361
2362
	};
2363
2364
2365
	Chart.Type.extend({
2366
		//Passing in a name registers this chart in the Chart namespace
2367
		name: "Doughnut",
2368
		//Providing a defaults will also register the deafults in the chart namespace
2369
		defaults : defaultConfig,
2370
		//Initialize is fired when the chart is initialized - Data is passed in as a parameter
2371
		//Config is automatically merged by the core of Chart.js, and is available at this.options
2372
		initialize:  function(data){
2373
2374
			//Declare segments as a static property to prevent inheriting across the Chart type prototype
2375
			this.segments = [];
2376
			this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) -	this.options.segmentStrokeWidth/2)/2;
2377
2378
			this.SegmentArc = Chart.Arc.extend({
2379
				ctx : this.chart.ctx,
2380
				x : this.chart.width/2,
2381
				y : this.chart.height/2
2382
			});
2383
2384
			//Set up tooltip events on the chart
2385
			if (this.options.showTooltips){
2386
				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
2387
					var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
2388
2389
					helpers.each(this.segments,function(segment){
2390
						segment.restore(["fillColor"]);
2391
					});
2392
					helpers.each(activeSegments,function(activeSegment){
2393
						activeSegment.fillColor = activeSegment.highlightColor;
2394
					});
2395
					this.showTooltip(activeSegments);
2396
				});
2397
			}
2398
			this.calculateTotal(data);
2399
2400
			helpers.each(data,function(datapoint, index){
2401
				this.addData(datapoint, index, true);
2402
			},this);
2403
2404
			this.render();
2405
		},
2406
		getSegmentsAtEvent : function(e){
2407
			var segmentsArray = [];
2408
2409
			var location = helpers.getRelativePosition(e);
2410
2411
			helpers.each(this.segments,function(segment){
2412
				if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
2413
			},this);
2414
			return segmentsArray;
2415
		},
2416
		addData : function(segment, atIndex, silent){
2417
			var index = atIndex || this.segments.length;
2418
			this.segments.splice(index, 0, new this.SegmentArc({
2419
				value : segment.value,
2420
				outerRadius : (this.options.animateScale) ? 0 : this.outerRadius,
2421
				innerRadius : (this.options.animateScale) ? 0 : (this.outerRadius/100) * this.options.percentageInnerCutout,
2422
				fillColor : segment.color,
2423
				highlightColor : segment.highlight || segment.color,
2424
				showStroke : this.options.segmentShowStroke,
2425
				strokeWidth : this.options.segmentStrokeWidth,
2426
				strokeColor : this.options.segmentStrokeColor,
2427
				startAngle : Math.PI * 1.5,
2428
				circumference : (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value),
2429
				label : segment.label
2430
			}));
2431
			if (!silent){
2432
				this.reflow();
2433
				this.update();
2434
			}
2435
		},
2436
		calculateCircumference : function(value){
2437
			return (Math.PI*2)*(Math.abs(value) / this.total);
2438
		},
2439
		calculateTotal : function(data){
2440
			this.total = 0;
2441
			helpers.each(data,function(segment){
2442
				this.total += Math.abs(segment.value);
2443
			},this);
2444
		},
2445
		update : function(){
2446
			this.calculateTotal(this.segments);
2447
2448
			// Reset any highlight colours before updating.
2449
			helpers.each(this.activeElements, function(activeElement){
2450
				activeElement.restore(['fillColor']);
2451
			});
2452
2453
			helpers.each(this.segments,function(segment){
2454
				segment.save();
2455
			});
2456
			this.render();
2457
		},
2458
2459
		removeData: function(atIndex){
2460
			var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
2461
			this.segments.splice(indexToDelete, 1);
2462
			this.reflow();
2463
			this.update();
2464
		},
2465
2466
		reflow : function(){
2467
			helpers.extend(this.SegmentArc.prototype,{
2468
				x : this.chart.width/2,
2469
				y : this.chart.height/2
2470
			});
2471
			this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) -	this.options.segmentStrokeWidth/2)/2;
2472
			helpers.each(this.segments, function(segment){
2473
				segment.update({
2474
					outerRadius : this.outerRadius,
2475
					innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
2476
				});
2477
			}, this);
2478
		},
2479
		draw : function(easeDecimal){
2480
			var animDecimal = (easeDecimal) ? easeDecimal : 1;
2481
			this.clear();
2482
			helpers.each(this.segments,function(segment,index){
2483
				segment.transition({
2484
					circumference : this.calculateCircumference(segment.value),
2485
					outerRadius : this.outerRadius,
2486
					innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
2487
				},animDecimal);
2488
2489
				segment.endAngle = segment.startAngle + segment.circumference;
2490
2491
				segment.draw();
2492
				if (index === 0){
2493
					segment.startAngle = Math.PI * 1.5;
2494
				}
2495
				//Check to see if it's the last segment, if not get the next and update the start angle
2496
				if (index < this.segments.length-1){
2497
					this.segments[index+1].startAngle = segment.endAngle;
2498
				}
2499
			},this);
2500
2501
		}
2502
	});
2503
2504
	Chart.types.Doughnut.extend({
2505
		name : "Pie",
2506
		defaults : helpers.merge(defaultConfig,{percentageInnerCutout : 0})
2507
	});
2508
2509
}).call(this);
2510
(function(){
2511
	"use strict";
2512
2513
	var root = this,
2514
		Chart = root.Chart,
2515
		helpers = Chart.helpers;
2516
2517
	var defaultConfig = {
2518
2519
		///Boolean - Whether grid lines are shown across the chart
2520
		scaleShowGridLines : true,
2521
2522
		//String - Colour of the grid lines
2523
		scaleGridLineColor : "rgba(0,0,0,.05)",
2524
2525
		//Number - Width of the grid lines
2526
		scaleGridLineWidth : 1,
2527
2528
		//Boolean - Whether to show horizontal lines (except X axis)
2529
		scaleShowHorizontalLines: true,
2530
2531
		//Boolean - Whether to show vertical lines (except Y axis)
2532
		scaleShowVerticalLines: true,
2533
2534
		//Boolean - Whether the line is curved between points
2535
		bezierCurve : true,
2536
2537
		//Number - Tension of the bezier curve between points
2538
		bezierCurveTension : 0.4,
2539
2540
		//Boolean - Whether to show a dot for each point
2541
		pointDot : true,
2542
2543
		//Number - Radius of each point dot in pixels
2544
		pointDotRadius : 4,
2545
2546
		//Number - Pixel width of point dot stroke
2547
		pointDotStrokeWidth : 1,
2548
2549
		//Number - amount extra to add to the radius to cater for hit detection outside the drawn point
2550
		pointHitDetectionRadius : 20,
2551
2552
		//Boolean - Whether to show a stroke for datasets
2553
		datasetStroke : true,
2554
2555
		//Number - Pixel width of dataset stroke
2556
		datasetStrokeWidth : 2,
2557
2558
		//Boolean - Whether to fill the dataset with a colour
2559
		datasetFill : true,
2560
2561
		//String - A legend template
2562
		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>"
2563
2564
	};
2565
2566
2567
	Chart.Type.extend({
2568
		name: "Line",
2569
		defaults : defaultConfig,
2570
		initialize:  function(data){
2571
			//Declare the extension of the default point, to cater for the options passed in to the constructor
2572
			this.PointClass = Chart.Point.extend({
2573
				strokeWidth : this.options.pointDotStrokeWidth,
2574
				radius : this.options.pointDotRadius,
2575
				display: this.options.pointDot,
2576
				hitDetectionRadius : this.options.pointHitDetectionRadius,
2577
				ctx : this.chart.ctx,
2578
				inRange : function(mouseX){
2579
					return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2));
2580
				}
2581
			});
2582
2583
			this.datasets = [];
2584
2585
			//Set up tooltip events on the chart
2586
			if (this.options.showTooltips){
2587
				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
2588
					var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
2589
					this.eachPoints(function(point){
2590
						point.restore(['fillColor', 'strokeColor']);
2591
					});
2592
					helpers.each(activePoints, function(activePoint){
2593
						activePoint.fillColor = activePoint.highlightFill;
2594
						activePoint.strokeColor = activePoint.highlightStroke;
2595
					});
2596
					this.showTooltip(activePoints);
2597
				});
2598
			}
2599
2600
			//Iterate through each of the datasets, and build this into a property of the chart
2601
			helpers.each(data.datasets,function(dataset){
2602
2603
				var datasetObject = {
2604
					label : dataset.label || null,
2605
					fillColor : dataset.fillColor,
2606
					strokeColor : dataset.strokeColor,
2607
					pointColor : dataset.pointColor,
2608
					pointStrokeColor : dataset.pointStrokeColor,
2609
					points : []
2610
				};
2611
2612
				this.datasets.push(datasetObject);
2613
2614
2615
				helpers.each(dataset.data,function(dataPoint,index){
2616
					//Add a new point for each piece of data, passing any required data to draw.
2617
					datasetObject.points.push(new this.PointClass({
2618
						value : dataPoint,
2619
						label : data.labels[index],
2620
						datasetLabel: dataset.label,
2621
						strokeColor : dataset.pointStrokeColor,
2622
						fillColor : dataset.pointColor,
2623
						highlightFill : dataset.pointHighlightFill || dataset.pointColor,
2624
						highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
2625
					}));
2626
				},this);
2627
2628
				this.buildScale(data.labels);
2629
2630
2631
				this.eachPoints(function(point, index){
2632
					helpers.extend(point, {
2633
						x: this.scale.calculateX(index),
2634
						y: this.scale.endPoint
2635
					});
2636
					point.save();
2637
				}, this);
2638
2639
			},this);
2640
2641
2642
			this.render();
2643
		},
2644
		update : function(){
2645
			this.scale.update();
2646
			// Reset any highlight colours before updating.
2647
			helpers.each(this.activeElements, function(activeElement){
2648
				activeElement.restore(['fillColor', 'strokeColor']);
2649
			});
2650
			this.eachPoints(function(point){
2651
				point.save();
2652
			});
2653
			this.render();
2654
		},
2655
		eachPoints : function(callback){
2656
			helpers.each(this.datasets,function(dataset){
2657
				helpers.each(dataset.points,callback,this);
2658
			},this);
2659
		},
2660
		getPointsAtEvent : function(e){
2661
			var pointsArray = [],
2662
				eventPosition = helpers.getRelativePosition(e);
2663
			helpers.each(this.datasets,function(dataset){
2664
				helpers.each(dataset.points,function(point){
2665
					if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
2666
				});
2667
			},this);
2668
			return pointsArray;
2669
		},
2670
		buildScale : function(labels){
2671
			var self = this;
2672
2673
			var dataTotal = function(){
2674
				var values = [];
2675
				self.eachPoints(function(point){
2676
					values.push(point.value);
2677
				});
2678
2679
				return values;
2680
			};
2681
2682
			var scaleOptions = {
2683
				templateString : this.options.scaleLabel,
2684
				height : this.chart.height,
2685
				width : this.chart.width,
2686
				ctx : this.chart.ctx,
2687
				textColor : this.options.scaleFontColor,
2688
				fontSize : this.options.scaleFontSize,
2689
				fontStyle : this.options.scaleFontStyle,
2690
				fontFamily : this.options.scaleFontFamily,
2691
				valuesCount : labels.length,
2692
				beginAtZero : this.options.scaleBeginAtZero,
2693
				integersOnly : this.options.scaleIntegersOnly,
2694
				calculateYRange : function(currentHeight){
2695
					var updatedRanges = helpers.calculateScaleRange(
2696
						dataTotal(),
2697
						currentHeight,
2698
						this.fontSize,
2699
						this.beginAtZero,
2700
						this.integersOnly
2701
					);
2702
					helpers.extend(this, updatedRanges);
2703
				},
2704
				xLabels : labels,
2705
				font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
2706
				lineWidth : this.options.scaleLineWidth,
2707
				lineColor : this.options.scaleLineColor,
2708
				showHorizontalLines : this.options.scaleShowHorizontalLines,
2709
				showVerticalLines : this.options.scaleShowVerticalLines,
2710
				gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
2711
				gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
2712
				padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth,
2713
				showLabels : this.options.scaleShowLabels,
2714
				display : this.options.showScale
2715
			};
2716
2717
			if (this.options.scaleOverride){
2718
				helpers.extend(scaleOptions, {
2719
					calculateYRange: helpers.noop,
2720
					steps: this.options.scaleSteps,
2721
					stepValue: this.options.scaleStepWidth,
2722
					min: this.options.scaleStartValue,
2723
					max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
2724
				});
2725
			}
2726
2727
2728
			this.scale = new Chart.Scale(scaleOptions);
2729
		},
2730
		addData : function(valuesArray,label){
2731
			//Map the values array for each of the datasets
2732
2733
			helpers.each(valuesArray,function(value,datasetIndex){
2734
				//Add a new point for each piece of data, passing any required data to draw.
2735
				this.datasets[datasetIndex].points.push(new this.PointClass({
2736
					value : value,
2737
					label : label,
2738
					x: this.scale.calculateX(this.scale.valuesCount+1),
2739
					y: this.scale.endPoint,
2740
					strokeColor : this.datasets[datasetIndex].pointStrokeColor,
2741
					fillColor : this.datasets[datasetIndex].pointColor
2742
				}));
2743
			},this);
2744
2745
			this.scale.addXLabel(label);
2746
			//Then re-render the chart.
2747
			this.update();
2748
		},
2749
		removeData : function(){
2750
			this.scale.removeXLabel();
2751
			//Then re-render the chart.
2752
			helpers.each(this.datasets,function(dataset){
2753
				dataset.points.shift();
2754
			},this);
2755
			this.update();
2756
		},
2757
		reflow : function(){
2758
			var newScaleProps = helpers.extend({
2759
				height : this.chart.height,
2760
				width : this.chart.width
2761
			});
2762
			this.scale.update(newScaleProps);
2763
		},
2764
		draw : function(ease){
2765
			var easingDecimal = ease || 1;
2766
			this.clear();
2767
2768
			var ctx = this.chart.ctx;
2769
2770
			// Some helper methods for getting the next/prev points
2771
			var hasValue = function(item){
2772
				return item.value !== null;
2773
			},
2774
			nextPoint = function(point, collection, index){
2775
				return helpers.findNextWhere(collection, hasValue, index) || point;
2776
			},
2777
			previousPoint = function(point, collection, index){
2778
				return helpers.findPreviousWhere(collection, hasValue, index) || point;
2779
			};
2780
2781
			this.scale.draw(easingDecimal);
2782
2783
2784
			helpers.each(this.datasets,function(dataset){
2785
				var pointsWithValues = helpers.where(dataset.points, hasValue);
2786
2787
				//Transition each point first so that the line and point drawing isn't out of sync
2788
				//We can use this extra loop to calculate the control points of this dataset also in this loop
2789
2790
				helpers.each(dataset.points, function(point, index){
2791
					if (point.hasValue()){
2792
						point.transition({
2793
							y : this.scale.calculateY(point.value),
2794
							x : this.scale.calculateX(index)
2795
						}, easingDecimal);
2796
					}
2797
				},this);
2798
2799
2800
				// Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point
2801
				// This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed
2802
				if (this.options.bezierCurve){
2803
					helpers.each(pointsWithValues, function(point, index){
2804
						var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0;
2805
						point.controlPoints = helpers.splineCurve(
2806
							previousPoint(point, pointsWithValues, index),
2807
							point,
2808
							nextPoint(point, pointsWithValues, index),
2809
							tension
2810
						);
2811
2812
						// Prevent the bezier going outside of the bounds of the graph
2813
2814
						// Cap puter bezier handles to the upper/lower scale bounds
2815
						if (point.controlPoints.outer.y > this.scale.endPoint){
2816
							point.controlPoints.outer.y = this.scale.endPoint;
2817
						}
2818
						else if (point.controlPoints.outer.y < this.scale.startPoint){
2819
							point.controlPoints.outer.y = this.scale.startPoint;
2820
						}
2821
2822
						// Cap inner bezier handles to the upper/lower scale bounds
2823
						if (point.controlPoints.inner.y > this.scale.endPoint){
2824
							point.controlPoints.inner.y = this.scale.endPoint;
2825
						}
2826
						else if (point.controlPoints.inner.y < this.scale.startPoint){
2827
							point.controlPoints.inner.y = this.scale.startPoint;
2828
						}
2829
					},this);
2830
				}
2831
2832
2833
				//Draw the line between all the points
2834
				ctx.lineWidth = this.options.datasetStrokeWidth;
2835
				ctx.strokeStyle = dataset.strokeColor;
2836
				ctx.beginPath();
2837
2838
				helpers.each(pointsWithValues, function(point, index){
2839
					if (index === 0){
2840
						ctx.moveTo(point.x, point.y);
2841
					}
2842
					else{
2843
						if(this.options.bezierCurve){
2844
							var previous = previousPoint(point, pointsWithValues, index);
2845
2846
							ctx.bezierCurveTo(
2847
								previous.controlPoints.outer.x,
2848
								previous.controlPoints.outer.y,
2849
								point.controlPoints.inner.x,
2850
								point.controlPoints.inner.y,
2851
								point.x,
2852
								point.y
2853
							);
2854
						}
2855
						else{
2856
							ctx.lineTo(point.x,point.y);
2857
						}
2858
					}
2859
				}, this);
2860
2861
				ctx.stroke();
2862
2863
				if (this.options.datasetFill && pointsWithValues.length > 0){
2864
					//Round off the line by going to the base of the chart, back to the start, then fill.
2865
					ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint);
2866
					ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint);
2867
					ctx.fillStyle = dataset.fillColor;
2868
					ctx.closePath();
2869
					ctx.fill();
2870
				}
2871
2872
				//Now draw the points over the line
2873
				//A little inefficient double looping, but better than the line
2874
				//lagging behind the point positions
2875
				helpers.each(pointsWithValues,function(point){
2876
					point.draw();
2877
				});
2878
			},this);
2879
		}
2880
	});
2881
2882
2883
}).call(this);
2884
2885
(function(){
2886
	"use strict";
2887
2888
	var root = this,
2889
		Chart = root.Chart,
2890
		//Cache a local reference to Chart.helpers
2891
		helpers = Chart.helpers;
2892
2893
	var defaultConfig = {
2894
		//Boolean - Show a backdrop to the scale label
2895
		scaleShowLabelBackdrop : true,
2896
2897
		//String - The colour of the label backdrop
2898
		scaleBackdropColor : "rgba(255,255,255,0.75)",
2899
2900
		// Boolean - Whether the scale should begin at zero
2901
		scaleBeginAtZero : true,
2902
2903
		//Number - The backdrop padding above & below the label in pixels
2904
		scaleBackdropPaddingY : 2,
2905
2906
		//Number - The backdrop padding to the side of the label in pixels
2907
		scaleBackdropPaddingX : 2,
2908
2909
		//Boolean - Show line for each value in the scale
2910
		scaleShowLine : true,
2911
2912
		//Boolean - Stroke a line around each segment in the chart
2913
		segmentShowStroke : true,
2914
2915
		//String - The colour of the stroke on each segement.
2916
		segmentStrokeColor : "#fff",
2917
2918
		//Number - The width of the stroke value in pixels
2919
		segmentStrokeWidth : 2,
2920
2921
		//Number - Amount of animation steps
2922
		animationSteps : 100,
2923
2924
		//String - Animation easing effect.
2925
		animationEasing : "easeOutBounce",
2926
2927
		//Boolean - Whether to animate the rotation of the chart
2928
		animateRotate : true,
2929
2930
		//Boolean - Whether to animate scaling the chart from the centre
2931
		animateScale : false,
2932
2933
		//String - A legend template
2934
		legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"
2935
	};
2936
2937
2938
	Chart.Type.extend({
2939
		//Passing in a name registers this chart in the Chart namespace
2940
		name: "PolarArea",
2941
		//Providing a defaults will also register the deafults in the chart namespace
2942
		defaults : defaultConfig,
2943
		//Initialize is fired when the chart is initialized - Data is passed in as a parameter
2944
		//Config is automatically merged by the core of Chart.js, and is available at this.options
2945
		initialize:  function(data){
2946
			this.segments = [];
2947
			//Declare segment class as a chart instance specific class, so it can share props for this instance
2948
			this.SegmentArc = Chart.Arc.extend({
2949
				showStroke : this.options.segmentShowStroke,
2950
				strokeWidth : this.options.segmentStrokeWidth,
2951
				strokeColor : this.options.segmentStrokeColor,
2952
				ctx : this.chart.ctx,
2953
				innerRadius : 0,
2954
				x : this.chart.width/2,
2955
				y : this.chart.height/2
2956
			});
2957
			this.scale = new Chart.RadialScale({
2958
				display: this.options.showScale,
2959
				fontStyle: this.options.scaleFontStyle,
2960
				fontSize: this.options.scaleFontSize,
2961
				fontFamily: this.options.scaleFontFamily,
2962
				fontColor: this.options.scaleFontColor,
2963
				showLabels: this.options.scaleShowLabels,
2964
				showLabelBackdrop: this.options.scaleShowLabelBackdrop,
2965
				backdropColor: this.options.scaleBackdropColor,
2966
				backdropPaddingY : this.options.scaleBackdropPaddingY,
2967
				backdropPaddingX: this.options.scaleBackdropPaddingX,
2968
				lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
2969
				lineColor: this.options.scaleLineColor,
2970
				lineArc: true,
2971
				width: this.chart.width,
2972
				height: this.chart.height,
2973
				xCenter: this.chart.width/2,
2974
				yCenter: this.chart.height/2,
2975
				ctx : this.chart.ctx,
2976
				templateString: this.options.scaleLabel,
2977
				valuesCount: data.length
2978
			});
2979
2980
			this.updateScaleRange(data);
2981
2982
			this.scale.update();
2983
2984
			helpers.each(data,function(segment,index){
2985
				this.addData(segment,index,true);
2986
			},this);
2987
2988
			//Set up tooltip events on the chart
2989
			if (this.options.showTooltips){
2990
				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
2991
					var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
2992
					helpers.each(this.segments,function(segment){
2993
						segment.restore(["fillColor"]);
2994
					});
2995
					helpers.each(activeSegments,function(activeSegment){
2996
						activeSegment.fillColor = activeSegment.highlightColor;
2997
					});
2998
					this.showTooltip(activeSegments);
2999
				});
3000
			}
3001
3002
			this.render();
3003
		},
3004
		getSegmentsAtEvent : function(e){
3005
			var segmentsArray = [];
3006
3007
			var location = helpers.getRelativePosition(e);
3008
3009
			helpers.each(this.segments,function(segment){
3010
				if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
0 ignored issues
show
Coding Style Best Practice introduced by
Curly braces around statements make for more readable code and help prevent bugs when you add further statements.

Consider adding curly braces around all statements when they are executed conditionally. This is optional if there is only one statement, but leaving them out can lead to unexpected behaviour if another statement is added later.

Consider:

if (a > 0)
    b = 42;

If you or someone else later decides to put another statement in, only the first statement will be executed.

if (a > 0)
    console.log("a > 0");
    b = 42;

In this case the statement b = 42 will always be executed, while the logging statement will be executed conditionally.

if (a > 0) {
    console.log("a > 0");
    b = 42;
}

ensures that the proper code will be executed conditionally no matter how many statements are added or removed.

Loading history...
3011
			},this);
3012
			return segmentsArray;
3013
		},
3014
		addData : function(segment, atIndex, silent){
3015
			var index = atIndex || this.segments.length;
3016
3017
			this.segments.splice(index, 0, new this.SegmentArc({
3018
				fillColor: segment.color,
3019
				highlightColor: segment.highlight || segment.color,
3020
				label: segment.label,
3021
				value: segment.value,
3022
				outerRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value),
3023
				circumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(),
3024
				startAngle: Math.PI * 1.5
3025
			}));
3026
			if (!silent){
3027
				this.reflow();
3028
				this.update();
3029
			}
3030
		},
3031
		removeData: function(atIndex){
3032
			var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
3033
			this.segments.splice(indexToDelete, 1);
3034
			this.reflow();
3035
			this.update();
3036
		},
3037
		calculateTotal: function(data){
3038
			this.total = 0;
3039
			helpers.each(data,function(segment){
3040
				this.total += segment.value;
3041
			},this);
3042
			this.scale.valuesCount = this.segments.length;
3043
		},
3044
		updateScaleRange: function(datapoints){
3045
			var valuesArray = [];
3046
			helpers.each(datapoints,function(segment){
3047
				valuesArray.push(segment.value);
3048
			});
3049
3050
			var scaleSizes = (this.options.scaleOverride) ?
3051
				{
3052
					steps: this.options.scaleSteps,
3053
					stepValue: this.options.scaleStepWidth,
3054
					min: this.options.scaleStartValue,
3055
					max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
3056
				} :
3057
				helpers.calculateScaleRange(
3058
					valuesArray,
3059
					helpers.min([this.chart.width, this.chart.height])/2,
3060
					this.options.scaleFontSize,
3061
					this.options.scaleBeginAtZero,
3062
					this.options.scaleIntegersOnly
3063
				);
3064
3065
			helpers.extend(
3066
				this.scale,
3067
				scaleSizes,
3068
				{
3069
					size: helpers.min([this.chart.width, this.chart.height]),
3070
					xCenter: this.chart.width/2,
3071
					yCenter: this.chart.height/2
3072
				}
3073
			);
3074
3075
		},
3076
		update : function(){
3077
			this.calculateTotal(this.segments);
3078
3079
			helpers.each(this.segments,function(segment){
3080
				segment.save();
3081
			});
3082
			
3083
			this.reflow();
3084
			this.render();
3085
		},
3086
		reflow : function(){
3087
			helpers.extend(this.SegmentArc.prototype,{
3088
				x : this.chart.width/2,
3089
				y : this.chart.height/2
3090
			});
3091
			this.updateScaleRange(this.segments);
3092
			this.scale.update();
3093
3094
			helpers.extend(this.scale,{
3095
				xCenter: this.chart.width/2,
3096
				yCenter: this.chart.height/2
3097
			});
3098
3099
			helpers.each(this.segments, function(segment){
3100
				segment.update({
3101
					outerRadius : this.scale.calculateCenterOffset(segment.value)
3102
				});
3103
			}, this);
3104
3105
		},
3106
		draw : function(ease){
3107
			var easingDecimal = ease || 1;
3108
			//Clear & draw the canvas
3109
			this.clear();
3110
			helpers.each(this.segments,function(segment, index){
3111
				segment.transition({
3112
					circumference : this.scale.getCircumference(),
3113
					outerRadius : this.scale.calculateCenterOffset(segment.value)
3114
				},easingDecimal);
3115
3116
				segment.endAngle = segment.startAngle + segment.circumference;
3117
3118
				// If we've removed the first segment we need to set the first one to
3119
				// start at the top.
3120
				if (index === 0){
3121
					segment.startAngle = Math.PI * 1.5;
3122
				}
3123
3124
				//Check to see if it's the last segment, if not get the next and update the start angle
3125
				if (index < this.segments.length - 1){
3126
					this.segments[index+1].startAngle = segment.endAngle;
3127
				}
3128
				segment.draw();
3129
			}, this);
3130
			this.scale.draw();
3131
		}
3132
	});
3133
3134
}).call(this);
3135
(function(){
3136
	"use strict";
3137
3138
	var root = this,
3139
		Chart = root.Chart,
3140
		helpers = Chart.helpers;
3141
3142
3143
3144
	Chart.Type.extend({
3145
		name: "Radar",
3146
		defaults:{
3147
			//Boolean - Whether to show lines for each scale point
3148
			scaleShowLine : true,
3149
3150
			//Boolean - Whether we show the angle lines out of the radar
3151
			angleShowLineOut : true,
3152
3153
			//Boolean - Whether to show labels on the scale
3154
			scaleShowLabels : false,
3155
3156
			// Boolean - Whether the scale should begin at zero
3157
			scaleBeginAtZero : true,
3158
3159
			//String - Colour of the angle line
3160
			angleLineColor : "rgba(0,0,0,.1)",
3161
3162
			//Number - Pixel width of the angle line
3163
			angleLineWidth : 1,
3164
3165
			//String - Point label font declaration
3166
			pointLabelFontFamily : "'Arial'",
3167
3168
			//String - Point label font weight
3169
			pointLabelFontStyle : "normal",
3170
3171
			//Number - Point label font size in pixels
3172
			pointLabelFontSize : 10,
3173
3174
			//String - Point label font colour
3175
			pointLabelFontColor : "#666",
3176
3177
			//Boolean - Whether to show a dot for each point
3178
			pointDot : true,
3179
3180
			//Number - Radius of each point dot in pixels
3181
			pointDotRadius : 3,
3182
3183
			//Number - Pixel width of point dot stroke
3184
			pointDotStrokeWidth : 1,
3185
3186
			//Number - amount extra to add to the radius to cater for hit detection outside the drawn point
3187
			pointHitDetectionRadius : 20,
3188
3189
			//Boolean - Whether to show a stroke for datasets
3190
			datasetStroke : true,
3191
3192
			//Number - Pixel width of dataset stroke
3193
			datasetStrokeWidth : 2,
3194
3195
			//Boolean - Whether to fill the dataset with a colour
3196
			datasetFill : true,
3197
3198
			//String - A legend template
3199
			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>"
3200
3201
		},
3202
3203
		initialize: function(data){
3204
			this.PointClass = Chart.Point.extend({
3205
				strokeWidth : this.options.pointDotStrokeWidth,
3206
				radius : this.options.pointDotRadius,
3207
				display: this.options.pointDot,
3208
				hitDetectionRadius : this.options.pointHitDetectionRadius,
3209
				ctx : this.chart.ctx
3210
			});
3211
3212
			this.datasets = [];
3213
3214
			this.buildScale(data);
3215
3216
			//Set up tooltip events on the chart
3217
			if (this.options.showTooltips){
3218
				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
3219
					var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
3220
3221
					this.eachPoints(function(point){
3222
						point.restore(['fillColor', 'strokeColor']);
3223
					});
3224
					helpers.each(activePointsCollection, function(activePoint){
3225
						activePoint.fillColor = activePoint.highlightFill;
3226
						activePoint.strokeColor = activePoint.highlightStroke;
3227
					});
3228
3229
					this.showTooltip(activePointsCollection);
3230
				});
3231
			}
3232
3233
			//Iterate through each of the datasets, and build this into a property of the chart
3234
			helpers.each(data.datasets,function(dataset){
3235
3236
				var datasetObject = {
3237
					label: dataset.label || null,
3238
					fillColor : dataset.fillColor,
3239
					strokeColor : dataset.strokeColor,
3240
					pointColor : dataset.pointColor,
3241
					pointStrokeColor : dataset.pointStrokeColor,
3242
					points : []
3243
				};
3244
3245
				this.datasets.push(datasetObject);
3246
3247
				helpers.each(dataset.data,function(dataPoint,index){
3248
					//Add a new point for each piece of data, passing any required data to draw.
3249
					var pointPosition;
3250
					if (!this.scale.animation){
3251
						pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint));
3252
					}
3253
					datasetObject.points.push(new this.PointClass({
3254
						value : dataPoint,
3255
						label : data.labels[index],
3256
						datasetLabel: dataset.label,
3257
						x: (this.options.animation) ? this.scale.xCenter : pointPosition.x,
0 ignored issues
show
Bug introduced by
The variable pointPosition does not seem to be initialized in case !this.scale.animation on line 3250 is false. Are you sure this can never be the case?
Loading history...
3258
						y: (this.options.animation) ? this.scale.yCenter : pointPosition.y,
3259
						strokeColor : dataset.pointStrokeColor,
3260
						fillColor : dataset.pointColor,
3261
						highlightFill : dataset.pointHighlightFill || dataset.pointColor,
3262
						highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
3263
					}));
3264
				},this);
3265
3266
			},this);
3267
3268
			this.render();
3269
		},
3270
		eachPoints : function(callback){
3271
			helpers.each(this.datasets,function(dataset){
3272
				helpers.each(dataset.points,callback,this);
3273
			},this);
3274
		},
3275
3276
		getPointsAtEvent : function(evt){
3277
			var mousePosition = helpers.getRelativePosition(evt),
3278
				fromCenter = helpers.getAngleFromPoint({
3279
					x: this.scale.xCenter,
3280
					y: this.scale.yCenter
3281
				}, mousePosition);
3282
3283
			var anglePerIndex = (Math.PI * 2) /this.scale.valuesCount,
3284
				pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex),
3285
				activePointsCollection = [];
3286
3287
			// If we're at the top, make the pointIndex 0 to get the first of the array.
3288
			if (pointIndex >= this.scale.valuesCount || pointIndex < 0){
3289
				pointIndex = 0;
3290
			}
3291
3292
			if (fromCenter.distance <= this.scale.drawingArea){
3293
				helpers.each(this.datasets, function(dataset){
3294
					activePointsCollection.push(dataset.points[pointIndex]);
3295
				});
3296
			}
3297
3298
			return activePointsCollection;
3299
		},
3300
3301
		buildScale : function(data){
3302
			this.scale = new Chart.RadialScale({
3303
				display: this.options.showScale,
3304
				fontStyle: this.options.scaleFontStyle,
3305
				fontSize: this.options.scaleFontSize,
3306
				fontFamily: this.options.scaleFontFamily,
3307
				fontColor: this.options.scaleFontColor,
3308
				showLabels: this.options.scaleShowLabels,
3309
				showLabelBackdrop: this.options.scaleShowLabelBackdrop,
3310
				backdropColor: this.options.scaleBackdropColor,
3311
				backdropPaddingY : this.options.scaleBackdropPaddingY,
3312
				backdropPaddingX: this.options.scaleBackdropPaddingX,
3313
				lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
3314
				lineColor: this.options.scaleLineColor,
3315
				angleLineColor : this.options.angleLineColor,
3316
				angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0,
3317
				// Point labels at the edge of each line
3318
				pointLabelFontColor : this.options.pointLabelFontColor,
3319
				pointLabelFontSize : this.options.pointLabelFontSize,
3320
				pointLabelFontFamily : this.options.pointLabelFontFamily,
3321
				pointLabelFontStyle : this.options.pointLabelFontStyle,
3322
				height : this.chart.height,
3323
				width: this.chart.width,
3324
				xCenter: this.chart.width/2,
3325
				yCenter: this.chart.height/2,
3326
				ctx : this.chart.ctx,
3327
				templateString: this.options.scaleLabel,
3328
				labels: data.labels,
3329
				valuesCount: data.datasets[0].data.length
3330
			});
3331
3332
			this.scale.setScaleSize();
3333
			this.updateScaleRange(data.datasets);
3334
			this.scale.buildYLabels();
3335
		},
3336
		updateScaleRange: function(datasets){
3337
			var valuesArray = (function(){
3338
				var totalDataArray = [];
3339
				helpers.each(datasets,function(dataset){
3340
					if (dataset.data){
3341
						totalDataArray = totalDataArray.concat(dataset.data);
3342
					}
3343
					else {
3344
						helpers.each(dataset.points, function(point){
3345
							totalDataArray.push(point.value);
3346
						});
3347
					}
3348
				});
3349
				return totalDataArray;
3350
			})();
3351
3352
3353
			var scaleSizes = (this.options.scaleOverride) ?
3354
				{
3355
					steps: this.options.scaleSteps,
3356
					stepValue: this.options.scaleStepWidth,
3357
					min: this.options.scaleStartValue,
3358
					max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
3359
				} :
3360
				helpers.calculateScaleRange(
3361
					valuesArray,
3362
					helpers.min([this.chart.width, this.chart.height])/2,
3363
					this.options.scaleFontSize,
3364
					this.options.scaleBeginAtZero,
3365
					this.options.scaleIntegersOnly
3366
				);
3367
3368
			helpers.extend(
3369
				this.scale,
3370
				scaleSizes
3371
			);
3372
3373
		},
3374
		addData : function(valuesArray,label){
3375
			//Map the values array for each of the datasets
3376
			this.scale.valuesCount++;
3377
			helpers.each(valuesArray,function(value,datasetIndex){
3378
				var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value));
3379
				this.datasets[datasetIndex].points.push(new this.PointClass({
3380
					value : value,
3381
					label : label,
3382
					x: pointPosition.x,
3383
					y: pointPosition.y,
3384
					strokeColor : this.datasets[datasetIndex].pointStrokeColor,
3385
					fillColor : this.datasets[datasetIndex].pointColor
3386
				}));
3387
			},this);
3388
3389
			this.scale.labels.push(label);
3390
3391
			this.reflow();
3392
3393
			this.update();
3394
		},
3395
		removeData : function(){
3396
			this.scale.valuesCount--;
3397
			this.scale.labels.shift();
3398
			helpers.each(this.datasets,function(dataset){
3399
				dataset.points.shift();
3400
			},this);
3401
			this.reflow();
3402
			this.update();
3403
		},
3404
		update : function(){
3405
			this.eachPoints(function(point){
3406
				point.save();
3407
			});
3408
			this.reflow();
3409
			this.render();
3410
		},
3411
		reflow: function(){
3412
			helpers.extend(this.scale, {
3413
				width : this.chart.width,
3414
				height: this.chart.height,
3415
				size : helpers.min([this.chart.width, this.chart.height]),
3416
				xCenter: this.chart.width/2,
3417
				yCenter: this.chart.height/2
3418
			});
3419
			this.updateScaleRange(this.datasets);
3420
			this.scale.setScaleSize();
3421
			this.scale.buildYLabels();
3422
		},
3423
		draw : function(ease){
3424
			var easeDecimal = ease || 1,
3425
				ctx = this.chart.ctx;
3426
			this.clear();
3427
			this.scale.draw();
3428
3429
			helpers.each(this.datasets,function(dataset){
3430
3431
				//Transition each point first so that the line and point drawing isn't out of sync
3432
				helpers.each(dataset.points,function(point,index){
3433
					if (point.hasValue()){
3434
						point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal);
3435
					}
3436
				},this);
3437
3438
3439
3440
				//Draw the line between all the points
3441
				ctx.lineWidth = this.options.datasetStrokeWidth;
3442
				ctx.strokeStyle = dataset.strokeColor;
3443
				ctx.beginPath();
3444
				helpers.each(dataset.points,function(point,index){
3445
					if (index === 0){
3446
						ctx.moveTo(point.x,point.y);
3447
					}
3448
					else{
3449
						ctx.lineTo(point.x,point.y);
3450
					}
3451
				},this);
3452
				ctx.closePath();
3453
				ctx.stroke();
3454
3455
				ctx.fillStyle = dataset.fillColor;
3456
				ctx.fill();
3457
3458
				//Now draw the points over the line
3459
				//A little inefficient double looping, but better than the line
3460
				//lagging behind the point positions
3461
				helpers.each(dataset.points,function(point){
3462
					if (point.hasValue()){
3463
						point.draw();
3464
					}
3465
				});
3466
3467
			},this);
3468
3469
		}
3470
3471
	});
3472
3473
3474
3475
3476
3477
}).call(this);