PageSourceSearch

https://sayfincap.com/js/chartJs/Chart.js

js sayfincap.com collected 2026-10-02 01:42:52 UTC 52,121 bytes, 1,607 lines download raw bytes

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

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.