PageSourceSearch

https://eletricista.adm.br/wp-content/plugins/profit_builder/js/chart.js?ver=1.0

js eletricista.adm.br collected 2026-10-06 13:16:24 UTC 45,116 bytes, 1,457 lines download raw bytes

1/*!
2 * Chart.js
3 * http://chartjs.org/
4 *
5 * Copyright 2013 Nick Downie
6 * Released under the MIT license
7 * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
8 */
9
10//Define the global Chart Variable as a class.
11var Chart = function(context){
12
13	var chart = this;
14	
15	
16	//Easing functions adapted from Robert Penner's easing equations
17	//http://www.robertpenner.com/easing/
18	
19	var animationOptions = {
20		linear : function (t){
21			return t;
22		},
23		easeInQuad: function (t) {
24			return t*t;
25		},
26		easeOutQuad: function (t) {
27			return -1 *t*(t-2);
28		},
29		easeInOutQuad: function (t) {
30			if ((t/=1/2) < 1) return 1/2*t*t;
31			return -1/2 * ((--t)*(t-2) - 1);
32		},
33		easeInCubic: function (t) {
34			return t*t*t;
35		},
36		easeOutCubic: function (t) {
37			return 1*((t=t/1-1)*t*t + 1);
38		},
39		easeInOutCubic: function (t) {
40			if ((t/=1/2) < 1) return 1/2*t*t*t;
41			return 1/2*((t-=2)*t*t + 2);
42		},
43		easeInQuart: function (t) {
44			return t*t*t*t;
45		},
46		easeOutQuart: function (t) {
47			return -1 * ((t=t/1-1)*t*t*t - 1);
48		},
49		easeInOutQuart: function (t) {
50			if ((t/=1/2) < 1) return 1/2*t*t*t*t;
51			return -1/2 * ((t-=2)*t*t*t - 2);
52		},
53		easeInQuint: function (t) {
54			return 1*(t/=1)*t*t*t*t;
55		},
56		easeOutQuint: function (t) {
57			return 1*((t=t/1-1)*t*t*t*t + 1);
58		},
59		easeInOutQuint: function (t) {
60			if ((t/=1/2) < 1) return 1/2*t*t*t*t*t;
61			return 1/2*((t-=2)*t*t*t*t + 2);
62		},
63		easeInSine: function (t) {
64			return -1 * Math.cos(t/1 * (Math.PI/2)) + 1;
65		},
66		easeOutSine: function (t) {
67			return 1 * Math.sin(t/1 * (Math.PI/2));
68		},
69		easeInOutSine: function (t) {
70			return -1/2 * (Math.cos(Math.PI*t/1) - 1);
71		},
72		easeInExpo: function (t) {
73			return (t==0) ? 1 : 1 * Math.pow(2, 10 * (t/1 - 1));
74		},
75		easeOutExpo: function (t) {
76			return (t==1) ? 1 : 1 * (-Math.pow(2, -10 * t/1) + 1);
77		},
78		easeInOutExpo: function (t) {
79			if (t==0) return 0;
80			if (t==1) return 1;
81			if ((t/=1/2) < 1) return 1/2 * Math.pow(2, 10 * (t - 1));
82			return 1/2 * (-Math.pow(2, -10 * --t) + 2);
83			},
84		easeInCirc: function (t) {
85			if (t>=1) return t;
86			return -1 * (Math.sqrt(1 - (t/=1)*t) - 1);
87		},
88		easeOutCirc: function (t) {
89			return 1 * Math.sqrt(1 - (t=t/1-1)*t);
90		},
91		easeInOutCirc: function (t) {
92			if ((t/=1/2) < 1) return -1/2 * (Math.sqrt(1 - t*t) - 1);
93			return 1/2 * (Math.sqrt(1 - (t-=2)*t) + 1);
94		},
95		easeInElastic: function (t) {
96			var s=1.70158;var p=0;var a=1;
97			if (t==0) return 0;  if ((t/=1)==1) return 1;  if (!p) p=1*.3;
98			if (a < Math.abs(1)) { a=1; var s=p/4; }
99			else var s = p/(2*Math.PI) * Math.asin (1/a);
100			return -(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p ));
101		},
102		easeOutElastic: function (t) {
103			var s=1.70158;var p=0;var a=1;
104			if (t==0) return 0;  if ((t/=1)==1) return 1;  if (!p) p=1*.3;
105			if (a < Math.abs(1)) { a=1; var s=p/4; }
106			else var s = p/(2*Math.PI) * Math.asin (1/a);
107			return a*Math.pow(2,-10*t) * Math.sin( (t*1-s)*(2*Math.PI)/p ) + 1;
108		},
109		easeInOutElastic: function (t) {
110			var s=1.70158;var p=0;var a=1;
111			if (t==0) return 0;  if ((t/=1/2)==2) return 1;  if (!p) p=1*(.3*1.5);
112			if (a < Math.abs(1)) { a=1; var s=p/4; }
113			else var s = p/(2*Math.PI) * Math.asin (1/a);
114			if (t < 1) return -.5*(a*Math.pow(2,10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p ));
115			return a*Math.pow(2,-10*(t-=1)) * Math.sin( (t*1-s)*(2*Math.PI)/p )*.5 + 1;
116		},
117		easeInBack: function (t) {
118			var s = 1.70158;
119			return 1*(t/=1)*t*((s+1)*t - s);
120		},
121		easeOutBack: function (t) {
122			var s = 1.70158;
123			return 1*((t=t/1-1)*t*((s+1)*t + s) + 1);
124		},
125		easeInOutBack: function (t) {
126			var s = 1.70158; 
127			if ((t/=1/2) < 1) return 1/2*(t*t*(((s*=(1.525))+1)*t - s));
128			return 1/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2);
129		},
130		easeInBounce: function (t) {
131			return 1 - animationOptions.easeOutBounce (1-t);
132		},
133		easeOutBounce: function (t) {
134			if ((t/=1) < (1/2.75)) {
135				return 1*(7.5625*t*t);
136			} else if (t < (2/2.75)) {
137				return 1*(7.5625*(t-=(1.5/2.75))*t + .75);
138			} else if (t < (2.5/2.75)) {
139				return 1*(7.5625*(t-=(2.25/2.75))*t + .9375);
140			} else {
141				return 1*(7.5625*(t-=(2.625/2.75))*t + .984375);
142			}
143		},
144		easeInOutBounce: function (t) {
145			if (t < 1/2) return animationOptions.easeInBounce (t*2) * .5;
146			return animationOptions.easeOutBounce (t*2-1) * .5 + 1*.5;
147		}
148	};
149
150	
151		var dpr;
152		if(!context.canvas.pixelDencityCalculated) {
153			dpr = 1;
154			context.canvas.pixelDencityCalculated = true;
155		} else {
156			dpr = window.devicePixelRatio;
157		}
158		var width = context.canvas.width;
159		var height = context.canvas.height/dpr;
160
161		
162
163	//High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
164	if (window.devicePixelRatio) {
165	
166		context.canvas.style.width = width + "px";
167		context.canvas.style.height = height + "px";
168		context.canvas.height = height* window.devicePixelRatio;
169		context.canvas.width = width * window.devicePixelRatio;
170		context.scale(window.devicePixelRatio, window.devicePixelRatio);
171	
172	}
173
174	this.PolarArea = function(data,options){
175	
176		chart.PolarArea.defaults = {
177			scaleOverlay : true,
178			scaleOverride : false,
179			scaleSteps : null,
180			scaleStepWidth : null,
181			scaleStartValue : null,
182			scaleShowLine : true,
183			scaleLineColor : "rgba(0,0,0,.1)",
184			scaleLineWidth : 1,
185			scaleShowLabels : true,
186			scaleLabel : "<%=value%>",
187			scaleFontFamily : "'Arial'",
188			scaleFontSize : 12,
189			scaleFontStyle : "normal",
190			scaleFontColor : "#666",
191			scaleShowLabelBackdrop : true,
192			scaleBackdropColor : "rgba(255,255,255,0.75)",
193			scaleBackdropPaddingY : 2,
194			scaleBackdropPaddingX : 2,
195			segmentShowStroke : true,
196			segmentStrokeColor : "#fff",
197			segmentStrokeWidth : 2,
198			animation : true,
199			animationSteps : 100,
200			animationEasing : "easeOutBounce",
201			animateRotate : true,
202			animateScale : false,
203			onAnimationComplete : null
204		};
205		
206		var config = (options)? mergeChartConfig(chart.PolarArea.defaults,options) : chart.PolarArea.defaults;
207		
208		return new PolarArea(data,config,context);
209	};
210
211	this.Radar = function(data,options){
212	
213		chart.Radar.defaults = {
214			scaleOverlay : false,
215			scaleOverride : false,
216			scaleSteps : null,
217			scaleStepWidth : null,
218			scaleStartValue : null,
219			scaleShowLine : true,
220			scaleLineColor : "rgba(0,0,0,.1)",
221			scaleLineWidth : 1,
222			scaleShowLabels : false,
223			scaleLabel : "<%=value%>",
224			scaleFontFamily : "'Arial'",
225			scaleFontSize : 12,
226			scaleFontStyle : "normal",
227			scaleFontColor : "#666",
228			scaleShowLabelBackdrop : true,
229			scaleBackdropColor : "rgba(255,255,255,0.75)",
230			scaleBackdropPaddingY : 2,
231			scaleBackdropPaddingX : 2,
232			angleShowLineOut : true,
233			angleLineColor : "rgba(0,0,0,.1)",
234			angleLineWidth : 1,			
235			pointLabelFontFamily : "'Arial'",
236			pointLabelFontStyle : "normal",
237			pointLabelFontSize : 12,
238			pointLabelFontColor : "#666",
239			pointDot : true,
240			pointDotRadius : 3,
241			pointDotStrokeWidth : 1,
242			datasetStroke : true,
243			datasetStrokeWidth : 2,
244			datasetFill : true,
245			animation : true,
246			animationSteps : 60,
247			animationEasing : "easeOutQuart",
248			onAnimationComplete : null
249		};
250		
251		var config = (options)? mergeChartConfig(chart.Radar.defaults,options) : chart.Radar.defaults;
252
253		return new Radar(data,config,context);
254	};
255	
256	this.Pie = function(data,options){
257		chart.Pie.defaults = {
258			segmentShowStroke : true,
259			segmentStrokeColor : "#fff",
260			segmentStrokeWidth : 2,
261			animation : true,
262			animationSteps : 100,
263			animationEasing : "easeOutBounce",
264			animateRotate : true,
265			animateScale : false,
266			onAnimationComplete : null
267		};		
268
269		var config = (options)? mergeChartConfig(chart.Pie.defaults,options) : chart.Pie.defaults;
270		
271		return new Pie(data,config,context);				
272	};
273	
274	this.Doughnut = function(data,options){
275	
276		chart.Doughnut.defaults = {
277			segmentShowStroke : true,
278			segmentStrokeColor : "#fff",
279			segmentStrokeWidth : 2,
280			percentageInnerCutout : 50,
281			animation : true,
282			animationSteps : 100,
283			animationEasing : "easeOutBounce",
284			animateRotate : true,
285			animateScale : false,
286			onAnimationComplete : null
287		};		
288
289		var config = (options)? mergeChartConfig(chart.Doughnut.defaults,options) : chart.Doughnut.defaults;
290		
291		return new Doughnut(data,config,context);			
292		
293	};
294
295	this.Line = function(data,options){
296	
297		chart.Line.defaults = {
298			scaleOverlay : false,
299			scaleOverride : false,
300			scaleSteps : null,
301			scaleStepWidth : null,
302			scaleStartValue : null,
303			scaleLineColor : "rgba(0,0,0,.1)",
304			scaleLineWidth : 1,
305			scaleShowLabels : true,
306			scaleLabel : "<%=value%>",
307			scaleFontFamily : "'Arial'",
308			scaleFontSize : 12,
309			scaleFontStyle : "normal",
310			scaleFontColor : "#666",
311			scaleShowGridLines : true,
312			scaleGridLineColor : "rgba(0,0,0,.05)",
313			scaleGridLineWidth : 1,
314			bezierCurve : true,
315			pointDot : true,
316			pointDotRadius : 4,
317			pointDotStrokeWidth : 2,
318			datasetStroke : true,
319			datasetStrokeWidth : 2,
320			datasetFill : true,
321			animation : true,
322			animationSteps : 60,
323			animationEasing : "easeOutQuart",
324			onAnimationComplete : null
325		};		
326		var config = (options) ? mergeChartConfig(chart.Line.defaults,options) : chart.Line.defaults;
327		
328		return new Line(data,config,context);
329	}
330	
331	this.Bar = function(data,options){
332		chart.Bar.defaults = {
333			scaleOverlay : false,
334			scaleOverride : false,
335			scaleSteps : null,
336			scaleStepWidth : null,
337			scaleStartValue : null,
338			scaleLineColor : "rgba(0,0,0,.1)",
339			scaleLineWidth : 1,
340			scaleShowLabels : true,
341			scaleLabel : "<%=value%>",
342			scaleFontFamily : "'Arial'",
343			scaleFontSize : 12,
344			scaleFontStyle : "normal",
345			scaleFontColor : "#666",
346			scaleShowGridLines : true,
347			scaleGridLineColor : "rgba(0,0,0,.05)",
348			scaleGridLineWidth : 1,
349			barShowStroke : true,
350			barStrokeWidth : 2,
351			barValueSpacing : 5,
352			barDatasetSpacing : 1,
353			animation : true,
354			animationSteps : 60,
355			animationEasing : "easeOutQuart",
356			onAnimationComplete : null
357		};		
358		var config = (options) ? mergeChartConfig(chart.Bar.defaults,options) : chart.Bar.defaults;
359		
360		return new Bar(data,config,context);		
361	}
362	
363	var clear = function(c){
364		c.clearRect(0, 0, width, height);
365	};
366
367	var PolarArea = function(data,config,ctx){
368		var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString;		
369		
370		
371		calculateDrawingSizes();
372		
373		valueBounds = getValueBounds();
374
375		labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null;
376
377		//Check and set the scale
378		if (!config.scaleOverride){
379			
380			calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
381		}
382		else {
383			calculatedScale = {
384				steps : config.scaleSteps,
385				stepValue : config.scaleStepWidth,
386				graphMin : config.scaleStartValue,
387				labels : []
388			}
389			for (var i=0; i<calculatedScale.steps; i++){
390				if(labelTemplateString){
391				calculatedScale.labels.push(tmpl(labelTemplateString,{value:(config.scaleStartValue + (config.scaleStepWidth * i)).toFixed(getDecimalPlaces (config.scaleStepWidth))}));
392				}
393			}
394		}
395		
396		scaleHop = maxSize/(calculatedScale.steps);
397
398		//Wrap in an animation loop wrapper
399		animationLoop(config,drawScale,drawAllSegments,ctx);
400
401		function calculateDrawingSizes(){
402			maxSize = (Min([width,height])/2);
403			//Remove whatever is larger - the font size or line width.
404			
405			maxSize -= Max([config.scaleFontSize*0.5,config.scaleLineWidth*0.5]);
406			
407			labelHeight = config.scaleFontSize*2;
408			//If we're drawing the backdrop - add the Y padding to the label height and remove from drawing region.
409			if (config.scaleShowLabelBackdrop){
410				labelHeight += (2 * config.scaleBackdropPaddingY);
411				maxSize -= config.scaleBackdropPaddingY*1.5;
412			}
413			
414			scaleHeight = maxSize;
415			//If the label height is less than 5, set it to 5 so we don't have lines on top of each other.
416			labelHeight = Default(labelHeight,5);
417		}
418		function drawScale(){
419			for (var i=0; i<calculatedScale.steps; i++){
420				//If the line object is there
421				if (config.scaleShowLine){
422					ctx.beginPath();
423					ctx.arc(width/2, height/2, scaleHop * (i + 1), 0, (Math.PI * 2), true);
424					ctx.strokeStyle = config.scaleLineColor;
425					ctx.lineWidth = config.scaleLineWidth;
426					ctx.stroke();
427				}
428
429				if (config.scaleShowLabels){
430					ctx.textAlign = "center";
431					ctx.font = config.scaleFontStyle + " " + config.scaleFontSize + "px " + config.scaleFontFamily;
432 					var label =  calculatedScale.labels[i];
433					//If the backdrop object is within the font object
434					if (config.scaleShowLabelBackdrop){
435						var textWidth = ctx.measureText(label).width;
436						ctx.fillStyle = config.scaleBackdropColor;
437						ctx.beginPath();
438						ctx.rect(
439							Math.round(width/2 - textWidth/2 - config.scaleBackdropPaddingX),     //X
440							Math.round(height/2 - (scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y
441							Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width
442							Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height
443						);
444						ctx.fill();
445					}
446					ctx.textBaseline = "middle";
447					ctx.fillStyle = config.scaleFontColor;
448					ctx.fillText(label,width/2,height/2 - (scaleHop * (i + 1)));
449				}
450			}
451		}
452		function drawAllSegments(animationDecimal){
453			var startAngle = -Math.PI/2,
454			angleStep = (Math.PI*2)/data.length,
455			scaleAnimation = 1,
456			rotateAnimation = 1;
457			if (config.animation) {
458				if (config.animateScale) {
459					scaleAnimation = animationDecimal;
460				}
461				if (config.animateRotate){
462					rotateAnimation = animationDecimal;
463				}
464			}
465
466			for (var i=0; i<data.length; i++){
467
468				ctx.beginPath();
469				ctx.arc(width/2,height/2,scaleAnimation * calculateOffset(data[i].value,calculatedScale,scaleHop),startAngle, startAngle + rotateAnimation*angleStep, false);
470				ctx.lineTo(width/2,height/2);
471				ctx.closePath();
472				ctx.fillStyle = data[i].color;
473				ctx.fill();
474
475				if(config.segmentShowStroke){
476					ctx.strokeStyle = config.segmentStrokeColor;
477					ctx.lineWidth = config.segmentStrokeWidth;
478					ctx.stroke();
479				}
480				startAngle += rotateAnimation*angleStep;
481			}
482		}
483		function getValueBounds() {
484			var upperValue = Number.MIN_VALUE;
485			var lowerValue = Number.MAX_VALUE;
486			for (var i=0; i<data.length; i++){
487				if (data[i].value > upperValue) {upperValue = data[i].value;}
488				if (data[i].value < lowerValue) {lowerValue = data[i].value;}
489			};
490
491			var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
492			var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
493			
494			return {
495				maxValue : upperValue,
496				minValue : lowerValue,
497				maxSteps : maxSteps,
498				minSteps : minSteps
499			};
500			
501
502		}
503	}
504
505	var Radar = function (data,config,ctx) {
506		var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString;	
507			
508		//If no labels are defined set to an empty array, so referencing length for looping doesn't blow up.
509		if (!data.labels) data.labels = [];
510		
511		calculateDrawingSizes();
512
513		var valueBounds = getValueBounds();
514
515		labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : null;
516
517		//Check and set the scale
518		if (!config.scaleOverride){
519			
520			calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
521		}
522		else {
523			calculatedScale = {
524				steps : config.scaleSteps,
525				stepValue : config.scaleStepWidth,
526				graphMin : config.scaleStartValue,
527				labels : []
528			}
529			for (var i=0; i<calculatedScale.steps; i++){
530				if(labelTemplateString){
531				calculatedScale.labels.push(tmpl(labelTemplateString,{value:(config.scaleStartValue + (config.scaleStepWidth * i)).toFixed(getDecimalPlaces (config.scaleStepWidth))}));
532				}
533			}
534		}
535		
536		scaleHop = maxSize/(calculatedScale.steps);
537		
538		animationLoop(config,drawScale,drawAllDataPoints,ctx);
539		
540		//Radar specific functions.
541		function drawAllDataPoints(animationDecimal){
542			var rotationDegree = (2*Math.PI)/data.datasets[0].data.length;
543
544			ctx.save();
545			//translate to the centre of the canvas.
546			ctx.translate(width/2,height/2);
547			
548			//We accept multiple data sets for radar charts, so show loop through each set
549			for (var i=0; i<data.datasets.length; i++){
550				ctx.beginPath();
551
552				ctx.moveTo(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[0],calculatedScale,scaleHop)));
553				for (var j=1; j<data.datasets[i].data.length; j++){
554					ctx.rotate(rotationDegree);	
555					ctx.lineTo(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)));
556			
557				}
558				ctx.closePath();
559				
560				
561				ctx.fillStyle = data.datasets[i].fillColor;
562				ctx.strokeStyle = data.datasets[i].strokeColor;
563				ctx.lineWidth = config.datasetStrokeWidth;
564				ctx.fill();
565				ctx.stroke();
566				
567								
568				if (config.pointDot){
569					ctx.fillStyle = data.datasets[i].pointColor;
570					ctx.strokeStyle = data.datasets[i].pointStrokeColor;
571					ctx.lineWidth = config.pointDotStrokeWidth;
572					for (var k=0; k<data.datasets[i].data.length; k++){
573						ctx.rotate(rotationDegree);
574						ctx.beginPath();
575						ctx.arc(0,animationDecimal*(-1*calculateOffset(data.datasets[i].data[k],calculatedScale,scaleHop)),config.pointDotRadius,2*Math.PI,false);
576						ctx.fill();
577						ctx.stroke();
578					}					
579					
580				}
581				ctx.rotate(rotationDegree);
582				
583			}
584			ctx.restore();
585			
586			
587		}
588		function drawScale(){
589			var rotationDegree = (2*Math.PI)/data.datasets[0].data.length;
590			ctx.save();
591		    ctx.translate(width / 2, height / 2);	
592			
593			if (config.angleShowLineOut){
594				ctx.strokeStyle = config.angleLineColor;		    	    
595				ctx.lineWidth = config.angleLineWidth;
596				for (var h=0; h<data.datasets[0].data.length; h++){
597					
598				    ctx.rotate(rotationDegree);
599					ctx.beginPath();
600					ctx.moveTo(0,0);
601					ctx.lineTo(0,-maxSize);
602					ctx.stroke();
603				}
604			}
605
606			for (var i=0; i<calculatedScale.steps; i++){
607				ctx.beginPath();
608				
609				if(config.scaleShowLine){
610					ctx.strokeStyle = config.scaleLineColor;
611					ctx.lineWidth = config.scaleLineWidth;
612					ctx.moveTo(0,-scaleHop * (i+1));					
613					for (var j=0; j<data.datasets[0].data.length; j++){
614					    ctx.rotate(rotationDegree);
615						ctx.lineTo(0,-scaleHop * (i+1));
616					}
617					ctx.closePath();
618					ctx.stroke();			
619							
620				}
621				
622				if (config.scaleShowLabels){				
623					ctx.textAlign = 'center';
624					ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily; 
625					ctx.textBaseline = "middle";
626					
627					if (config.scaleShowLabelBackdrop){
628						var textWidth = ctx.measureText(calculatedScale.labels[i]).width;
629						ctx.fillStyle = config.scaleBackdropColor;
630						ctx.beginPath();
631						ctx.rect(
632							Math.round(- textWidth/2 - config.scaleBackdropPaddingX),     //X
633							Math.round((-scaleHop * (i + 1)) - config.scaleFontSize*0.5 - config.scaleBackdropPaddingY),//Y
634							Math.round(textWidth + (config.scaleBackdropPaddingX*2)), //Width
635							Math.round(config.scaleFontSize + (config.scaleBackdropPaddingY*2)) //Height
636						);
637						ctx.fill();
638					}						
639					ctx.fillStyle = config.scaleFontColor;
640					ctx.fillText(calculatedScale.labels[i],0,-scaleHop*(i+1));
641				}
642
643			}
644			for (var k=0; k<data.labels.length; k++){				
645			ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily;
646			ctx.fillStyle = config.pointLabelFontColor;
647				var opposite = Math.sin(rotationDegree*k) * (maxSize + config.pointLabelFontSize);
648				var adjacent = Math.cos(rotationDegree*k) * (maxSize + config.pointLabelFontSize);
649				
650				if(rotationDegree*k == Math.PI || rotationDegree*k == 0){
651					ctx.textAlign = "center";
652				}
653				else if(rotationDegree*k > Math.PI){
654					ctx.textAlign = "right";
655				}
656				else{
657					ctx.textAlign = "left";
658				}
659				
660				ctx.textBaseline = "middle";
661				
662				ctx.fillText(data.labels[k],opposite,-adjacent);
663				
664			}
665			ctx.restore();
666		};
667		function calculateDrawingSizes(){
668			maxSize = (Min([width,height])/2);
669
670			labelHeight = config.scaleFontSize*2;
671			
672			var labelLength = 0;
673			for (var i=0; i<data.labels.length; i++){
674				ctx.font = config.pointLabelFontStyle + " " + config.pointLabelFontSize+"px " + config.pointLabelFontFamily;
675				var textMeasurement = ctx.measureText(data.labels[i]).width;
676				if(textMeasurement>labelLength) labelLength = textMeasurement;
677			}
678			
679			//Figure out whats the largest - the height of the text or the width of what's there, and minus it from the maximum usable size.
680			maxSize -= Max([labelLength,((config.pointLabelFontSize/2)*1.5)]);				
681			
682			maxSize -= config.pointLabelFontSize;
683			maxSize = CapValue(maxSize, null, 0);
684			scaleHeight = maxSize;
685			//If the label height is less than 5, set it to 5 so we don't have lines on top of each other.
686			labelHeight = Default(labelHeight,5);
687		};
688		function getValueBounds() {
689			var upperValue = Number.MIN_VALUE;
690			var lowerValue = Number.MAX_VALUE;
691			
692			for (var i=0; i<data.datasets.length; i++){
693				for (var j=0; j<data.datasets[i].data.length; j++){
694					if (data.datasets[i].data[j] > upperValue){upperValue = data.datasets[i].data[j]}
695					if (data.datasets[i].data[j] < lowerValue){lowerValue = data.datasets[i].data[j]}
696				}
697			}
698
699			var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
700			var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
701			
702			return {
703				maxValue : upperValue,
704				minValue : lowerValue,
705				maxSteps : maxSteps,
706				minSteps : minSteps
707			};
708			
709
710		}
711	}
712
713	var Pie = function(data,config,ctx){
714		var segmentTotal = 0;
715		
716		//In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge.
717		var pieRadius = Min([height/2,width/2]) - 5;
718		
719		for (var i=0; i<data.length; i++){
720			segmentTotal += data[i].value;
721		}
722		
723		
724		animationLoop(config,null,drawPieSegments,ctx);
725				
726		function drawPieSegments (animationDecimal){
727			var cumulativeAngle = -Math.PI/2,
728			scaleAnimation = 1,
729			rotateAnimation = 1;
730			if (config.animation) {
731				if (config.animateScale) {
732					scaleAnimation = animationDecimal;
733				}
734				if (config.animateRotate){
735					rotateAnimation = animationDecimal;
736				}
737			}
738			for (var i=0; i<data.length; i++){
739				var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2));
740				ctx.beginPath();
741				ctx.arc(width/2,height/2,scaleAnimation * pieRadius,cumulativeAngle,cumulativeAngle + segmentAngle);
742				ctx.lineTo(width/2,height/2);
743				ctx.closePath();
744				ctx.fillStyle = data[i].color;
745				ctx.fill();
746				
747				if(config.segmentShowStroke){
748					ctx.lineWidth = config.segmentStrokeWidth;
749					ctx.strokeStyle = config.segmentStrokeColor;
750					ctx.stroke();
751				}
752				cumulativeAngle += segmentAngle;
753			}			
754		}		
755	}
756
757	var Doughnut = function(data,config,ctx){
758		var segmentTotal = 0;
759		
760		//In case we have a canvas that is not a square. Minus 5 pixels as padding round the edge.
761		var doughnutRadius = Min([height/2,width/2]) - 5;
762		
763		var cutoutRadius = doughnutRadius * (config.percentageInnerCutout/100);
764		
765		for (var i=0; i<data.length; i++){
766			segmentTotal += data[i].value;
767		}
768		
769		
770		animationLoop(config,null,drawPieSegments,ctx);
771		
772		
773		function drawPieSegments (animationDecimal){
774			var cumulativeAngle = -Math.PI/2,
775			scaleAnimation = 1,
776			rotateAnimation = 1;
777			if (config.animation) {
778				if (config.animateScale) {
779					scaleAnimation = animationDecimal;
780				}
781				if (config.animateRotate){
782					rotateAnimation = animationDecimal;
783				}
784			}
785			for (var i=0; i<data.length; i++){
786				var segmentAngle = rotateAnimation * ((data[i].value/segmentTotal) * (Math.PI*2));
787				ctx.beginPath();
788				ctx.arc(width/2,height/2,scaleAnimation * doughnutRadius,cumulativeAngle,cumulativeAngle + segmentAngle,false);
789				ctx.arc(width/2,height/2,scaleAnimation * cutoutRadius,cumulativeAngle + segmentAngle,cumulativeAngle,true);
790				ctx.closePath();
791				ctx.fillStyle = data[i].color;
792				ctx.fill();
793				
794				if(config.segmentShowStroke){
795					ctx.lineWidth = config.segmentStrokeWidth;
796					ctx.strokeStyle = config.segmentStrokeColor;
797					ctx.stroke();
798				}
799				cumulativeAngle += segmentAngle;
800			}			
801		}			
802		
803		
804		
805	}
806
807	var Line = function(data,config,ctx){
808		var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY, rotateLabels = 0;
809			
810		calculateDrawingSizes();
811		
812		valueBounds = getValueBounds();
813		//Check and set the scale
814		labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : "";
815		if (!config.scaleOverride){
816			
817			calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
818		}
819		else {
820			calculatedScale = {
821				steps : config.scaleSteps,
822				stepValue : config.scaleStepWidth,
823				graphMin : config.scaleStartValue,
824				labels : []
825			}
826			for (var i=0; i<calculatedScale.steps; i++){
827				if(labelTemplateString){
828				calculatedScale.labels.push(tmpl(labelTemplateString,{value:(config.scaleStartValue + (config.scaleStepWidth * i)).toFixed(getDecimalPlaces (config.scaleStepWidth))}));
829				}
830			}
831		}
832		
833		scaleHop = Math.floor(scaleHeight/calculatedScale.steps);
834		calculateXAxisSize();
835		animationLoop(config,drawScale,drawLines,ctx);		
836		
837		function drawLines(animPc){
838			for (var i=0; i<data.datasets.length; i++){
839				ctx.strokeStyle = data.datasets[i].strokeColor;
840				ctx.lineWidth = config.datasetStrokeWidth;
841				ctx.beginPath();
842				ctx.moveTo(yAxisPosX, xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[0],c
842alculatedScale,scaleHop)))
843
844				for (var j=1; j<data.datasets[i].data.length; j++){
845					if (config.bezierCurve){
846						ctx.bezierCurveTo(xPos(j-0.5),yPos(i,j-1),xPos(j-0.5),yPos(i,j),xPos(j),yPos(i,j));
847					}
848					else{
849						ctx.lineTo(xPos(j),yPos(i,j));
850					}
851				}
852				ctx.stroke();
853				if (config.datasetFill){
854					ctx.lineTo(yAxisPosX + (valueHop*(data.datasets[i].data.length-1)),xAxisPosY);
855					ctx.lineTo(yAxisPosX,xAxisPosY);
856					ctx.closePath();
857					ctx.fillStyle = data.datasets[i].fillColor;
858					ctx.fill();
859				}
860				else{
861					ctx.closePath();
862				}
863				if(config.pointDot){
864					ctx.fillStyle = data.datasets[i].pointColor;
865					ctx.strokeStyle = data.datasets[i].pointStrokeColor;
866					ctx.lineWidth = config.pointDotStrokeWidth;
867					for (var k=0; k<data.datasets[i].data.length; k++){
868						ctx.beginPath();
869						ctx.arc(yAxisPosX + (valueHop *k),xAxisPosY - animPc*(calculateOffset(data.datasets[i].data[k],calculatedScale,scaleHop)),config.pointDotRadius,0,Math.PI*2,true);
870						ctx.fill();
871						ctx.stroke();
872					}
873				}
874			}
875			
876			function yPos(dataSet,iteration){
877				return xAxisPosY - animPc*(calculateOffset(data.datasets[dataSet].data[iteration],calculatedScale,scaleHop));			
878			}
879			function xPos(iteration){
880				return yAxisPosX + (valueHop * iteration);
881			}
882		}
883		function drawScale(){
884			//X axis line
885			ctx.lineWidth = config.scaleLineWidth;
886			ctx.strokeStyle = config.scaleLineColor;
887			ctx.beginPath();
888			ctx.moveTo(width-widestXLabel/2+5,xAxisPosY);
889			ctx.lineTo(width-(widestXLabel/2)-xAxisLength-5,xAxisPosY);
890			ctx.stroke();
891			
892			
893			if (rotateLabels > 0){
894				ctx.save();
895				ctx.textAlign = "right";
896			}
897			else{
898				ctx.textAlign = "center";
899			}
900			ctx.fillStyle = config.scaleFontColor;
901			for (var i=0; i<data.labels.length; i++){
902				ctx.save();
903				if (rotateLabels > 0){
904					ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize);
905					ctx.rotate(-(rotateLabels * (Math.PI/180)));
906					ctx.fillText(data.labels[i], 0,0);
907					ctx.restore();
908				}
909				
910				else{
911					ctx.fillText(data.labels[i], yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize+3);					
912				}
913
914				ctx.beginPath();
915				ctx.moveTo(yAxisPosX + i * valueHop, xAxisPosY+3);
916				
917				//Check i isnt 0, so we dont go over the Y axis twice.
918				if(config.scaleShowGridLines && i>0){
919					ctx.lineWidth = config.scaleGridLineWidth;
920					ctx.strokeStyle = config.scaleGridLineColor;					
921					ctx.lineTo(yAxisPosX + i * valueHop, 5);
922				}
923				else{
924					ctx.lineTo(yAxisPosX + i * valueHop, xAxisPosY+3);				
925				}
926				ctx.stroke();
927			}
928			
929			//Y axis
930			ctx.lineWidth = config.scaleLineWidth;
931			ctx.strokeStyle = config.scaleLineColor;
932			ctx.beginPath();
933			ctx.moveTo(yAxisPosX,xAxisPosY+5);
934			ctx.lineTo(yAxisPosX,5);
935			ctx.stroke();
936			
937			ctx.textAlign = "right";
938			ctx.textBaseline = "middle";
939			for (var j=0; j<calculatedScale.steps; j++){
940				ctx.beginPath();
941				ctx.moveTo(yAxisPosX-3,xAxisPosY - ((j+1) * scaleHop));
942				if (config.scaleShowGridLines){
943					ctx.lineWidth = config.scaleGridLineWidth;
944					ctx.strokeStyle = config.scaleGridLineColor;
945					ctx.lineTo(yAxisPosX + xAxisLength + 5,xAxisPosY - ((j+1) * scaleHop));					
946				}
947				else{
948					ctx.lineTo(yAxisPosX-0.5,xAxisPosY - ((j+1) * scaleHop));
949				}
950				
951				ctx.stroke();
952				
953				if (config.scaleShowLabels){
954					ctx.fillText(calculatedScale.labels[j],yAxisPosX-8,xAxisPosY - ((j+1) * scaleHop));
955				}
956			}
957			
958			
959		}
960		function calculateXAxisSize(){
961			var longestText = 1;
962			//if we are showing the labels
963			if (config.scaleShowLabels){
964				ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
965				for (var i=0; i<calculatedScale.labels.length; i++){
966					var measuredText = ctx.measureText(calculatedScale.labels[i]).width;
967					longestText = (measuredText > longestText)? measuredText : longestText;
968				}
969				//Add a little extra padding from the y axis
970				longestText +=10;
971			}
972			xAxisLength = width - longestText - widestXLabel;
973			valueHop = Math.floor(xAxisLength/(data.labels.length-1));	
974				
975			yAxisPosX = width-widestXLabel/2-xAxisLength;
976			xAxisPosY = scaleHeight + config.scaleFontSize/2;				
977		}		
978		function calculateDrawingSizes(){
979			maxSize = height;
980
981			//Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees.
982			ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
983			widestXLabel = 1;
984			for (var i=0; i<data.labels.length; i++){
985				var textLength = ctx.measureText(data.labels[i]).width;
986				//If the text length is longer - make that equal to longest text!
987				widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel;
988			}
989			if (width/data.labels.length < widestXLabel){
990				rotateLabels = 45;
991				if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){
992					rotateLabels = 90;
993					maxSize -= widestXLabel; 
994				}
995				else{
996					maxSize -= Math.sin(rotateLabels) * widestXLabel;
997				}
998			}
999			else{
1000				maxSize -= config.scaleFontSize;
1001			}
1002			
1003			//Add a little padding between the x line and the text
1004			maxSize -= 5;
1005			
1006			
1007			labelHeight = config.scaleFontSize;
1008			
1009			maxSize -= labelHeight;
1010			//Set 5 pixels greater than the font size to allow for a little padding from the X axis.
1011			
1012			scaleHeight = maxSize;
1013			
1014			//Then get the area above we can safely draw on.
1015			
1016		}		
1017		function getValueBounds() {
1018			var upperValue = Number.MIN_VALUE;
1019			var lowerValue = Number.MAX_VALUE;
1020			for (var i=0; i<data.datasets.length; i++){
1021				for (var j=0; j<data.datasets[i].data.length; j++){
1022					if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] };
1023					if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] };
1024				}
1025			};
1026	
1027			var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
1028			var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
1029			
1030			return {
1031				maxValue : upperValue,
1032				minValue : lowerValue,
1033				maxSteps : maxSteps,
1034				minSteps : minSteps
1035			};
1036			
1037	
1038		}
1039
1040		
1041	}
1042	
1043	var Bar = function(data,config,ctx){
1044		var maxSize, scaleHop, calculatedScale, labelHeight, scaleHeight, valueBounds, labelTemplateString, valueHop,widestXLabel, xAxisLength,yAxisPosX,xAxisPosY,barWidth, rotateLabels = 0;
1045			
1046		calculateDrawingSizes();
1047		
1048		valueBounds = getValueBounds();
1049		//Check and set the scale
1050		labelTemplateString = (config.scaleShowLabels)? config.scaleLabel : "";
1051		if (!config.scaleOverride){
1052			calculatedScale = calculateScale(scaleHeight,valueBounds.maxSteps,valueBounds.minSteps,valueBounds.maxValue,valueBounds.minValue,labelTemplateString);
1053		}
1054		else {
1055			calculatedScale = {
1056				steps : config.scaleSteps,
1057				stepValue : config.scaleStepWidth,
1058				graphMin : config.scaleStartValue,
1059				labels : []
1060			}
1061			for (var i=0; i<calculatedScale.steps; i++){
1062				if(labelTemplateString){
1063				calculatedScale.labels.push(tmpl(labelTemplateString,{value:(config.scaleStartValue + (config.scaleStepWidth * i)).toFixed(getDecimalPlaces (config.scaleStepWidth))}));
1064				}
1065			}
1066		}
1067		
1068		scaleHop = Math.floor(scaleHeight/calculatedScale.steps);
1069		calculateXAxisSize();
1070		animationLoop(config,drawScale,drawBars,ctx);		
1071		
1072		function drawBars(animPc){
1073			ctx.lineWidth = config.barStrokeWidth;
1074			for (var i=0; i<data.datasets.length; i++){
1075					ctx.fillStyle = data.datasets[i].fillColor;
1076					ctx.strokeStyle = data.datasets[i].strokeColor;
1077				for (var j=0; j<data.datasets[i].data.length; j++){
1078					var barOffset = yAxisPosX + config.barValueSpacing + valueHop*j + barWidth*i + config.barDatasetSpacing*i + config.barStrokeWidth*i;
1079					
1080					ctx.beginPath();
1081					ctx.moveTo(barOffset, xAxisPosY);
1082					ctx.lineTo(barOffset, xAxisPosY - animPc*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2));
1083					ctx.lineTo(barOffset + barWidth, xAxisPosY - animPc*calculateOffset(data.datasets[i].data[j],calculatedScale,scaleHop)+(config.barStrokeWidth/2));
1084					ctx.lineTo(barOffset + barWidth, xAxisPosY);
1085					if(config.barShowStroke){
1086						ctx.stroke();
1087					}
1088					ctx.closePath();
1089					ctx.fill();
1090				}
1091			}
1092			
1093		}
1094		function drawScale(){
1095			//X axis line
1096			ctx.lineWidth = config.scaleLineWidth;
1097			ctx.strokeStyle = config.scaleLineColor;
1098			ctx.beginPath();
1099			ctx.moveTo(width-widestXLabel/2+5,xAxisPosY);
1100			ctx.lineTo(width-(widestXLabel/2)-xAxisLength-5,xAxisPosY);
1101			ctx.stroke();
1102			
1103			
1104			if (rotateLabels > 0){
1105				ctx.save();
1106				ctx.textAlign = "right";
1107			}
1108			else{
1109				ctx.textAlign = "center";
1110			}
1111			ctx.fillStyle = config.scaleFontColor;
1112			for (var i=0; i<data.labels.length; i++){
1113				ctx.save();
1114				if (rotateLabels > 0){
1115					ctx.translate(yAxisPosX + i*valueHop,xAxisPosY + config.scaleFontSize);
1116					ctx.rotate(-(rotateLabels * (Math.PI/180)));
1117					ctx.fillText(data.labels[i], 0,0);
1118					ctx.restore();
1119				}
1120				
1121				else{
1122					ctx.fillText(data.labels[i], yAxisPosX + i*valueHop + valueHop/2,xAxisPosY + config.scaleFontSize+3);					
1123				}
1124
1125				ctx.beginPath();
1126				ctx.moveTo(yAxisPosX + (i+1) * valueHop, xAxisPosY+3);
1127				
1128				//Check i isnt 0, so we dont go over the Y axis twice.
1129					ctx.lineWidth = config.scaleGridLineWidth;
1130					ctx.strokeStyle = config.scaleGridLineColor;					
1131					ctx.lineTo(yAxisPosX + (i+1) * valueHop, 5);
1132				ctx.stroke();
1133			}
1134			
1135			//Y axis
1136			ctx.lineWidth = config.scaleLineWidth;
1137			ctx.strokeStyle = config.scaleLineColor;
1138			ctx.beginPath();
1139			ctx.moveTo(yAxisPosX,xAxisPosY+5);
1140			ctx.lineTo(yAxisPosX,5);
1141			ctx.stroke();
1142			
1143			ctx.textAlign = "right";
1144			ctx.textBaseline = "middle";
1145			for (var j=0; j<calculatedScale.steps; j++){
1146				ctx.beginPath();
1147				ctx.moveTo(yAxisPosX-3,xAxisPosY - ((j+1) * scaleHop));
1148				if (config.scaleShowGridLines){
1149					ctx.lineWidth = config.scaleGridLineWidth;
1150					ctx.strokeStyle = config.scaleGridLineColor;
1151					ctx.lineTo(yAxisPosX + xAxisLength + 5,xAxisPosY - ((j+1) * scaleHop));					
1152				}
1153				else{
1154					ctx.lineTo(yAxisPosX-0.5,xAxisPosY - ((j+1) * scaleHop));
1155				}
1156				
1157				ctx.stroke();
1158				if (config.scaleShowLabels){
1159					ctx.fillText(calculatedScale.labels[j],yAxisPosX-8,xAxisPosY - ((j+1) * scaleHop));
1160				}
1161			}
1162			
1163			
1164		}
1165		function calculateXAxisSize(){
1166			var longestText = 1;
1167			//if we are showing the labels
1168			if (config.scaleShowLabels){
1169				ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
1170				for (var i=0; i<calculatedScale.labels.length; i++){
1171					var measuredText = ctx.measureText(calculatedScale.labels[i]).width;
1172					longestText = (measuredText > longestText)? measuredText : longestText;
1173				}
1174				//Add a little extra padding from the y axis
1175				longestText +=10;
1176			}
1177			xAxisLength = width - longestText - widestXLabel;
1178			valueHop = Math.floor(xAxisLength/(data.labels.length));	
1179			
1180			barWidth = (valueHop - config.scaleGridLineWidth*2 - (config.barValueSpacing*2) - (config.barDatasetSpacing*data.datasets.length-1) - ((config.barStrokeWidth/2)*data.datasets.length-1))/data.datasets.length;
1181			
1182			yAxisPosX = width-widestXLabel/2-xAxisLength;
1183			xAxisPosY = scaleHeight + config.scaleFontSize/2;				
1184		}		
1185		function calculateDrawingSizes(){
1186			maxSize = height;
1187
1188			//Need to check the X axis first - measure the length of each text metric, and figure out if we need to rotate by 45 degrees.
1189			ctx.font = config.scaleFontStyle + " " + config.scaleFontSize+"px " + config.scaleFontFamily;
1190			widestXLabel = 1;
1191			for (var i=0; i<data.labels.length; i++){
1192				var textLength = ctx.measureText(data.labels[i]).width;
1193				//If the text length is longer - make that equal to longest text!
1194				widestXLabel = (textLength > widestXLabel)? textLength : widestXLabel;
1195			}
1196			if (width/data.labels.length < widestXLabel){
1197				rotateLabels = 45;
1198				if (width/data.labels.length < Math.cos(rotateLabels) * widestXLabel){
1199					rotateLabels = 90;
1200					maxSize -= widestXLabel; 
1201				}
1202				else{
1203					maxSize -= Math.sin(rotateLabels) * widestXLabel;
1204				}
1205			}
1206			else{
1207				maxSize -= config.scaleFontSize;
1208			}
1209			
1210			//Add a little padding between the x line and the text
1211			maxSize -= 5;
1212			
1213			
1214			labelHeight = config.scaleFontSize;
1215			
1216			maxSize -= labelHeight;
1217			//Set 5 pixels greater than the font size to allow for a little padding from the X axis.
1218			
1219			scaleHeight = maxSize;
1220			
1221			//Then get the area above we can safely draw on.
1222			
1223		}		
1224		function getValueBounds() {
1225			var upperValue = Number.MIN_VALUE;
1226			var lowerValue = Number.MAX_VALUE;
1227			for (var i=0; i<data.datasets.length; i++){
1228				for (var j=0; j<data.datasets[i].data.length; j++){
1229					if ( data.datasets[i].data[j] > upperValue) { upperValue = data.datasets[i].data[j] };
1230					if ( data.datasets[i].data[j] < lowerValue) { lowerValue = data.datasets[i].data[j] };
1231				}
1232			};
1233	
1234			var maxSteps = Math.floor((scaleHeight / (labelHeight*0.66)));
1235			var minSteps = Math.floor((scaleHeight / labelHeight*0.5));
1236			
1237			return {
1238				maxValue : upperValue,
1239				minValue : lowerValue,
1240				maxSteps : maxSteps,
1241				minSteps : minSteps
1242			};
1243			
1244	
1245		}
1246	}
1247	
1248	function calculateOffset(val,calculatedScale,scaleHop){
1249		var outerValue = calculatedScale.steps * calculatedScale.stepValue;
1250		var adjustedValue = val - calculatedScale.graphMin;
1251		var scalingFactor = CapValue(adjustedValue/outerValue,1,0);
1252		return (scaleHop*calculatedScale.steps) * scalingFactor;
1253	}
1254	
1255	function animationLoop(config,drawScale,drawData,ctx){
1256		var animFrameAmount = (config.animation)? 1/CapValue(config.animationSteps,Number.MAX_VALUE,1) : 1,
1257			easingFunction = animationOptions[config.animationEasing],
1258			percentAnimComplete =(config.animation)? 0 : 1;
1259		
1260	
1261		
1262		if (typeof drawScale !== "function") drawScale = function(){};
1263		
1264		requestAnimFrame(animLoop);
1265		
1266		function animateFrame(){
1267			var easeAdjustedAnimationPercent =(config.animation)? CapValue(easingFunction(percentAnimComplete),null,0) : 1;
1268			clear(ctx);
1269			if(config.scaleOverlay){
1270				drawData(easeAdjustedAnimationPercent);
1271				drawScale();
1272			} else {
1273				drawScale();
1274				drawData(easeAdjustedAnimationPercent);
1275			}				
1276		}
1277		function animLoop(){
1278			//We need to check if the animation is incomplete (less than 1), or complete (1).
1279				percentAnimComplete += animFrameAmount;
1280				animateFrame();	
1281				//Stop the loop continuing forever
1282				if (percentAnimComplete <= 1){
1283					requestAnimFrame(animLoop);
1284				}
1285				else{
1286					if (typeof config.onAnimationComplete == "function") config.onAnimationComplete();
1287				}
1288			
1289		}		
1290		
1291	}
1292
1293	//Declare global functions to be called within this namespace here.
1294	
1295	
1296	// shim layer with setTimeout fallback
1297	var requestAnimFrame = (function(){
1298		return window.requestAnimationFrame ||
1299			window.webkitRequestAnimationFrame ||
1300			window.mozRequestAnimationFrame ||
1301			window.oRequestAnimationFrame ||
1302			window.msRequestAnimationFrame ||
1303			function(callback) {
1304				window.setTimeout(callback, 1000 / 60);
1305			};
1306	})();
1307	
1308	function calculateScale(drawingHeight,maxSteps,minSteps,maxValue,minValue,labelTemplateString){
1309			var graphMin,graphMax,graphRange,stepValue,numberOfSteps,valueRange,rangeOrderOfMagnitude,decimalNum;
1310			
1311			valueRange = maxValue - minValue;
1312
1313			
1314			rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange);
1315
1316        	graphMin = Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);
1317            
1318            graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude);
1319            
1320            graphRange = graphMax - graphMin;
1321          
1322            stepValue = Math.pow(10, rangeOrderOfMagnitude);
1323            var stepTest = stepValue;
1324            var rangeTest = rangeOrderOfMagnitude;
1325         
1326                       
1327            
1328	        numberOfSteps = Math.round(graphRange / stepValue);
1329	        
1330	        //Compare number of steps to the max and min for that size graph, and add in half steps if need be.	        
1331	      while(numberOfSteps < minSteps || numberOfSteps > maxSteps) {
1332	        	if (numberOfSteps < minSteps){
1333			        stepValue /= 2;
1334			        numberOfSteps = Math.round(graphRange/stepValue);
1335		        }
1336		        else{
1337			        stepValue *=2;
1338			        numberOfSteps = Math.round(graphRange/stepValue);
1339		        }
1340	        };
1341	        
1342
1343	        
1344	        //Create an array of all the labels by interpolating the string.
1345	        
1346	        var labels = [];
1347	        
1348	        if(labelTemplateString){
1349		        //Fix floating point errors by setting to fixed the on the same decimal as the stepValue.
1350		        for (var i=1; i<numberOfSteps+1; i++){
1351		        	labels.push(tmpl(labelTemplateString,{value:(graphMin + (stepValue*i)).toFixed(getDecimalPlaces (stepValue))}));
1352		        }
1353	        }
1354		
1355	        return {
1356		        steps : numberOfSteps,
1357				stepValue : stepValue,
1358				graphMin : graphMin,
1359				labels : labels		        
1360		        
1361	        }
1362		
1363			function calculateOrderOfMagnitude(val){
1364			  return Math.floor(Math.log(val) / Math.LN10);
1365			}		
1366
1367
1368	}
1369	
1370	//Max value from array
1371	function Max( array ){
1372		return Math.max.apply( Math, array );
1373	};
1374	//Min value from array
1375	function Min( array ){
1376		return Math.min.apply( Math, array );
1377	};
1378	//Default if undefined
1379	function Default(userDeclared,valueIfFalse){
1380		if(!userDeclared){
1381			return valueIfFalse;
1382		} else {
1383			return userDeclared;
1384		}
1385	};
1386	//Is a number function
1387	function isNumber(n) {
1388		return !isNaN(parseFloat(n)) && isFinite(n);
1389	}
1390	//Apply cap a value at a high or low number
1391	function CapValue(valueToCap, maxValue, minValue){
1392		if(isNumber(maxValue)) {
1393			if( valueToCap > maxValue ) {
1394				return maxValue;
1395			}
1396		}
1397		if(isNumber(minValue)){
1398			if ( valueToCap < minValue ){
1399				return minValue;
1400			}
1401		}
1402		return valueToCap;
1403	}
1404	function getDecimalPlaces (num){
1405		var numberOfDecimalPlaces;
1406		if (num%1!=0){
1407			return num.toString().split(".")[1].length
1408		}
1409		else{
1410			return 0;
1411		}
1412		
1413	} 
1414	
1415	function mergeChartConfig(defaults,userDefined){
1416		var returnObj = {};
1417	    for (var attrname in defaults) { returnObj[attrname] = defaults[attrname]; }
1418	    for (var attrname in userDefined) { returnObj[attrname] = userDefined[attrname]; }
1419	    return returnObj;
1420	}
1421	
1422	//Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
1423	  var cache = {};
1424	 
1425	  function tmpl(str, data){
1426	    // Figure out if we're getting a template, or if we need to
1427	    // load the template - and be sure to cache the result.
1428	    var fn = !/\W/.test(str) ?
1429	      cache[str] = cache[str] ||
1430	        tmpl(document.getElementById(str).innerHTML) :
1431	     
1432	      // Generate a reusable function that will serve as a template
1433	      // generator (and which will be cached).
1434	      new Function("obj",
1435	        "var p=[],print=function(){p.push.apply(p,arguments);};" +
1436	       
1437	        // Introduce the data as local variables using with(){}
1438	        "with(obj){p.push('" +
1439	       
1440	        // Convert the template into pure JavaScript
1441	        str
1442	          .replace(/[\r\t\n]/g, " ")
1443	          .split("<%").join("\t")
1444	          .replace(/((^|%>)[^\t]*)'/g, "$1\r")
1445	          .replace(/\t=(.*?)%>/g, "',$1,'")
1446	          .split("\t").join("');")
1447	          .split("%>").join("p.push('")
1448	          .split("\r").join("\\'")
1449	      + "');}return p.join('');");
1450	   
1451	    // Provide some basic currying to the user
1452	    return data ? fn( data ) : fn;
1453	  };
1454}
1455
1456
1457

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.