PageSourceSearch

https://merubetiri.id/assets/plugins/chartjs/Chart.js

js merubetiri.id collected 2026-09-26 18:02:56 UTC 109,606 bytes, 3,477 lines download raw bytes

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;
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;
48		var height = this.height = context.canvas.height;
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){
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;
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;
221				});
222			});
223			return base;
224		},
225		merge = helpers.merge = function(base,master){
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;
238				}
239				return -1;
240			}
241		},
242		where = helpers.where = function(collection, filterCallback){
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){
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			}
264		},
265		findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex){
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			}
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);
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);
304		},
305		amd = helpers.amd = (typeof define == 'function' && define.amd),
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){
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){
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){
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",
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){
507			var labelsArray = new Array(numberOfSteps);
508			if (labelTemplateString){
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;
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;
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;
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;
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;
579				if (t === 1) return 1;
580				if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1));
581				return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
582			},
583			easeInCirc: function (t) {
584				if (t >= 1) return t;
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);
592				return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
593			},
594			easeInElastic: function (t) {
595				var s = 1.70158;
596				var p = 0;
597				var a = 1;
598				if (t === 0) return 0;
599				if ((t /= 1) == 1) return 1;
600				if (!p) p = 1 * 0.3;
601				if (a < Math.abs(1)) {
602					a = 1;
603					s = p / 4;
604				} else s = p / (2 * Math.PI) * Math.asin(1 / a);
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;
609				var p = 0;
610				var a = 1;
611				if (t === 0) return 0;
612				if ((t /= 1) == 1) return 1;
613				if (!p) p = 1 * 0.3;
614				if (a < Math.abs(1)) {
615					a = 1;
616					s = p / 4;
617				} else s = p / (2 * Math.PI) * Math.asin(1 / a);
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;
622				var p = 0;
623				var a = 1;
624				if (t === 0) return 0;
625				if ((t /= 1 / 2) == 2) return 1;
626				if (!p) p = 1 * (0.3 * 1.5);
627				if (a < Math.abs(1)) {
628					a = 1;
629					s = p / 4;
630				} else s = p / (2 * Math.PI) * Math.asin(1 / a);
631				if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
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));
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;
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){
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){
749			// Create the events object if it's not already present
750			if (!chartInstance.events) chartInstance.events = {};
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
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 = [];
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) {
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()){
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);
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;
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)
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)
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;
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 >
1554 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;
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),
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;
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);
1844
1845			furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);
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){
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[index],
2142						highlightFill : dataset.highlightFill[index],
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;
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}
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);
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);
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);
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,
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);

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.