如何在givin js文件中添加图例到HTML5圆环图 [英] How to add legend to HTML5 doughnut chart in the givin js file

查看:72
本文介绍了如何在givin js文件中添加图例到HTML5圆环图的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

Hello Everyone Iam在一个名为chatdata的js文件中有2个JS文件和一个aspx页面

我们已经为图表提供了数据

Hello Everyone Iam having 2 JS files and one aspx page in one js file named chatdata
we have given the data for the chart

var gridbordercolor = "#eee";
var golbaldou;
function LoadChart() {
    debugger;
$.ajax({
                type: "POST",
                url: "CS.aspx/GetCustomers",
                data: "{}",
                contentType: "application/json; charset=utf-8",
                dataType: "json",
                success: function (r) {                    
                    var data = eval(r.d);
                    golbaldou = data;
                    new Chart(document.getElementById("doughnut").getContext("2d")).Doughnut(data);
                    document.getElementById("doughnut").fillStyle = 'rgba(255,0,0,.4)';
                    document.getElementById("doughnut").fillRect(20, 20, 20, 80);
                    },
                failure: function (response) {
                    alert('There was an error.');
                }
            });
        }




        var InitiateChartJS = function () {
            return {
                init: function () {
                    debugger;
                    LoadChart();
                    var doughnutData = golbaldou;
                    var lineChartData = {
                        labels: ["", "", "", "", "", "", ""],
                        datasets: [
                    {
                        fillColor: "rgba(93, 178, 255,.4)",
                        strokeColor: "rgba(93, 178, 255,.7)",
                        pointColor: "rgba(93, 178, 255,.7)",
                        pointStrokeColor: "#fff",
                        data: [65, 59, 90, 81, 56, 55, 40]
                    },
                    {
                        fillColor: "rgba(215, 61, 50,.4)",
                        strokeColor: "rgba(215, 61, 50,.6)",
                        pointColor: "rgba(215, 61, 50,.6)",
                        pointStrokeColor: "#fff",
                        data: [28, 48, 40, 19, 96, 27, 100]
                    }
                ]

                    };
                    var pieData = [
                    {
                        value: 30,
                        color: themeprimary
                    },
                    {
                        value: 50,
                        color: themesecondary
                    },
                    {
                        value: 100,
                        color: themefourthcolor
                    }

            ];
                    var barChartData = {
                        labels: ["January", "February", "March", "April", "May", "June", "July"],
                        datasets: [
                    {
                        fillColor: themeprimary,
                        strokeColor: themeprimary,
                        data: [65, 59, 90, 81, 56, 55, 40]
                    },
                    {
                        fillColor: themethirdcolor,
                        strokeColor: themethirdcolor,
                        data: [28, 48, 40, 19, 96, 27, 100]
                    }
                ]

                    };
                    var chartData = [
                    {
                        value: Math.random(),
                        color: themeprimary
                    },
                    {
                        value: Math.random(),
                        color: themesecondary
                    },
                    {
                        value: Math.random(),
                        color: themethirdcolor
                    },
                    {
                        value: Math.random(),
                        color: themefourthcolor
                    },
                    {
                        value: Math.random(),
                        color: themefifthcolor
                    },
                    {
                        value: Math.random(),
                        color: "#ed4e2a"
                    }
            ];
                    var radarChartData = {
                        labels: ["", "", "", "", "", "", ""],
                        datasets: [
                    {
                        fillColor: "rgba(140,196,116,0.5)",
                        strokeColor: "rgba(140,196,116,.7)",
                        pointColor: "rgba(140,196,116,.7)",
                        pointStrokeColor: "#fff",
                        data: [65, 59, 90, 81, 56, 55, 40]
                    },
                    {
                        fillColor: "rgba(215,61,50,0.5)",
                        strokeColor: "rgba(215,61,50,.7)",
                        pointColor: "rgba(215,61,50,.7)",
                        pointStrokeColor: "#fff",
                        data: [28, 48, 40, 19, 96, 27, 100]
                    }
                ]

                    };
                   
                    new Chart(document.getElementById("line").getContext("2d")).Line(lineChartData);
                    new Chart(document.getElementById("radar").getContext("2d")).Radar(radarChartData);
                    new Chart(document.getElementById("polarArea").getContext("2d")).PolarArea(chartData);
                    new Chart(document.getElementById("bar").getContext("2d")).Bar(barChartData);
                    new Chart(document.getElementById("pie").getContext("2d")).Pie(pieData);

                }
            };
        } ();





和另一个我们有这个是我们从模板得到的这是为了行动图表



如下





and onother one we have this is what we got from a template this is for the action of chart

which is as follows

/*!
 * Chart.js
 * http://chartjs.org/
 * Version: 1.0.2
 *
 * Copyright 2015 Nick Downie
 * Released under the MIT license
 * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
 */


(function(){

	"use strict";

	//Declare root variable - window in the browser, global on the server
	var root = this,
		previous = root.Chart;

	//Occupy the global variable of Chart, and create a simple base class
	var Chart = function(context){
		var chart = this;
		this.canvas = context.canvas;

		this.ctx = context;

		//Variables global to the chart
		var computeDimension = function(element,dimension)
		{
			if (element['offset'+dimension])
			{
				return element['offset'+dimension];
			}
			else
			{
				return document.defaultView.getComputedStyle(element).getPropertyValue(dimension);
			}
		}

		var width = this.width = computeDimension(context.canvas,'Width');
		var height = this.height = computeDimension(context.canvas,'Height');

		// Firefox requires this to work correctly
		context.canvas.width  = width;
		context.canvas.height = height;

		var width = this.width = context.canvas.width;
		var height = this.height = context.canvas.height;
		this.aspectRatio = this.width / this.height;
		//High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
		helpers.retinaScale(this);

		return this;
	};
	//Globally expose the defaults to allow for user updating/changing
	Chart.defaults = {
		global: {
			// Boolean - Whether to animate the chart
			animation: true,

			// Number - Number of animation steps
			animationSteps: 60,

			// String - Animation easing effect
			animationEasing: "easeOutQuart",

			// Boolean - If we should show the scale at all
			showScale: true,

			// Boolean - If we want to override with a hard coded scale
			scaleOverride: false,

			// ** Required if scaleOverride is true **
			// Number - The number of steps in a hard coded scale
			scaleSteps: null,
			// Number - The value jump in the hard coded scale
			scaleStepWidth: null,
			// Number - The scale starting value
			scaleStartValue: null,

			// String - Colour of the scale line
			scaleLineColor: "rgba(0,0,0,.1)",

			// Number - Pixel width of the scale line
			scaleLineWidth: 1,

			// Boolean - Whether to show labels on the scale
			scaleShowLabels: true,

			// Interpolated JS string - can access value
			scaleLabel: "<%=value%>",

			// Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there
			scaleIntegersOnly: true,

			// Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
			scaleBeginAtZero: false,

			// String - Scale label font declaration for the scale label
			scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",

			// Number - Scale label font size in pixels
			scaleFontSize: 12,

			// String - Scale label font weight style
			scaleFontStyle: "normal",

			// String - Scale label font colour
			scaleFontColor: "#666",

			// Boolean - whether or not the chart should be responsive and resize when the browser does.
			responsive: false,

			// Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
			maintainAspectRatio: true,

			// Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
			showTooltips: true,

			// Boolean - Determines whether to draw built-in tooltip or call custom tooltip function
			customTooltips: false,

			// Array - Array of string names to attach tooltip events
			tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],

			// String - Tooltip background colour
			tooltipFillColor: "rgba(0,0,0,0.8)",

			// String - Tooltip label font declaration for the scale label
			tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",

			// Number - Tooltip label font size in pixels
			tooltipFontSize: 14,

			// String - Tooltip font weight style
			tooltipFontStyle: "normal",

			// String - Tooltip label font colour
			tooltipFontColor: "#fff",

			// String - Tooltip title font declaration for the scale label
			tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",

			// Number - Tooltip title font size in pixels
			tooltipTitleFontSize: 14,

			// String - Tooltip title font weight style
			tooltipTitleFontStyle: "bold",

			// String - Tooltip title font colour
			tooltipTitleFontColor: "#fff",

			// Number - pixel width of padding around tooltip text
			tooltipYPadding: 6,

			// Number - pixel width of padding around tooltip text
			tooltipXPadding: 6,

			// Number - Size of the caret on the tooltip
			tooltipCaretSize: 8,

			// Number - Pixel radius of the tooltip border
			tooltipCornerRadius: 6,

			// Number - Pixel offset from point x to tooltip edge
			tooltipXOffset: 10,

			// String - Template string for single tooltips
			tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>",

			// String - Template string for single tooltips
			multiTooltipTemplate: "<%= value %>",

			// String - Colour behind the legend colour block
			multiTooltipKeyBackground: '#fff',

			// Function - Will fire on animation progression.
			onAnimationProgress: function(){},

			// Function - Will fire on animation completion.
			onAnimationComplete: function(){}

		}
	};

	//Create a dictionary of chart types, to allow for extension of existing types
	Chart.types = {};

	//Global Chart helpers object for utility methods and classes
	var helpers = Chart.helpers = {};

		//-- Basic js utility methods
	var each = helpers.each = function(loopable,callback,self){
			var additionalArgs = Array.prototype.slice.call(arguments, 3);
			// Check to see if null or undefined firstly.
			if (loopable){
				if (loopable.length === +loopable.length){
					var i;
					for (i=0; i<loopable.length; i++){
						callback.apply(self,[loopable[i], i].concat(additionalArgs));
					}
				}
				else{
					for (var item in loopable){
						callback.apply(self,[loopable[item],item].concat(additionalArgs));
					}
				}
			}
		},
		clone = helpers.clone = function(obj){
			var objClone = {};
			each(obj,function(value,key){
				if (obj.hasOwnProperty(key)) objClone[key] = value;
			});
			return objClone;
		},
		extend = helpers.extend = function(base){
			each(Array.prototype.slice.call(arguments,1), function(extensionObject) {
				each(extensionObject,function(value,key){
					if (extensionObject.hasOwnProperty(key)) base[key] = value;
				});
			});
			return base;
		},
		merge = helpers.merge = function(base,master){
			//Merge properties in left object over to a shallow clone of object right.
			var args = Array.prototype.slice.call(arguments,0);
			args.unshift({});
			return extend.apply(null, args);
		},
		indexOf = helpers.indexOf = function(arrayToSearch, item){
			if (Array.prototype.indexOf) {
				return arrayToSearch.indexOf(item);
			}
			else{
				for (var i = 0; i < arrayToSearch.length; i++) {
					if (arrayToSearch[i] === item) return i;
				}
				return -1;
			}
		},
		where = helpers.where = function(collection, filterCallback){
			var filtered = [];

			helpers.each(collection, function(item){
				if (filterCallback(item)){
					filtered.push(item);
				}
			});

			return filtered;
		},
		findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex){
			// Default to start of the array
			if (!startIndex){
				startIndex = -1;
			}
			for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
				var currentItem = arrayToSearch[i];
				if (filterCallback(currentItem)){
					return currentItem;
				}
			}
		},
		findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex){
			// Default to end of the array
			if (!startIndex){
				startIndex = arrayToSearch.length;
			}
			for (var i = startIndex - 1; i >= 0; i--) {
				var currentItem = arrayToSearch[i];
				if (filterCallback(currentItem)){
					return currentItem;
				}
			}
		},
		inherits = helpers.inherits = function(extensions){
			//Basic javascript inheritance based on the model created in Backbone.js
			var parent = this;
			var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function(){ return parent.apply(this, arguments); };

			var Surrogate = function(){ this.constructor = ChartElement;};
			Surrogate.prototype = parent.prototype;
			ChartElement.prototype = new Surrogate();

			ChartElement.extend = inherits;

			if (extensions) extend(ChartElement.prototype, extensions);

			ChartElement.__super__ = parent.prototype;

			return ChartElement;
		},
		noop = helpers.noop = function(){},
		uid = helpers.uid = (function(){
			var id=0;
			return function(){
				return "chart-" + id++;
			};
		})(),
		warn = helpers.warn = function(str){
			//Method for warning of errors
			if (window.console && typeof window.console.warn == "function") console.warn(str);
		},
		amd = helpers.amd = (typeof define == 'function' && define.amd),
		//-- Math methods
		isNumber = helpers.isNumber = function(n){
			return !isNaN(parseFloat(n)) && isFinite(n);
		},
		max = helpers.max = function(array){
			return Math.max.apply( Math, array );
		},
		min = helpers.min = function(array){
			return Math.min.apply( Math, array );
		},
		cap = helpers.cap = function(valueToCap,maxValue,minValue){
			if(isNumber(maxValue)) {
				if( valueToCap > maxValue ) {
					return maxValue;
				}
			}
			else if(isNumber(minValue)){
				if ( valueToCap < minValue ){
					return minValue;
				}
			}
			return valueToCap;
		},
		getDecimalPlaces = helpers.getDecimalPlaces = function(num){
			if (num%1!==0 && isNumber(num)){
				return num.toString().split(".")[1].length;
			}
			else {
				return 0;
			}
		},
		toRadians = helpers.radians = function(degrees){
			return degrees * (Math.PI/180);
		},
		// Gets the angle from vertical upright to the point about a centre.
		getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint){
			var distanceFromXCenter = anglePoint.x - centrePoint.x,
				distanceFromYCenter = anglePoint.y - centrePoint.y,
				radialDistanceFromCenter = Math.sqrt( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);


			var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter);

			//If the segment is in the top left quadrant, we need to add another rotation to the angle
			if (distanceFromXCenter < 0 && distanceFromYCenter < 0){
				angle += Math.PI*2;
			}

			return {
				angle: angle,
				distance: radialDistanceFromCenter
			};
		},
		aliasPixel = helpers.aliasPixel = function(pixelWidth){
			return (pixelWidth % 2 === 0) ? 0 : 0.5;
		},
		splineCurve = helpers.splineCurve = function(FirstPoint,MiddlePoint,AfterPoint,t){
			//Props to Rob Spencer at scaled innovation for his post on splining between points
			//http://scaledinnovation.com/analytics/splines/aboutSplines.html
			var d01=Math.sqrt(Math.pow(MiddlePoint.x-FirstPoint.x,2)+Math.pow(MiddlePoint.y-FirstPoint.y,2)),
				d12=Math.sqrt(Math.pow(AfterPoint.x-MiddlePoint.x,2)+Math.pow(AfterPoint.y-MiddlePoint.y,2)),
				fa=t*d01/(d01+d12),// scaling factor for triangle Ta
				fb=t*d12/(d01+d12);
			return {
				inner : {
					x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x),
					y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y)
				},
				outer : {
					x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x),
					y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y)
				}
			};
		},
		calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val){
			return Math.floor(Math.log(val) / Math.LN10);
		},
		calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly){

			//Set a minimum step of two - a point at the top of the graph, and a point at the base
			var minSteps = 2,
				maxSteps = Math.floor(drawingSize/(textSize * 1.5)),
				skipFitting = (minSteps >= maxSteps);

			var maxValue = max(valuesArray),
				minValue = min(valuesArray);

			// We need some degree of seperation here to calculate the scales if all the values are the same
			// Adding/minusing 0.5 will give us a range of 1.
			if (maxValue === minValue){
				maxValue += 0.5;
				// So we don't end up with a graph with a negative start value if we've said always start from zero
				if (minValue >= 0.5 && !startFromZero){
					minValue -= 0.5;
				}
				else{
					// Make up a whole number above the values
					maxValue += 0.5;
				}
			}

			var	valueRange = Math.abs(maxValue - minValue),
				rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
				graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
				graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
				graphRange = graphMax - graphMin,
				stepValue = Math.pow(10, rangeOrderOfMagnitude),
				numberOfSteps = Math.round(graphRange / stepValue);

			//If we have more space on the graph we'll use it to give more definition to the data
			while((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {
				if(numberOfSteps > maxSteps){
					stepValue *=2;
					numberOfSteps = Math.round(graphRange/stepValue);
					// Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
					if (numberOfSteps % 1 !== 0){
						skipFitting = true;
					}
				}
				//We can fit in double the amount of scale points on the scale
				else{
					//If user has declared ints only, and the step value isn't a decimal
					if (integersOnly && rangeOrderOfMagnitude >= 0){
						//If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
						if(stepValue/2 % 1 === 0){
							stepValue /=2;
							numberOfSteps = Math.round(graphRange/stepValue);
						}
						//If it would make it a float break out of the loop
						else{
							break;
						}
					}
					//If the scale doesn't have to be an int, make the scale more granular anyway.
					else{
						stepValue /=2;
						numberOfSteps = Math.round(graphRange/stepValue);
					}

				}
			}

			if (skipFitting){
				numberOfSteps = minSteps;
				stepValue = graphRange / numberOfSteps;
			}

			return {
				steps : numberOfSteps,
				stepValue : stepValue,
				min : graphMin,
				max	: graphMin + (numberOfSteps * stepValue)
			};

		},
		/* jshint ignore:start */
		// Blows up jshint errors based on the new Function constructor
		//Templating methods
		//Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
		template = helpers.template = function(templateString, valuesObject){

			// If templateString is function rather than string-template - call the function for valuesObject

			if(templateString instanceof Function){
			 	return templateString(valuesObject);
		 	}

			var cache = {};
			function tmpl(str, data){
				// Figure out if we're getting a template, or if we need to
				// load the template - and be sure to cache the result.
				var fn = !/\W/.test(str) ?
				cache[str] = cache[str] :

				// Generate a reusable function that will serve as a template
				// generator (and which will be cached).
				new Function("obj",
					"var p=[],print=function(){p.push.apply(p,arguments);};" +

					// Introduce the data as local variables using with(){}
					"with(obj){p.push('" +

					// Convert the template into pure JavaScript
					str
						.replace(/[\r\t\n]/g, " ")
						.split("<%").join("\t")
						.replace(/((^|%>)[^\t]*)'/g, "$1\r")
						.replace(/\t=(.*?)%>/g, "',$1,'")
						.split("\t").join("');")
						.split("%>").join("p.push('")
						.split("\r").join("\\'") +
					"');}return p.join('');"
				);

				// Provide some basic currying to the user
				return data ? fn( data ) : fn;
			}
			return tmpl(templateString,valuesObject);
		},
		/* jshint ignore:end */
		generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){
			var labelsArray = new Array(numberOfSteps);
			if (labelTemplateString){
				each(labelsArray,function(val,index){
					labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))});
				});
			}
			return labelsArray;
		},
		//--Animation methods
		//Easing functions adapted from Robert Penner's easing equations
		//http://www.robertpenner.com/easing/
		easingEffects = helpers.easingEffects = {
			linear: function (t) {
				return t;
			},
			easeInQuad: function (t) {
				return t * t;
			},
			easeOutQuad: function (t) {
				return -1 * t * (t - 2);
			},
			easeInOutQuad: function (t) {
				if ((t /= 1 / 2) < 1) return 1 / 2 * t * t;
				return -1 / 2 * ((--t) * (t - 2) - 1);
			},
			easeInCubic: function (t) {
				return t * t * t;
			},
			easeOutCubic: function (t) {
				return 1 * ((t = t / 1 - 1) * t * t + 1);
			},
			easeInOutCubic: function (t) {
				if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t;
				return 1 / 2 * ((t -= 2) * t * t + 2);
			},
			easeInQuart: function (t) {
				return t * t * t * t;
			},
			easeOutQuart: function (t) {
				return -1 * ((t = t / 1 - 1) * t * t * t - 1);
			},
			easeInOutQuart: function (t) {
				if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t;
				return -1 / 2 * ((t -= 2) * t * t * t - 2);
			},
			easeInQuint: function (t) {
				return 1 * (t /= 1) * t * t * t * t;
			},
			easeOutQuint: function (t) {
				return 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
			},
			easeInOutQuint: function (t) {
				if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t;
				return 1 / 2 * ((t -= 2) * t * t * t * t + 2);
			},
			easeInSine: function (t) {
				return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
			},
			easeOutSine: function (t) {
				return 1 * Math.sin(t / 1 * (Math.PI / 2));
			},
			easeInOutSine: function (t) {
				return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
			},
			easeInExpo: function (t) {
				return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
			},
			easeOutExpo: function (t) {
				return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
			},
			easeInOutExpo: function (t) {
				if (t === 0) return 0;
				if (t === 1) return 1;
				if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1));
				return 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
			},
			easeInCirc: function (t) {
				if (t >= 1) return t;
				return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
			},
			easeOutCirc: function (t) {
				return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
			},
			easeInOutCirc: function (t) {
				if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
				return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
			},
			easeInElastic: function (t) {
				var s = 1.70158;
				var p = 0;
				var a = 1;
				if (t === 0) return 0;
				if ((t /= 1) == 1) return 1;
				if (!p) p = 1 * 0.3;
				if (a < Math.abs(1)) {
					a = 1;
					s = p / 4;
				} else s = p / (2 * Math.PI) * Math.asin(1 / a);
				return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
			},
			easeOutElastic: function (t) {
				var s = 1.70158;
				var p = 0;
				var a = 1;
				if (t === 0) return 0;
				if ((t /= 1) == 1) return 1;
				if (!p) p = 1 * 0.3;
				if (a < Math.abs(1)) {
					a = 1;
					s = p / 4;
				} else s = p / (2 * Math.PI) * Math.asin(1 / a);
				return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
			},
			easeInOutElastic: function (t) {
				var s = 1.70158;
				var p = 0;
				var a = 1;
				if (t === 0) return 0;
				if ((t /= 1 / 2) == 2) return 1;
				if (!p) p = 1 * (0.3 * 1.5);
				if (a < Math.abs(1)) {
					a = 1;
					s = p / 4;
				} else s = p / (2 * Math.PI) * Math.asin(1 / a);
				if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
				return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
			},
			easeInBack: function (t) {
				var s = 1.70158;
				return 1 * (t /= 1) * t * ((s + 1) * t - s);
			},
			easeOutBack: function (t) {
				var s = 1.70158;
				return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
			},
			easeInOutBack: function (t) {
				var s = 1.70158;
				if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
				return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
			},
			easeInBounce: function (t) {
				return 1 - easingEffects.easeOutBounce(1 - t);
			},
			easeOutBounce: function (t) {
				if ((t /= 1) < (1 / 2.75)) {
					return 1 * (7.5625 * t * t);
				} else if (t < (2 / 2.75)) {
					return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
				} else if (t < (2.5 / 2.75)) {
					return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
				} else {
					return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
				}
			},
			easeInOutBounce: function (t) {
				if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5;
				return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
			}
		},
		//Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
		requestAnimFrame = helpers.requestAnimFrame = (function(){
			return window.requestAnimationFrame ||
				window.webkitRequestAnimationFrame ||
				window.mozRequestAnimationFrame ||
				window.oRequestAnimationFrame ||
				window.msRequestAnimationFrame ||
				function(callback) {
					return window.setTimeout(callback, 1000 / 60);
				};
		})(),
		cancelAnimFrame = helpers.cancelAnimFrame = (function(){
			return window.cancelAnimationFrame ||
				window.webkitCancelAnimationFrame ||
				window.mozCancelAnimationFrame ||
				window.oCancelAnimationFrame ||
				window.msCancelAnimationFrame ||
				function(callback) {
					return window.clearTimeout(callback, 1000 / 60);
				};
		})(),
		animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){

			var currentStep = 0,
				easingFunction = easingEffects[easingString] || easingEffects.linear;

			var animationFrame = function(){
				currentStep++;
				var stepDecimal = currentStep/totalSteps;
				var easeDecimal = easingFunction(stepDecimal);

				callback.call(chartInstance,easeDecimal,stepDecimal, currentStep);
				onProgress.call(chartInstance,easeDecimal,stepDecimal);
				if (currentStep < totalSteps){
					chartInstance.animationFrame = requestAnimFrame(animationFrame);
				} else{
					onComplete.apply(chartInstance);
				}
			};
			requestAnimFrame(animationFrame);
		},
		//-- DOM methods
		getRelativePosition = helpers.getRelativePosition = function(evt){
			var mouseX, mouseY;
			var e = evt.originalEvent || evt,
				canvas = evt.currentTarget || evt.srcElement,
				boundingRect = canvas.getBoundingClientRect();

			if (e.touches){
				mouseX = e.touches[0].clientX - boundingRect.left;
				mouseY = e.touches[0].clientY - boundingRect.top;

			}
			else{
				mouseX = e.clientX - boundingRect.left;
				mouseY = e.clientY - boundingRect.top;
			}

			return {
				x : mouseX,
				y : mouseY
			};

		},
		addEvent = helpers.addEvent = function(node,eventType,method){
			if (node.addEventListener){
				node.addEventListener(eventType,method);
			} else if (node.attachEvent){
				node.attachEvent("on"+eventType, method);
			} else {
				node["on"+eventType] = method;
			}
		},
		removeEvent = helpers.removeEvent = function(node, eventType, handler){
			if (node.removeEventListener){
				node.removeEventListener(eventType, handler, false);
			} else if (node.detachEvent){
				node.detachEvent("on"+eventType,handler);
			} else{
				node["on" + eventType] = noop;
			}
		},
		bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){
			// Create the events object if it's not already present
			if (!chartInstance.events) chartInstance.events = {};

			each(arrayOfEvents,function(eventName){
				chartInstance.events[eventName] = function(){
					handler.apply(chartInstance, arguments);
				};
				addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]);
			});
		},
		unbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) {
			each(arrayOfEvents, function(handler,eventName){
				removeEvent(chartInstance.chart.canvas, eventName, handler);
			});
		},
		getMaximumWidth = helpers.getMaximumWidth = function(domNode){
			var container = domNode.parentNode;
			// TODO = check cross browser stuff with this.
			return container.clientWidth;
		},
		getMaximumHeight = helpers.getMaximumHeight = function(domNode){
			var container = domNode.parentNode;
			// TODO = check cross browser stuff with this.
			return container.clientHeight;
		},
		getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support
		retinaScale = helpers.retinaScale = function(chart){
			var ctx = chart.ctx,
				width = chart.canvas.width,
				height = chart.canvas.height;

			if (window.devicePixelRatio) {
				ctx.canvas.style.width = width + "px";
				ctx.canvas.style.height = height + "px";
				ctx.canvas.height = height * window.devicePixelRatio;
				ctx.canvas.width = width * window.devicePixelRatio;
				ctx.scale(window.devicePixelRatio, window.devicePixelRatio);
			}
		},
		//-- Canvas methods
		clear = helpers.clear = function(chart){
			chart.ctx.clearRect(0,0,chart.width,chart.height);
		},
		fontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){
			return fontStyle + " " + pixelSize+"px " + fontFamily;
		},
		longestText = helpers.longestText = function(ctx,font,arrayOfStrings){
			ctx.font = font;
			var longest = 0;
			each(arrayOfStrings,function(string){
				var textWidth = ctx.measureText(string).width;
				longest = (textWidth > longest) ? textWidth : longest;
			});
			return longest;
		},
		drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){
			ctx.beginPath();
			ctx.moveTo(x + radius, y);
			ctx.lineTo(x + width - radius, y);
			ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
			ctx.lineTo(x + width, y + height - radius);
			ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
			ctx.lineTo(x + radius, y + height);
			ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
			ctx.lineTo(x, y + radius);
			ctx.quadraticCurveTo(x, y, x + radius, y);
			ctx.closePath();
		};


	//Store a reference to each instance - allowing us to globally resize chart instances on window resize.
	//Destroy method on the chart will remove the instance of the chart from this reference.
	Chart.instances = {};

	Chart.Type = function(data,options,chart){
		this.options = options;
		this.chart = chart;
		this.id = uid();
		//Add the chart instance to the global namespace
		Chart.instances[this.id] = this;

		// Initialize is always called when a chart type is created
		// By default it is a no op, but it should be extended
		if (options.responsive){
			this.resize();
		}
		this.initialize.call(this,data);
	};

	//Core methods that'll be a part of every chart type
	extend(Chart.Type.prototype,{
		initialize : function(){return this;},
		clear : function(){
			clear(this.chart);
			return this;
		},
		stop : function(){
			// Stops any current animation loop occuring
			cancelAnimFrame(this.animationFrame);
			return this;
		},
		resize : function(callback){
			this.stop();
			var canvas = this.chart.canvas,
				newWidth = getMaximumWidth(this.chart.canvas),
				newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);

			canvas.width = this.chart.width = newWidth;
			canvas.height = this.chart.height = newHeight;

			retinaScale(this.chart);

			if (typeof callback === "function"){
				callback.apply(this, Array.prototype.slice.call(arguments, 1));
			}
			return this;
		},
		reflow : noop,
		render : function(reflow){
			if (reflow){
				this.reflow();
			}
			if (this.options.animation && !reflow){
				helpers.animationLoop(
					this.draw,
					this.options.animationSteps,
					this.options.animationEasing,
					this.options.onAnimationProgress,
					this.options.onAnimationComplete,
					this
				);
			}
			else{
				this.draw();
				this.options.onAnimationComplete.call(this);
			}
			return this;
		},
		generateLegend : function(){
			return template(this.options.legendTemplate,this);
		},
		destroy : function(){
			this.clear();
			unbindEvents(this, this.events);
			var canvas = this.chart.canvas;

			// Reset canvas height/width attributes starts a fresh with the canvas context
			canvas.width = this.chart.width;
			canvas.height = this.chart.height;

			// < IE9 doesn't support removeProperty
			if (canvas.style.removeProperty) {
				canvas.style.removeProperty('width');
				canvas.style.removeProperty('height');
			} else {
				canvas.style.removeAttribute('width');
				canvas.style.removeAttribute('height');
			}

			delete Chart.instances[this.id];
		},
		showTooltip : function(ChartElements, forceRedraw){
			// Only redraw the chart if we've actually changed what we're hovering on.
			if (typeof this.activeElements === 'undefined') this.activeElements = [];

			var isChanged = (function(Elements){
				var changed = false;

				if (Elements.length !== this.activeElements.length){
					changed = true;
					return changed;
				}

				each(Elements, function(element, index){
					if (element !== this.activeElements[index]){
						changed = true;
					}
				}, this);
				return changed;
			}).call(this, ChartElements);

			if (!isChanged && !forceRedraw){
				return;
			}
			else{
				this.activeElements = ChartElements;
			}
			this.draw();
			if(this.options.customTooltips){
				this.options.customTooltips(false);
			}
			if (ChartElements.length > 0){
				// If we have multiple datasets, show a MultiTooltip for all of the data points at that index
				if (this.datasets && this.datasets.length > 1) {
					var dataArray,
						dataIndex;

					for (var i = this.datasets.length - 1; i >= 0; i--) {
						dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments;
						dataIndex = indexOf(dataArray, ChartElements[0]);
						if (dataIndex !== -1){
							break;
						}
					}
					var tooltipLabels = [],
						tooltipColors = [],
						medianPosition = (function(index) {

							// Get all the points at that particular index
							var Elements = [],
								dataCollection,
								xPositions = [],
								yPositions = [],
								xMax,
								yMax,
								xMin,
								yMin;
							helpers.each(this.datasets, function(dataset){
								dataCollection = dataset.points || dataset.bars || dataset.segments;
								if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){
									Elements.push(dataCollection[dataIndex]);
								}
							});

							helpers.each(Elements, function(element) {
								xPositions.push(element.x);
								yPositions.push(element.y);


								//Include any colour information about the element
								tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element));
								tooltipColors.push({
									fill: element._saved.fillColor || element.fillColor,
									stroke: element._saved.strokeColor || element.strokeColor
								});

							}, this);

							yMin = min(yPositions);
							yMax = max(yPositions);

							xMin = min(xPositions);
							xMax = max(xPositions);

							return {
								x: (xMin > this.chart.width/2) ? xMin : xMax,
								y: (yMin + yMax)/2
							};
						}).call(this, dataIndex);

					new Chart.MultiTooltip({
						x: medianPosition.x,
						y: medianPosition.y,
						xPadding: this.options.tooltipXPadding,
						yPadding: this.options.tooltipYPadding,
						xOffset: this.options.tooltipXOffset,
						fillColor: this.options.tooltipFillColor,
						textColor: this.options.tooltipFontColor,
						fontFamily: this.options.tooltipFontFamily,
						fontStyle: this.options.tooltipFontStyle,
						fontSize: this.options.tooltipFontSize,
						titleTextColor: this.options.tooltipTitleFontColor,
						titleFontFamily: this.options.tooltipTitleFontFamily,
						titleFontStyle: this.options.tooltipTitleFontStyle,
						titleFontSize: this.options.tooltipTitleFontSize,
						cornerRadius: this.options.tooltipCornerRadius,
						labels: tooltipLabels,
						legendColors: tooltipColors,
						legendColorBackground : this.options.multiTooltipKeyBackground,
						title: ChartElements[0].label,
						chart: this.chart,
						ctx: this.chart.ctx,
						custom: this.options.customTooltips
					}).draw();

				} else {
					each(ChartElements, function(Element) {
						var tooltipPosition = Element.tooltipPosition();
						new Chart.Tooltip({
							x: Math.round(tooltipPosition.x),
							y: Math.round(tooltipPosition.y),
							xPadding: this.options.tooltipXPadding,
							yPadding: this.options.tooltipYPadding,
							fillColor: this.options.tooltipFillColor,
							textColor: this.options.tooltipFontColor,
							fontFamily: this.options.tooltipFontFamily,
							fontStyle: this.options.tooltipFontStyle,
							fontSize: this.options.tooltipFontSize,
							caretHeight: this.options.tooltipCaretSize,
							cornerRadius: this.options.tooltipCornerRadius,
							text: template(this.options.tooltipTemplate, Element),
							chart: this.chart,
							custom: this.options.customTooltips
						}).draw();
					}, this);
				}
			}
			return this;
		},
		toBase64Image : function(){
			return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
		}
	});

	Chart.Type.extend = function(extensions){

		var parent = this;

		var ChartType = function(){
			return parent.apply(this,arguments);
		};

		//Copy the prototype object of the this class
		ChartType.prototype = clone(parent.prototype);
		//Now overwrite some of the properties in the base class with the new extensions
		extend(ChartType.prototype, extensions);

		ChartType.extend = Chart.Type.extend;

		if (extensions.name || parent.prototype.name){

			var chartName = extensions.name || parent.prototype.name;
			//Assign any potential default values of the new chart type

			//If none are defined, we'll use a clone of the chart type this is being extended from.
			//I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart
			//doesn't define some defaults of their own.

			var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};

			Chart.defaults[chartName] = extend(baseDefaults,extensions.defaults);

			Chart.types[chartName] = ChartType;

			//Register this new chart type in the Chart prototype
			Chart.prototype[chartName] = function(data,options){
				var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {});
				return new ChartType(data,config,this);
			};
		} else{
			warn("Name not provided for this chart, so it hasn't been registered");
		}
		return parent;
	};

	Chart.Element = function(configuration){
		extend(this,configuration);
		this.initialize.apply(this,arguments);
		this.save();
	};
	extend(Chart.Element.prototype,{
		initialize : function(){},
		restore : function(props){
			if (!props){
				extend(this,this._saved);
			} else {
				each(props,function(key){
					this[key] = this._saved[key];
				},this);
			}
			return this;
		},
		save : function(){
			this._saved = clone(this);
			delete this._saved._saved;
			return this;
		},
		update : function(newProps){
			each(newProps,function(value,key){
				this._saved[key] = this[key];
				this[key] = value;
			},this);
			return this;
		},
		transition : function(props,ease){
			each(props,function(value,key){
				this[key] = ((value - this._saved[key]) * ease) + this._saved[key];
			},this);
			return this;
		},
		tooltipPosition : function(){
			return {
				x : this.x,
				y : this.y
			};
		},
		hasValue: function(){
			return isNumber(this.value);
		}
	});

	Chart.Element.extend = inherits;


	Chart.Point = Chart.Element.extend({
		display: true,
		inRange: function(chartX,chartY){
			var hitDetectionRange = this.hitDetectionRadius + this.radius;
			return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2));
		},
		draw : function(){
			if (this.display){
				var ctx = this.ctx;
				ctx.beginPath();

				ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2);
				ctx.closePath();

				ctx.strokeStyle = this.strokeColor;
				ctx.lineWidth = this.strokeWidth;

				ctx.fillStyle = this.fillColor;

				ctx.fill();
				ctx.stroke();
			}


			//Quick debug for bezier curve splining
			//Highlights control points and the line between them.
			//Handy for dev - stripped in the min version.

			// ctx.save();
			// ctx.fillStyle = "black";
			// ctx.strokeStyle = "black"
			// ctx.beginPath();
			// ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2);
			// ctx.fill();

			// ctx.beginPath();
			// ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2);
			// ctx.fill();

			// ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);
			// ctx.lineTo(this.x, this.y);
			// ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);
			// ctx.stroke();

			// ctx.restore();



		}
	});

	Chart.Arc = Chart.Element.extend({
		inRange : function(chartX,chartY){

			var pointRelativePosition = helpers.getAngleFromPoint(this, {
				x: chartX,
				y: chartY
			});

			//Check if within the range of the open/close angle
			var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle),
				withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius);

			return (betweenAngles && withinRadius);
			//Ensure within the outside of the arc centre, but inside arc outer
		},
		tooltipPosition : function(){
			var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2),
				rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius;
			return {
				x : this.x + (Math.cos(centreAngle) * rangeFromCentre),
				y : this.y + (Math.sin(centreAngle) * rangeFromCentre)
			};
		},
		draw : function(animationPercent){

			var easingDecimal = animationPercent || 1;

			var ctx = this.ctx;

			ctx.beginPath();

			ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle);

			ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true);

			ctx.closePath();
			ctx.strokeStyle = this.strokeColor;
			ctx.lineWidth = this.strokeWidth;

			ctx.fillStyle = this.fillColor;

			ctx.fill();
			ctx.lineJoin = 'bevel';

			if (this.showStroke){
				ctx.stroke();
			}
		}
	});

	Chart.Rectangle = Chart.Element.extend({
		draw : function(){
			var ctx = this.ctx,
				halfWidth = this.width/2,
				leftX = this.x - halfWidth,
				rightX = this.x + halfWidth,
				top = this.base - (this.base - this.y),
				halfStroke = this.strokeWidth / 2;

			// Canvas doesn't allow us to stroke inside the width so we can
			// adjust the sizes to fit if we're setting a stroke on the line
			if (this.showStroke){
				leftX += halfStroke;
				rightX -= halfStroke;
				top += halfStroke;
			}

			ctx.beginPath();

			ctx.fillStyle = this.fillColor;
			ctx.strokeStyle = this.strokeColor;
			ctx.lineWidth = this.strokeWidth;

			// It'd be nice to keep this class totally generic to any rectangle
			// and simply specify which border to miss out.
			ctx.moveTo(leftX, this.base);
			ctx.lineTo(leftX, top);
			ctx.lineTo(rightX, top);
			ctx.lineTo(rightX, this.base);
			ctx.fill();
			if (this.showStroke){
				ctx.stroke();
			}
		},
		height : function(){
			return this.base - this.y;
		},
		inRange : function(chartX,chartY){
			return (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base);
		}
	});

	Chart.Tooltip = Chart.Element.extend({
		draw : function(){

			var ctx = this.chart.ctx;

			ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);

			this.xAlign = "center";
			this.yAlign = "above";

			//Distance between the actual element.y position and the start of the tooltip caret
			var caretPadding = this.caretPadding = 2;

			var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding,
				tooltipRectHeight = this.fontSize + 2*this.yPadding,
				tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding;

			if (this.x + tooltipWidth/2 >this.chart.width){
				this.xAlign = "left";
			} else if (this.x - tooltipWidth/2 < 0){
				this.xAlign = "right";
			}

			if (this.y - tooltipHeight < 0){
				this.yAlign = "below";
			}


			var tooltipX = this.x - tooltipWidth/2,
				tooltipY = this.y - tooltipHeight;

			ctx.fillStyle = this.fillColor;

			// Custom Tooltips
			if(this.custom){
				this.custom(this);
			}
			else{
				switch(this.yAlign)
				{
				case "above":
					//Draw a caret above the x/y
					ctx.beginPath();
					ctx.moveTo(this.x,this.y - caretPadding);
					ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight));
					ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight));
					ctx.closePath();
					ctx.fill();
					break;
				case "below":
					tooltipY = this.y + caretPadding + this.caretHeight;
					//Draw a caret below the x/y
					ctx.beginPath();
					ctx.moveTo(this.x, this.y + caretPadding);
					ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight);
					ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight);
					ctx.closePath();
					ctx.fill();
					break;
				}

				switch(this.xAlign)
				{
				case "left":
					tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight);
					break;
				case "right":
					tooltipX = this.x - (this.cornerRadius + this.caretHeight);
					break;
				}

				drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius);

				ctx.fill();

				ctx.fillStyle = this.textColor;
				ctx.textAlign = "center";
				ctx.textBaseline = "middle";
				ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2);
			}
		}
	});

	Chart.MultiTooltip = Chart.Element.extend({
		initialize : function(){
			this.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);

			this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily);

			this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5;

			this.ctx.font = this.titleFont;

			var titleWidth = this.ctx.measureText(this.title).width,
				//Label has a legend square as well so account for this.
				labelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3,
				longestTextWidth = max([labelWidth,titleWidth]);

			this.width = longestTextWidth + (this.xPadding*2);


			var halfHeight = this.height/2;

			//Check to ensure the height will fit on the canvas
			if (this.y - halfHeight < 0 ){
				this.y = halfHeight;
			} else if (this.y + halfHeight > this.chart.height){
				this.y = this.chart.height - halfHeight;
			}

			//Decide whether to align left or right based on position on canvas
			if (this.x > this.chart.width/2){
				this.x -= this.xOffset + this.width;
			} else {
				this.x += this.xOffset;
			}


		},
		getLineHeight : function(index){
			var baseLineHeight = this.y - (this.height/2) + this.yPadding,
				afterTitleIndex = index-1;

			//If the index is zero, we're getting the title
			if (index === 0){
				return baseLineHeight + this.titleFontSize/2;
			} else{
				return baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5;
			}

		},
		draw : function(){
			// Custom Tooltips
			if(this.custom){
				this.custom(this);
			}
			else{
				drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius);
				var ctx = this.ctx;
				ctx.fillStyle = this.fillColor;
				ctx.fill();
				ctx.closePath();

				ctx.textAlign = "left";
				ctx.textBaseline = "middle";
				ctx.fillStyle = this.titleTextColor;
				ctx.font = this.titleFont;

				ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0));

				ctx.font = this.font;
				helpers.each(this.labels,function(label,index){
					ctx.fillStyle = this.textColor;
					ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));

					//A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
					//ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
					//Instead we'll make a white filled block to put the legendColour palette over.

					ctx.fillStyle = this.legendColorBackground;
					ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);

					ctx.fillStyle = this.legendColors[index].fill;
					ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);


				},this);
			}
		}
	});

	Chart.Scale = Chart.Element.extend({
		initialize : function(){
			this.fit();
		},
		buildYLabels : function(){
			this.yLabels = [];

			var stepDecimalPlaces = getDecimalPlaces(this.stepValue);

			for (var i=0; i<=this.steps; i++){
				this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
			}
			this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0;
		},
		addXLabel : function(label){
			this.xLabels.push(label);
			this.valuesCount++;
			this.fit();
		},
		removeXLabel : function(){
			this.xLabels.shift();
			this.valuesCount--;
			this.fit();
		},
		// Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use
		fit: function(){
			// First we need the width of the yLabels, assuming the xLabels aren't rotated

			// To do that we need the base line at the top and base of the chart, assuming there is no x label rotation
			this.startPoint = (this.display) ? this.fontSize : 0;
			this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels

			// Apply padding settings to the start and end point.
			this.startPoint += this.padding;
			this.endPoint -= this.padding;

			// Cache the starting height, so can determine if we need to recalculate the scale yAxis
			var cachedHeight = this.endPoint - this.startPoint,
				cachedYLabelWidth;

			// Build the current yLabels so we have an idea of what size they'll be to start
			/*
			 *	This sets what is returned from calculateScaleRange as static properties of this class:
			 *
				this.steps;
				this.stepValue;
				this.min;
				this.max;
			 *
			 */
			this.calculateYRange(cachedHeight);

			// With these properties set we can now build the array of yLabels
			// and also the width of the largest yLabel
			this.buildYLabels();

			this.calculateXLabelRotation();

			while((cachedHeight > this.endPoint - this.startPoint)){
				cachedHeight = this.endPoint - this.startPoint;
				cachedYLabelWidth = this.yLabelWidth;

				this.calculateYRange(cachedHeight);
				this.buildYLabels();

				// Only go through the xLabel loop again if the yLabel width has changed
				if (cachedYLabelWidth < this.yLabelWidth){
					this.calculateXLabelRotation();
				}
			}

		},
		calculateXLabelRotation : function(){
			//Get the width of each grid by calculating the difference
			//between x offsets between 0 and 1.

			this.ctx.font = this.font;

			var firstWidth = this.ctx.measureText(this.xLabels[0]).width,
				lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,
				firstRotated,
				lastRotated;


			this.xScalePaddingRight = lastWidth/2 + 3;
			this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10;

			this.xLabelRotation = 0;
			if (this.display){
				var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels),
					cosRotation,
					firstRotatedWidth;
				this.xLabelWidth = originalLabelWidth;
				//Allow 3 pixels x2 padding either side for label readability
				var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;

				//Max label rotate should be 90 - also act as a loop counter
				while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){
					cosRotation = Math.cos(toRadians(this.xLabelRotation));

					firstRotated = cosRotation * firstWidth;
					lastRotated = cosRotation * lastWidth;

					// We're right aligning the text now.
					if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){
						this.xScalePaddingLeft = firstRotated + this.fontSize / 2;
					}
					this.xScalePaddingRight = this.fontSize/2;


					this.xLabelRotation++;
					this.xLabelWidth = cosRotation * originalLabelWidth;

				}
				if (this.xLabelRotation > 0){
					this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3;
				}
			}
			else{
				this.xLabelWidth = 0;
				this.xScalePaddingRight = this.padding;
				this.xScalePaddingLeft = this.padding;
			}

		},
		// Needs to be overidden in each Chart type
		// Otherwise we need to pass all the data into the scale class
		calculateYRange: noop,
		drawingArea: function(){
			return this.startPoint - this.endPoint;
		},
		calculateY : function(value){
			var scalingFactor = this.drawingArea() / (this.min - this.max);
			return this.endPoint - (scalingFactor * (value - this.min));
		},
		calculateX : function(index){
			var isRotated = (this.xLabelRotation > 0),
				// innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
				innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
				valueWidth = innerWidth/Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1),
				valueOffset = (valueWidth * index) + this.xScalePaddingLeft;

			if (this.offsetGridLines){
				valueOffset += (valueWidth/2);
			}

			return Math.round(valueOffset);
		},
		update : function(newProps){
			helpers.extend(this, newProps);
			this.fit();
		},
		draw : function(){
			var ctx = this.ctx,
				yLabelGap = (this.endPoint - this.startPoint) / this.steps,
				xStart = Math.round(this.xScalePaddingLeft);
			if (this.display){
				ctx.fillStyle = this.textColor;
				ctx.font = this.font;
				each(this.yLabels,function(labelString,index){
					var yLabelCenter = this.endPoint - (yLabelGap * index),
						linePositionY = Math.round(yLabelCenter),
						drawHorizontalLine = this.showHorizontalLines;

					ctx.textAlign = "right";
					ctx.textBaseline = "middle";
					if (this.showLabels){
						ctx.fillText(labelString,xStart - 10,yLabelCenter);
					}

					// This is X axis, so draw it
					if (index === 0 && !drawHorizontalLine){
						drawHorizontalLine = true;
					}

					if (drawHorizontalLine){
						ctx.beginPath();
					}

					if (index > 0){
						// This is a grid line in the centre, so drop that
						ctx.lineWidth = this.gridLineWidth;
						ctx.strokeStyle = this.gridLineColor;
					} else {
						// This is the first line on the scale
						ctx.lineWidth = this.lineWidth;
						ctx.strokeStyle = this.lineColor;
					}

					linePositionY += helpers.aliasPixel(ctx.lineWidth);

					if(drawHorizontalLine){
						ctx.moveTo(xStart, linePositionY);
						ctx.lineTo(this.width, linePositionY);
						ctx.stroke();
						ctx.closePath();
					}

					ctx.lineWidth = this.lineWidth;
					ctx.strokeStyle = this.lineColor;
					ctx.beginPath();
					ctx.moveTo(xStart - 5, linePositionY);
					ctx.lineTo(xStart, linePositionY);
					ctx.stroke();
					ctx.closePath();

				},this);

				each(this.xLabels,function(label,index){
					var xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
						// Check to see if line/bar here and decide where to place the line
						linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
						isRotated = (this.xLabelRotation > 0),
						drawVerticalLine = this.showVerticalLines;

					// This is Y axis, so draw it
					if (index === 0 && !drawVerticalLine){
						drawVerticalLine = true;
					}

					if (drawVerticalLine){
						ctx.beginPath();
					}

					if (index > 0){
						// This is a grid line in the centre, so drop that
						ctx.lineWidth = this.gridLineWidth;
						ctx.strokeStyle = this.gridLineColor;
					} else {
						// This is the first line on the scale
						ctx.lineWidth = this.lineWidth;
						ctx.strokeStyle = this.lineColor;
					}

					if (drawVerticalLine){
						ctx.moveTo(linePos,this.endPoint);
						ctx.lineTo(linePos,this.startPoint - 3);
						ctx.stroke();
						ctx.closePath();
					}


					ctx.lineWidth = this.lineWidth;
					ctx.strokeStyle = this.lineColor;


					// Small lines at the bottom of the base grid line
					ctx.beginPath();
					ctx.moveTo(linePos,this.endPoint);
					ctx.lineTo(linePos,this.endPoint + 5);
					ctx.stroke();
					ctx.closePath();

					ctx.save();
					ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8);
					ctx.rotate(toRadians(this.xLabelRotation)*-1);
					ctx.font = this.font;
					ctx.textAlign = (isRotated) ? "right" : "center";
					ctx.textBaseline = (isRotated) ? "middle" : "top";
					ctx.fillText(label, 0, 0);
					ctx.restore();
				},this);

			}
		}

	});

	Chart.RadialScale = Chart.Element.extend({
		initialize: function(){
			this.size = min([this.height, this.width]);
			this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
		},
		calculateCenterOffset: function(value){
			// Take into account half font size + the yPadding of the top value
			var scalingFactor = this.drawingArea / (this.max - this.min);

			return (value - this.min) * scalingFactor;
		},
		update : function(){
			if (!this.lineArc){
				this.setScaleSize();
			} else {
				this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
			}
			this.buildYLabels();
		},
		buildYLabels: function(){
			this.yLabels = [];

			var stepDecimalPlaces = getDecimalPlaces(this.stepValue);

			for (var i=0; i<=this.steps; i++){
				this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
			}
		},
		getCircumference : function(){
			return ((Math.PI*2) / this.valuesCount);
		},
		setScaleSize: function(){
			/*
			 * Right, this is really confusing and there is a lot of maths going on here
			 * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
			 *
			 * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
			 *
			 * Solution:
			 *
			 * We assume the radius of the polygon is half the size of the canvas at first
			 * at each index we check if the text overlaps.
			 *
			 * Where it does, we store that angle and that index.
			 *
			 * After finding the largest index and angle we calculate how much we need to remove
			 * from the shape radius to move the point inwards by that x.
			 *
			 * We average the left and right distances to get the maximum shape radius that can fit in the box
			 * along with labels.
			 *
			 * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
			 * on each side, removing that from the size, halving it and adding the left x protrusion width.
			 *
			 * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
			 * and position it in the most space efficient manner
			 *
			 * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
			 */


			// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
			// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
			var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]),
				pointPosition,
				i,
				textWidth,
				halfTextWidth,
				furthestRight = this.width,
				furthestRightIndex,
				furthestRightAngle,
				furthestLeft = 0,
				furthestLeftIndex,
				furthestLeftAngle,
				xProtrusionLeft,
				xProtrusionRight,
				radiusReductionRight,
				radiusReductionLeft,
				maxWidthRadius;
			this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
			for (i=0;i<this.valuesCount;i++){
				// 5px to space the text slightly out - similar to what we do in the draw function.
				pointPosition = this.getPointPosition(i, largestPossibleRadius);
				textWidth = this.ctx.measureText(template(this.templateString, { value: this.labels[i] })).width + 5;
				if (i === 0 || i === this.valuesCount/2){
					// If we're at index zero, or exactly the middle, we're at exactly the top/bottom
					// of the radar chart, so text will be aligned centrally, so we'll half it and compare
					// w/left and right text sizes
					halfTextWidth = textWidth/2;
					if (pointPosition.x + halfTextWidth > furthestRight) {
						furthestRight = pointPosition.x + halfTextWidth;
						furthestRightIndex = i;
					}
					if (pointPosition.x - halfTextWidth < furthestLeft) {
						furthestLeft = pointPosition.x - halfTextWidth;
						furthestLeftIndex = i;
					}
				}
				else if (i < this.valuesCount/2) {
					// Less than half the values means we'll left align the text
					if (pointPosition.x + textWidth > furthestRight) {
						furthestRight = pointPosition.x + textWidth;
						furthestRightIndex = i;
					}
				}
				else if (i > this.valuesCount/2){
					// More than half the values means we'll right align the text
					if (pointPosition.x - textWidth < furthestLeft) {
						furthestLeft = pointPosition.x - textWidth;
						furthestLeftIndex = i;
					}
				}
			}

			xProtrusionLeft = furthestLeft;

			xProtrusionRight = Math.ceil(furthestRight - this.width);

			furthestRightAngle = this.getIndexAngle(furthestRightIndex);

			furthestLeftAngle = this.getIndexAngle(furthestLeftIndex);

			radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2);

			radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2);

			// Ensure we actually need to reduce the size of the chart
			radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
			radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;

			this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2;

			//this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
			this.setCenterPoint(radiusReductionLeft, radiusReductionRight);

		},
		setCenterPoint: function(leftMovement, rightMovement){

			var maxRight = this.width - rightMovement - this.drawingArea,
				maxLeft = leftMovement + this.drawingArea;

			this.xCenter = (maxLeft + maxRight)/2;
			// Always vertically in the centre as the text height doesn't change
			this.yCenter = (this.height/2);
		},

		getIndexAngle : function(index){
			var angleMultiplier = (Math.PI * 2) / this.valuesCount;
			// Start from the top instead of right, so remove a quarter of the circle

			return index * angleMultiplier - (Math.PI/2);
		},
		getPointPosition : function(index, distanceFromCenter){
			var thisAngle = this.getIndexAngle(index);
			return {
				x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
				y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
			};
		},
		draw: function(){
			if (this.display){
				var ctx = this.ctx;
				each(this.yLabels, function(label, index){
					// Don't draw a centre value
					if (index > 0){
						var yCenterOffset = index * (this.drawingArea/this.steps),
							yHeight = this.yCenter - yCenterOffset,
							pointPosition;

						// Draw circular lines around the scale
						if (this.lineWidth > 0){
							ctx.strokeStyle = this.lineColor;
							ctx.lineWidth = this.lineWidth;

							if(this.lineArc){
								ctx.beginPath();
								ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2);
								ctx.closePath();
								ctx.stroke();
							} else{
								ctx.beginPath();
								for (var i=0;i<this.valuesCount;i++)
								{
									pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue)));
									if (i === 0){
										ctx.moveTo(pointPosition.x, pointPosition.y);
									} else {
										ctx.lineTo(pointPosition.x, pointPosition.y);
									}
								}
								ctx.closePath();
								ctx.stroke();
							}
						}
						if(this.showLabels){
							ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
							if (this.showLabelBackdrop){
								var labelWidth = ctx.measureText(label).width;
								ctx.fillStyle = this.backdropColor;
								ctx.fillRect(
									this.xCenter - labelWidth/2 - this.backdropPaddingX,
									yHeight - this.fontSize/2 - this.backdropPaddingY,
									labelWidth + this.backdropPaddingX*2,
									this.fontSize + this.backdropPaddingY*2
								);
							}
							ctx.textAlign = 'center';
							ctx.textBaseline = "middle";
							ctx.fillStyle = this.fontColor;
							ctx.fillText(label, this.xCenter, yHeight);
						}
					}
				}, this);

				if (!this.lineArc){
					ctx.lineWidth = this.angleLineWidth;
					ctx.strokeStyle = this.angleLineColor;
					for (var i = this.valuesCount - 1; i >= 0; i--) {
						if (this.angleLineWidth > 0){
							var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max));
							ctx.beginPath();
							ctx.moveTo(this.xCenter, this.yCenter);
							ctx.lineTo(outerPosition.x, outerPosition.y);
							ctx.stroke();
							ctx.closePath();
						}
						// Extra 3px out for some label spacing
						var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
						ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
						ctx.fillStyle = this.pointLabelFontColor;

						var labelsCount = this.labels.length,
							halfLabelsCount = this.labels.length/2,
							quarterLabelsCount = halfLabelsCount/2,
							upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
							exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
						if (i === 0){
							ctx.textAlign = 'center';
						} else if(i === halfLabelsCount){
							ctx.textAlign = 'center';
						} else if (i < halfLabelsCount){
							ctx.textAlign = 'left';
						} else {
							ctx.textAlign = 'right';
						}

						// Set the correct text baseline based on outer positioning
						if (exactQuarter){
							ctx.textBaseline = 'middle';
						} else if (upperHalf){
							ctx.textBaseline = 'bottom';
						} else {
							ctx.textBaseline = 'top';
						}

						ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
					}
				}
			}
		}
	});

	// Attach global event to resize each chart instance when the browser resizes
	helpers.addEvent(window, "resize", (function(){
		// Basic debounce of resize function so it doesn't hurt performance when resizing browser.
		var timeout;
		return function(){
			clearTimeout(timeout);
			timeout = setTimeout(function(){
				each(Chart.instances,function(instance){
					// If the responsive flag is set in the chart instance config
					// Cascade the resize event down to the chart.
					if (instance.options.responsive){
						instance.resize(instance.render, true);
					}
				});
			}, 50);
		};
	})());


	if (amd) {
		define(function(){
			return Chart;
		});
	} else if (typeof module === 'object' && module.exports) {
		module.exports = Chart;
	}

	root.Chart = Chart;

	Chart.noConflict = function(){
		root.Chart = previous;
		return Chart;
	};

}).call(this);

(function(){
	"use strict";

	var root = this,
		Chart = root.Chart,
		helpers = Chart.helpers;


	var defaultConfig = {
		//Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
		scaleBeginAtZero : true,

		//Boolean - Whether grid lines are shown across the chart
		scaleShowGridLines : true,

		//String - Colour of the grid lines
		scaleGridLineColor : "rgba(0,0,0,.05)",

		//Number - Width of the grid lines
		scaleGridLineWidth : 1,

		//Boolean - Whether to show horizontal lines (except X axis)
		scaleShowHorizontalLines: true,

		//Boolean - Whether to show vertical lines (except Y axis)
		scaleShowVerticalLines: true,

		//Boolean - If there is a stroke on each bar
		barShowStroke : true,

		//Number - Pixel width of the bar stroke
		barStrokeWidth : 2,

		//Number - Spacing between each of the X value sets
		barValueSpacing : 5,

		//Number - Spacing between data sets within X values
		barDatasetSpacing : 1,

		//String - A legend template
		legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].fillColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"

	};


	Chart.Type.extend({
		name: "Bar",
		defaults : defaultConfig,
		initialize:  function(data){

			//Expose options as a scope variable here so we can access it in the ScaleClass
			var options = this.options;

			this.ScaleClass = Chart.Scale.extend({
				offsetGridLines : true,
				calculateBarX : function(datasetCount, datasetIndex, barIndex){
					//Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar
					var xWidth = this.calculateBaseWidth(),
						xAbsolute = this.calculateX(barIndex) - (xWidth/2),
						barWidth = this.calculateBarWidth(datasetCount);

					return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth/2;
				},
				calculateBaseWidth : function(){
					return (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing);
				},
				calculateBarWidth : function(datasetCount){
					//The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
					var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing);

					return (baseWidth / datasetCount);
				}
			});

			this.datasets = [];

			//Set up tooltip events on the chart
			if (this.options.showTooltips){
				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
					var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : [];

					this.eachBars(function(bar){
						bar.restore(['fillColor', 'strokeColor']);
					});
					helpers.each(activeBars, function(activeBar){
						activeBar.fillColor = activeBar.highlightFill;
						activeBar.strokeColor = activeBar.highlightStroke;
					});
					this.showTooltip(activeBars);
				});
			}

			//Declare the extension of the default point, to cater for the options passed in to the constructor
			this.BarClass = Chart.Rectangle.extend({
				strokeWidth : this.options.barStrokeWidth,
				showStroke : this.options.barShowStroke,
				ctx : this.chart.ctx
			});

			//Iterate through each of the datasets, and build this into a property of the chart
			helpers.each(data.datasets,function(dataset,datasetIndex){

				var datasetObject = {
					label : dataset.label || null,
					fillColor : dataset.fillColor,
					strokeColor : dataset.strokeColor,
					bars : []
				};

				this.datasets.push(datasetObject);

				helpers.each(dataset.data,function(dataPoint,index){
					//Add a new point for each piece of data, passing any required data to draw.
					datasetObject.bars.push(new this.BarClass({
						value : dataPoint,
						label : data.labels[index],
						datasetLabel: dataset.label,
						strokeColor : dataset.strokeColor,
						fillColor : dataset.fillColor,
						highlightFill : dataset.highlightFill || dataset.fillColor,
						highlightStroke : dataset.highlightStroke || dataset.strokeColor
					}));
				},this);

			},this);

			this.buildScale(data.labels);

			this.BarClass.prototype.base = this.scale.endPoint;

			this.eachBars(function(bar, index, datasetIndex){
				helpers.extend(bar, {
					width : this.scale.calculateBarWidth(this.datasets.length),
					x: this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
					y: this.scale.endPoint
				});
				bar.save();
			}, this);

			this.render();
		},
		update : function(){
			this.scale.update();
			// Reset any highlight colours before updating.
			helpers.each(this.activeElements, function(activeElement){
				activeElement.restore(['fillColor', 'strokeColor']);
			});

			this.eachBars(function(bar){
				bar.save();
			});
			this.render();
		},
		eachBars : function(callback){
			helpers.each(this.datasets,function(dataset, datasetIndex){
				helpers.each(dataset.bars, callback, this, datasetIndex);
			},this);
		},
		getBarsAtEvent : function(e){
			var barsArray = [],
				eventPosition = helpers.getRelativePosition(e),
				datasetIterator = function(dataset){
					barsArray.push(dataset.bars[barIndex]);
				},
				barIndex;

			for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) {
				for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) {
					if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){
						helpers.each(this.datasets, datasetIterator);
						return barsArray;
					}
				}
			}

			return barsArray;
		},
		buildScale : function(labels){
			var self = this;

			var dataTotal = function(){
				var values = [];
				self.eachBars(function(bar){
					values.push(bar.value);
				});
				return values;
			};

			var scaleOptions = {
				templateString : this.options.scaleLabel,
				height : this.chart.height,
				width : this.chart.width,
				ctx : this.chart.ctx,
				textColor : this.options.scaleFontColor,
				fontSize : this.options.scaleFontSize,
				fontStyle : this.options.scaleFontStyle,
				fontFamily : this.options.scaleFontFamily,
				valuesCount : labels.length,
				beginAtZero : this.options.scaleBeginAtZero,
				integersOnly : this.options.scaleIntegersOnly,
				calculateYRange: function(currentHeight){
					var updatedRanges = helpers.calculateScaleRange(
						dataTotal(),
						currentHeight,
						this.fontSize,
						this.beginAtZero,
						this.integersOnly
					);
					helpers.extend(this, updatedRanges);
				},
				xLabels : labels,
				font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
				lineWidth : this.options.scaleLineWidth,
				lineColor : this.options.scaleLineColor,
				showHorizontalLines : this.options.scaleShowHorizontalLines,
				showVerticalLines : this.options.scaleShowVerticalLines,
				gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
				gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
				padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0,
				showLabels : this.options.scaleShowLabels,
				display : this.options.showScale
			};

			if (this.options.scaleOverride){
				helpers.extend(scaleOptions, {
					calculateYRange: helpers.noop,
					steps: this.options.scaleSteps,
					stepValue: this.options.scaleStepWidth,
					min: this.options.scaleStartValue,
					max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
				});
			}

			this.scale = new this.ScaleClass(scaleOptions);
		},
		addData : function(valuesArray,label){
			//Map the values array for each of the datasets
			helpers.each(valuesArray,function(value,datasetIndex){
				//Add a new point for each piece of data, passing any required data to draw.
				this.datasets[datasetIndex].bars.push(new this.BarClass({
					value : value,
					label : label,
					x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1),
					y: this.scale.endPoint,
					width : this.scale.calculateBarWidth(this.datasets.length),
					base : this.scale.endPoint,
					strokeColor : this.datasets[datasetIndex].strokeColor,
					fillColor : this.datasets[datasetIndex].fillColor
				}));
			},this);

			this.scale.addXLabel(label);
			//Then re-render the chart.
			this.update();
		},
		removeData : function(){
			this.scale.removeXLabel();
			//Then re-render the chart.
			helpers.each(this.datasets,function(dataset){
				dataset.bars.shift();
			},this);
			this.update();
		},
		reflow : function(){
			helpers.extend(this.BarClass.prototype,{
				y: this.scale.endPoint,
				base : this.scale.endPoint
			});
			var newScaleProps = helpers.extend({
				height : this.chart.height,
				width : this.chart.width
			});
			this.scale.update(newScaleProps);
		},
		draw : function(ease){
			var easingDecimal = ease || 1;
			this.clear();

			var ctx = this.chart.ctx;

			this.scale.draw(easingDecimal);

			//Draw all the bars for each dataset
			helpers.each(this.datasets,function(dataset,datasetIndex){
				helpers.each(dataset.bars,function(bar,index){
					if (bar.hasValue()){
						bar.base = this.scale.endPoint;
						//Transition then draw
						bar.transition({
							x : this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
							y : this.scale.calculateY(bar.value),
							width : this.scale.calculateBarWidth(this.datasets.length)
						}, easingDecimal).draw();
					}
				},this);

			},this);
		}
	});


}).call(this);

(function(){
	"use strict";

	var root = this,
		Chart = root.Chart,
		//Cache a local reference to Chart.helpers
		helpers = Chart.helpers;

	var defaultConfig = {
		//Boolean - Whether we should show a stroke on each segment
		segmentShowStroke : true,

		//String - The colour of each segment stroke
		segmentStrokeColor : "#fff",

		//Number - The width of each segment stroke
		segmentStrokeWidth : 2,

		//The percentage of the chart that we cut out of the middle.
		percentageInnerCutout : 50,

		//Number - Amount of animation steps
		animationSteps : 100,

		//String - Animation easing effect
		animationEasing : "easeOutBounce",

		//Boolean - Whether we animate the rotation of the Doughnut
		animateRotate : true,

		//Boolean - Whether we animate scaling the Doughnut from the centre
		animateScale : false,

		//String - A legend template
		legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"

	};


	Chart.Type.extend({
		//Passing in a name registers this chart in the Chart namespace
		name: "Doughnut",
		//Providing a defaults will also register the deafults in the chart namespace
		defaults : defaultConfig,
		//Initialize is fired when the chart is initialized - Data is passed in as a parameter
		//Config is automatically merged by the core of Chart.js, and is available at this.options
		initialize:  function(data){

			//Declare segments as a static property to prevent inheriting across the Chart type prototype
			this.segments = [];
			this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) -	this.options.segmentStrokeWidth/2)/2;

			this.SegmentArc = Chart.Arc.extend({
				ctx : this.chart.ctx,
				x : this.chart.width/2,
				y : this.chart.height/2
			});

			//Set up tooltip events on the chart
			if (this.options.showTooltips){
				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
					var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];

					helpers.each(this.segments,function(segment){
						segment.restore(["fillColor"]);
					});
					helpers.each(activeSegments,function(activeSegment){
						activeSegment.fillColor = activeSegment.highlightColor;
					});
					this.showTooltip(activeSegments);
				});
			}
			this.calculateTotal(data);

			helpers.each(data,function(datapoint, index){
				this.addData(datapoint, index, true);
			},this);

			this.render();
		},
		getSegmentsAtEvent : function(e){
			var segmentsArray = [];

			var location = helpers.getRelativePosition(e);

			helpers.each(this.segments,function(segment){
				if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
			},this);
			return segmentsArray;
		},
		addData : function(segment, atIndex, silent){
			var index = atIndex || this.segments.length;
			this.segments.splice(index, 0, new this.SegmentArc({
				value : segment.value,
				outerRadius : (this.options.animateScale) ? 0 : this.outerRadius,
				innerRadius : (this.options.animateScale) ? 0 : (this.outerRadius/100) * this.options.percentageInnerCutout,
				fillColor : segment.color,
				highlightColor : segment.highlight || segment.color,
				showStroke : this.options.segmentShowStroke,
				strokeWidth : this.options.segmentStrokeWidth,
				strokeColor : this.options.segmentStrokeColor,
				startAngle : Math.PI * 1.5,
				circumference : (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value),
				label : segment.label
			}));
			if (!silent){
				this.reflow();
				this.update();
			}
		},
		calculateCircumference : function(value){
			return (Math.PI*2)*(Math.abs(value) / this.total);
		},
		calculateTotal : function(data){
			this.total = 0;
			helpers.each(data,function(segment){
				this.total += Math.abs(segment.value);
			},this);
		},
		update : function(){
			this.calculateTotal(this.segments);

			// Reset any highlight colours before updating.
			helpers.each(this.activeElements, function(activeElement){
				activeElement.restore(['fillColor']);
			});

			helpers.each(this.segments,function(segment){
				segment.save();
			});
			this.render();
		},

		removeData: function(atIndex){
			var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
			this.segments.splice(indexToDelete, 1);
			this.reflow();
			this.update();
		},

		reflow : function(){
			helpers.extend(this.SegmentArc.prototype,{
				x : this.chart.width/2,
				y : this.chart.height/2
			});
			this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) -	this.options.segmentStrokeWidth/2)/2;
			helpers.each(this.segments, function(segment){
				segment.update({
					outerRadius : this.outerRadius,
					innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
				});
			}, this);
		},
		draw : function(easeDecimal){
			var animDecimal = (easeDecimal) ? easeDecimal : 1;
			this.clear();
			helpers.each(this.segments,function(segment,index){
				segment.transition({
					circumference : this.calculateCircumference(segment.value),
					outerRadius : this.outerRadius,
					innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
				},animDecimal);

				segment.endAngle = segment.startAngle + segment.circumference;

				segment.draw();
				if (index === 0){
					segment.startAngle = Math.PI * 1.5;
				}
				//Check to see if it's the last segment, if not get the next and update the start angle
				if (index < this.segments.length-1){
					this.segments[index+1].startAngle = segment.endAngle;
				}
			},this);

		}
	});

	Chart.types.Doughnut.extend({
		name : "Pie",
		defaults : helpers.merge(defaultConfig,{percentageInnerCutout : 0})
	});

}).call(this);
(function(){
	"use strict";

	var root = this,
		Chart = root.Chart,
		helpers = Chart.helpers;

	var defaultConfig = {

		///Boolean - Whether grid lines are shown across the chart
		scaleShowGridLines : true,

		//String - Colour of the grid lines
		scaleGridLineColor : "rgba(0,0,0,.05)",

		//Number - Width of the grid lines
		scaleGridLineWidth : 1,

		//Boolean - Whether to show horizontal lines (except X axis)
		scaleShowHorizontalLines: true,

		//Boolean - Whether to show vertical lines (except Y axis)
		scaleShowVerticalLines: true,

		//Boolean - Whether the line is curved between points
		bezierCurve : true,

		//Number - Tension of the bezier curve between points
		bezierCurveTension : 0.4,

		//Boolean - Whether to show a dot for each point
		pointDot : true,

		//Number - Radius of each point dot in pixels
		pointDotRadius : 4,

		//Number - Pixel width of point dot stroke
		pointDotStrokeWidth : 1,

		//Number - amount extra to add to the radius to cater for hit detection outside the drawn point
		pointHitDetectionRadius : 20,

		//Boolean - Whether to show a stroke for datasets
		datasetStroke : true,

		//Number - Pixel width of dataset stroke
		datasetStrokeWidth : 2,

		//Boolean - Whether to fill the dataset with a colour
		datasetFill : true,

		//String - A legend template
		legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"

	};


	Chart.Type.extend({
		name: "Line",
		defaults : defaultConfig,
		initialize:  function(data){
			//Declare the extension of the default point, to cater for the options passed in to the constructor
			this.PointClass = Chart.Point.extend({
				strokeWidth : this.options.pointDotStrokeWidth,
				radius : this.options.pointDotRadius,
				display: this.options.pointDot,
				hitDetectionRadius : this.options.pointHitDetectionRadius,
				ctx : this.chart.ctx,
				inRange : function(mouseX){
					return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2));
				}
			});

			this.datasets = [];

			//Set up tooltip events on the chart
			if (this.options.showTooltips){
				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
					var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
					this.eachPoints(function(point){
						point.restore(['fillColor', 'strokeColor']);
					});
					helpers.each(activePoints, function(activePoint){
						activePoint.fillColor = activePoint.highlightFill;
						activePoint.strokeColor = activePoint.highlightStroke;
					});
					this.showTooltip(activePoints);
				});
			}

			//Iterate through each of the datasets, and build this into a property of the chart
			helpers.each(data.datasets,function(dataset){

				var datasetObject = {
					label : dataset.label || null,
					fillColor : dataset.fillColor,
					strokeColor : dataset.strokeColor,
					pointColor : dataset.pointColor,
					pointStrokeColor : dataset.pointStrokeColor,
					points : []
				};

				this.datasets.push(datasetObject);


				helpers.each(dataset.data,function(dataPoint,index){
					//Add a new point for each piece of data, passing any required data to draw.
					datasetObject.points.push(new this.PointClass({
						value : dataPoint,
						label : data.labels[index],
						datasetLabel: dataset.label,
						strokeColor : dataset.pointStrokeColor,
						fillColor : dataset.pointColor,
						highlightFill : dataset.pointHighlightFill || dataset.pointColor,
						highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
					}));
				},this);

				this.buildScale(data.labels);


				this.eachPoints(function(point, index){
					helpers.extend(point, {
						x: this.scale.calculateX(index),
						y: this.scale.endPoint
					});
					point.save();
				}, this);

			},this);


			this.render();
		},
		update : function(){
			this.scale.update();
			// Reset any highlight colours before updating.
			helpers.each(this.activeElements, function(activeElement){
				activeElement.restore(['fillColor', 'strokeColor']);
			});
			this.eachPoints(function(point){
				point.save();
			});
			this.render();
		},
		eachPoints : function(callback){
			helpers.each(this.datasets,function(dataset){
				helpers.each(dataset.points,callback,this);
			},this);
		},
		getPointsAtEvent : function(e){
			var pointsArray = [],
				eventPosition = helpers.getRelativePosition(e);
			helpers.each(this.datasets,function(dataset){
				helpers.each(dataset.points,function(point){
					if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point);
				});
			},this);
			return pointsArray;
		},
		buildScale : function(labels){
			var self = this;

			var dataTotal = function(){
				var values = [];
				self.eachPoints(function(point){
					values.push(point.value);
				});

				return values;
			};

			var scaleOptions = {
				templateString : this.options.scaleLabel,
				height : this.chart.height,
				width : this.chart.width,
				ctx : this.chart.ctx,
				textColor : this.options.scaleFontColor,
				fontSize : this.options.scaleFontSize,
				fontStyle : this.options.scaleFontStyle,
				fontFamily : this.options.scaleFontFamily,
				valuesCount : labels.length,
				beginAtZero : this.options.scaleBeginAtZero,
				integersOnly : this.options.scaleIntegersOnly,
				calculateYRange : function(currentHeight){
					var updatedRanges = helpers.calculateScaleRange(
						dataTotal(),
						currentHeight,
						this.fontSize,
						this.beginAtZero,
						this.integersOnly
					);
					helpers.extend(this, updatedRanges);
				},
				xLabels : labels,
				font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
				lineWidth : this.options.scaleLineWidth,
				lineColor : this.options.scaleLineColor,
				showHorizontalLines : this.options.scaleShowHorizontalLines,
				showVerticalLines : this.options.scaleShowVerticalLines,
				gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
				gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)",
				padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth,
				showLabels : this.options.scaleShowLabels,
				display : this.options.showScale
			};

			if (this.options.scaleOverride){
				helpers.extend(scaleOptions, {
					calculateYRange: helpers.noop,
					steps: this.options.scaleSteps,
					stepValue: this.options.scaleStepWidth,
					min: this.options.scaleStartValue,
					max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
				});
			}


			this.scale = new Chart.Scale(scaleOptions);
		},
		addData : function(valuesArray,label){
			//Map the values array for each of the datasets

			helpers.each(valuesArray,function(value,datasetIndex){
				//Add a new point for each piece of data, passing any required data to draw.
				this.datasets[datasetIndex].points.push(new this.PointClass({
					value : value,
					label : label,
					x: this.scale.calculateX(this.scale.valuesCount+1),
					y: this.scale.endPoint,
					strokeColor : this.datasets[datasetIndex].pointStrokeColor,
					fillColor : this.datasets[datasetIndex].pointColor
				}));
			},this);

			this.scale.addXLabel(label);
			//Then re-render the chart.
			this.update();
		},
		removeData : function(){
			this.scale.removeXLabel();
			//Then re-render the chart.
			helpers.each(this.datasets,function(dataset){
				dataset.points.shift();
			},this);
			this.update();
		},
		reflow : function(){
			var newScaleProps = helpers.extend({
				height : this.chart.height,
				width : this.chart.width
			});
			this.scale.update(newScaleProps);
		},
		draw : function(ease){
			var easingDecimal = ease || 1;
			this.clear();

			var ctx = this.chart.ctx;

			// Some helper methods for getting the next/prev points
			var hasValue = function(item){
				return item.value !== null;
			},
			nextPoint = function(point, collection, index){
				return helpers.findNextWhere(collection, hasValue, index) || point;
			},
			previousPoint = function(point, collection, index){
				return helpers.findPreviousWhere(collection, hasValue, index) || point;
			};

			this.scale.draw(easingDecimal);


			helpers.each(this.datasets,function(dataset){
				var pointsWithValues = helpers.where(dataset.points, hasValue);

				//Transition each point first so that the line and point drawing isn't out of sync
				//We can use this extra loop to calculate the control points of this dataset also in this loop

				helpers.each(dataset.points, function(point, index){
					if (point.hasValue()){
						point.transition({
							y : this.scale.calculateY(point.value),
							x : this.scale.calculateX(index)
						}, easingDecimal);
					}
				},this);


				// Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point
				// This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed
				if (this.options.bezierCurve){
					helpers.each(pointsWithValues, function(point, index){
						var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0;
						point.controlPoints = helpers.splineCurve(
							previousPoint(point, pointsWithValues, index),
							point,
							nextPoint(point, pointsWithValues, index),
							tension
						);

						// Prevent the bezier going outside of the bounds of the graph

						// Cap puter bezier handles to the upper/lower scale bounds
						if (point.controlPoints.outer.y > this.scale.endPoint){
							point.controlPoints.outer.y = this.scale.endPoint;
						}
						else if (point.controlPoints.outer.y < this.scale.startPoint){
							point.controlPoints.outer.y = this.scale.startPoint;
						}

						// Cap inner bezier handles to the upper/lower scale bounds
						if (point.controlPoints.inner.y > this.scale.endPoint){
							point.controlPoints.inner.y = this.scale.endPoint;
						}
						else if (point.controlPoints.inner.y < this.scale.startPoint){
							point.controlPoints.inner.y = this.scale.startPoint;
						}
					},this);
				}


				//Draw the line between all the points
				ctx.lineWidth = this.options.datasetStrokeWidth;
				ctx.strokeStyle = dataset.strokeColor;
				ctx.beginPath();

				helpers.each(pointsWithValues, function(point, index){
					if (index === 0){
						ctx.moveTo(point.x, point.y);
					}
					else{
						if(this.options.bezierCurve){
							var previous = previousPoint(point, pointsWithValues, index);

							ctx.bezierCurveTo(
								previous.controlPoints.outer.x,
								previous.controlPoints.outer.y,
								point.controlPoints.inner.x,
								point.controlPoints.inner.y,
								point.x,
								point.y
							);
						}
						else{
							ctx.lineTo(point.x,point.y);
						}
					}
				}, this);

				ctx.stroke();

				if (this.options.datasetFill && pointsWithValues.length > 0){
					//Round off the line by going to the base of the chart, back to the start, then fill.
					ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint);
					ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint);
					ctx.fillStyle = dataset.fillColor;
					ctx.closePath();
					ctx.fill();
				}

				//Now draw the points over the line
				//A little inefficient double looping, but better than the line
				//lagging behind the point positions
				helpers.each(pointsWithValues,function(point){
					point.draw();
				});
			},this);
		}
	});


}).call(this);

(function(){
	"use strict";

	var root = this,
		Chart = root.Chart,
		//Cache a local reference to Chart.helpers
		helpers = Chart.helpers;

	var defaultConfig = {
		//Boolean - Show a backdrop to the scale label
		scaleShowLabelBackdrop : true,

		//String - The colour of the label backdrop
		scaleBackdropColor : "rgba(255,255,255,0.75)",

		// Boolean - Whether the scale should begin at zero
		scaleBeginAtZero : true,

		//Number - The backdrop padding above & below the label in pixels
		scaleBackdropPaddingY : 2,

		//Number - The backdrop padding to the side of the label in pixels
		scaleBackdropPaddingX : 2,

		//Boolean - Show line for each value in the scale
		scaleShowLine : true,

		//Boolean - Stroke a line around each segment in the chart
		segmentShowStroke : true,

		//String - The colour of the stroke on each segement.
		segmentStrokeColor : "#fff",

		//Number - The width of the stroke value in pixels
		segmentStrokeWidth : 2,

		//Number - Amount of animation steps
		animationSteps : 100,

		//String - Animation easing effect.
		animationEasing : "easeOutBounce",

		//Boolean - Whether to animate the rotation of the chart
		animateRotate : true,

		//Boolean - Whether to animate scaling the chart from the centre
		animateScale : false,

		//String - A legend template
		legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>"
	};


	Chart.Type.extend({
		//Passing in a name registers this chart in the Chart namespace
		name: "PolarArea",
		//Providing a defaults will also register the deafults in the chart namespace
		defaults : defaultConfig,
		//Initialize is fired when the chart is initialized - Data is passed in as a parameter
		//Config is automatically merged by the core of Chart.js, and is available at this.options
		initialize:  function(data){
			this.segments = [];
			//Declare segment class as a chart instance specific class, so it can share props for this instance
			this.SegmentArc = Chart.Arc.extend({
				showStroke : this.options.segmentShowStroke,
				strokeWidth : this.options.segmentStrokeWidth,
				strokeColor : this.options.segmentStrokeColor,
				ctx : this.chart.ctx,
				innerRadius : 0,
				x : this.chart.width/2,
				y : this.chart.height/2
			});
			this.scale = new Chart.RadialScale({
				display: this.options.showScale,
				fontStyle: this.options.scaleFontStyle,
				fontSize: this.options.scaleFontSize,
				fontFamily: this.options.scaleFontFamily,
				fontColor: this.options.scaleFontColor,
				showLabels: this.options.scaleShowLabels,
				showLabelBackdrop: this.options.scaleShowLabelBackdrop,
				backdropColor: this.options.scaleBackdropColor,
				backdropPaddingY : this.options.scaleBackdropPaddingY,
				backdropPaddingX: this.options.scaleBackdropPaddingX,
				lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
				lineColor: this.options.scaleLineColor,
				lineArc: true,
				width: this.chart.width,
				height: this.chart.height,
				xCenter: this.chart.width/2,
				yCenter: this.chart.height/2,
				ctx : this.chart.ctx,
				templateString: this.options.scaleLabel,
				valuesCount: data.length
			});

			this.updateScaleRange(data);

			this.scale.update();

			helpers.each(data,function(segment,index){
				this.addData(segment,index,true);
			},this);

			//Set up tooltip events on the chart
			if (this.options.showTooltips){
				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
					var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
					helpers.each(this.segments,function(segment){
						segment.restore(["fillColor"]);
					});
					helpers.each(activeSegments,function(activeSegment){
						activeSegment.fillColor = activeSegment.highlightColor;
					});
					this.showTooltip(activeSegments);
				});
			}

			this.render();
		},
		getSegmentsAtEvent : function(e){
			var segmentsArray = [];

			var location = helpers.getRelativePosition(e);

			helpers.each(this.segments,function(segment){
				if (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
			},this);
			return segmentsArray;
		},
		addData : function(segment, atIndex, silent){
			var index = atIndex || this.segments.length;

			this.segments.splice(index, 0, new this.SegmentArc({
				fillColor: segment.color,
				highlightColor: segment.highlight || segment.color,
				label: segment.label,
				value: segment.value,
				outerRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value),
				circumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(),
				startAngle: Math.PI * 1.5
			}));
			if (!silent){
				this.reflow();
				this.update();
			}
		},
		removeData: function(atIndex){
			var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
			this.segments.splice(indexToDelete, 1);
			this.reflow();
			this.update();
		},
		calculateTotal: function(data){
			this.total = 0;
			helpers.each(data,function(segment){
				this.total += segment.value;
			},this);
			this.scale.valuesCount = this.segments.length;
		},
		updateScaleRange: function(datapoints){
			var valuesArray = [];
			helpers.each(datapoints,function(segment){
				valuesArray.push(segment.value);
			});

			var scaleSizes = (this.options.scaleOverride) ?
				{
					steps: this.options.scaleSteps,
					stepValue: this.options.scaleStepWidth,
					min: this.options.scaleStartValue,
					max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
				} :
				helpers.calculateScaleRange(
					valuesArray,
					helpers.min([this.chart.width, this.chart.height])/2,
					this.options.scaleFontSize,
					this.options.scaleBeginAtZero,
					this.options.scaleIntegersOnly
				);

			helpers.extend(
				this.scale,
				scaleSizes,
				{
					size: helpers.min([this.chart.width, this.chart.height]),
					xCenter: this.chart.width/2,
					yCenter: this.chart.height/2
				}
			);

		},
		update : function(){
			this.calculateTotal(this.segments);

			helpers.each(this.segments,function(segment){
				segment.save();
			});
			
			this.reflow();
			this.render();
		},
		reflow : function(){
			helpers.extend(this.SegmentArc.prototype,{
				x : this.chart.width/2,
				y : this.chart.height/2
			});
			this.updateScaleRange(this.segments);
			this.scale.update();

			helpers.extend(this.scale,{
				xCenter: this.chart.width/2,
				yCenter: this.chart.height/2
			});

			helpers.each(this.segments, function(segment){
				segment.update({
					outerRadius : this.scale.calculateCenterOffset(segment.value)
				});
			}, this);

		},
		draw : function(ease){
			var easingDecimal = ease || 1;
			//Clear & draw the canvas
			this.clear();
			helpers.each(this.segments,function(segment, index){
				segment.transition({
					circumference : this.scale.getCircumference(),
					outerRadius : this.scale.calculateCenterOffset(segment.value)
				},easingDecimal);

				segment.endAngle = segment.startAngle + segment.circumference;

				// If we've removed the first segment we need to set the first one to
				// start at the top.
				if (index === 0){
					segment.startAngle = Math.PI * 1.5;
				}

				//Check to see if it's the last segment, if not get the next and update the start angle
				if (index < this.segments.length - 1){
					this.segments[index+1].startAngle = segment.endAngle;
				}
				segment.draw();
			}, this);
			this.scale.draw();
		}
	});

}).call(this);
(function(){
	"use strict";

	var root = this,
		Chart = root.Chart,
		helpers = Chart.helpers;



	Chart.Type.extend({
		name: "Radar",
		defaults:{
			//Boolean - Whether to show lines for each scale point
			scaleShowLine : true,

			//Boolean - Whether we show the angle lines out of the radar
			angleShowLineOut : true,

			//Boolean - Whether to show labels on the scale
			scaleShowLabels : false,

			// Boolean - Whether the scale should begin at zero
			scaleBeginAtZero : true,

			//String - Colour of the angle line
			angleLineColor : "rgba(0,0,0,.1)",

			//Number - Pixel width of the angle line
			angleLineWidth : 1,

			//String - Point label font declaration
			pointLabelFontFamily : "'Arial'",

			//String - Point label font weight
			pointLabelFontStyle : "normal",

			//Number - Point label font size in pixels
			pointLabelFontSize : 10,

			//String - Point label font colour
			pointLabelFontColor : "#666",

			//Boolean - Whether to show a dot for each point
			pointDot : true,

			//Number - Radius of each point dot in pixels
			pointDotRadius : 3,

			//Number - Pixel width of point dot stroke
			pointDotStrokeWidth : 1,

			//Number - amount extra to add to the radius to cater for hit detection outside the drawn point
			pointHitDetectionRadius : 20,

			//Boolean - Whether to show a stroke for datasets
			datasetStroke : true,

			//Number - Pixel width of dataset stroke
			datasetStrokeWidth : 2,

			//Boolean - Whether to fill the dataset with a colour
			datasetFill : true,

			//String - A legend template
			legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>"

		},

		initialize: function(data){
			this.PointClass = Chart.Point.extend({
				strokeWidth : this.options.pointDotStrokeWidth,
				radius : this.options.pointDotRadius,
				display: this.options.pointDot,
				hitDetectionRadius : this.options.pointHitDetectionRadius,
				ctx : this.chart.ctx
			});

			this.datasets = [];

			this.buildScale(data);

			//Set up tooltip events on the chart
			if (this.options.showTooltips){
				helpers.bindEvents(this, this.options.tooltipEvents, function(evt){
					var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];

					this.eachPoints(function(point){
						point.restore(['fillColor', 'strokeColor']);
					});
					helpers.each(activePointsCollection, function(activePoint){
						activePoint.fillColor = activePoint.highlightFill;
						activePoint.strokeColor = activePoint.highlightStroke;
					});

					this.showTooltip(activePointsCollection);
				});
			}

			//Iterate through each of the datasets, and build this into a property of the chart
			helpers.each(data.datasets,function(dataset){

				var datasetObject = {
					label: dataset.label || null,
					fillColor : dataset.fillColor,
					strokeColor : dataset.strokeColor,
					pointColor : dataset.pointColor,
					pointStrokeColor : dataset.pointStrokeColor,
					points : []
				};

				this.datasets.push(datasetObject);

				helpers.each(dataset.data,function(dataPoint,index){
					//Add a new point for each piece of data, passing any required data to draw.
					var pointPosition;
					if (!this.scale.animation){
						pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint));
					}
					datasetObject.points.push(new this.PointClass({
						value : dataPoint,
						label : data.labels[index],
						datasetLabel: dataset.label,
						x: (this.options.animation) ? this.scale.xCenter : pointPosition.x,
						y: (this.options.animation) ? this.scale.yCenter : pointPosition.y,
						strokeColor : dataset.pointStrokeColor,
						fillColor : dataset.pointColor,
						highlightFill : dataset.pointHighlightFill || dataset.pointColor,
						highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
					}));
				},this);

			},this);

			this.render();
		},
		eachPoints : function(callback){
			helpers.each(this.datasets,function(dataset){
				helpers.each(dataset.points,callback,this);
			},this);
		},

		getPointsAtEvent : function(evt){
			var mousePosition = helpers.getRelativePosition(evt),
				fromCenter = helpers.getAngleFromPoint({
					x: this.scale.xCenter,
					y: this.scale.yCenter
				}, mousePosition);

			var anglePerIndex = (Math.PI * 2) /this.scale.valuesCount,
				pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex),
				activePointsCollection = [];

			// If we're at the top, make the pointIndex 0 to get the first of the array.
			if (pointIndex >= this.scale.valuesCount || pointIndex < 0){
				pointIndex = 0;
			}

			if (fromCenter.distance <= this.scale.drawingArea){
				helpers.each(this.datasets, function(dataset){
					activePointsCollection.push(dataset.points[pointIndex]);
				});
			}

			return activePointsCollection;
		},

		buildScale : function(data){
			this.scale = new Chart.RadialScale({
				display: this.options.showScale,
				fontStyle: this.options.scaleFontStyle,
				fontSize: this.options.scaleFontSize,
				fontFamily: this.options.scaleFontFamily,
				fontColor: this.options.scaleFontColor,
				showLabels: this.options.scaleShowLabels,
				showLabelBackdrop: this.options.scaleShowLabelBackdrop,
				backdropColor: this.options.scaleBackdropColor,
				backdropPaddingY : this.options.scaleBackdropPaddingY,
				backdropPaddingX: this.options.scaleBackdropPaddingX,
				lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
				lineColor: this.options.scaleLineColor,
				angleLineColor : this.options.angleLineColor,
				angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0,
				// Point labels at the edge of each line
				pointLabelFontColor : this.options.pointLabelFontColor,
				pointLabelFontSize : this.options.pointLabelFontSize,
				pointLabelFontFamily : this.options.pointLabelFontFamily,
				pointLabelFontStyle : this.options.pointLabelFontStyle,
				height : this.chart.height,
				width: this.chart.width,
				xCenter: this.chart.width/2,
				yCenter: this.chart.height/2,
				ctx : this.chart.ctx,
				templateString: this.options.scaleLabel,
				labels: data.labels,
				valuesCount: data.datasets[0].data.length
			});

			this.scale.setScaleSize();
			this.updateScaleRange(data.datasets);
			this.scale.buildYLabels();
		},
		updateScaleRange: function(datasets){
			var valuesArray = (function(){
				var totalDataArray = [];
				helpers.each(datasets,function(dataset){
					if (dataset.data){
						totalDataArray = totalDataArray.concat(dataset.data);
					}
					else {
						helpers.each(dataset.points, function(point){
							totalDataArray.push(point.value);
						});
					}
				});
				return totalDataArray;
			})();


			var scaleSizes = (this.options.scaleOverride) ?
				{
					steps: this.options.scaleSteps,
					stepValue: this.options.scaleStepWidth,
					min: this.options.scaleStartValue,
					max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
				} :
				helpers.calculateScaleRange(
					valuesArray,
					helpers.min([this.chart.width, this.chart.height])/2,
					this.options.scaleFontSize,
					this.options.scaleBeginAtZero,
					this.options.scaleIntegersOnly
				);

			helpers.extend(
				this.scale,
				scaleSizes
			);

		},
		addData : function(valuesArray,label){
			//Map the values array for each of the datasets
			this.scale.valuesCount++;
			helpers.each(valuesArray,function(value,datasetIndex){
				var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value));
				this.datasets[datasetIndex].points.push(new this.PointClass({
					value : value,
					label : label,
					x: pointPosition.x,
					y: pointPosition.y,
					strokeColor : this.datasets[datasetIndex].pointStrokeColor,
					fillColor : this.datasets[datasetIndex].pointColor
				}));
			},this);

			this.scale.labels.push(label);

			this.reflow();

			this.update();
		},
		removeData : function(){
			this.scale.valuesCount--;
			this.scale.labels.shift();
			helpers.each(this.datasets,function(dataset){
				dataset.points.shift();
			},this);
			this.reflow();
			this.update();
		},
		update : function(){
			this.eachPoints(function(point){
				point.save();
			});
			this.reflow();
			this.render();
		},
		reflow: function(){
			helpers.extend(this.scale, {
				width : this.chart.width,
				height: this.chart.height,
				size : helpers.min([this.chart.width, this.chart.height]),
				xCenter: this.chart.width/2,
				yCenter: this.chart.height/2
			});
			this.updateScaleRange(this.datasets);
			this.scale.setScaleSize();
			this.scale.buildYLabels();
		},
		draw : function(ease){
			var easeDecimal = ease || 1,
				ctx = this.chart.ctx;
			this.clear();
			this.scale.draw();

			helpers.each(this.datasets,function(dataset){

				//Transition each point first so that the line and point drawing isn't out of sync
				helpers.each(dataset.points,function(point,index){
					if (point.hasValue()){
						point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal);
					}
				},this);



				//Draw the line between all the points
				ctx.lineWidth = this.options.datasetStrokeWidth;
				ctx.strokeStyle = dataset.strokeColor;
				ctx.beginPath();
				helpers.each(dataset.points,function(point,index){
					if (index === 0){
						ctx.moveTo(point.x,point.y);
					}
					else{
						ctx.lineTo(point.x,point.y);
					}
				},this);
				ctx.closePath();
				ctx.stroke();

				ctx.fillStyle = dataset.fillColor;
				ctx.fill();

				//Now draw the points over the line
				//A little inefficient double looping, but better than the line
				//lagging behind the point positions
				helpers.each(dataset.points,function(point){
					if (point.hasValue()){
						point.draw();
					}
				});

			},this);

		}

	});





}).call(this);





The below is my html code in aspx page:





The below is my html code in aspx page:

<div class="widget">
                            <div class="widget-header ">
                                <span class="widget-caption">Doughnut Chart</span>
                                <div class="widget-buttons">
                                    <a href="#" data-toggle="collapse">
                                        <i class="fa fa-minus"></i>
                                    </a>
                                    <a href="#" data-toggle="dispose">
                                        <i class="fa fa-times"></i>
                                    </a>
                                </div>
                            </div>
                            <div class="widget-body">

                                <div class="chartcontainer">
                              <canvas id="doughnut" height="300"></canvas>
                                </div>
                            </div>
                        </div>







This works perfect but can any one say how can i add legend to the side of the Doughnut Chart pls help me.



What I have tried:



I do not know how to edit the jquery. I Can bring the data in json format. But Iam unable to know how to apply this on charts. The till now is a template where we purchased.




This works perfect but can any one say how can i add legend to the side of the Doughnut Chart pls help me.

What I have tried:

I do not know how to edit the jquery. I Can bring the data in json format. But Iam unable to know how to apply this on charts. The till now is a template where we purchased.

推荐答案

.ajax({
type: \"POST\",
url: \"CS.aspx/GetCustomers\",
data: \"{}\",
contentType: \"application/json; charset=utf-8\",
dataType: \"json\",
success: function (r) {
var data = eval(r.d);
golbaldou = data;
new Chart(document.getElementById(\"doughnut\").getContext(< span class=\"code-string\">\"2d\")).Doughnut(data);
document.getElementById(\"doughnut\").fillStyle = 'rgba(255,0,0,.4)';
document.getElementById(\"doughnut\").fillRect(20, 20, 20, 80);
},
failure: function (response) {
alert('There was an error.');
}
});
}




var InitiateChartJS = function () {
return {
init: function () {
debugger;
LoadChart();
var doughnutData = golbaldou;
var lineChartData = {
labels: [\"\", \"\", \"\", \"\", \"\", \"\", \"\"],
datasets: [
{
fillColor: \"rgba(93, 178, 255,.4)\",
strokeColor: \"rgba(93, 178, 255,.7)\",
pointColor: \"rgba(93, 178, 255,.7)\",
pointStrokeColor: \"#fff\",
data: [65, 59, 90, 81, 56, 55, 40]
},
{
fillColor: \"rgba(215, 61, 50,.4)\",
strokeColor: \"rgba(215, 61, 50,.6)\",
pointColor: \"rgba(215, 61, 50,.6)\",
pointStrokeColor: \"#fff\",
data: [28, 48, 40, 19, 96, 27, 100]
}
]

};
var pieData = [
{
value: 30,
color: themeprimary
},
{
value: 50,
color: themesecondary
},
{
value: 100,
color: themefourthcolor
}

];
var barChartData = {
labels: [\"January\", \"February\", \"March\", \"April\", \"May\", \"June\", \"July\"],
datasets: [
{
fillColor: themeprimary,
strokeColor: themeprimary,
data: [65, 59, 90, 81, 56, 55, 40]
},
{
fillColor: themethirdcolor,
strokeColor: themethirdcolor,
data: [28, 48, 40, 19, 96, 27, 100]
}
]

};
var chartData = [
{
value: Math.random(),
color: themeprimary
},
{
value: Math.random(),
color: themesecondary
},
{
value: Math.random(),
color: themethirdcolor
},
{
value: Math.random(),
color: themefourthcolor
},
{
value: Math.random(),
color: themefifthcolor
},
{
value: Math.random(),
color: \"#ed4e2a\"
}
];
var radarChartData = {
labels: [\"\", \"\", \"\", \"\", \"\", \"\", \"\"],
datasets: [
{
fillColor: \"rgba(140,196,116,0.5)\",
strokeColor: \"rgba(140,196,116,.7)\",
pointColor: \"rgba(140,196,116,.7)\",
pointStrokeColor: \"#fff\",
data: [65, 59, 90, 81, 56, 55, 40]
},
{
fillColor: \"rgba(215,61,50,0.5)\",
strokeColor: \"rgba(215,61,50,.7)\",
pointColor: \"rgba(215,61,50,.7)\",
pointStrokeColor: \"#f ff\",
data: [28, 48, 40, 19, 96, 27, 100]
}
]

};

new Chart(document.getElementById(\"line\").getContext(\"2d\")).Line(lineChartData);
new Chart(document.getElementById(\"radar\").getContext(\"2d\")).Radar(radarChartData);
new Chart(document.getElementById(\"polarArea\").getContext(\"2d\")).PolarArea(chartData);
new Chart(document.getElementById(\"bar\").getContext(\"2d\")).Bar(barChartData);
new Chart(document.getElementById(\"pie\").getContext(\"2d\")).Pie(pieData);

}
};
} ();
.ajax({ type: "POST", url: "CS.aspx/GetCustomers", data: "{}", contentType: "application/json; charset=utf-8", dataType: "json", success: function (r) { var data = eval(r.d); golbaldou = data; new Chart(document.getElementById("doughnut").getContext("2d")).Doughnut(data); document.getElementById("doughnut").fillStyle = 'rgba(255,0,0,.4)'; document.getElementById("doughnut").fillRect(20, 20, 20, 80); }, failure: function (response) { alert('There was an error.'); } }); } var InitiateChartJS = function () { return { init: function () { debugger; LoadChart(); var doughnutData = golbaldou; var lineChartData = { labels: ["", "", "", "", "", "", ""], datasets: [ { fillColor: "rgba(93, 178, 255,.4)", strokeColor: "rgba(93, 178, 255,.7)", pointColor: "rgba(93, 178, 255,.7)", pointStrokeColor: "#fff", data: [65, 59, 90, 81, 56, 55, 40] }, { fillColor: "rgba(215, 61, 50,.4)", strokeColor: "rgba(215, 61, 50,.6)", pointColor: "rgba(215, 61, 50,.6)", pointStrokeColor: "#fff", data: [28, 48, 40, 19, 96, 27, 100] } ] }; var pieData = [ { value: 30, color: themeprimary }, { value: 50, color: themesecondary }, { value: 100, color: themefourthcolor } ]; var barChartData = { labels: ["January", "February", "March", "April", "May", "June", "July"], datasets: [ { fillColor: themeprimary, strokeColor: themeprimary, data: [65, 59, 90, 81, 56, 55, 40] }, { fillColor: themethirdcolor, strokeColor: themethirdcolor, data: [28, 48, 40, 19, 96, 27, 100] } ] }; var chartData = [ { value: Math.random(), color: themeprimary }, { value: Math.random(), color: themesecondary }, { value: Math.random(), color: themethirdcolor }, { value: Math.random(), color: themefourthcolor }, { value: Math.random(), color: themefifthcolor }, { value: Math.random(), color: "#ed4e2a" } ]; var radarChartData = { labels: ["", "", "", "", "", "", ""], datasets: [ { fillColor: "rgba(140,196,116,0.5)", strokeColor: "rgba(140,196,116,.7)", pointColor: "rgba(140,196,116,.7)", pointStrokeColor: "#fff", data: [65, 59, 90, 81, 56, 55, 40] }, { fillColor: "rgba(215,61,50,0.5)", strokeColor: "rgba(215,61,50,.7)", pointColor: "rgba(215,61,50,.7)", pointStrokeColor: "#fff", data: [28, 48, 40, 19, 96, 27, 100] } ] }; new Chart(document.getElementById("line").getContext("2d")).Line(lineChartData); new Chart(document.getElementById("radar").getContext("2d")).Radar(radarChartData); new Chart(document.getElementById("polarArea").getContext("2d")).PolarArea(chartData); new Chart(document.getElementById("bar").getContext("2d")).Bar(barChartData); new Chart(document.getElementById("pie").getContext("2d")).Pie(pieData); } }; } ();





and onother one we have this is what we got from a template this is for the action of chart



which is as follows





and onother one we have this is what we got from a template this is for the action of chart

which is as follows

/*!
 * Chart.js
 * http://chartjs.org/
 * Version: 1.0.2
 *
 * Copyright 2015 Nick Downie
 * Released under the MIT license
 * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md
 */


(function(){

	"use strict";

	//Declare root variable - window in the browser, global on the server
	var root = this,
		previous = root.Chart;

	//Occupy the global variable of Chart, and create a simple base class
	var Chart = function(context){
		var chart = this;
		this.canvas = context.canvas;

		this.ctx = context;

		//Variables global to the chart
		var computeDimension = function(element,dimension)
		{
			if (element['offset'+dimension])
			{
				return element['offset'+dimension];
			}
			else
			{
				return document.defaultView.getComputedStyle(element).getPropertyValue(dimension);
			}
		}

		var width = this.width = computeDimension(context.canvas,'Width');
		var height = this.height = computeDimension(context.canvas,'Height');

		// Firefox requires this to work correctly
		context.canvas.width  = width;
		context.canvas.height = height;

		var width = this.width = context.canvas.width;
		var height = this.height = context.canvas.height;
		this.aspectRatio = this.width / this.height;
		//High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale.
		helpers.retinaScale(this);

		return this;
	};
	//Globally expose the defaults to allow for user updating/changing
	Chart.defaults = {
		global: {
			// Boolean - Whether to animate the chart
			animation: true,

			// Number - Number of animation steps
			animationSteps: 60,

			// String - Animation easing effect
			animationEasing: "easeOutQuart",

			// Boolean - If we should show the scale at all
			showScale: true,

			// Boolean - If we want to override with a hard coded scale
			scaleOverride: false,

			// ** Required if scaleOverride is true **
			// Number - The number of steps in a hard coded scale
			scaleSteps: null,
			// Number - The value jump in the hard coded scale
			scaleStepWidth: null,
			// Number - The scale starting value
			scaleStartValue: null,

			// String - Colour of the scale line
			scaleLineColor: "rgba(0,0,0,.1)",

			// Number - Pixel width of the scale line
			scaleLineWidth: 1,

			// Boolean - Whether to show labels on the scale
			scaleShowLabels: true,

			// Interpolated JS string - can access value
			scaleLabel: "<%=value%>",

			// Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there
			scaleIntegersOnly: true,

			// Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
			scaleBeginAtZero: false,

			// String - Scale label font declaration for the scale label
			scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",

			// Number - Scale label font size in pixels
			scaleFontSize: 12,

			// String - Scale label font weight style
			scaleFontStyle: "normal",

			// String - Scale label font colour
			scaleFontColor: "#666",

			// Boolean - whether or not the chart should be responsive and resize when the browser does.
			responsive: false,

			// Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container
			maintainAspectRatio: true,

			// Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove
			showTooltips: true,

			// Boolean - Determines whether to draw built-in tooltip or call custom tooltip function
			customTooltips: false,

			// Array - Array of string names to attach tooltip events
			tooltipEvents: ["mousemove", "touchstart", "touchmove", "mouseout"],

			// String - Tooltip background colour
			tooltipFillColor: "rgba(0,0,0,0.8)",

			// String - Tooltip label font declaration for the scale label
			tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",

			// Number - Tooltip label font size in pixels
			tooltipFontSize: 14,

			// String - Tooltip font weight style
			tooltipFontStyle: "normal",

			// String - Tooltip label font colour
			tooltipFontColor: "#fff",

			// String - Tooltip title font declaration for the scale label
			tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",

			// Number - Tooltip title font size in pixels
			tooltipTitleFontSize: 14,

			// String - Tooltip title font weight style
			tooltipTitleFontStyle: "bold",

			// String - Tooltip title font colour
			tooltipTitleFontColor: "#fff",

			// Number - pixel width of padding around tooltip text
			tooltipYPadding: 6,

			// Number - pixel width of padding around tooltip text
			tooltipXPadding: 6,

			// Number - Size of the caret on the tooltip
			tooltipCaretSize: 8,

			// Number - Pixel radius of the tooltip border
			tooltipCornerRadius: 6,

			// Number - Pixel offset from point x to tooltip edge
			tooltipXOffset: 10,

			// String - Template string for single tooltips
			tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>",

			// String - Template string for single tooltips
			multiTooltipTemplate: "<%= value %>",

			// String - Colour behind the legend colour block
			multiTooltipKeyBackground: '#fff',

			// Function - Will fire on animation progression.
			onAnimationProgress: function(){},

			// Function - Will fire on animation completion.
			onAnimationComplete: function(){}

		}
	};

	//Create a dictionary of chart types, to allow for extension of existing types
	Chart.types = {};

	//Global Chart helpers object for utility methods and classes
	var helpers = Chart.helpers = {};

		//-- Basic js utility methods
	var each = helpers.each = function(loopable,callback,self){
			var additionalArgs = Array.prototype.slice.call(arguments, 3);
			// Check to see if null or undefined firstly.
			if (loopable){
				if (loopable.length === +loopable.length){
					var i;
					for (i=0; i<loopable.length; i++){
						callback.apply(self,[loopable[i], i].concat(additionalArgs));
					}
				}
				else{
					for (var item in loopable){
						callback.apply(self,[loopable[item],item].concat(additionalArgs));
					}
				}
			}
		},
		clone = helpers.clone = function(obj){
			var objClone = {};
			each(obj,function(value,key){
				if (obj.hasOwnProperty(key)) objClone[key] = value;
			});
			return objClone;
		},
		extend = helpers.extend = function(base){
			each(Array.prototype.slice.call(arguments,1), function(extensionObject) {
				each(extensionObject,function(value,key){
					if (extensionObject.hasOwnProperty(key)) base[key] = value;
				});
			});
			return base;
		},
		merge = helpers.merge = function(base,master){
			//Merge properties in left object over to a shallow clone of object right.
			var args = Array.prototype.slice.call(arguments,0);
			args.unshift({});
			return extend.apply(null, args);
		},
		indexOf = helpers.indexOf = function(arrayToSearch, item){
			if (Array.prototype.indexOf) {
				return arrayToSearch.indexOf(item);
			}
			else{
				for (var i = 0; i < arrayToSearch.length; i++) {
					if (arrayToSearch[i] === item) return i;
				}
				return -1;
			}
		},
		where = helpers.where = function(collection, filterCallback){
			var filtered = [];

			helpers.each(collection, function(item){
				if (filterCallback(item)){
					filtered.push(item);
				}
			});

			return filtered;
		},
		findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex){
			// Default to start of the array
			if (!startIndex){
				startIndex = -1;
			}
			for (var i = startIndex + 1; i < arrayToSearch.length; i++) {
				var currentItem = arrayToSearch[i];
				if (filterCallback(currentItem)){
					return currentItem;
				}
			}
		},
		findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex){
			// Default to end of the array
			if (!startIndex){
				startIndex = arrayToSearch.length;
			}
			for (var i = startIndex - 1; i >= 0; i--) {
				var currentItem = arrayToSearch[i];
				if (filterCallback(currentItem)){
					return currentItem;
				}
			}
		},
		inherits = helpers.inherits = function(extensions){
			//Basic javascript inheritance based on the model created in Backbone.js
			var parent = this;
			var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function(){ return parent.apply(this, arguments); };

			var Surrogate = function(){ this.constructor = ChartElement;};
			Surrogate.prototype = parent.prototype;
			ChartElement.prototype = new Surrogate();

			ChartElement.extend = inherits;

			if (extensions) extend(ChartElement.prototype, extensions);

			ChartElement.__super__ = parent.prototype;

			return ChartElement;
		},
		noop = helpers.noop = function(){},
		uid = helpers.uid = (function(){
			var id=0;
			return function(){
				return "chart-" + id++;
			};
		})(),
		warn = helpers.warn = function(str){
			//Method for warning of errors
			if (window.console && typeof window.console.warn == "function") console.warn(str);
		},
		amd = helpers.amd = (typeof define == 'function' && define.amd),
		//-- Math methods
		isNumber = helpers.isNumber = function(n){
			return !isNaN(parseFloat(n)) && isFinite(n);
		},
		max = helpers.max = function(array){
			return Math.max.apply( Math, array );
		},
		min = helpers.min = function(array){
			return Math.min.apply( Math, array );
		},
		cap = helpers.cap = function(valueToCap,maxValue,minValue){
			if(isNumber(maxValue)) {
				if( valueToCap > maxValue ) {
					return maxValue;
				}
			}
			else if(isNumber(minValue)){
				if ( valueToCap < minValue ){
					return minValue;
				}
			}
			return valueToCap;
		},
		getDecimalPlaces = helpers.getDecimalPlaces = function(num){
			if (num%1!==0 && isNumber(num)){
				return num.toString().split(".")[1].length;
			}
			else {
				return 0;
			}
		},
		toRadians = helpers.radians = function(degrees){
			return degrees * (Math.PI/180);
		},
		// Gets the angle from vertical upright to the point about a centre.
		getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint){
			var distanceFromXCenter = anglePoint.x - centrePoint.x,
				distanceFromYCenter = anglePoint.y - centrePoint.y,
				radialDistanceFromCenter = Math.sqrt( distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter);


			var angle = Math.PI * 2 + Math.atan2(distanceFromYCenter, distanceFromXCenter);

			//If the segment is in the top left quadrant, we need to add another rotation to the angle
			if (distanceFromXCenter < 0 && distanceFromYCenter < 0){
				angle += Math.PI*2;
			}

			return {
				angle: angle,
				distance: radialDistanceFromCenter
			};
		},
		aliasPixel = helpers.aliasPixel = function(pixelWidth){
			return (pixelWidth % 2 === 0) ? 0 : 0.5;
		},
		splineCurve = helpers.splineCurve = function(FirstPoint,MiddlePoint,AfterPoint,t){
			//Props to Rob Spencer at scaled innovation for his post on splining between points
			//http://scaledinnovation.com/analytics/splines/aboutSplines.html
			var d01=Math.sqrt(Math.pow(MiddlePoint.x-FirstPoint.x,2)+Math.pow(MiddlePoint.y-FirstPoint.y,2)),
				d12=Math.sqrt(Math.pow(AfterPoint.x-MiddlePoint.x,2)+Math.pow(AfterPoint.y-MiddlePoint.y,2)),
				fa=t*d01/(d01+d12),// scaling factor for triangle Ta
				fb=t*d12/(d01+d12);
			return {
				inner : {
					x : MiddlePoint.x-fa*(AfterPoint.x-FirstPoint.x),
					y : MiddlePoint.y-fa*(AfterPoint.y-FirstPoint.y)
				},
				outer : {
					x: MiddlePoint.x+fb*(AfterPoint.x-FirstPoint.x),
					y : MiddlePoint.y+fb*(AfterPoint.y-FirstPoint.y)
				}
			};
		},
		calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val){
			return Math.floor(Math.log(val) / Math.LN10);
		},
		calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly){

			//Set a minimum step of two - a point at the top of the graph, and a point at the base
			var minSteps = 2,
				maxSteps = Math.floor(drawingSize/(textSize * 1.5)),
				skipFitting = (minSteps >= maxSteps);

			var maxValue = max(valuesArray),
				minValue = min(valuesArray);

			// We need some degree of seperation here to calculate the scales if all the values are the same
			// Adding/minusing 0.5 will give us a range of 1.
			if (maxValue === minValue){
				maxValue += 0.5;
				// So we don't end up with a graph with a negative start value if we've said always start from zero
				if (minValue >= 0.5 && !startFromZero){
					minValue -= 0.5;
				}
				else{
					// Make up a whole number above the values
					maxValue += 0.5;
				}
			}

			var	valueRange = Math.abs(maxValue - minValue),
				rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange),
				graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
				graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude),
				graphRange = graphMax - graphMin,
				stepValue = Math.pow(10, rangeOrderOfMagnitude),
				numberOfSteps = Math.round(graphRange / stepValue);

			//If we have more space on the graph we'll use it to give more definition to the data
			while((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) {
				if(numberOfSteps > maxSteps){
					stepValue *=2;
					numberOfSteps = Math.round(graphRange/stepValue);
					// Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps.
					if (numberOfSteps % 1 !== 0){
						skipFitting = true;
					}
				}
				//We can fit in double the amount of scale points on the scale
				else{
					//If user has declared ints only, and the step value isn't a decimal
					if (integersOnly && rangeOrderOfMagnitude >= 0){
						//If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float
						if(stepValue/2 % 1 === 0){
							stepValue /=2;
							numberOfSteps = Math.round(graphRange/stepValue);
						}
						//If it would make it a float break out of the loop
						else{
							break;
						}
					}
					//If the scale doesn't have to be an int, make the scale more granular anyway.
					else{
						stepValue /=2;
						numberOfSteps = Math.round(graphRange/stepValue);
					}

				}
			}

			if (skipFitting){
				numberOfSteps = minSteps;
				stepValue = graphRange / numberOfSteps;
			}

			return {
				steps : numberOfSteps,
				stepValue : stepValue,
				min : graphMin,
				max	: graphMin + (numberOfSteps * stepValue)
			};

		},
		/* jshint ignore:start */
		// Blows up jshint errors based on the new Function constructor
		//Templating methods
		//Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/
		template = helpers.template = function(templateString, valuesObject){

			// If templateString is function rather than string-template - call the function for valuesObject

			if(templateString instanceof Function){
			 	return templateString(valuesObject);
		 	}

			var cache = {};
			function tmpl(str, data){
				// Figure out if we're getting a template, or if we need to
				// load the template - and be sure to cache the result.
				var fn = !/\W/.test(str) ?
				cache[str] = cache[str] :

				// Generate a reusable function that will serve as a template
				// generator (and which will be cached).
				new Function("obj",
					"var p=[],print=function(){p.push.apply(p,arguments);};" +

					// Introduce the data as local variables using with(){}
					"with(obj){p.push('" +

					// Convert the template into pure JavaScript
					str
						.replace(/[\r\t\n]/g, " ")
						.split("<%").join("\t")
						.replace(/((^|%>)[^\t]*)'/g, "


1\r\")
\t\t\t\t\t\t.replace(/\t=(.*?)%>/g, \"',


1,'\")
\t\t\t\t\t\t.split(\"\t\").join(\"'
);\")
\t\t\t\t\t\t.split(\"
%>\").join(\"p.push('\")
\t\t\t\t\t\t.split(\"\r\").join(\"\\'
\") +
\t\t\t\t\t\"
');}return p.join('');\"
\t\t\t\t);

\t\t\t\t// Provide some basic currying to the user
\t\t\t\treturn data ? fn( data ) : fn;
\t\t\t}
\t\t\treturn tmpl(templateString,valuesObject);
\t\t},
\t\t/* jshint ignore:end */
\t\tgenerateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){
\t\t\tvar labelsArray = new Array(numberOfSteps);
\t\t\tif (labelTemplateString){
\t\t\t\teach(labelsArray,function(val,index){
\t\t\t\t\tlabelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))});
\t\t\t\t});
\t\t\t}
\t\t\treturn labelsArray;
\t\t},
\t\t//--Animation methods
\t\t//Easing functions adapted from Robert Penner'
s easing equations
\t\t//http://www.robertpenner.com/easing/
\t\teasingEffects = helpers.easingEffects = {
\t\t\tlinear: function (t) {
\t\t\t\treturn t;
\t\t\t},
\t\t\teaseInQuad: function (t) {
\t\t\t\treturn t * t;
\t\t\t},
\t\t\teaseOutQuad: function (t) {
\t\t\t\treturn -1 * t * (t - 2);
\t\t\t},
\t\t\teaseInOutQuad: function (t) {
\t\t\t\tif ((t /= 1 / 2) < 1) return 1 / 2 * t * t;
\t\t\t\treturn -1 / 2 * ((--t) * (t - 2) - 1);
\t\t\t},
\t\t\teaseInCubic: function (t) {
\t\t\t\treturn t * t * t;
\t\t\t},
\t\t\teaseOutCubic: function (t) {
\t\t\t\treturn 1 * ((t = t / 1 - 1) * t * t + 1);
\t\t\t},
\t\t\teaseInOutCubic: function (t) {
\t\t\t\tif ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t;
\t\t\t\treturn 1 / 2 * ((t -= 2) * t * t + 2);
\t\t\t},
\t\t\teaseInQuart: function (t) {
\t\t\t\treturn t * t * t * t;
\t\t\t},
\t\t\teaseOutQuart: function (t) {
\t\t\t\treturn -1 * ((t = t / 1 - 1) * t * t * t - 1);
\t\t\t},
\t\t\teaseInOutQuart: function (t) {
\t\t\t\tif ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t;
\t\t\t\treturn -1 / 2 * ((t -= 2) * t * t * t - 2);
\t\t\t},
\t\t\teaseInQuint: function (t) {
\t\t\t\treturn 1 * (t /= 1) * t * t * t * t;
\t\t\t},
\t\t\teaseOutQuint: function (t) {
\t\t\t\treturn 1 * ((t = t / 1 - 1) * t * t * t * t + 1);
\t\t\t},
\t\t\teaseInOutQuint: function (t) {
\t\t\t\tif ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t;
\t\t\t\treturn 1 / 2 * ((t -= 2) * t * t * t * t + 2);
\t\t\t},
\t\t\teaseInSine: function (t) {
\t\t\t\treturn -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1;
\t\t\t},
\t\t\teaseOutSine: function (t) {
\t\t\t\treturn 1 * Math.sin(t / 1 * (Math.PI / 2));
\t\t\t},
\t\t\teaseInOutSine: function (t) {
\t\t\t\treturn -1 / 2 * (Math.cos(Math.PI * t / 1) - 1);
\t\t\t},
\t\t\teaseInExpo: function (t) {
\t\t\t\treturn (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1));
\t\t\t},
\t\t\teaseOutExpo: function (t) {
\t\t\t\treturn (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1);
\t\t\t},
\t\t\teaseInOutExpo: function (t) {
\t\t\t\tif (t === 0) return 0;
\t\t\t\tif (t === 1) return 1;
\t\t\t\tif ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1));
\t\t\t\treturn 1 / 2 * (-Math.pow(2, -10 * --t) + 2);
\t\t\t},
\t\t\teaseInCirc: function (t) {
\t\t\t\tif (t >= 1) return t;
\t\t\t\treturn -1 * (Math.sqrt(1 - (t /= 1) * t) - 1);
\t\t\t},
\t\t\teaseOutCirc: function (t) {
\t\t\t\treturn 1 * Math.sqrt(1 - (t = t / 1 - 1) * t);
\t\t\t},
\t\t\teaseInOutCirc: function (t) {
\t\t\t\tif ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1);
\t\t\t\treturn 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1);
\t\t\t},
\t\t\teaseInElastic: function (t) {
\t\t\t\tvar s = 1.70158;
\t\t\t\tvar p = 0;
\t\t\t\tvar a = 1;
\t\t\t\tif (t === 0) return 0;
\t\t\t\tif ((t /= 1) == 1) return 1;
\t\t\t\tif (!p) p = 1 * 0.3;
\t\t\t\tif (a < Math.abs(1)) {
\t\t\t\t\ta = 1;
\t\t\t\t\ts = p / 4;
\t\t\t\t} else s = p / (2 * Math.PI) * Math.asin(1 / a);
\t\t\t\treturn -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
\t\t\t},
\t\t\teaseOutElastic: function (t) {
\t\t\t\tvar s = 1.70158;
\t\t\t\tvar p = 0;
\t\t\t\tvar a = 1;
\t\t\t\tif (t === 0) return 0;
\t\t\t\tif ((t /= 1) == 1) return 1;
\t\t\t\tif (!p) p = 1 * 0.3;
\t\t\t\tif (a < Math.abs(1)) {
\t\t\t\t\ta = 1;
\t\t\t\t\ts = p / 4;
\t\t\t\t} else s = p / (2 * Math.PI) * Math.asin(1 / a);
\t\t\t\treturn a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1;
\t\t\t},
\t\t\teaseInOutElastic: function (t) {
\t\t\t\tvar s = 1.70158;
\t\t\t\tvar p = 0;
\t\t\t\tvar a = 1;
\t\t\t\tif (t === 0) return 0;
\t\t\t\tif ((t /= 1 / 2) == 2) return 1;
\t\t\t\tif (!p) p = 1 * (0.3 * 1.5);
\t\t\t\tif (a < Math.abs(1)) {
\t\t\t\t\ta = 1;
\t\t\t\t\ts = p / 4;
\t\t\t\t} else s = p / (2 * Math.PI) * Math.asin(1 / a);
\t\t\t\tif (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p));
\t\t\t\treturn a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1;
\t\t\t},
\t\t\teaseInBack: function (t) {
\t\t\t\tvar s = 1.70158;
\t\t\t\treturn 1 * (t /= 1) * t * ((s + 1) * t - s);
\t\t\t},
\t\t\teaseOutBack: function (t) {
\t\t\t\tvar s = 1.70158;
\t\t\t\treturn 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1);
\t\t\t},
\t\t\teaseInOutBack: function (t) {
\t\t\t\tvar s = 1.70158;
\t\t\t\tif ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s));
\t\t\t\treturn 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2);
\t\t\t},
\t\t\teaseInBounce: function (t) {
\t\t\t\treturn 1 - easingEffects.easeOutBounce(1 - t);
\t\t\t},
\t\t\teaseOutBounce: function (t) {
\t\t\t\tif ((t /= 1) < (1 / 2.75)) {
\t\t\t\t\treturn 1 * (7.5625 * t * t);
\t\t\t\t} else if (t < (2 / 2.75)) {
\t\t\t\t\treturn 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75);
\t\t\t\t} else if (t < (2.5 / 2.75)) {
\t\t\t\t\treturn 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375);
\t\t\t\t} else {
\t\t\t\t\treturn 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375);
\t\t\t\t}
\t\t\t},
\t\t\teaseInOutBounce: function (t) {
\t\t\t\tif (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5;
\t\t\t\treturn easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5;
\t\t\t}
\t\t},
\t\t//Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/
\t\trequestAnimFrame = helpers.requestAnimFrame = (function(){
\t\t\treturn window.requestAnimationFrame ||
\t\t\t\twindow.webkitRequestAnimationFrame ||
\t\t\t\twindow.mozRequestAnimationFrame ||
\t\t\t\twindow.oRequestAnimationFrame ||
\t\t\t\twindow.msRequestAnimationFrame ||
\t\t\t\tfunction(callback) {
\t\t\t\t\treturn window.setTimeout(callback, 1000 / 60);
\t\t\t\t};
\t\t})(),
\t\tcancelAnimFrame = helpers.cancelAnimFrame = (function(){
\t\t\treturn window.cancelAnimationFrame ||
\t\t\t\twindow.webkitCancelAnimationFrame ||
\t\t\t\twindow.mozCancelAnimationFrame ||
\t\t\t\twindow.oCancelAnimationFra me ||
\t\t\t\twindow.msCancelAnimationFrame ||
\t\t\t\tfunction(callback) {
\t\t\t\t\treturn window.clearTimeout(callback, 1000 / 60);
};
\t\t})(),
\t\tanimationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){

\t\t\tvar currentStep = 0,
\t\t\t\teasingFunction = easingEffects[easingString] || easingEffects.linear;

\t\t\tvar animationFrame = function(){
\t\t\t\tcurrentStep++;
\t\t\t\tvar stepDecimal = currentStep/totalSteps;
\t\t\t\tvar easeDecimal = easingFunction(stepDecimal);

\t\t\t\tcallback.call(chartInstance,easeDecimal,stepDecimal, currentStep);
\t\t\t\tonProgress.call(chartInstance,easeDecimal,stepDecimal);
\t\t\t\tif (currentStep < totalSteps){
\t\t\t\t\tchartInstance.animationFrame = requestAnimFrame(animationFrame);
\t\t\t\t} else{
\t\t\t\t\tonComplete.apply(chartInstance);
\t\t\t\t}
\t\t\t};
\t\t\trequestAnimFrame(animationFrame);
\t\t},
\t\t//-- DOM methods
\t\tgetRelativePosition = helpers.getRelativePosition = function(evt){
\t\t\tvar mouseX, mouseY;
\t\t\tvar e = evt.originalEvent || evt,
\t\t\t\tcanvas = evt.currentTarget || evt.srcElement,
\t\t\t\tboundingRect = canvas.getBoundingClientRect();

\t\t\tif (e.touches){
\t\t\t\tmouseX = e.touches[0].clientX - boundingRect.left;
\t\t\t\tmouseY = e.touches[0].clientY - boundingRect.top;

\t\t\t}
\t\t\telse{
\t\t\t\tmouseX = e.clientX - boundingRect.left;
\t\t\t\tmouseY = e.clientY - boundingRect.top;
\t\t\t}

\t\t\treturn {
\t\t\t\tx : mouseX,
\t\t\t\ty : mouseY
\t\t\t};

\t\t},
\t\taddEvent = helpers.addEvent = function(node,eventType,method){
\t\t\tif (node.addEventListener){
\t\t\t\tnode.addEventListener(eventType,method);
\t\t\t} else if (node.attachEvent){
\t\t\t\tnode.attachEvent(\"on\"+eventType, method);
\t\t\t} else {
\t\t\t\tnode[\"on\"+eventType] = method;
\t\t\t}
\t\t},
\t\tremoveEvent = helpers.removeEvent = function(node, eventType, handler){
\t\t\tif (node.removeEventListener){
\t\t\t\tnode.removeEventListener(eventType, handler, false);
\t\t\t} else if (node.detachEvent){
\t\t\t\tnode.detachEvent(\"on\"+eventType,handler);
\t\t\t} else{
\t\t\t\tnode[\"on\" + eventType] = noop;
\t\t\t}
\t\t},
\t\tbindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){
\t\t\t// Create the events object if it's not already present
\t\t\tif (!chartInstance.events) chartInstance.events = {};

\t\t\teach(arrayOfEvents,function(eventName){
\t\t\t\tchartInstance.events[eventName] = function(){
\t\t\t\t\thandler.apply(chartInstance, arguments);
\t\t\t\t};
\t\t\t\taddEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]);
\t\t\t});
\t\t},
\t\tunbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) {
\t\t\teach(arrayOfEvents, function(handler,eventName){
\t\t\t\tremoveEvent(chartInstance.chart.canvas, eventName, handler);
\t\t\t});
\t\t},
\t\tgetMaximumWidth = helpers.getMaximumWidth = function(domNode){
\t\t\tvar container = domNode.parentNode;
\t\t\t// TODO = check cross browser stuff with this.
\t\t\treturn container.clientWidth;
\t\t},
\t\tgetMaximumHeight = helpers.getMaximumHeight = function(domNode){
\t\t\tvar container = domNode.parentNode;
\t\t\t// TODO = check cross browser stuff with this.
\t\t\treturn container.clientHeight;
\t\t},
\t\tgetMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support
\t\tretinaScale = helpers.retinaScale = function(chart){
\t\t\tvar ctx = chart.ctx,
\t\t\t\twidth = chart.canvas.width,
\t\t\t\theight = chart.canvas.height;

\t\t\tif (window.devicePixelRatio) {
\t\t\t\tctx.canvas.style.width = width + \"px\";
\t\t\t\tctx.canvas.style.height = height + \"px\";
\t\t\t\tctx.canvas.height = height * window.devicePixelRatio;
\t\t\t\tctx.canvas.width = width * window.devicePixelRatio;
\t\t\t\tctx.scale(window.devicePixelRatio, window.devicePixelRatio);
\t\t\t}
\t\t},
\t\t//-- Canvas methods
\t\tclear = helpers.clear = function(chart){
\t\t\tchart.ctx.clearRect(0,0,chart.width,chart.height);
\t\t},
\t\tfontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){
\t\t\treturn fontStyle + \" \" + pixelSize+\"px \" + fontFamily;
\t\t},
\t\tlongestText = helpers.longestText = function(ctx,font,arrayOfStrings){
\t\t\tctx.font = font;
\t\t\tvar longest = 0;
\t\t\teach(arrayOfStrings,function(string){
\t\t\t\tvar textWidth = ctx.measureText(string).width;
\t\t\t\tlongest = (textWidth > longest) ? textWidth : longest;
\t\t\t});
\t\t\treturn longest;
\t\t},
\t\tdrawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){
\t\t\tctx.beginPath();
\t\t\tctx.moveTo(x + radius, y);
\t\t\tctx.lineTo(x + width - radius, y);
\t\t\tctx.quadraticCurveTo(x + width, y, x + width, y + radius);
\t\t\tctx.lineTo(x + width, y + height - radius);
\t\t\tctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
\t\t\tctx.lineTo(x + radius, y + height);
\t\t\tctx.quadraticCurveTo(x, y + height, x, y + height - radius);
\t\t\tctx.lineTo(x, y + radius);
\t\t\tctx.quadraticCurveTo(x, y, x + radius, y);
\t\t\tctx.closePath();
};


\t//Store a reference to each instance - allowing us to globally resize chart instances on window resize.
\t//Destroy method on the chart will remove the instance of the chart from this reference.
\tChart.instances = {};

\tChart.Type = function(data,options,chart){
\t\tthis.options = options;
\t\tthis.chart = chart;
\t\tthis.id = uid();
\t\t//Add the chart instance to the global namespace
\t\tChart.instances[this.id] = this;

\t\t// Initialize is always called when a chart type is created
\t\t// By default it is a no op, but it should be extended
\t\tif (options.responsive){
\t\t\tthis.resize();
\t\t}
\t\tthis.initialize.call(this,data);
};

\t//Core methods that'll be a part of every chart type
\textend(Chart.Type.prototype,{
\t\tinitialize : function(){return this;},
\t\tclear : function(){
\t\t\tclear(this.chart);
\t\t\treturn this;
\t\t},
\t\tstop : function(){
\t\t\t// Stops any current animation loop occuring
\t\t\tcancelAnimFrame(this.animationFrame);
\t\t\treturn this;
\t\t},
\t\tresize : function(callback){
\t\t\tthis.stop();
\t\t\tvar canvas = this.chart.canvas,
\t\t\t\tnewWidth = getMaximumWidth(this.chart.canvas),
\t\t\t\tnewHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas);

\t\t\tcanvas.width = this.chart.width = newWidth;
\t\t\tcanvas.height = this.chart.height = newHeight;

\t\t\tretinaScale(this.chart);

\t\t\tif (typeof callback === \"function\"){
\t\t\t\tcallback.apply(this, Array.prototype.slice.call(arguments, 1));
\t\t\t}
\t\t\treturn this;
\t\t},
\t\treflow : noop,
\t\trender : function(reflow){
\t\t\tif (reflow){
\t\t\t\tthis.reflow();
\t\t\t}
\t\t\tif (this.options.animation && !reflow){
\t\t\t\thelpers.animationLoop(
\t\t\t\t\tthis.draw,
\t\t\t\t\tthis.options.animationSteps,
\t\t\t\t\tthis.options.animationEasing,
\t\t\t\t\tthis.options.onAnimationProgress,
\t\t\t\t\tthis.options.onAnimationComplete,
\t\t\t\t\tthis
\t\t\t\t);
\t\t\t}
\t\t\telse{
\t\t\t\tthis.draw();
\t\t\t\tthis.options.onAnimationComplete.call(this);
\t\t\t}
\t\t\treturn this;
\t\t},
\t\tgenerateLegend : function(){
\t\t\treturn template(this.options.legendTemplate,this);
\t\t},
\t\tdestroy : function(){
\t\t\tthis.clear();
\t\t\tunbindEvents(this, this.events);
\t\t\tvar canvas = this.chart.canvas;

\t\t\t// Reset canvas height/width attributes starts a fresh with the canvas context
\t\t\tcanvas.width = this.chart.width;
\t\t\tcanvas.height = this.chart.height;

\t\t\t// < IE9 doesn't support removeProperty
\t\t\tif (canvas.style.removeProperty) {
\t\t\t\tcanvas.style.removeProperty('width');
\t\t\t\tcanvas.style.removeProperty('height');
\t\t\t} else {
\t\t\t\tcanvas.style.removeAttribute('width');
\t\t\t\tcanvas.style.removeAttribute('height');
\t\t\t}

\t\t\tdelete Chart.instances[this.id];
\t\t},
\t\tshowTooltip : function(ChartElements, forceRedraw){
\t\t\t// Only redraw the chart if we've actually changed what we're hovering on.
\t\t\tif (typeof this.activeElements === 'undefined') this.activeElements = [];

\t\t\tvar isChanged = (function(Elements){
\t\t\t\tvar changed = false;

\t\t\t\tif (Elements.length !== this.activeElements.length){
\t\t\t\t\tchanged = true;
\t\t\t\t\treturn changed;
\t\t\t\t}

\t\t\t\teach(Elements, function(element, index){
\t\t\t\t\tif (element !== this.activeElements[index]){
\t\t\t\t\t\tchanged = true;
\t\t\t\t\t}
\t\t\t\t}, this);
\t\t\t\treturn changed;
\t\t\t}).call(this, ChartElements);

\t\t\tif (!isChanged && !forceRedraw){
\t\t\t\treturn;
\t\t\t}
\t\t\telse{
\t\t\t\tthis.activeElements = ChartElements;
\t\t\t}
\t\t\tthis.draw();
\t\t\tif(this.options.customTooltips){
\t\t\t\tthis.options.customTooltips(false);
\t\t\t}
\t\t\tif (ChartElements.length > 0){
\t\t\t\t// If we have multiple datasets, show a MultiTooltip for all of the data points at that index
\t\t\t\tif (this.datasets && this.datasets.length > 1) {
\t\t\t\t\tvar dataArray,
\t\t\t\t\t\tdataIndex;

\t\t\t\t\tfor (var i = this.datasets.length - 1; i >= 0; i--) {
\t\t\t\t\t\tdataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments;
\t\t\t\t\t\tdataIndex = indexOf(dataArray, ChartElements[0]);
\t\t\t\t\t\tif (dataIndex !== -1){
\t\t\t\t\t\t\tbreak;
\t\t\t\t\t\t}
\t\t\t\t\t}
\t\t\t\t\tvar tooltipLabels = [],
\t\t\t\t\t\ttooltipColors = [],
\t\t\t\t\t\tmedianPosition = (function(index) {

\t\t\t\t\t\t\t// Get all the points at that particular index
\t\t\t\t\t\t\tvar Elements = [],
\t\t\t\t\t\t\t\tdataCollection,
\t\t\t\t\t\t\t\txPositions = [],
\t\t\t\t\t\t\t\tyPositions = [],
\t\t\t\t\t\t\t\txMax,
\t\t\t\t\t\t\t\tyMax,
\t\t\t\t\t\t\t\txMin,
\t\t\t\t\t\t\t\tyMin;
\t\t\t\t\t\t\thelpers.each(this.datasets, function(dataset){
\t\t\t\t\t\t\t\tdataCollection = dataset.points || dataset.bars || dataset.segments;
\t\t\t\t\t\t\t\tif (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){
\t\t\t\t\t\t\t\t\tElements.push(dataCollection[dataIndex]);
\t\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t});

\t\t\t\t\t\t\thelpers.each(Elements, function(element) {
\t\t\t\t\t\t\t\txPo sitions.push(element.x);
\t\t\t\t\t\t\t\tyPositions.push(element.y);


\t\t\t\t\t\t\t\t//Include any colour information about the element
\t\t\t\t\t\t\t\ttooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element));
\t\t\t\t\t\t\t\ttooltipColors.push({
\t\t\t\t\t\t\t\t\tfill: element._saved.fillColor || element.fillColor,
\t\t\t\t\t\t\t\t\tstroke: element._saved.strokeColor || element.strokeColor
\t\t\t\t\t\t\t\t});

\t\t\t\t\t\t\t}, this);

\t\t\t\t\t\t\tyMin = min(yPositions);
\t\t\t\t\t\t\tyMax = max(yPositions);

\t\t\t\t\t\t\txMin = min(xPositions);
\t\t\t\t\t\t\txMax = max(xPositions);

\t\t\t\t\t\t\treturn {
\t\t\t\t\t\t\t\tx: (xMin > this.chart.width/2) ? xMin : xMax,
\t\t\t\t\t\t\t\ty: (yMin + yMax)/2
\t\t\t\t\t\t\t};
\t\t\t\t\t\t}).call(this, dataIndex);

\t\t\t\t\tnew Chart.MultiTooltip({
\t\t\t\t\t\tx: medianPosition.x,
\t\t\t\t\t\ty: medianPosition.y,
\t\t\t\t\t\txPadding: this.options.tooltipXPadding,
\t\t\t\t\t\tyPadding: this.options.tooltipYPadding,
\t\t\t\t\t\txOffset: this.options.tooltipXOffset,
\t\t\t\t\t\tfillColor: this.options.tooltipFillColor,
\t\t\t\t\t\ttextColor: this.options.tooltipFontColor,
\t\t\t\t\t\tfontFamily: this.options.tooltipFontFamily,
\t\t\t\t\t\tfontStyle: this.options.tooltipFontStyle,
\t\t\t\t\t\tfontSize: this.options.tooltipFontSize,
\t\t\t\t\t\ttitleTextColor: this.options.tooltipTitleFontColor,
\t\t\t\t\t\ttitleFontFamily: this.options.t ooltipTitleFontFamily,
\t\t\t\t\t\ttitleFontStyle: this.options.tooltipTitleFontStyle,
\t\t\t\t\t\ttitleFontSize: this.options.tooltipTitleFontSize,
\t\t\t\t\t\tcornerRadius: this.options.tooltipCornerRadius,
\t\t\t\t\t\tlabels: tooltipLabels,
\t\t\t\t\t\tlegendColors: tooltipColors,
\t\t\t\t\t\tlegendColorBackground : this.options.multiTooltipKeyBackground,
\t\t\t\t\t\ttitle: ChartElements[0].label,
\t\t\t\t\t\tchart: this.chart,
\t\t\t\t\t\tctx: this.chart.ctx,
\t\t\t\t\t\tcustom: this.options.customTooltips
\t\t\t\t\t}).draw();

\t\t\t\t} else {
\t\t\t\t\teach(ChartElements, function(Element) {
\t\t\t\t\t\tvar tooltipPosition = Element.tooltipPosition();
\t\t\t\t\t\tnew Chart.Tooltip({
\t\t\t\t\t\t\tx: Math.round(tooltipPosition.x),
\t\t\t\t\t\t\ty: Math.round(tooltipPosition.y),
\t\t\t\t\t\t\txPadding: this.options.tooltipXPadding,
\t\t\t\t\t\t\tyPadding: this.options.tooltipYPadding,
\t\t\t\t\t\t\tfillColor: this.options.tooltipFillColor,
\t\t\t\t\t\t\ttextColor: this.options.tooltipFontColor,
\t\t\t\t\t\t\tfontFamily: this.options.tooltipFontFamily,
\t\t\t\t\t\t\tfontStyle: this.options.tooltipFontStyle,
\t\t\t\t\t\t\tfontSize: this.options.tooltipFontSize,
\t\t\t\t\t\t\tcaretHeight: this.options.tooltipCaretSize,
\t\t\t\t\t\t\tcornerRadius: this.options.tooltipCornerRadius,
\t\t\t\t\t\t\ttext: template(this.options.tooltipTemplate, Element),
\t\t\t\t\t\t\tchart: this.chart,
\t\t\t\t\t\t\tcustom: this.options.customTooltips
\t\t\t\t\t\t}).draw();
\t\t\t\t\t}, this);
\t\t\t\t}
\t\t\t}
\t\t\treturn this;
\t\t},
\t\ttoBase64Image : function(){
\t\t\treturn this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments);
}
});

\tChart.Type.extend = function(extensions){

\t\tvar parent = this;

\t\tvar ChartType = function(){
\t\t\treturn parent.apply(this,arguments);
};

\t\t//Copy the prototype object of the this class
\t\tChartType.prototype = clone(parent.prototype);
\t\t//Now overwrite some of the properties in the base class with the new extensions
\t\textend(ChartType.prototype, extensions);

\t\tChartType.extend = Chart.Type.extend;

\t\tif (extensions.name || parent.prototype.name){

\t\t\tvar chartName = extensions.name || parent.prototype.name;
\t\t\t//Assign any potential default values of the new chart type

\t\t\t//If none are defined, we'll use a clone of the chart type this is being extended from.
\t\t\t//I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart
\t\t\t//doesn't define some defaults of their own.

\t\t\tvar baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {};

\t\t\tChart.defaults[chartName] = extend(baseDefaults,extensions.defaults);

\t\t\tChart.types[chartName] = ChartType;

\t\t\t//Register this new chart type in the Chart prototype
\t\t\tChart.prototype[chartName] = function(data,options){
\t\t\t\tvar config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {});
\t\t\t\treturn new ChartType(data,config,this);
\t\t\t};
\t\t} else{
\t\t\twarn(\"Name not provided for this chart, so it hasn't been registered\");
\t\t}
\t\treturn parent;
};

\tChart.Element = function(configuration){
\t\textend(this,configuration);
\t\tthis.initialize.apply(this,arguments);
\t\tthis.save();
};
\textend(Chart.Element.prototype,{
\t\tinitialize : function(){},
\t\trestore : function(props){
\t\t\tif (!props){
\t\t\t\textend(this,this._saved);
\t\t\t} else {
\t\t\t\teach(props,function(key){
\t\t\t\t\tthis[key] = this._saved[key];
\t\t\t\t},this);
\t\t\t}
\t\t\treturn this;
\t\t},
\t\tsave : function(){
\t\t\tthis._saved = clone(this);
\t\t\tdelete this._saved._saved;
\t\t\treturn this;
\t\t},
\t\tupdate : function(newProps){
\t\t\teach(newProps,function(value,key){
\t\t\t\tthis._saved[key] = this[key];
\t\t\t\tthis[key] = value;
\t\t\t},this);
\t\t\treturn this;
\t\t},
\t\ttransition : function(props,ease){
\t\t\teach(props,function(value,key){
\t\t\t\tthis[key] = ((value - this._saved[key]) * ease) + this._saved[key];
\t\t\t},this);
\t\t\treturn this;
\t\t},
\t\ttooltipPosition : function(){
\t\t\treturn {
\t\t\t\tx : this.x,
\t\t\t\ty : this.y
\t\t\t};
\t\t},
\t\thasValue: function(){
\t\t\treturn isNumber(this.value);
}
});

\tChart.Element.extend = inherits;


\tChart.Point = Chart.Element.extend({
\t\tdisplay: true,
\t\tinRange: function(chartX,chartY){
\t\t\tvar hitDetectionRange = this.hitDetectionRadius + this.radius;
\t\t\treturn ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2));
\t\t},
\t\tdraw : function(){
\t\t\tif (this.display){
\t\t\t\tvar ctx = this.ctx;
\t\t\t\tctx.beginPath();

\t\t\t\tctx.arc(this.x, this.y, this.ra dius, 0, Math.PI*2);
\t\t\t\tctx.closePath();

\t\t\t\tctx.strokeStyle = this.strokeColor;
\t\t\t\tctx.lineWidth = this.strokeWidth;

\t\t\t\tctx.fillStyle = this.fillColor;

\t\t\t\tctx.fill();
\t\t\t\tctx.stroke();
\t\t\t}


\t\t\t//Quick debug for bezier curve splining
\t\t\t//Highlights control points and the line between them.
\t\t\t//Handy for dev - stripped in the min version.

\t\t\t// ctx.save();
\t\t\t// ctx.fillStyle = \"black\";
\t\t\t// ctx.strokeStyle = \"black\"
\t\t\t// ctx.beginPath();
\t\t\t// ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2);
\t\t\t// ctx.fill();

\t\t\t// ctx.beginPath();
\t\t\t// ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2);
\t\t\t// ctx.fill();

\t\t\t// ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y);
\t\t\t// ctx.lineTo(this.x, this.y);
\t\t\t// ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y);
\t\t\t// ctx.stroke();

\t\t\t// ctx.restore();



\t\t}
\t});

\tChart.Arc = Chart.Element.extend({
\t\tinRange : function(chartX,chartY){

\t\t\tvar pointRelativePosition = helpers.getAngleFromPoint(this, {
\t\t\t\tx: chartX,
\t\t\t\ty: chartY
\t\t\t});

\t\t\t//Check if within the range of the open/close angle
\t\t\tvar betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle),
\t\t\t\twithinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.oute rRadius);

\t\t\treturn (betweenAngles && withinRadius);
\t\t\t//Ensure within the outside of the arc centre, but inside arc outer
\t\t},
\t\ttooltipPosition : function(){
\t\t\tvar centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2),
\t\t\t\trangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius;
\t\t\treturn {
\t\t\t\tx : this.x + (Math.cos(centreAngle) * rangeFromCentre),
\t\t\t\ty : this.y + (Math.sin(centreAngle) * rangeFromCentre)
\t\t\t};
\t\t},
\t\tdraw : function(animationPercent){

\t\t\tvar easingDecimal = animationPercent || 1 ;

\t\t\tvar ctx = this.ctx;

\t\t\tctx.beginPath();

\t\t\tctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle);

\t\t\tctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true);

\t\t\tctx.closePath();
\t\t\tctx.strokeStyle = this.strokeColor;
\t\t\tctx.lineWidth = this.strokeWidth;

\t\t\tctx.fillStyle = this.fillColor;

\t\t\tctx.fill();
\t\t\tctx.lineJoin = 'bevel';

\t\t\tif (this.showStroke){
\t\t\t\tctx.stroke();
}
}
});

\tChart.Rectangle = Chart.Element.extend({
\t\tdraw : function(){
\t\t\tvar ctx = this.ctx,
\t\t\t\thalfWidth = this.width/2,
\t\t\t\tleftX = this.x - halfWidth,
\t\t\t\trightX = this.x + halfWidth,
\t\t\t\ttop = this.base - (this.base - this.y),
\t\t\t\thalfStroke = this.strokeWidth / 2;

\t\t\t// Canvas doesn't allow us to stroke inside the width so we can
\t\t\t// adjust the sizes to fit if we're setting a stroke on the line

\t\t\tif (this.showStroke){
\t\t\t\tleftX += halfStroke;
\t\t\t\trightX -= halfStroke;
\t\t\t\ttop += halfStroke;
\t\t\t}

\t\t\tctx.beginPath();

\t\t\tctx.fillStyle = this.fillColor;
\t\t\tctx.strokeStyle = this.strokeColor;
\t\t\tctx.lineWidth = this.strokeWidth;

\t\t\t// It'd be nice to keep this class totally generic to any rectangle
\t\t\t// and simply specify which border to miss out.
\t\t\tctx.moveTo(leftX, this.base);
\t\t\tctx.lineTo(leftX, top);
\t\t\tctx.lineTo(rightX, top);
\t\t\tctx.lineTo(rightX, this.base);
\t\t\tctx.fill();
\t\t\tif (this.showStroke){
\t\t\t\tctx.stroke();
\t\t\t}
\t\t},
\t\theight : function(){
\t\t\treturn this.base - this.y;
\t\t},
\t\tinRange : function(chartX,chartY){
\t\t\treturn (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base);
}
});

\tChart.Tooltip = Chart.Element.extend({
\t\tdraw : function(){

\t\t\tvar ctx = this.chart.ctx;

\t\t\tctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);

\t\t\tthis.xAlign = \"center\";
\t\t\tthis.yAlign = \"above\";

\t\t\t//Distance between the actual element.y position and the start of the tooltip caret
\t\t\tvar caretPadding = this.caretPadding = 2;

\t\t\tvar tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding,
\t\t\t\ttooltipRectHeight = this.fontSize + 2*this.yPadding,
\t\t\t\ttooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding;

\t\t\tif (this.x + tooltipWidth/2 >this.chart.width){
\t\t\t\tthis.xAlign = \"left\";
\t\t\t} else if (this.x - tooltipWidth/2 < 0){
\t\t\t\tthis.xAlign = \"right\";
\t\t\t}

\t\t\tif (this.y - tooltipHeight < 0){
\t\t\t\tthis.yAlign = \"below\";
\t\t\t}


\t\t\tvar tooltipX = this.x - tooltipWidth/2,
\t\t\t\ttooltipY = this.y - tooltipHeight;

\t\t\tctx.fillStyle = this.fillColor;

\t\t\t// Custom Tooltips
\t\t\tif(this.custom){
\t\t\t\tthis.custom(this);
\t\t\t}
\t\t\telse{
\t\t\t\tswitch(this.yAlign)
\t\t\t\t{
\t\t\t\tcase \"above\":
\t\t\t\t\t//Draw a caret above the x/y
\t\t\t\t\tctx.beginPath();
\t\t\t\t\tctx.moveTo(this.x,this.y - caretPadding);
\t\t\t\t\tctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight));
\t\t\t\t\tctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight));
\t\t\t\t\tctx.closePath();
\t\t\t\t\tctx.fill();
break ;
\t\t\t\tcase \"below\":
\t\t\t\t\ttooltipY = this.y + caretPadding + this.caretHeight;
\t\t\t\t\t//Draw a caret below the x/y
\t\t\t\t\tctx.beginPath();
\t\t\t\t\tctx.moveTo(this.x, this.y + caretPadding);
\t\t\t\t\tctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight);
\t\t\t\t\tctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight);
\t\t\t\t\tctx.closePath();
\t\t\t\t\tctx.fill();
break ;
\t\t\t\t}

\t\t\t\tswitch(this.xAlign)
\t\t\t\t{
\t\t\t\tcase \"left\":
\t\t\t\t\ttooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight);
break ;
\t\t\t\tcase \"right\":
\t\t\t\t\ttooltipX = this.x - (this.cornerRadius + this.caretHeight);
break ;
\t\t\t\t}

\t\t\t\tdrawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius);

\t\t\t\tctx.fill();

\t\t\t\tctx.fillStyle = this.textColor;
\t\t\t\tctx.textAlign = \"center\";
\t\t\t\tctx.textBaseline = \"middle\";
\t\t\t\tctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2);
}
}
});

\tChart.MultiTooltip = Chart.Element.extend({
\t\tinitialize : function(){
\t\t\tthis.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);

\t\t\tthis.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily);

\t\t\tthis.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5;

\t\t\tthis.ctx.font = this.titleFont;

\t\t\tvar titleWidth = this.ctx.measureText(this.title).width,
\t\t\t\t//Label has a legend square as well so account for this.
\t\t\t\tlabelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3,
\t\t\t\tlongestTextWidth = max([labelWidth,titleWidth]);

\t\t\tthis.width = longestTextWidth + (this.xPadding*2);


\t\t\tvar halfHeight = this.height/2;

\t\t\t//Check to ensure the height will fit on the canvas
\t\t\tif (this.y - halfHeight < 0 ){
\t\t\t\tthis.y = halfHeight;
\t\t\t} else if (this.y + halfHeight > this.chart.height){
\t\t\t\tthis.y = this.chart.height - halfHeight;
\t\t\t}

\t\t\t//Decide whether to align left or right based on position on canvas
\t\t\tif (this.x > this.chart.width/2){
\t\t\t\tthis.x -= this.xOffset + this.width;
\t\t\t} else {
\t\t\t\tthis.x += this.xOffset;
\t\t\t}


\t\t},
\t\tgetLineHeight : function(index){
\t\t\tvar baseLineHeight = this.y - (this.height/2) + this.yPadding,
\t\t\t\tafterTitleIndex = index-1;

\t\t\t//If the index is zero, we're getting the title
\t\t\tif (index === 0){
\t\t\t\treturn baseLineHeight + this.titleFontSize/2;
\t\t\t} else{
\t\t\t\treturn baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5;
\t\t\t}

\t\t},
\t\tdraw : function(){
\t\t\t// Custom Tooltips
\t\t\tif(this.custom){
\t\t\t\tthis.custom(this);
\t\t\t}
\t\t\telse{
\t\t\t\tdrawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius);
\t\t\t\tvar ctx = this.ctx;
\t\t\t\tctx.fillStyle = this.fillColor;
\t\t\t\tctx.fill();
\t\t\t\tctx.closePath();

\t\t\t\tctx.textAlign = \"left\";
\t\t\t\tctx.textBaseline = \"middle\";
\t\t\t\tctx.fillStyle = this.titleTextColor;
\t\t\t\tctx.font = this.titleFont;

\t\t\t\tctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0));

\t\t\t\tctx.font = this.font;
\t\t\t\thelpers.each(this.labels,function(label,index){
\t\t\t\t\tctx.fillStyle = this.textColor;
\t\t\t\t\tctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1));

\t\t\t\t\t//A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
\t\t\t\t\t//ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);
\t\t\t\t\t//Instead we'll make a white filled block to put the legendColour palette ov er.

\t\t\t\t\tctx.fillStyle = this.legendColorBackground;
\t\t\t\t\tctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);

\t\t\t\t\tctx.fillStyle = this.legendColors[index].fill;
\t\t\t\t\tctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize);


\t\t\t\t},this);
}
}
});

\tChart.Scale = Chart.Element.extend({
\t\tinitialize : function(){
\t\t\tthis.fit();
\t\t},
\t\tbuildYLabels : function(){
\t\t\tthis.yLabels = [];

\t\t\tvar stepDecimalPlaces = getDecimalPlaces(this.stepValue);

\t\t\tfor (var i=0; i<=this.steps; i++){
\t\t\t\tthis.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
\t\t\t}
\t\t\tthis.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0;
\t\t},
\t\taddXLabel : function(label){
\t\t\tthis.xLabels.push(label);
\t\t\tthis.valuesCount++;
\t\t\tthis.fit();
\t\t},
\t\tremoveXLabel : function(){
\t\t\tthis.xLabels.shift();
\t\t\tthis.valuesCount--;
\t\t\tthis.fit();
\t\t},
\t\t// Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use
\t\tfit: function(){
\t\t\t// First we need the width of the yLabels, assuming the xLabels aren't rotated

\t\t\t// To do that we need the base line at the top and base of the chart, assuming there is no x label rotation
\t\t\tthis.startPoint = (this.display) ? this.fontSize : 0;
\t\t\tthis.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels

\t\t\t// Apply padding settings to the start and end point.
\t\t\tthis.startPoint += this.padding;
\t\t\tthis.endPoint -= this.padding;

\t\t\t// Cache the starting height, so can determine if we need to recalculate the scale yAxis
\t\t\tvar cachedHeight = this.endPoint - this.startPoint,
\t\t\t\tcachedYLabelWidth;

\t\t\t// Build the current yLabels so we have an idea of what size they'll be to start
\t\t\t/*
\t\t\t *\tThis sets what is returned from calculateScaleRange as static properties of this class:
\t\t\t *
\t\t\t\tthis.steps;
\t\t\t\tthis.stepValue;
\t\t\t\tthis.min;
\t\t\t\tthis.max;
\t\t\t *
\t\t\t */

\t\t\tthis.calculateYRange(cachedHeight);

\t\t\t// With these properties set we can now build the array of yLabels
\t\t\t// and also the width of the largest yLabel
\t\t\tthis.buildYLabels();

\t\t\tthis.calculateXLabelRotation();

\t\t\twhile((cachedHeight > this.endPoint - this.startPoint)){
\t\t\t\tcachedHeight = this.endPoint - this.startPoint;
\t\t\t\tcachedYLabelWidth = this.yLabelWidth;

\t\t\t\tthis.calculateYRange(cachedHeight);
\t\t\t\tthis.buildYLabels();

\t\t\t\t// Only go through the xLabel loop again if the yLabel width has changed
\t\t\t\tif (cachedYLabelWidth < this.yLabelWidth){
\t\t\t\t\tthis.calculateXLabelRotation();
\t\t\t\t}
\t\t\t}

\t\t},
\t\tcalculateXLabelRotation : function(){
\t\t\t//Get the width of each grid by calculating the difference
\t\t\t//between x offsets between 0 and 1.

\t\t\tthis.ctx.font = this.font;

\t\t\tvar firstWidth = this.ctx.measureText(this.xLabels[0]).width,
\t\t\t\tlastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width,
\t\t\t\tfirstRotated,
\t\t\t\tlastRotated;


\t\t\tthis.xScalePaddingRight = lastWidth/2 + 3;
\t\t\tthis.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10;

\t\t\tthis.xLabelRotation = 0;
\t\t\tif (this.display){
\t\t\t\tvar originalLabelWidth = longestText(this.ctx,this.font,this.xLabels),
\t\t\t\t\tcosRotation,
\t\t\t\t\tfirstRotatedWidth;
\t\t\t\tthis.xLabelWidth = originalLabelWidth;
\t\t\t\t//Allow 3 pixels x2 padding either side for label readability
\t\t\t\tvar xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6;

\t\t\t\t//Max label rotate should be 90 - also act as a loop counter
\t\t\t\twhile ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){
\t\t\t\t\tcosRotation = Math.cos(toRadians(this.xLabelRotation));

\t\t\t\t\tfirstRotated = cosRotation * firstWidth;
\t\t\t\t\tlastRotated = cosRotation * lastWidth;

\t\t\t\t\t// We're right aligning the text now.
\t\t\t\t\tif (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){
\t\t\t\t\t\tthis.xScalePaddingLeft = firstRotated + this.fontSize / 2;
\t\t\t\t\t}
\t\t\t\t\tthis.xScalePaddingRight = this.fontSize/2;


\t\t\t\t\tthis.xLabelRotation++;
\t\t\t\t\tthis.xLabelWidth = cosRotation * originalLabelWidth;

\t\t\t\t}
\t\t\t\tif (this.xLabelRotation > 0){
\t\t\t\t\tthis.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3;
\t\t\t\t}
\t\t\t}
\t\t\telse{
\t\t\t\tthis.xLabelWidth = 0;
\t\t\t\tthis.xScalePaddingRight = this.padding;
\t\t\t\tthis.xScalePaddingLeft = this.padding;
\t\t\t}

\t\t},
\t\t// Needs to be overidden in each Chart type
\t\t// Otherwise we need to pass all the data into the scale class
\t\tcalculateYRange: noop,
\t\tdrawingArea: function(){
\t\t\treturn this.startPoint - this.endPoint;
\t\t},
\t\tcalculateY : function(value){
\t\t\tvar scalingFactor = this.drawingArea() / (this.min - this.max);
\t\t\treturn this.endPoint - (scalingFactor * (value - this.min));
\t\t},
\t\tcalculateX : function(index){
\t\t\tvar isRotated = (this.xLabelRotation > 0),
\t\t\t\t// innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding,
\t\t\t\tinnerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight),
\t\t\t\tvalueWidth = innerWidth/Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1),
\t\t\t\tvalueOffset = (valueWidth * index) + this.xScalePaddingLeft;

\t\t\tif (this.offsetGridLines){
\t\t\t\tvalueOffset += (valueWidth/2);
\t\t\t}

\t\t\treturn Math.round(valueOffset);
\t\t},
\t\tupdate : function(newProps){
\t\t\thelpers.extend(this, newProps);
\t\t\tthis.fit();
\t\t},
\t\tdraw : function(){
\t\t\tvar ctx = this.ctx,
\t\t\t\tyLabelGap = (this.endPoint - this.startPoint) / this.steps,
\t\t\t\txStart = Math.round(this.xScalePaddingLeft);
\t\t\tif (this.display){
\t\t\t\tctx.fillStyle = this.textColor;
\t\t\t\tctx.font = this.font;
\t\t\t\teach(this.yLabels,function(labelString,index){
\t\t\t\t\tvar yLabelCenter = this.endPoint - (yLabelGap * index),
\t\t\t\t\t\tlinePositionY = Math.round(yLabelCenter),
\t\t\t\t\t\tdrawHorizontalLine = this.showHorizontalLines;

\t\t\t\t\tctx.textAlign = \"right\";
\t\t\t\t\tctx.textBaseline = \"middle\";
\t\t\t\t\tif (this.showLabels){
\t\t\t\t\t\tctx.fillText(labelString,xStart - 10,yLabelCenter);
\t\t\t\t\t}

\t\t\t\t\t// This is X axis, so draw it
\t\t\t\t\tif (index === 0 &&放大器; !drawHorizontalLine){
\t\t\t\t\t\tdrawHorizontalLine = true;
\t\t\t\t\t}

\t\t\t\t\tif (drawHorizontalLine){
\t\t\t\t\t\tctx.beginPath();
\t\t\t\t\t}

\t\t\t\t\tif (index > 0){
\t\t\t\t\t\t// This is a grid line in the centre, so drop that
\t\t\t\t\t\tctx.lineWidth = this.gridLineWidth;
\t\t\t\t\t\tctx.strokeStyle = this.gridLineColor;
\t\t\t\t\t} else {
\t\t\t\t\t\t// This is the first line on the scale
\t\t\t\t\t\tctx.lineWidth = this.lineWidth;
\t\t\t\t\t\tctx.strokeStyle = this.lineColor;
\t\t\t\t\t}

\t\t\t\t\tlinePositionY += helpers.aliasPixel(ctx.lineWidth);

\t\t\t\t\tif(drawHorizontalLine){
\t\t\t\t\t\tctx.moveTo(xStart, linePositionY);
\t\t\t\t\t\tctx.lineTo(this.width, linePositionY);
\t\t\t\t\t\tctx.stroke();
\t\t\t\t\t\tctx.closePath();
\t\t\t\t\t}

\t\t\t\t\tctx.lineWidth = this.lineWidth;
\t\t\t\t\tctx.strokeStyle = this.lineColor;
\t\t\t\t\tctx.beginPath();
\t\t\t\t\tctx.moveTo(xStart - 5, linePositionY);
\t\t\t\t\tctx.lineTo(xStart, linePositionY);
\t\t\t\t\tctx.stroke();
\t\t\t\t\tctx.closePath();

\t\t\t\t},this);

\t\t\t\teach(this.xLabels,function(label,index){
\t\t\t\t\tvar xPos = this.calculateX(index) + aliasPixel(this.lineWidth),
\t\t\t\t\t\t// Check to see if line/bar here and decide where to place the line
\t\t\t\t\t\tlinePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth),
\t\t\t\t\t\tisRotated = (this.xLabelRotation > 0),
\t\t\t\t\t\tdrawVerticalLine = this.showVerticalLines;

\t\t\t\t\t// This is Y axis, so draw it
\t\t\t\t\tif (index === 0 && !drawVerticalLine){
\t\t\t\t\t\tdrawVerticalLine = true;
\t\t\t\t\t}

\t\t\t\t\tif (drawVerticalLine){
\t\t\t\t\t\tctx.beginPath();
\t\t\t\t\t}

\t\t\t\t\tif (index > 0){
\t\t\t\t\t\t// This is a grid line in the centre, so drop that
\t\t\t\t\t\tctx.lineWidth = this.gridLineWidth;
\t\t\t\t\t\tctx.strokeStyle = this.gridLineColor;
\t\t\t\t\t} else {
\t\t\t\t\t\t// This is the first line on the scale
\t\t\t\t\t\tctx.lineWidth = this.lineWidth;
\t\t\t\t\t\tctx.strokeStyle = this.lineColor;
\t\t\t\t\t}

\t\t\t\t\tif (drawVerticalLine){
\t\t\t\t\t\tctx.moveTo(linePos,this.endPoint);
\t\t\t\t\t\tctx.lineTo(linePos,this.startPoint - 3);
\t\t\t\t\t\tctx.stroke();
\t\t\t\t\t\tctx.closePath();
\t\t\t\t\t}


\t\t\t\t\tctx.lineWidth = this.lineWidth;
\t\t\t\t\tctx.strokeStyle = this.lineColor;


\t\t\t\t\t// Small lines at the bottom of the base grid line
\t\t\t\t\tctx.beginPath();
\t\t\t\t\tctx.moveTo(linePos,this.endPoint);
\t\t\t\t\tctx.lineTo(linePos,this.endPoint + 5);
\t\t\t\t\tctx.stroke();
\t\t\t\t\tctx.closePath();

\t\t\t\t\tctx.save();
\t\t\t\t\tctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8);
\t\t\t\t\tctx.rotate(toRadians(this.xLabelRotation)*-1);
\t\t\t\t\tctx.font = this.font;
\t\t\t\t\tctx.textAlign = (isRotated) ? \"right\" : \"center\";
\t\t\t\t\tctx.textBaseline = (isRotated) ? \"middle\" : \"top\";
\t\t\t\t\tctx.fillText(label, 0, 0);
\t\t\t\t\tctx.restore();
\t\t\t\t},this);

\t\t\t}
\t\t}

\t});

\tChart.RadialScale = Chart.Element.extend({
\t\tinitialize: function(){
\t\t\tthis.size = min([this.height, this.width]);
\t\t\tthis.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
\t\t},
\t\tcalculateCenterOffset: function(value){
\t\t\t// Take into account half font size + the yPadding of the top value
\t\t\tvar scalingFactor = this.drawingArea / (this.max - this .min);

\t\t\treturn (value - this.min) * scalingFactor;
\t\t},
\t\tupdate : function(){
\t\t\tif (!this.lineArc){
\t\t\t\tthis.setScaleSize();
\t\t\t} else {
\t\t\t\tthis.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2);
\t\t\t}
\t\t\tthis.buildYLabels();
\t\t},
\t\tbuildYLabels: function(){
\t\t\tthis.yLabels = [];

\t\t\tvar stepDecimalPlaces = getDecimalPlaces(this.stepValue);

\t\t\tfor (var i=0; i<=this.steps; i++){
\t\t\t\tthis.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)}));
\t\t\t}
\t\t},
\t\tgetCircumference : function(){
\t\t\treturn ((Math.PI*2) / this.valuesCount);
\t\t},
\t\tsetScaleSize: function(){
\t\t\t/*
\t\t\t * Right, this is really confusing and there is a lot of maths going on here
\t\t\t * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9
\t\t\t *
\t\t\t * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif
\t\t\t *
\t\t\t * Solution:
\t\t\t *
\t\t\t * We assume the radius of the polygon is half the size of the canvas at first
\t\t\t * at each index we check if the text overlaps.
\t\t\t *
\t\t\t * Where it does, we store that angle and that index.
\t\t\t *
\t\t\t * After finding the largest index and angle we calculate how much we need to remove
\t\t\t * from the shape radius to move the point inwards by that x.
\t\t\t *
\t\t\t * We average the left and right distances to get the maximum shape radius that can fit in the box
\t\t\t * along with labels.
\t\t\t *
\t\t\t * Once we have that, we can find the centre point for the chart, by taking the x text protrusion
\t\t\t * on each side, removing that from the size, halving it and adding the left x protrusion width.
\t\t\t *
\t\t\t * This will mean we have a shape fitted to the canvas, as large as it can be with the labels
\t\t\t * and position it in the most space efficient manner
\t\t\t *
\t\t\t * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif
\t\t\t */



\t\t\t// Get maximum radius of the polygon. Either half the height (minus the text width) or half the width.
\t\t\t// Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points
\t\t\tvar largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]),
\t\t\t\tpointPosition,
\t\t\t\ti,
\t\t\t\ttextWidth,
\t\t\t\thalfTextWidth,
\t\t\t\tfurthestRight = this.width,
\t\t\t\tfurthestRightIndex,
\t\t\t\tfurthestRightAngle,
\t\t\t\tfurthestLeft = 0,
\t\t\t\tfurthestLeftIndex,
\t\t\t\tfurthestLeftAngle,
\t\t\t\txProtrusionLeft,
\t\t\t\txProtrusionRight,
\t\t\t\tradiusReductionRight,
\t\t\t\tradiusReductionLeft,
\t\t\t\tmaxWidthRadius;
\t\t\tthis.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
\t\t\tfor (i=0;i<this.valuesCount;i++){
\t\t\t\t// 5px to space the text slightly out - similar to what we do in the draw function.
\t\t\t\tpointPosition = this.getPointPosition(i, largestPossibleRadius);
\t\t\t\ttextWidth = this.ctx.measureText(template(this.templateString, { value: this.labels[i] })).width + 5;
\t\t\t\tif (i === 0 || i === this.valuesCount/2){
\t\t\t\t\t// If we're at index zero, or exactly the middle, we're at exactly the top/bottom
\t\t\t\t\t// of the radar chart, so text will be aligned centrally, so we'll half it and compare
\t\t\t\t\t// w/left and right text sizes
\t\t\t\t\thalfTextWidth = textWidth/2;
\t\t\t\t\tif (pointPosition.x + halfTextWidth > furthestRight) {
\t\t\t\t\t\tfurthestRight = pointPosition.x + halfTextWidth;
\t\t\t\t\t\tfurthestRightIndex = i;
\t\t\t\t\t}
\t\t\t\t\tif (pointPosition.x - halfTextWidth < furthestLeft) {
\t\t\t\t\t\tfurthestLeft = pointPosition.x - halfTextWidth;
\t\t\t\t\t\tfurthestLeftIndex = i;
\t\t\t\t\t}
\t\t\t\t}
\t\t\t\telse if (i < this.valuesCount/2) {
\t\t\t\t\t// Less than half the values means we'll left align the text
\t\t\t\t\tif (pointPosition.x + textWidth > furthestRight) {
\t\t\t\t\t\tfurthestRight = pointPosition.x + textWidth;
\t\t\t\t\t\tfurthestRightIndex = i;
\t\t\t\t\t}
\t\t\t\t}
\t\t\t\telse if (i > this.valuesCount/2){
\t\t\t\t\t// More than half the values means we'll right align the text
\t\t\t\t\tif (pointPosition.x - textWidth < furthestLeft) {
\t\t\t\t\t\tfurthestLeft = pointPosition.x - textWidth;
\t\t\t\t\t\tfurthestLeftIndex = i;
\t\t\t\t\t}
\t\t\t\t}
\t\t\t}

\t\t\txProtrusionLeft = furthestLeft;

\t\t\txProtrusionRight = Math.ceil(furthestRight - this.width);

\t\t\tfurthestRightAngle = this.getIndexAngle(furthestRightIndex);

\t\t\tfurthestLeftAngle = this.getIndexAngle(furthestLeftIndex);

\t\t\tradiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2);

\t\t\tradiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2);

\t\t\t// Ensure we actually need to reduce the size of the chart
\t\t\tradiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0;
\t\t\tradiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0;

\t\t\tthis.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2;

\t\t\t//this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2])
\t\t\tthis.setCenterPoint(radiusReductionLeft, radiusReductionRight);

\t\t},
\t\tsetCenterPoint: function(leftMovement, rightMovement){

\t\t\tvar maxRight = this.width - rightMovement - this.drawingArea,
\t\t\t\tmaxLeft = leftMovement + this.drawingArea;

\t\t\tthis.xCenter = (maxLeft + maxRight)/2;
\t\t\t// Always vertically in the centre as the text height doesn't change
\t\t\tthis.yCenter = (this.height/2);
\t\t},

\t\tgetIndexAngle : function(index){
\t\t\tvar angleMultiplier = (Math.PI * 2) / this.valuesCount;
\t\t\t// Start from the top instead of right, so remove a quarter of the circle

\t\t\treturn index * angleMultiplier - (Math.PI/2);
\t\t},
\t\tgetPointPosition : function(index, distanceFromCenter){
\t\t\tvar thisAngle = this.getIndexAngle(index);
\t\t\treturn {
\t\t\t\tx : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter,
\t\t\t\ty : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter
\t\t\t};
\t\t},
\t\tdraw: function(){
\t\t\tif (this.display){
\t\t\t\tvar ctx = this.ctx;
\t\t\t\teach(this.yLabels, function(label, index){
\t\t\t\t\t// Don't draw a centre value
\t\t\t\t\tif (index > 0){
\t\t\t\t\t\tvar yCenterOffset = index * (this.drawingArea/this.steps),
\t\t\t\t\t\t\tyHeight = this.yCenter - yCenterOffset,
\t\t\t\t\t\t\tpointPosition;

\t\t\t\t\t\t// Draw circular lines around the scale
\t\t\t\t\t\tif (this.lineWidth > 0){
\t\t\t\t\t\t\tctx.strokeStyle = this.lineColor;
\t\t\t\t\t\t\tctx.lineWidth = this.lineWidth;

\t\t\t\t\t\t\tif(this.lineArc){
\t\t\t\t\t\t\t\tctx.beginPath();
\t\t\t\t\t\t\t\tctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2);
\t\t\t\t\t\t\t\tctx.closePath();
\t\t\t\t\t\t\t\tctx.stroke();
\t\t\t\t\t\t\t} else{
\t\t\t\t\t\t\t\tctx.beginPath();
\t\t\t\t\t\t\t\tfor (var i=0;i<this.valuesCount;i++)
\t\t\t\t\t\t\t\t{
\t\t\t\t\t\t\t\t\tpointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue)));
\t\t\t\t\t\t\t\t\tif (i === 0){
\t\t\t\t\t\t\t\t\t\tctx.moveTo(pointPosition.x, pointPosition.y);
\t\t\t\t\t\t\t\t\t} else {
\t\t\t\t\t\t\t\t\t\tctx.lineTo(pointPosition.x, pointPosition.y);
\t\t\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t\t}
\t\t\t\t\t\t\t\tctx.closePath();
\t\t\t\t\t\t\t\tctx.stroke();
\t\t\t\t\t\t\t}
\t\t\t\t\t\t}
\t\t\t\t\t\tif(this.showLabels){
\t\t\t\t\t\t\tctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily);
\t\t\t\t\t\t\tif (this.showLabelBackdrop){
\t\t\t\t\t\t\t\tvar labelWidth = ctx.measureText(label).width;
\t\t\t\t\t\t\t\tctx.fillStyle = this.backdropColor;
\t\t\t\t\t\t\t\tctx.fillRect(
\t\t\t\t\t\t\t\t\tthis.xCenter - labelWidth/2 - this.backdropPaddingX,
\t\t\t\t\t\t\t\t\tyHeight - this.fontSize/2 - this.backdropPaddingY,
\t\t\t\t\t\t\t\t\tlabelWidth + this.backdropPaddingX*2,
\t\t\t\t\t\t\t\t\tthis.fontSize + this.backdropPaddingY*2
\t\t\t\t\t\t\t\t);
\t\t\t\t\t\t\t}
\t\t\t\t\t\t\tctx.textAlign = 'center';
\t\t\t\t\t\t\tctx.textBaseline = \"middle\";
\t\t\t\t\t\t\tctx.fillStyle = this.fontColor;
\t\t\t\t\t\t\tctx.fillText(label, this.xCenter, yHeight);
\t\t\t\t\t\t}
\t\t\t\t\t}
\t\t\t\t}, this);

\t\t\t\tif (!this.lineArc){
\t\t\t\t\tctx.lineWidth = this.angleLineWidth;
\t\t\t\t\tctx.strokeStyle = this.angleLineColor;
\t\t\t\t\tfor (var i = this.valuesCount - 1; i >= 0; i--) {
\t\t\t\t\t\tif (this.angleLineWidth > 0){
\t\t\t\t\t\t\tvar outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max));
\t\t\t\t\t\t\tctx.beginPath();
\t\t\t\t\t\t\tctx.moveTo(this.xCenter, this.yCenter);
\t\t\t\t\t\t\tctx.lineTo(outerPosition.x, outerPosition.y);
\t\t\t\t\t\t\tctx.stroke();
\t\t\t\t\t\t\tctx.closePath();
\t\t\t\t\t\t}
\t\t\t\t\t\t// Extra 3px out for some label spacing
\t\t\t\t\t\tvar pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5);
\t\t\t\t\t\tctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily);
\t\t\t\t\t\tctx.fillStyle = this.pointLabelFontColor;

\t\t\t\t\t\tvar labelsCount = this.labels.length,
\t\t\t\t\t\t\thalfLabelsCount = this.labels.length/2,
\t\t\t\t\t\t\tquarterLabelsCount = halfLabelsCount/2,
\t\t\t\t\t\t\tupperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount),
\t\t\t\t\t\t\texactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount);
\t\t\t\t\t\tif (i === 0){
\t\t\t\t\t\t\tctx.textAlign = 'center';
\t\t\t\t\t\t} else if(i === halfLabelsCount){
\t\t\t\t\t\t\tctx.textAlign = 'center';
\t\t\t\t\t\t} else if (i < halfLabelsCount){
\t\t\t\t\t\t\tctx.textAlign = 'left';
\t\t\t\t\t\t} else {
\t\t\t\t\t\t\tctx.textAlign = 'right';
\t\t\t\t\t\t}

\t\t\t\t\t\t// Set the correct text baseline based on outer positioning
\t\t\t\t\t\tif (exactQuarter){
\t\t\t\t\t\t\tctx.textBaseline = 'middle';
\t\t\t\t\t\t} else if (upperHalf){
\t\t\t\t\t\t\tctx.textBaseline = 'bottom';
\t\t\t\t\t\t} else {
\t\t\t\t\t\t\tctx.textBaseline = 'top';
\t\t\t\t\t\t}

\t\t\t\t\t\tctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y);
\t\t\t\t\t}
\t\t\t\t}
\t\t\t}
\t\t}
\t});

\t// Attach global event to resize each chart instance when the browser resizes
\thelpers.addEvent(window, \"resize\", (function(){
\t\t// Basic debounce of resize function so it doesn't hurt performance when resizing browser.
\t\tvar timeout;
\t\treturn function(){
\t\t\tclearTimeout(timeout);
\t\t\ttimeout = setTimeout(function(){
\t\t\t\teach(Chart.instances,function(instance){
\t\t\t\t\t// If the responsive flag is set in the chart instance config
\t\t\t\t\t// Cascade the resize event down to the chart.
\t\t\t\t\tif (instanc e.options.responsive){
\t\t\t\t\t\tinstance.resize(instance.render, true);
}
});
\t\t\t}, 50);
};
\t})());


\tif (amd) {
\t\tdefine(function(){
\t\t\treturn Chart;
\t\t});
\t} else if (typeof module === 'object' && module.exports) {
\t\tmodule.exports = Chart;
\t}

\troot.Chart = Chart;

\tChart.noConflict = function(){
\t\troot.Chart = previous;
\t\treturn Chart;
};

}).call(this);

(function(){
\t\"use strict\";

\tvar root = this,
\t\tChart = root.Chart,
\t\thelpers = Chart.helpers;


\tvar defaultConfig = {
\t\t//Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value
\t\tscaleBeginAtZero : true,

\t\t//Boolean - Whether grid lines are shown across the chart
\t\tscaleShowGridLines : true,

\t\t//String - Colour of the grid lines
\t\tscaleGridLineColor : \"rgb a(0,0,0,.05)\",

\t\t//Number - Width of the grid lines
\t\tscaleGridLineWidth : 1,

\t\t//Boolean - Whether to show horizontal lines (except X axis)
\t\tscaleShowHorizontalLines: true,

\t\t//Boolean - Whether to show vertical lines (except Y axis)
\t\tscaleShowVerticalLines: true,

\t\t//Boolean - If there is a stroke on each bar
\t\tbarShowStroke : true,

\t\t//Number - Pixel width of the bar stroke
\t\tbarStrokeWidth : 2, $b$ b
\t\t//Number - Spacing between each of the X value sets
\t\tbarValueSpacing : 5,

\t\t//Number - Spacing between data sets within X values
\t\tbarDatasetSpacing : 1,

\t\t//String - A legend template
\t\tlegendTemplate : \"<ul class=\\"<%=name.toLowerCase()%>-legend\\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\\"background-color:<%=datasets[i].fillColor%>\\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>\"

\t};


\tChart.Type.extend({
\t\tname: \"Bar\",
\t\tdefaults : defaultConfig,
\t\tinitialize: function(data){

\t\t\t//Expose options as a scope variable here so we can access it in the ScaleClass
\t\t\tvar options = this.options;

\t\t\tthis.ScaleClass = Chart.Scale.extend({
\t\t\t\toffsetGridLines : true,
\t\t\t\tcalculateBarX : function(datasetCount, datasetIndex, barIndex){
\t\t\t\t\t//Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar
\t\t\t\t\tvar xWidth = this.calculateBaseWidth(),
\t\t\t\t\t\txAbsolute = this.calculateX(barIndex) - (xWidth/2),
\t\t\t\t\t\tbarWidth = this.calculateBarWidth(datasetCount);

\t\t\t\t\treturn xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth/2;
\t\t\t\t},
\t\t\t\tcalculateBaseWidth : function(){
\t\t\t\t\treturn (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing);
\t\t\t\t},
\t\t\t\tcalculateBarWidth : function(datasetCount){
\t\t\t\t\t//The padding between datasets is to the right of each bar, providing that there are more than 1 dataset
\t\t\t\t\tvar baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing);

\t\t\t\t\treturn (baseWidth / datasetCount);
}
});

\t\t\tthis.datasets = [];

\t\t\t//Set up tooltip events on the chart
\t\t\tif (this.options.showTooltips){
\t\t\t\thelpers.bindEvents(this, this.options.tooltipEvents, function(evt){
\t\t\t\t\tvar activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : [];

\t\t\t\t\tthis.eachBars(function(bar){
\t\t\t\t\t\tbar.restore(['fillColor', 'strokeColor']);
\t\t\t\t\t});
\t\t\t\t\thelpers.each(activeBars, function(activeBar){
\t\t\t\t\t\tactiveBar.fillColor = activeBar.highlightFill;
\t\t\t\t\t\tacti veBar.strokeColor = activeBar.highlightStroke;
});
\t\t\t\t\tthis.showTooltip(activeBars);
\t\t\t\t});
\t\t\t}

\t\t\t//Declare the extension of the default point, to cater for the options passed in to the constructor
\t\t\tthis.BarClass = Chart.Rectangle.extend({
\t\t\t\tstrokeWidth : this.options.barStrokeWidth,
\t\t\t\tshowStroke : this.options.barShowStroke,
\t\t\t\tctx : this.chart.ctx
\t\t\t});

\t\t\t//Iterate through each of the datasets, and build this into a property of the chart
\t\t\thelpers.each(data.datasets,function(dataset,datasetIndex){

\t\t\t\tvar datasetObject = {
\t\t\t\t\tlabel : dataset.label || null,
\t\t\t\t\tfillColor : dataset.fillColor,
\t\t\t\t\tstrokeColor : dataset.strokeColor,
\t\t\t\t\tbars : []
\t\t\t\t};

\t\t\t\tthis.datasets.push(datasetObject);

\t\t\t\thelpers.each(dataset.data,function(dataPoint,index){
\t\t\t\t\t//Add a new point for each piece of data, passing any required data to draw.
\t\t\t\t\tdatasetObject.bars.push(new this.BarClass({
\t\t\t\t\t\tvalue : dataPoint,
\t\t\t\t\t\tlabel : data.labels[index],
\t\t\t\t\t\tdatasetLabel: dataset.label,
\t\t\t\t\t\tstrokeColor : dataset.strokeColor,
\t\t\t\t\t\tfillColor : dataset.fillColor,
\t\t\t\t\t\thighlightFill : dataset.highlightFill || dataset.fillColor,
\t\t\t\t\t\thighlightStroke : dataset.highlightStroke || dataset.strokeColor
\t\t\t\t\t}));
\t\t\t\t},this);

\t\t\t},this);

\t\t\tthis.buildScale(data.labels);

\t\t\tthis.BarClass.prototype.base = this.scale.endPoint;

\t\t\tthis.eachBars(function(bar, index, datasetIndex){
\t\t\t\thelpers.extend(bar, {
\t\t\t\t\twidth : this.scale.calculateBarWidth(this.datasets.length),
\t\t\t\t\tx: this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
\t\t\t\t\ty: this.scale.endPoint
\t\t\t\t});
\t\t\t\tbar.save();
\t\t\t}, this);

\t\t\tthis.render();
\t\t},
\t\tupdate : function(){
\t\t\tthis.scale.update();
\t\t\t// Reset any highlight colours before updating.
\t\t\thelpers.each(this.activeElements, function(activeElement){
\t\t\t\tactiveElement.restore(['fillColor', 'strokeColor']);
\t\t\t});

\t\t\tthis.eachBars(function(bar){
\t\t\t\tbar.save();
\t\t\t});
\t\t\tthis.render();
\t\t},
\t\teachBars : function(callback){
\t\t\thelpers.each(this.datasets,function(dataset, datasetIndex){
\t\t\t\thelpers.each(dataset.bars, callback, this, datasetIndex);
\t\t\t},this);
\t\t},
\t\tgetBarsAtEvent : function(e){
\t\t\tvar barsArray = [],
\t\t\t\teventPosition = helpers.getRelativePosition(e),
\t\t\t\tdatasetIterator = function(dataset){
\t\t\t\t\tbarsArray.push(dataset.bars[barIndex]);
\t\t\t\t},
\t\t\t\tbarIndex;

\t\t\tfor (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) {
\t\t\t\tfor (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) {
\t\t\t\t\tif (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){
\t\t\t\t\t\thelpers.each(this.datasets, datasetIterator);
\t\t\t\t\t\treturn barsArray;
\t\t\t\t\t}
\t\t\t\t}
\t\t\t}

\t\t\treturn barsArray;
\t\t},
\t\tbuildScale : function(labels){
\t\t\tvar self = this;

\t\t\tvar dataTotal = function(){
\t\t\t\tvar values = [];
\t\t\t\tself.eachBars(function(bar){
\t\t\t\t\tvalues.push(bar.value);
\t\t\t\t});
\t\t\t\treturn values;
\t\t\t};

\t\t\tvar scaleOptions = {
\t\t\t\ttemplateString : this.options.scaleLabel,
\t\t\t\theight : this.chart.height,
\t\t\t\twidth : this.chart.width,
\t\t\t\tctx : this.chart.ctx,
\t\t\t\ttextColor : this.options.scaleFontColor,
\t\t\t\tfontSize : this.options.scaleFontSize,
\t\t\t\tfontStyle : this.options.scaleFontStyle,
\t\t\t\tfontFamily : this.options.scaleFontFamily,
\t\t\t\tvaluesCount : labels.length,
\t\t\t\tbeginAtZero : this.options.scaleBeginAtZero,
\t\t\t\tintegersOnly : this.options.scaleIntegersOnly,
\t\t\t\tcalculateYRange: function(currentHeight){
\t\t\t\t\tv ar updatedRanges = helpers.calculateScaleRange(
\t\t\t\t\t\tdataTotal(),
\t\t\t\t\t\tcurrentHeight,
\t\t\t\t\t\tthis.fontSize,
\t\t\t\t\t\tthis.beginAtZero,
\t\t\t\t\t\tthis.integersOnly
\t\t\t\t\t);
\t\t\t\t\thelpers.extend(this, updatedRanges);
\t\t\t\t},
\t\t\t\txLabels : labels,
\t\t\t\tfont : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
\t\t\t\tlineWidth : this.options.scaleLineWidth,
\t\t\t\tlineColor : this.options.scaleLineColor,
\t\t\t\tshowHorizontalLines : this.options.scaleShowHorizontalLines,
\t\t\t\tshowVerticalLines : this.options.scaleShowVerticalLines,
\t\t\t\tgridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
\t\t\t\tgridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : \"rgba(0,0,0,0)\",
\t\t\t\tpadding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0,
\t\t\t\tshowLabels : this.options.scaleShowLabels,
\t\t\t\tdisplay : this.options.showScale
\t\t\t};

\t\t\tif (this.options.scaleOverride){
\t\t\t\thelpers.extend(scaleOptions, {
\t\t\t\t\tcalculateYRange: helpers.noop,
\t\t\t\t\tsteps: this.options.scaleSteps,
\t\t\t\t\tstepValue: this.options.scaleStepWidth,
\t\t\t\t\tmin: this.options.scaleStartValue,
\t\t\t\t\tmax: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
\t\t\t\t});
\t\t\t}

\t\t\tthis.scale = new this.ScaleClass(scaleOptions);
\t\t},
\t\taddData : function(valuesArray,label){
\t\t\t//Map the values array for each of the datasets
\t\t\thelpers.each(valuesArray,function(value,datasetIndex){
\t\t\t\t//Add a new point for each piece of data, passing any required data to draw.
\t\t\t\tthis.datasets[datasetIndex].bars.push(new this.BarClass({
\t\t\t\t\tvalue : value,
\t\t\t\t\tlabel : label,
\t\t\t\t\tx: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1),
\t\t\t\t\ty: this.scale.endPoint, $b $b \t\t\t\t\twidth : this.scale.calculateBarWidth(this.datasets.length),
\t\t\t\t\tbase : this.scale.endPoint,
\t\t\t\t\tstrokeColor : this.datasets[datasetIndex].strokeColor,
\t\t\t\t\tfillColor : this.datasets[datasetIndex].fillColor
\t\t\t\t}));
\t\t\t},this);

\t\t\tthis.scale.addXLabel(label);
\t\t\t//Then re-render the chart.
\t\t\tthis.update();
\t\t},
\t\tremoveData : function(){
\t\t\tthis.scale.removeXLabel();
\t\t\t//Then re-render the chart.
\t\t\thelpers.each(this.datasets,function(dataset){
\t\t\t\tdataset.bars.shift();
\t\t\t},this);
\t\t\tthis.update();
\t\t},
\t\treflow : function(){
\t\t\thelpers.extend(this.BarClass.prototype,{
\t\t\t\ty: this.scale.endPoint,
\t\t\t\tbase : this.scale.endPoint
\t\t\t});
\t\t\tvar newScaleProps = helpers.extend({
\t\t\t\theight : this.chart.height,
\t\t\t\twidth : this.chart.width
\t\t\t});
\t\t\tthis.scale.update(newScaleProps);
\t\t},
\t\tdraw : function(ease){
\t\t\tvar easingDecimal = ease || 1 ;
\t\t\tthis.clear();

\t\t\tvar ctx = this.chart.ctx;

\t\t\tthis.scale.draw(easingDecimal);

\t\t\t//Draw all the bars for each dataset
\t\t\thelpers.each(this.datasets,function(dataset,datasetIndex){
\t\t\t\thelpers.each(dataset.bars,function(bar,index){
\t\t\t\t\tif (bar.hasValue()){
\t\t\t\t\t\tbar.base = this.scale.endPoint;
\t\t\t\t\t\t//Transition then draw
\t\t\t\t\t\tbar.transition({
\t\t\t\t\t\t\tx : this.scale.calculateBarX(this.datasets.length, datasetIndex, index),
\t\t\t\t\t\t\ty : this.scale.calculateY(bar.value),
\t\t\t\t\t\t\twidth : this.scale.calculateBarWidth(this.datasets.length)
\t\t\t\t\t\t}, easingDecimal).draw();
\t\t\t\t\t}
\t\t\t\t},this);

\t\t\t},this);
}
});


}).call(this);

(function(){
\t\"use strict\";

\tvar root = this,
\t\tChart = root.Chart,
\t\t//Cache a local reference to Chart.helpers
\t\thelpers = Chart.helpers;

\tvar defaultConfig = {
\t\t//Boolean - Whether we should show a stroke on each segment
\t\tsegmentShowStroke : true,

\t\t//String - The colour of each segment stroke
\t\tsegmentStrokeColor : \"#fff\",

\t\t//Number - The width of each segment stroke
\t\tsegmentStrokeWidth : 2,

\t\t//The percentage of the chart that we cut out of the middle.
\t\tpercentageInnerCutout : 50,

\t\t//Number - Amount of animation steps
\t\tanimationSteps : 100,

\t\t//String - Animation easing effect
\t\tanimationEasing : \"easeOutBounce\",

\t\t//Boolean - Whether we animate the rotation of the Doughnut
\t\tanimateRotate : true,

\t\t//Boolean - Whether we animate scaling t he Doughnut from the centre
\t\tanimateScale : false,

\t\t//String - A legend template
\t\tlegendTemplate : \"<ul class=\\"<%=name.toLowerCase()%>-legend\\"><% for (var i=0; i<segments.length; i++){%><li><span style=\\"background-color:<%=segments[i].fillColor%>\\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>\"

\t};


\tChart.Type.extend({
\t\t//Passing in a name registers this chart in the Chart namespace
\t\tname: \"Doughnut\",
\t\t//Providing a defaults will also register the deafults in the chart namespace
\t\tdefaults : defaultConfig,
\t\t//Initialize is fired when the chart is initialized - Data is passed in as a parameter
\t\t//Config is automatically merged by the core of Chart.js, and is availabl e at this.options
\t\tinitialize: function(data){

\t\t\t//Declare segments as a static property to prevent inheriting across the Chart type prototype
\t\t\tthis.segments = [];
\t\t\tthis.outerRadius = (helpers.min([this.chart.width,this.chart.height]) -\tthis.options.segmentStrokeWidth/2)/2;

\t\t\tthis.SegmentArc = Chart.Arc.extend({
\t\t\t\tctx : this.chart.ctx,
\t\t\t\tx : this.chart.width/2,
\t\t\t\ty : this.chart.height/2
\t\t\t});

\t\t\t//Set up tooltip events on the chart
\t\t\tif (this.options.showTooltips){
\t\t\t\thelpers.bindEvents(this, this.options.tooltipEvents, function(evt){
\t\t\t\t\tvar activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];

\t\t\t\t\thelpers.each(this.segments,function(segment){
\t\t\t\t\t\tsegment.restore([\"fillColor\"]);
\t\t\t\t\t});
\t\t\t\t\thelpers.each(activeSegments,function(activeSegment){
\t\t\t\t\t\tactiveSegment.fillColor = activeSegment.highlightColor;
\t\t\t\t\t});
\t\t\t\t\t this.showTooltip(activeSegments);
\t\t\t\t});
\t\t\t}
\t\t\tthis.calculateTotal(data);

\t\t\thelpers.each(data,function(datapoint, index){
\t\t\t\tthis.addData(datapoint, index, true);
\t\t\t},this);

\t\t\tthis.render();
\t\t},
\t\tgetSegmentsAtEvent : function(e){
\t\t\tvar segmentsArray = [];

\t\t\tvar location = helpers.getRelativePosition(e);

\t\t\thelpers.each(this.segments,function(segment){
\t\t\t\tif (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
\t\t\t},this);
\t\t\treturn segmentsArray;
\t\t},
\t\taddData : function(segment, atIndex, silent){
\t\t\tvar index = atIndex || this.segments.length;
\t\t\tthis.segments.splice(index, 0, new this.SegmentArc({
\t\t\t\tvalue : segment.value,
\t\t\t\touterRadius : (this.options.animateScale) ? 0 : this.outerRadius,
\t\t\t\tinnerRadius : (this.options.animateScale) ? 0 : (this.outerRadius/100) * this.options.percentageInnerCutout,
\t\t\t\tfillColor : segment.color,
\t\t\t\thighlightColor : segment.highlight || segment.color,
\t\t\t\tshowStroke : this.options.segmentShowStroke,
\t\t\t\tstrokeWidth : this.options.segmentStrokeWid th,
\t\t\t\tstrokeColor : this.options.segmentStrokeColor,
\t\t\t\tstartAngle : Math.PI * 1.5,
\t\t\t\tcircumference : (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value),
\t\t\t\tlabel : segment.label
\t\t\t}));
\t\t\tif (!silent){
\t\t\t\tthis.reflow();
\t\t\t\tthis.update();
\t\t\t}
\t\t},
\t\tcalculateCircumference : function(value){
\t\t\treturn (Math.PI*2)*(Math.abs(value) / this.total);
\t\t},
\t\tcalculateTotal : function(data){
\t\t\tthis.total = 0;
\t\t\thelpers.each(data,function(segment){
\t\t\t\tthis.total += Math.abs(segment.value);
\t\t\t},this);
\t\t},
\t\tupdate : function(){
\t\t\tthis.calculateTotal(this.segments);

\t\t\t// Reset any highlight colours before updating.
\t\t\thelpers.each(this.activeElements, function(activeElement){
\t\t\t\tactiveElement.restore(['fillColor']);
\t\t\t});

\t\t\thelpers.each(this.segments,function(segment){
\t\t\t\tsegment.save();
\t\t\t});
\t\t\tthis.render();
\t\t},

\t\tremoveData: function(atIndex){
\t\t\tvar indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
\t\t\tthis.segments.splice(indexToDelete, 1);
\t\t\tthis.reflow();
\t\t\tthis.update();
\t\t},

\t\treflow : function(){
\t\t\thelpers.extend(this.SegmentArc.prototype,{
\t\t\t\tx : this.chart.width/2,
\t\t\t\ty : this.chart.height/2
\t\t\t});
\t\t\tthis.outerRadius = (helpers.min([this.chart.width,this.chart.height]) -\tthis.options.segmentStrokeWidth/2)/2;
\t\t\thelpers.each(this.segments, function(segment){
\t\t\t\tsegment.update({
\t\t\t\t\touterRadius : this.outerRadius,
\t\t\t\t\tinnerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
\t\t\t\t});
\t\t\t}, this);
\t\t},
\t\tdraw : function(easeDecimal){
\t\t\tvar animDecimal = (easeDecimal) ? easeDecimal : 1;
\t\t\tthis.clear();
\t\t\thelpers.each(this.segments,function(segment,index){
\t\t\t\tsegment.transition({
\t\t\t\t\tcircumference : this.calculateCircumference(segment.value),
\t\t\t\t\touterRadius : this.outerRadius,
\t\t\t\t\tinnerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout
\t\t\t\t},animDecimal);

\t\t\t\tsegment.endAngle = segment.startAngle + segment.circumference;

\t\t\t\tsegment.draw();
\t\t\t\tif (index === 0){
\t\t\t\t\tsegment.startAngle = Math.PI * 1.5;
\t\t\t\t}
\t\t\t\t//Check to see if it's the last segment, if not get the next and update the start angle
\t\t\t\tif (index < this.segments.length-1){
\t\t\t\t\tthis.segments[index+1].startAngle = segment.endAngle;
\t\t\t\t}
\t\t\t},this);

\t\t}
\t});

\tChart.types.Doughnut.extend({
\t\tname : \"Pie\",
\t\tdefaults : helpers.merge(defaultConfig,{percentageInnerCutout : 0})
\t});

}).call(this);
(function(){
\t\"use strict\";

\tvar root = this,
\t\tChart = root.Chart,
\t\thelpers = Chart.helpers;

\tvar defaultConfig = {

\t\t///Boolean - Whether grid lines are shown across the chart
\t\tscaleShowGridLines : true,

\t\t//String - Colour of the grid lines
\t\tscaleGridLineColor : \"rgba(0,0,0,.05)\",

\t\t//Number - Width of the grid lines
\t\tscaleGridLineWidth : 1,

\t\t//Boolean - Whether to show horizontal lines (except X axis)
\t\tscaleShowHorizontalLines: true,

\t\t//Boolean - Whether to show vertical lines (except Y axis)
\t\tscaleShowVerticalLines: true,

\t\t//Boolean - Whether the line is curved between points
\t\tbezierCurve : true,

\t\t//Number - Tension of the bezier curve between points
\t\tbezierCurveTension : 0.4,

\t\t//Boolean - Whether to show a dot for each point
\t\tpointDot : true,

\t\t//Number - Radius of each point dot in pixels
\t\tpointDotRadius : 4,

\t\t//Number - Pixel width of point dot stroke
\t\tpointDotStrokeWidth : 1,

\t\t//Number - amount extra to add to the radius to cater for hit detection outside the drawn point
\t\tpointHitDetectionRadius : 20,

\t\t//Boolean - Whether to show a stroke for datasets
\t\tdatasetStroke : true,

\t\t//Number - Pixel width of dataset stroke
\t\tdatasetStrokeWidth : 2,
$ b$b \t\t//Boolean - Whether to fill the dataset with a colour
\t\tdatasetFill : true,

\t\t//String - A legend template
\t\tlegendTemplate : \"<ul class=\\"<%=name.toLowerCase()%>-legend\\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\\"background-color:<%=datasets[i].strokeColor%>\\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>\"

\t};


\tChart.Type.extend({
\t\tname: \"Line\",
\t\tdefaults : defaultConfig,
\t\tinitialize: function(data){
\t\t\t//Declare the extension of the default point, to cater for the options passed in to the constructor
\t\t\tthis.PointClass = Chart.Point.extend({
\t\t\t\tstrokeWidth : this.options.pointDotStrokeWidth,
\t\t\t\tradius : this.options.pointDotRadius,
\t\t\t\tdisplay: this.options.pointDot,
\t\t\t\thitDetectionRadius : this.options.pointHitDetectionRadius,
\t\t\t\tctx : this.chart.ctx,
\t\t\t\tinRange : function(mouseX){
\t\t\t\t\treturn (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2));
}
});

\t\t\tthis.datasets = [];

\t\t\t//Set up tooltip events on the chart
\t\t\tif (this.options.showTooltips){
\t\t\t\thelpers.bindEvents(this, this.options.tooltipEvents, function(evt){
\t\t\t\t\tvar activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];
\t\t\t\t\tthis.eachPoints(function(point){
\t\t\t\t\t\tpoint.restore(['fillColor', 'strokeColor']);
\t\t\t\t\t});
\t\t\t\t\thelpers.each(activePoints, function(activePoint){
\t\t\t\t\t\tactivePoint.fillColor = activePoint.highlightFill; $b$ b \t\t\t\t\t\tactivePoint.strokeColor = activePoint.highlightStroke;
});
\t\t\t\t\tthis.showTooltip(activePoints);
\t\t\t\t});
\t\t\t}

\t\t\t//Iterate through each of the datasets, and build this into a property of the chart
\t\t\thelpers.each(data.datasets,function(dataset){

\t\t\t\tvar datasetObject = {
\t\t\t\t\tlabel : dataset.label || null,
\t\t\t\t\tfillColor : dataset.fillColor,
\t\t\t\t\tstrokeColor : dataset.strokeColor,
\t\t\t\t\tpointColor : dataset.pointColor,
\t\t\t\t\tpointStrokeColor : dataset.pointStrokeColor,
\t\t\t\t\tpoints : []
\t\t\t\t};

\t\t\t\tthis.datasets.push(datasetObject);


\t\t\t\thelpers.each(dataset.data,function(dataPoint,index){
\t\t\t\t\t//Add a new point for each piece of data, passing any required data to draw.
\t\t\t\t\tdatasetObject.points.push(new this.PointClass({
\t\t\t\t\t\tvalue : dataPoint,
\t\t\t\t\t\tlabel : data.labels[index],
\t\t\t\t\t\tdatasetLabel: dataset.label,
\t\t\t\t\t\tstrokeColor : dataset.pointStrokeColor,
\t\t\t\t\t\tfillColor : dataset.pointColor,
\t\t\t\t\t\thighlightFill : dataset.pointHighlightFill || dataset.pointColor,
\t\t\t\t\t\thighlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
\t\t\t\t\t}));
\t\t\t\t},this);

\t\t\t\tthis.buildScale(data.labels);


\t\t\t\tthis.eachPoints(function(point, index){
\t\t\t\t\thelpers.extend(point, {
\t\t\t\t\t\tx: this.scale.calculateX(index),
\t\t\t\t\t\ty: this.scale.endPoint
\t\t\t\t\t});
\t\t\t\t\tpoint.save();
\t\t\t\t}, this);

\t\t\t},this);


\t\t\tthis.render();
\t\t},
\t\tupdate : function(){
\t\t\tthis.scale.update();
\t\t\t// Reset any highlight colours before updating.
\t\t\thelpers.each(this.activeElements, function(activeElement){
\t\t\t\tactiveElement.restore(['fillColor', 'strokeColor']);
\t\t\t});
\t\t\tthis.eachPoints(function(point){
\t\t\t\tpoint.save();
\t\t\t});
\t\t\tthis.render();
\t\t},
\t\teachPoints : function(callback){
\t\t\thelpers.each(this.datasets,function(dataset){
\t\t\t\thelpers.each(dataset.points,callback,this);
\t\t\t},this);
\t\t},
\t\tgetPointsAtEvent : function(e){
\t\t\tvar pointsArray = [],
\t\t\t\teventPosition = helpers.getRelativePosition(e);
\t\t\thelpers.each(this.datasets,function(dataset){
\t\t\t\thelpers.each(dataset.points,function(point){
\t\t\t\t\tif (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point);
\t\t\t\t});
\t\t\t},this);
\t\t\treturn pointsArray;
\t\t},
\t\tbuildScale : function(labels){
\t\t\tvar self = this;

\t\t\tvar dataTotal = function(){
\t\t\t\tvar values = [];
\t\t\t\tself.eachPoints(function(point){
\t\t\t\t\tvalues.push(point.value);
\t\t\t\t});

\t\t\t\treturn values;
\t\t\t};

\t\t\tvar scaleOptions = {
\t\t\t\ttemplateString : this.options.scaleLabel,
\t\t\t\theight : this.chart.height,
\t\t\t\twidth : this.chart.width,
\t\t\t\tctx : this.chart.ctx,
\t\t\t\ttextColor : this.options.scaleFontColor,
\t\t\t\tfontSize : this.options.scaleFontSize,
\t\t\t\tfontStyle : this.options.scaleFontStyle,
\t\t\t\tfontFamily : this.options.scaleFontFamily,
\t\t\t\tvaluesCount : labels.length,
\t\t\t\tbeginAtZero : this.options.scaleBeginAtZero,
\t\t\t\tintegersOnly : this.options.scaleIntegersOnly,
\t\t\t\tcalculateYRange : function(currentHeight){
\t\t\t\t\t var updatedRanges = helpers.calculateScaleRange(
\t\t\t\t\t\tdataTotal(),
\t\t\t\t\t\tcurrentHeight,
\t\t\t\t\t\tthis.fontSize,
\t\t\t\t\t\tthis.beginAtZero,
\t\t\t\t\t\tthis.integersOnly
\t\t\t\t\t);
\t\t\t\t\thelpers.extend(this, updatedRanges);
\t\t\t\t},
\t\t\t\txLabels : labels,
\t\t\t\tfont : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily),
\t\t\t\tlineWidth : this.options.scaleLineWidth,
\t\t\t\tlineColor : this.options.scaleLineColor,
\t\t\t\tshowHorizontalLines : this.options.scaleShowHorizontalLines,
\t\t\t\tshowVerticalLines : this.options.scaleShowVerticalLines,
\t\t\t\tgridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0,
\t\t\t\tgridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : \"rgba(0,0,0,0)\",
\t\t\t\tpadding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth,
\t\t\t\tshowLabels : this.options.scaleShowLabels,
\t\t\t\tdisplay : this.options.showScale
\t\t\t};

\t\t\tif (this.options.scaleOverride){
\t\t\t\thelpers.extend(scaleOptions, {
\t\t\t\t\tcalculateYRange: helpers.noop,
\t\t\t\t\tsteps: this.options.scaleSteps,
\t\t\t\t\tstepValue: this.options.scaleStepWidth,
\t\t\t\t\tmin: this.options.scaleStartValue,
\t\t\t\t\tmax: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
\t\t\t\t});
\t\t\t}


\t\t\tthis.scale = new Chart.Scale(scaleOptions);
\t\t},
\t\taddData : function(valuesArray,label){
\t\t\t//Map the values array for each of the datasets

\t\t\thelpers.each(valuesArray,function(value,datasetIndex){
\t\t\t\t//Add a new point for each piece of data, passing any required data to draw.
\t\t\t\tthis.datasets[datasetIndex].points.push(new this.PointClass({
\t\t\t\t\tvalue : value,
\t\t\t\t\tlabel : label,
\t\t\t\t\tx: this.scale.calculateX(this.scale.valuesCount+1),
\t\t\t\t\ty: this.scale.endPoint,
\t\t\t\t\tstrokeColor : this.da tasets[datasetIndex].pointStrokeColor,
\t\t\t\t\tfillColor : this.datasets[datasetIndex].pointColor
\t\t\t\t}));
\t\t\t},this);

\t\t\tthis.scale.addXLabel(label);
\t\t\t//Then re-render the chart.
\t\t\tthis.update();
\t\t},
\t\tremoveData : function(){
\t\t\tthis.scale.removeXLabel();
\t\t\t//Then re-render the chart.
\t\t\thelpers.each(this.datasets,function(dataset){
\t\t\t\tdataset.points.shift();
\t\t\t},this);
\t\t\tthis.update();
\t\t},
\t\treflow : function(){
\t\t\tvar newScaleProps = helpers.extend({
\t\t\t\theight : this.chart.height,
\t\t\t\twidth : this.chart.width
\t\t\t});
\t\t\tthis.scale.update(newScaleProps);
\t\t},
\t\tdraw : function(ease){
\t\t\tvar easingDecimal = ease || 1 ;
\t\t\tthis.clear();

\t\t\tvar ctx = this.chart.ctx;

\t\t\t// Some helper methods for getting the next/prev points
\t\t\tvar hasValue = function(item){
\t\t\t\treturn item.value !== null;
\t\t\t},
\t\t\tnextPoint = function(point, collection, index){
\t\t\t\treturn helpers.findNextWhere(collection, hasValue, index) ||点;
\t\t\t},
\t\t\tpreviousPoint = function(point, collection, index){
\t\t\t\treturn helpers.findPreviousWhere(collection, hasValue, index) ||点;
\t\t\t};

\t\t\tthis.scale.draw(easingDecimal);


\t\t\thelpers.each(this.datasets,function(dataset){
\t\t\t\tvar pointsWithValues = helpers.where(dataset.points, hasValue);

\t\t\t\t//Transition each point first so that the line and point drawing isn't out of sync
\t\t\t\t//We can use this extra loop to calculate the control points of this dataset also in this loop

\t\t\t\thelpers.each(dataset.points, function(point, index){
\t\t\t\t\tif (point.hasValue()){
\t\t\t\t\t\tpoint.transition({
\t\t\t\t\t\t\ty : this.scale.calculateY(point.value),
\t\t\t\t\t\t\tx : this.scale.calculateX(index)
\t\t\t\t\t\t}, easingDecimal);
\t\t\t\t\t}
\t\t\t\t},this);


\t\t\t\t// Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point
\t\t\t\t// This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed
\t\t\t\tif (this.options.bezierCurve){
\t\t\t\t\thelpers.each(pointsWithValues, function(point, index){
\t\t\t\t\t\tvar tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0;
\t\t\t\t\t\tpoint.controlPoints = helpers.splineCurve(
\t\t\t\t\t\t\tpreviousPoint(point, pointsWithValues, index),
\t\t\t\t\t\t\tpoint,
\t\t\t\t\t\t\tnextPoint(point, pointsWithValues, index),
\t\t\t\t\t\t\ttension
\t\t\t\t\t\t);

\t\t\t\t\t\t// Prevent the bezier going outside of the bounds of the graph

\t\t\t\t\t\t// Cap puter bezier handles to the upper/lower scale bounds
\t\t\t\t\t\tif (point.controlPoints.outer.y > this.scale.endPoint){
\t\t\t\t\t\t\tpoint.controlPoints.outer.y = this.scale.endPoint;
\t\t\t\t\t\t}
\t\t\t\t\t\telse if (point.controlPoints.outer.y < this.scale.startPoint){
\t\t\t\t\t\t\tpoint.controlPoints.outer.y = this.scale.startPoint;
\t\t\t\t\t\t}

\t\t\t\t\t\t// Cap inner bezier handles to the upper/lower scale bounds
\t\t\t\t\t\tif (point.controlPoints.inner.y > this.scale.endPoint){
\t\t\t\t\t\t\tpoint.controlPoints.inner.y = this.scale.endPoint;
\t\t\t\t\t\t}
\t\t\t\t\t\telse if (point.controlPoints.inner.y < this.scale.startPoint){
\t\t\t\t\t\t\tpoint.controlPoints.inner.y = this.scale.startPoint;
\t\t\t\t\t\t}
\t\t\t\t\t},this);
\t\t\t\t}


\t\t\t\t//Draw the line between all the points
\t\t\t\tctx.lineWidth = this.options.datasetStrokeWidth;
\t\t\t\tctx.strokeStyle = dataset.strokeColor;
\t\t\t\tctx.beginPath();

\t\t\t\thelpers.each(pointsWithValues, function(point, index){
\t\t\t\t\tif (index === 0){
\t\t\t\t\t\tctx.moveTo(point.x, point.y);
\t\t\t\t\t}
\t\t\t\t\telse{
\t\t\t\t\t\tif(this.options.bezierCurve){
\t\t\t\t\t\t\tvar previous = previousPoint(point, pointsWithValues, index);

\t\t\t\t\t\t\tctx.bezierCurveTo(
\t\t\t\t\t\t\t\tprevious.controlPoints.outer.x,
\t\t\t\t\t\t\t\tprevious.controlPoints.outer.y,
\t\t\t\t\t\t\t\tpoint.controlPoints.inner.x,
\t\t\t\t\t\t\t\tpoint.controlPoints.inner.y,
\t\t\t\t\t\t\t\tpoint.x,
\t\t\t\t\t\t\t\tpoint.y
\t\t\t\t\t\t\t);
\t\t\t\t\t\t}
\t\t\t\t\t\telse{
\t\t\t\t\t\t\tctx.lineTo(point.x,point.y);
\t\t\t\t\t\t}
\t\t\t\t\t}
\t\t\t\t}, this);

\t\t\t\tctx.stroke();

\t\t\t\tif (this.options.datasetFill && pointsWithValues.length > 0){
\t\t\t\t\t//Round off the line by going to the base of the chart, back to the start, then fill.
\t\t\t\t\tctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint);
\t\t\t\t\tctx.lineTo(pointsWithValues[0].x, this.scale.endPoint);
\t\t\t\t\tctx.fillStyle = dataset.fillColor;
\t\t\t\t\tctx.closePath();
\t\t\t\t\tctx.fill();
\t\t\t\t}

\t\t\t\t//Now draw the points over the line
\t\t\t\t//A little inefficient double looping, but better than the line
\t\t\t\t//lagging behind the point positions
\t\t\t\thelpers.each(pointsWithValues,function(point){
\t\t\t\t\tpoint.draw();
\t\t\t\t});
\t\t\t},this);
}
});


}).call(this);

(function(){
\t\"use strict\";

\tvar root = this,
\t\tChart = root.Chart,
\t\t//Cache a local reference to Chart.helpers
\t\thelpers = Chart.helpers;

\tvar defaultConfig = {
\t\t//Boolean - Show a backdrop to the scale label
\t\tscaleShowLabelBackdrop : true,

\t\t//String - The colour of the label backdrop
\t\tscaleBackdropColor : \"rgba(255,255,255,0.75)\",

\t\t// Boolean - Whether the scale shou ld begin at zero
\t\tscaleBeginAtZero : true,

\t\t//Number - The backdrop padding above & below the label in pixels
\t\tscaleBackdropPaddingY : 2,

\t\t//Number - The backdrop padding to the side of the label in pixels
\t\tscaleBackdropPaddingX : 2,

\t\t//Boolean - Show line for each value in the scale
\t\tscaleShowLine : true,

\t\t//Boolean - Stroke a line around each segment in the chart
\t\tsegmentShowStroke : true,

\t\t//String - The colour of the stroke on each segement.
\t\tsegmentStrokeColor : \"#fff\",

\t\t// Number - The width of the stroke value in pixels
\t\tsegmentStrokeWidth : 2,

\t\t//Number - Amount of animation steps
\t\tanimationSteps : 100,

\t\t//String - Animation easing effect.
\t\tanimationEasing : \"easeOutBounce\",

\t\t//Boolean - Whether to animate the rotation of the chart
\t\tanimateRotate : true,

\t\t//Boolean - Whether to animate scaling the chart from the centre
\t\tanimateScale : false,

\t\t //String - A legend template
\t\tlegendTemplate : \"<ul class=\\"<%=name.toLowerCase()%>-legend\\"><% for (var i=0; i<segments.length; i++){%><li><span style=\\"background-color:<%=segments[i].fillColor%>\\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>\"
\t};


\tChart.Type.extend({
\t\t//Passing in a name registers this chart in the Chart namespace
\t\tname: \"PolarArea\",
\t\t//Providing a defaults will also register the deafults in the chart namespace
\t\tdefaults : defaultConfig,
\t\t//Initialize is fired when the chart is initialized - Data is passed in as a parameter
\t\t//Config is automatically merged by the core of Chart.js, and is available at this.options
\t\tinitialize: function(data){
\t\t\tthis.segments = [];
\t\t\t//Declare segment class as a chart instance specific class, so it can share props for this instance
\t\t\tthis.SegmentArc = Chart.Arc.extend({
\t\t\t\tshowStroke : this.options.segmentShowStroke,
\t\t\t\tstrokeWidth : this.options.segmentStrokeWidth,
\t\t\t\tstrokeColor : this.options.segmentStrokeColor,
\t\t\t\tctx : this.chart.ctx,
\t\t\t\tinnerRadius : 0,
\t\t\t\tx : this.chart.width/2,
\t\t\t\ty : this.chart.height/2
\t\t\t});
\t\t\tthis.scale = new Chart.RadialScale({
\t\t\t\tdisplay: this.options.showScale,
\t\t\t\tfontStyle: this.options.scaleFontStyle,
\t\t\t\tfontSize: this.options.scaleFontSize,
\t\t\t\tfontFamily: this.options.scaleFontFamily,
\t\t\t\tfontColor: this.options.scaleFontColor,
\t\t\t\tshowLabels: this.options.scaleShowLabels,
\t\t\t\tshowLabelBackdrop: this.options.scaleShowLabelBackdrop,
\t\t\t\tbackdropColor: this.options.scaleBackdropColor,
\t\t\t\tbackdropPaddingY : this.options.scaleBackdropPaddingY,
\t\t\t\tbackdropPaddingX: this.options.scaleBackdropPaddingX,
\t\t\t\tlineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
\t\t\t\tlineColor: this.options.scaleLineColor,
\t\t\t\tlineArc: true,
\t\t\t\twidth: this.chart.width,
\t\t\t\theight: this.chart.height,
\t\t\t\txCenter: this.chart.width/2,
\t\t\t\tyCenter: this.chart.height/2,
\t\t\t\tctx : this.chart.ctx,
\t\t\t\ttemplateString: this.options.scaleLabel,
\t\t\t\tvaluesCount: data.length
\t\t\t});

\t\t\tthis.updateScaleRange(data);

\t\t\tthis.scale.update();

\t\t\thelpers.each(data,function(segment,index){
\t\t\t\tthis.addData(segment,index,true);
\t\t\t},this);

\t\t\t//Set up tooltip events on the chart
\t\t\tif (this.options.showTooltips){
\t\t\t\thelpers.bindEvents(this, this.options.tooltipEvents, function(evt){
\t\t\t\t\tvar activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : [];
\t\t\t\t\thelpers.each(this.segments,function(segment){
\t\t\t\t\t\tsegment.restore([\"fillColor\"]);
\t\t\t\t\t});
\t\t\t\t\thelpers.each(activeSegments,function(activeSegment){
\t\t\t\t\t\tactiveSegment.fillColor = activeSegment.highlightColor;
\t\t\t\t\t});
\t\t\t\t\tthis.showTooltip(activeSegments);
\t\t\t\t});
\t\t\t}

\t\t\tthis.render();
\t\t},
\t\tgetSegmentsAtEvent : function(e){
\t\t\tvar segmentsArray = [];

\t\t\tvar location = helpers.getRelativePosition(e);

\t\t\thelpers.each(this.segments,function(segment){
\t\t\t\tif (segment.inRange(location.x,location.y)) segmentsArray.push(segment);
\t\t\t},this);
\t\t\treturn segmentsArray;
\t\t},
\t\taddData : function(segment, atIndex, silent){
\t\t\tvar index = atIndex || this.segments.length;

\t\t\tthis.segments.splice(index, 0, new this.SegmentArc({
\t\t\t\tfillColor: segment.color,
\t\t\t\thighlightColor: segment.highlight || segment.color,
\t\t\t\tlabel: segment.label,
\t\t\t\tvalue: segment.value,
\t\t\t\touterRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value),
\t\t\t\tcircumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(),
\t\t\t\tstartAngle: Math.PI * 1.5
\t\t\t}));
\t\t\tif (!silent){
\t\t\t\tthis.reflow();
\t\t\t\tthis.update();
\t\t\t}
\t\t},
\t\tremoveData: function(atIndex){
\t\t\tvar indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1;
\t\t\tthis.segments.splice(indexToDelete, 1);
\t\t\tthis.reflow();
\t\t\tthis.update();
\t\t},
\t\tcalculateTotal: function(data){
\t\t\tthis.total = 0;
\t\t\thelpers.each(data,function(segment){
\t\t\t\tthis.total += segment.value;
\t\t\t},this);
\t\t\tthis.scale.valuesCount = this.segments.length;
\t\t},
\t\tupdateScaleRange: function(datapoints){
\t\t\tvar valuesArray = [];
\t\t\thelpers.each(datapoints,function(segment){
\t\t\t\tvaluesArray.push(segment.value);
\t\t\t});

\t\t\tvar scaleSizes = (this.options.scaleOverride) ?
\t\t\t\t{
\t\t\t\t\tsteps: this.options.scaleSteps,
\t\t\t\t\tstepValue: this.options.scaleStepWidth,
\t\t\t\t\tmin: this.options.scaleStartValue,
\t\t\t\t\tmax: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
\t\t\t\t} :
\t\t\t\thelpers.calculateScaleRange(
\t\t\t\t\tvaluesArray,
\t\t\t\t\thelpers.min([this.chart.width, this.chart.height])/2,
\t\t\t\t\tthis.options.scaleFontSize,
\t\t\t\t\tthis.options.scaleBeginAtZero,
\t\t\t\t\tthis.options.scaleIntegersOnly
\t\t\t\t);

\t\t\thelpers.extend(
\t\t\t\tthis.scale,
\t\t\t\tscaleSizes,
\t\t\t\t{
\t\t\t\t\tsize: helpers.min([this.chart.width, this.chart.height]),
\t\t\t\t\txCenter: this.chart.width/2,
\t\t\t\t\tyCenter: this.chart.height/2
\t\t\t\t}
\t\t\t);

\t\t},
\t\tupdate : function(){
\t\t\tthis.calculateTotal(this.segments);

\t\t\thelpers.each(this.segments,function(segment){
\t\t\t\tsegment.save();
\t\t\t});
\t\t\t
\t\t\tthis.reflow();
\t\t\tthis.render();
\t\t},
\t\treflow : function(){
\t\t\thelpers.extend(this.SegmentArc.prototype,{
\t\t\t\tx : this.chart.width/2,
\t\t\t\ty : this.chart.height/2
\t\t\t});
\t\t\tthis.updateScaleRange(this.segments);
\t\t\tthis.scale.update();

\t\t\thelpers.extend(this.scale,{
\t\t\t\txCenter: this.chart.width/2,
\t\t\t\tyCenter: this.chart.height/2
\t\t\t});

\t\t\thelpers.each(this.segments, function(segment){
\t\t\t\tsegment.update({
\t\t\t\t\touterRadius : this.scale.calculateCenterOffset(segment.value)
\t\t\t\t});
\t\t\t}, this);

\t\t},
\t\tdraw : function(ease){
\t\t\tvar easingDecimal = ease || 1 ;
\t\t\t//Clear & draw the canvas
\t\t\tthis.clear();
\t\t\thelpers.each(this.segments,function(segment, index){
\t\t\t\tsegment.transition({
\t\t\t\t\tcircumference : this.scale.getCircumference(),
\t\t\t\t\touterRadius : this.scale.calculateCenterOffset(segment.value)
\t\t\t\t},easingDecimal);

\t\t\t\tsegment.endAngle = segment.startAngle + segment.circumference;

\t\t\t\t// If we've removed the first segment we need to set the first one to
\t\t\t\t// start at the top.
\t\t\t\tif (index === 0){
\t\t\t\t\tsegment.startAngle = Math.PI * 1.5;
\t\t\t\t}

\t\t\t\t//Check to see if it's the last segment, if not get the next and update the start angle
\t\t\t\tif (index < this.segments.length - 1){
\t\t\t\t\tthis.segments[index+1].startAngle = segment.endAngle;
\t\t\t\t}
\t\t\t\tsegment.draw();
\t\t\t}, this);
\t\t\tthis.scale.draw();
}
});

}).call(this);
(function(){
\t\"use strict\";

\tvar root = this,
\t\tChart = root.Chart,
\t\thelpers = Chart.helpers;



\tChart.Type.extend({
\t\tname: \"Radar\",
\t\tdefaults:{
\t\t\t//Boolean - Whether to show lines for each scale point
\t\t\tscaleShowLine : true,

\t\t\t//Boolean - Whether we show the angle lines out of the radar
\t\t\tangleShowLineOut : true,

\t\t\t//Boolean - Whether to show labels on the scale
\t\t\tscaleShowLabels : fa lse,

\t\t\t// Boolean - Whether the scale should begin at zero
\t\t\tscaleBeginAtZero : true,

\t\t\t//String - Colour of the angle line
\t\t\tangleLineColor : \"rgba(0,0,0,.1)\",

\t\t\t//Number - Pixel width of the angle line
\t\t\tangleLineWidth : 1,

\t\t\t//String - Point label font declaration
\t\t\tpointLabelFontFamily : \"'Arial'\",

\t\t\t//String - Point label font weight
\t\t\tpointLabelFontStyle : \"normal\",

\t\t\t//Number - Point label font size in pixels
\t\t\tpointLabelFontSize : 10,

\t\t\t//String - Point label font colour
\t\t\tpointLabelFontColor : \"#666\",

\t\t\t//Boolean - Whether to show a dot for each point
\t\t\tpointDot : true,

\t\t\t//Number - Radius of each point dot in pixels
\t\t\tpointDotRadius : 3,

\t\t\t//Number - Pixel width of point dot stroke
\t\t\tpo intDotStrokeWidth : 1,

\t\t\t//Number - amount extra to add to the radius to cater for hit detection outside the drawn point
\t\t\tpointHitDetectionRadius : 20,

\t\t\t//Boolean - Whether to show a stroke for datasets
\t\t\tdatasetStroke : true,

\t\t\t//Number - Pixel width of dataset stroke
\t\t\tdatasetStrokeWidth : 2,

\t\t\t//Boolean - Whether to fill the dataset with a colour
\t\t\tdatasetFill : true,

\t\t\t//String - A legend template
\t\t\tlegendTemplate : \"<ul class=\\"<%=name.toLowerCase()%>-legend\\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\\"background-color:<%=datasets[i].strokeColor%>\\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>\"

\t\t},

\t\tinitialize: function(data){
\t\t\tthis.PointClass = Chart.Point.extend({
\t\t\t\tstrokeWidth : this.options.pointDotStrokeWidth,
\t\t\t\tradius : this.options.pointDotRadius,
\t\t\t\tdisplay: this.options.pointDot,
\t\t\t\thitDetectionRadius : this.options.pointHitDetectionRadius,
\t\t\t\tctx : this.chart.ctx
\t\t\t});

\t\t\tthis.datasets = [];

\t\t\tthis.buildScale(data);

\t\t\t//Set up tooltip events on the chart
\t\t\tif (this.options.showTooltips){
\t\t\t\thelpers.bindEvents(this, this.options.tooltipEvents, function(evt){
\t\t\t\t\tvar activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : [];

\t\t\t\t\tthis.eachPoints(function(point){
\t\t\t\t\t\tpoint.restore(['fillColor', 'strokeColor']);
\t\t\t\t\t});
\t\t\t\t\thelpers.each(activePointsCollection, function(activePoint){
\t\t\t\t\t\tactivePoint.fillColor = activePoint.highlightFill;
\t\t\t\t\t\tactivePoint.strokeColor = activePoint.highlightStroke;
\t\t\t\t\t});

\t\t\t\t\tthis.showTooltip(activePointsCollection);
\t\t\t\t});
\t\t\t}

\t\t\t//Iterate through each of the datasets, and build this into a property of the chart
\t\t\thelpers.each(data.datasets,function(dataset){

\t\t\t\tvar datasetObject = {
\t\t\t\t\tlabel: dataset.label || null,
\t\t\t\t\tfillColor : dataset.fillColor,
\t\t\t\t\tstrokeColor : dataset.strokeColor,
\t\t\t\t\tpointColor : dataset.pointColor,
\t\t\t\t\tpointStrokeColor : dataset.pointStrokeColor,
\t\t\t\t\tpoints : []
\t\t\t\t};

\t\t\t\tthis.datasets.push(datasetObject);

\t\t\t\thelpers.each(dataset.data,function(dataPoint,index){
\t\t\t\t\t//Add a new point for each piece of data, passing any required data to draw.
\t\t\t\t\tvar pointPosition;
\t\t\t\t\t if (!this.scale.animation){
\t\t\t\t\t\tpointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint));
\t\t\t\t\t}
\t\t\t\t\tdatasetObject.points.push(new this.PointClass({
\t\t\t\t\t\tvalue : dataPoint,
\t\t\t\t\t\tlabel : data.labels[index],
\t\t\t\t\t\tdatasetLabel: dataset.label,
\t\t\t\t\t\tx: (this.options.animation) ? this.scale.xCenter : pointPosition.x,
\t\t\t\t\t\ty: (this.options.animation) ? this.scale.yCenter : pointPosition.y,
\t\t\t\t\t\tstrokeColor : dataset.pointStrokeColor,
\t\t\t\t\t\tfillColor : dataset.pointColor,
\t\t\t\t\t\thighlightFill : dataset.pointHighlightFill || dataset.pointColor,
\t\t\t\t\t\thighlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor
\t\t\t\t\t}));
\t\t\t\t},this);

\t\t\t},this);

\t\t\tthis.render();
\t\t},
\t\teachPoints : function(callback){
\t\t\thelpers.each(this.datasets,function(dataset){
\t\t\t\thelpers.each(dataset.points,callback,this);
\t\t\t},this);
\t\t},

\t\tgetPointsAtEvent : function(evt){
\t\t\tvar mousePosition = helpers.getRelativePosition(evt),
\t\t\t\tfromCenter = helpers.getAngleFromPoint({
\t\t\t\t\tx: this.scale.xCenter,
\t\t\t\t\ty: this.scale.yCenter
\t\t\t\t}, mousePosition);

\t\t\tvar anglePerIndex = (Math.PI * 2) /this.scale.valuesCount,
\t\t\t\tpointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex),
\t\t\t\tactivePointsCollection = [];

\t\t\t// If we're at the top, make the pointIndex 0 to get the first of the array.
\t\t\tif (pointIndex >= this.scale.valuesCount || pointIndex < 0){
\t\t\t\tpointIndex = 0;
\t\t\t}

\t\t\tif (fromCenter.distance <= this.scale.drawingArea){
\t\t\t\thelpers.each(this.datasets, function(dataset){
\t\t\t\t\tactivePointsCollection.push(dataset.points[pointIndex]);
\t\t\t\t});
\t\t\t}

\t\t\treturn activePointsCollection;
\t\t},

\t\tbuildScale : function(data){
\t\t\tthis.scale = new Chart.RadialScale({
\t\t\t\tdisplay: this.options.showScale,
\t\t\t\tfontStyle: this.options.scaleFontStyle,
\t\t\t\tfontSize: this.options.scaleFontSize,
\t\t\t\tfontFamily: this.options.scaleFontFamily,
\t\t\t\tfontColor: this.options.scaleFontColor,
\t\t\t\tshowLabels: this.options.scaleShowLabels,
\t\t\t\tshowLabelBackdrop: this.options.scaleShowLabelBackdrop,
\t\t\t\tbackdropColor: this.options.scaleBackdropColor,
\t\t\t\tbackdropPaddingY : this.options.scaleBackdropPaddingY,
\t\t\t\tbackdropPaddingX: this.options.scaleBackdropPaddingX,
\t\t\t\tlineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0,
\t\t\t\tlineColor: this.options.scaleLineColor,
\t\t\t\tangleLineColor : this.options.angleLineColor,
\t\t\t\tangleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0,
\t\t\t\t// Point labels at the edge of each line
\t\t\t\tpointLabelFontColor : this.options.pointLabelFontColor,
\t\t\t\tpointLabelFontSize : this.options.pointLabelFontSize,
\t\t\t\tpointLabelFontFamily : this.options.pointLabelFontFamily,
\t\t\t\tpointLabelFontStyle : this.options.pointLabelFontStyle,
\t\t\t\theight : this.chart.height,
\t\t\t\twidth: this.chart.width,
\t\t\t\txCenter: this.chart.width/2,
\t\t\t\tyCenter: this.chart.height/2,
\t\t\t\tctx : this.chart.ctx,
\t\t\t\ttemplateString: this.options.scaleLabel,
\t\t\t\tlabels: data.labels,
\t\t\t\tvaluesCount: data.datasets[0].data.length
\t\t\t});

\t\t\tthis.scale.setScaleSize();
\t\t\tthis.updateScaleRange(data.datasets);
\t\t\tthis.scale.buildYLabels();
\t\t},
\t\tupdateScaleRange: function(datasets){
\t\t\tvar valuesArray = (function(){
\t\t\t\tvar totalDataArray = [];
\t\t\t\thelpers.each(datasets,function(dataset){
\t\t\t\t\tif (dataset.data){
\t\t\t\t\t\ttotalDataArray = totalDataArray.concat(dataset.data);
\t\t\t\t\t}
\t\t\t\t\telse {
\t\t\t\t\t\thelpers.each(dataset.points, function(point){
\t\t\t\t\t\t\ttotalDataArray.push(point.value);
\t\t\t\t\t\t});
\t\t\t\t\t}
\t\t\t\t});
\t\t\t\treturn totalDataArray;
\t\t\t})();


\t\t\tvar scaleSizes = (this.options.scaleOverride) ?
\t\t\t\t{
\t\t\t\t\tsteps: this.options.scaleSteps,
\t\t\t\t\tstepValue: this.options.scaleStepWidth,
\t\t\t\t\tmin: this.options.scaleStartValue,
\t\t\t\t\tmax: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth)
\t\t\t\t} :
\t\t\t\thelpers.calculateScaleRange(
\t\t\t\t\tvaluesArray,
\t\t\t\t\thelpers.min([this.chart.width, this.chart.height])/2,
\t\t\t\t\tthis.options.scaleFontSize,
\t\t\t\t\tthis.options.scaleBeginAtZero,
\t\t\t\t\tthis.options.scaleIntegersOnly
\t\t\t\t);

\t\t\thelpers.extend(
\t\t\t\tthis.scale,
\t\t\t\tscaleSizes
\t\t\t);

\t\t},
\t\taddData : function(valuesArray,label){
\t\t\t//Map the values array for each of the datasets
\t\t\tthis.scale.valuesCount++;
\t\t\thelpers.each(valuesArray,function(value,datasetIndex){
\t\t\t\tvar pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value));
\t\t\t\tthis.datasets[datasetIndex].points.push(new this.PointClass({
\t\t\t\t\tvalue : value,
\t\t\t\t\tlabel : label,
\t\t\t\t\tx: pointPosition.x,
\t\t\t\t\ty: pointPosition.y,
\t\t\t\t\tstrokeColor : this.datasets[datasetIndex].pointStrokeColor,
\t\t\t\t\tfillColor : this.datasets[datasetIndex].pointColor
\t\t\t\t}));
\t\t\t},this);

\t\t\tthis.scale.labels.push(label);

\t\t\tthis.reflow();

\t\t\tthis.update();
\t\t},
\t\tremoveData : function(){
\t\t\tthis.scale.valuesCount--;
\t\t\tthis.scale.labels.shift();
\t\t\thelpers.each(this.datasets,function(dataset){
\t\t\t\tdataset.points.shift();
\t\t\t},this);
\t\t\tthis.reflow();
\t\t\tthis.update();
\t\t},
\t\tupdate : function(){
\t\t\tthis.eachPoints(function(point){
\t\t\t\tpoint.save();
\t\t\t});
\t\t\tthis.reflow();
\t\t\tthis.render();
\t\t},
\t\treflow: function(){
\t\t\thelpers.extend(this.scale, {
\t\t\t\twidth : this.chart.width,
\t\t\t\theight: this.chart.height,
\t\t\t\tsize : helpers.min([this.chart.width, this.chart.height]),
\t\t\t\txCenter: this.chart.width/2,
\t\t\t\tyCenter: this.chart.height/2
\t\t\t});
\t\t\tthis.updateScaleRange(this.datasets);
\t\t\tthis.scale.setScaleSize();
\t\t\tthis.scale.buildYLabels();
\t\t},
\t\tdraw : function(ease){
\t\t\tvar easeDecimal = ease || 1,
\t\t\t\tctx = this.chart.ctx;
\t\t\tthis.clear();
\t\t\tthis.scale.draw();

\t\t\thelpers.each(this.datasets,function(dataset){

\t\t\t\t//Transition each point first so that the line and point drawing isn't out of sync
\t\t\t\thelpers.each(dataset.points,function(point,index){
\t\t\t\t\tif (point.hasValue()){
\t\t\t\t\t\tpoint.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal);
\t\t\t\t\t}
\t\t\t\t},this);



\t\t\t\t//Draw the line between all the points
\t\t\t\tctx.lineWidth = this.options.datasetStrokeWidth;
\t\t\t\tctx.strokeStyle = dataset.strokeColor;
\t\t\t\tctx.beginPath();
\t\t\t\thelpers.ea ch(dataset.points,function(point,index){
\t\t\t\t\tif (index === 0){
\t\t\t\t\t\tctx.moveTo(point.x,point.y);
\t\t\t\t\t}
\t\t\t\t\telse{
\t\t\t\t\t\tctx.lineTo(point.x,point.y);
\t\t\t\t\t}
\t\t\t\t},this);
\t\t\t\tctx.closePath();
\t\t\t\tctx.stroke();

\t\t\t\tctx.fillStyle = dataset.fillColor;
\t\t\t\tctx.fill();

\t\t\t\t//Now draw the points over the line
\t\t\t\t//A little inefficient double looping, but better than the line
\t\t\t\t//lagging behind the point positions
\t\t\t\thelpers.each(dataset.points,function(point){
\t\t\t\t\tif (point.hasValue()){
\t\t\t\t\t\tpoint.draw();
\t\t\t\t\t}
\t\t\t\t});

\t\t\t},this);

\t\t}

\t});





}).call(this);
1,'") .split("\t").join("');") .split("%>").join("p.push('") .split("\r").join("\\'") + "');}return p.join('');" ); // Provide some basic currying to the user return data ? fn( data ) : fn; } return tmpl(templateString,valuesObject); }, /* jshint ignore:end */ generateLabels = helpers.generateLabels = function(templateString,numberOfSteps,graphMin,stepValue){ var labelsArray = new Array(numberOfSteps); if (labelTemplateString){ each(labelsArray,function(val,index){ labelsArray[index] = template(templateString,{value: (graphMin + (stepValue*(index+1)))}); }); } return labelsArray; }, //--Animation methods //Easing functions adapted from Robert Penner's easing equations //http://www.robertpenner.com/easing/ easingEffects = helpers.easingEffects = { linear: function (t) { return t; }, easeInQuad: function (t) { return t * t; }, easeOutQuad: function (t) { return -1 * t * (t - 2); }, easeInOutQuad: function (t) { if ((t /= 1 / 2) < 1) return 1 / 2 * t * t; return -1 / 2 * ((--t) * (t - 2) - 1); }, easeInCubic: function (t) { return t * t * t; }, easeOutCubic: function (t) { return 1 * ((t = t / 1 - 1) * t * t + 1); }, easeInOutCubic: function (t) { if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t; return 1 / 2 * ((t -= 2) * t * t + 2); }, easeInQuart: function (t) { return t * t * t * t; }, easeOutQuart: function (t) { return -1 * ((t = t / 1 - 1) * t * t * t - 1); }, easeInOutQuart: function (t) { if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t; return -1 / 2 * ((t -= 2) * t * t * t - 2); }, easeInQuint: function (t) { return 1 * (t /= 1) * t * t * t * t; }, easeOutQuint: function (t) { return 1 * ((t = t / 1 - 1) * t * t * t * t + 1); }, easeInOutQuint: function (t) { if ((t /= 1 / 2) < 1) return 1 / 2 * t * t * t * t * t; return 1 / 2 * ((t -= 2) * t * t * t * t + 2); }, easeInSine: function (t) { return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1; }, easeOutSine: function (t) { return 1 * Math.sin(t / 1 * (Math.PI / 2)); }, easeInOutSine: function (t) { return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1); }, easeInExpo: function (t) { return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1)); }, easeOutExpo: function (t) { return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1); }, easeInOutExpo: function (t) { if (t === 0) return 0; if (t === 1) return 1; if ((t /= 1 / 2) < 1) return 1 / 2 * Math.pow(2, 10 * (t - 1)); return 1 / 2 * (-Math.pow(2, -10 * --t) + 2); }, easeInCirc: function (t) { if (t >= 1) return t; return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1); }, easeOutCirc: function (t) { return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t); }, easeInOutCirc: function (t) { if ((t /= 1 / 2) < 1) return -1 / 2 * (Math.sqrt(1 - t * t) - 1); return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1); }, easeInElastic: function (t) { var s = 1.70158; var p = 0; var a = 1; if (t === 0) return 0; if ((t /= 1) == 1) return 1; if (!p) p = 1 * 0.3; if (a < Math.abs(1)) { a = 1; s = p / 4; } else s = p / (2 * Math.PI) * Math.asin(1 / a); return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); }, easeOutElastic: function (t) { var s = 1.70158; var p = 0; var a = 1; if (t === 0) return 0; if ((t /= 1) == 1) return 1; if (!p) p = 1 * 0.3; if (a < Math.abs(1)) { a = 1; s = p / 4; } else s = p / (2 * Math.PI) * Math.asin(1 / a); return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1; }, easeInOutElastic: function (t) { var s = 1.70158; var p = 0; var a = 1; if (t === 0) return 0; if ((t /= 1 / 2) == 2) return 1; if (!p) p = 1 * (0.3 * 1.5); if (a < Math.abs(1)) { a = 1; s = p / 4; } else s = p / (2 * Math.PI) * Math.asin(1 / a); if (t < 1) return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1; }, easeInBack: function (t) { var s = 1.70158; return 1 * (t /= 1) * t * ((s + 1) * t - s); }, easeOutBack: function (t) { var s = 1.70158; return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1); }, easeInOutBack: function (t) { var s = 1.70158; if ((t /= 1 / 2) < 1) return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)); return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); }, easeInBounce: function (t) { return 1 - easingEffects.easeOutBounce(1 - t); }, easeOutBounce: function (t) { if ((t /= 1) < (1 / 2.75)) { return 1 * (7.5625 * t * t); } else if (t < (2 / 2.75)) { return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75); } else if (t < (2.5 / 2.75)) { return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375); } else { return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375); } }, easeInOutBounce: function (t) { if (t < 1 / 2) return easingEffects.easeInBounce(t * 2) * 0.5; return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5; } }, //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ requestAnimFrame = helpers.requestAnimFrame = (function(){ return window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.mozRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || function(callback) { return window.setTimeout(callback, 1000 / 60); }; })(), cancelAnimFrame = helpers.cancelAnimFrame = (function(){ return window.cancelAnimationFrame || window.webkitCancelAnimationFrame || window.mozCancelAnimationFrame || window.oCancelAnimationFrame || window.msCancelAnimationFrame || function(callback) { return window.clearTimeout(callback, 1000 / 60); }; })(), animationLoop = helpers.animationLoop = function(callback,totalSteps,easingString,onProgress,onComplete,chartInstance){ var currentStep = 0, easingFunction = easingEffects[easingString] || easingEffects.linear; var animationFrame = function(){ currentStep++; var stepDecimal = currentStep/totalSteps; var easeDecimal = easingFunction(stepDecimal); callback.call(chartInstance,easeDecimal,stepDecimal, currentStep); onProgress.call(chartInstance,easeDecimal,stepDecimal); if (currentStep < totalSteps){ chartInstance.animationFrame = requestAnimFrame(animationFrame); } else{ onComplete.apply(chartInstance); } }; requestAnimFrame(animationFrame); }, //-- DOM methods getRelativePosition = helpers.getRelativePosition = function(evt){ var mouseX, mouseY; var e = evt.originalEvent || evt, canvas = evt.currentTarget || evt.srcElement, boundingRect = canvas.getBoundingClientRect(); if (e.touches){ mouseX = e.touches[0].clientX - boundingRect.left; mouseY = e.touches[0].clientY - boundingRect.top; } else{ mouseX = e.clientX - boundingRect.left; mouseY = e.clientY - boundingRect.top; } return { x : mouseX, y : mouseY }; }, addEvent = helpers.addEvent = function(node,eventType,method){ if (node.addEventListener){ node.addEventListener(eventType,method); } else if (node.attachEvent){ node.attachEvent("on"+eventType, method); } else { node["on"+eventType] = method; } }, removeEvent = helpers.removeEvent = function(node, eventType, handler){ if (node.removeEventListener){ node.removeEventListener(eventType, handler, false); } else if (node.detachEvent){ node.detachEvent("on"+eventType,handler); } else{ node["on" + eventType] = noop; } }, bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler){ // Create the events object if it's not already present if (!chartInstance.events) chartInstance.events = {}; each(arrayOfEvents,function(eventName){ chartInstance.events[eventName] = function(){ handler.apply(chartInstance, arguments); }; addEvent(chartInstance.chart.canvas,eventName,chartInstance.events[eventName]); }); }, unbindEvents = helpers.unbindEvents = function (chartInstance, arrayOfEvents) { each(arrayOfEvents, function(handler,eventName){ removeEvent(chartInstance.chart.canvas, eventName, handler); }); }, getMaximumWidth = helpers.getMaximumWidth = function(domNode){ var container = domNode.parentNode; // TODO = check cross browser stuff with this. return container.clientWidth; }, getMaximumHeight = helpers.getMaximumHeight = function(domNode){ var container = domNode.parentNode; // TODO = check cross browser stuff with this. return container.clientHeight; }, getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support retinaScale = helpers.retinaScale = function(chart){ var ctx = chart.ctx, width = chart.canvas.width, height = chart.canvas.height; if (window.devicePixelRatio) { ctx.canvas.style.width = width + "px"; ctx.canvas.style.height = height + "px"; ctx.canvas.height = height * window.devicePixelRatio; ctx.canvas.width = width * window.devicePixelRatio; ctx.scale(window.devicePixelRatio, window.devicePixelRatio); } }, //-- Canvas methods clear = helpers.clear = function(chart){ chart.ctx.clearRect(0,0,chart.width,chart.height); }, fontString = helpers.fontString = function(pixelSize,fontStyle,fontFamily){ return fontStyle + " " + pixelSize+"px " + fontFamily; }, longestText = helpers.longestText = function(ctx,font,arrayOfStrings){ ctx.font = font; var longest = 0; each(arrayOfStrings,function(string){ var textWidth = ctx.measureText(string).width; longest = (textWidth > longest) ? textWidth : longest; }); return longest; }, drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx,x,y,width,height,radius){ ctx.beginPath(); ctx.moveTo(x + radius, y); ctx.lineTo(x + width - radius, y); ctx.quadraticCurveTo(x + width, y, x + width, y + radius); ctx.lineTo(x + width, y + height - radius); ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); ctx.lineTo(x + radius, y + height); ctx.quadraticCurveTo(x, y + height, x, y + height - radius); ctx.lineTo(x, y + radius); ctx.quadraticCurveTo(x, y, x + radius, y); ctx.closePath(); }; //Store a reference to each instance - allowing us to globally resize chart instances on window resize. //Destroy method on the chart will remove the instance of the chart from this reference. Chart.instances = {}; Chart.Type = function(data,options,chart){ this.options = options; this.chart = chart; this.id = uid(); //Add the chart instance to the global namespace Chart.instances[this.id] = this; // Initialize is always called when a chart type is created // By default it is a no op, but it should be extended if (options.responsive){ this.resize(); } this.initialize.call(this,data); }; //Core methods that'll be a part of every chart type extend(Chart.Type.prototype,{ initialize : function(){return this;}, clear : function(){ clear(this.chart); return this; }, stop : function(){ // Stops any current animation loop occuring cancelAnimFrame(this.animationFrame); return this; }, resize : function(callback){ this.stop(); var canvas = this.chart.canvas, newWidth = getMaximumWidth(this.chart.canvas), newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas); canvas.width = this.chart.width = newWidth; canvas.height = this.chart.height = newHeight; retinaScale(this.chart); if (typeof callback === "function"){ callback.apply(this, Array.prototype.slice.call(arguments, 1)); } return this; }, reflow : noop, render : function(reflow){ if (reflow){ this.reflow(); } if (this.options.animation && !reflow){ helpers.animationLoop( this.draw, this.options.animationSteps, this.options.animationEasing, this.options.onAnimationProgress, this.options.onAnimationComplete, this ); } else{ this.draw(); this.options.onAnimationComplete.call(this); } return this; }, generateLegend : function(){ return template(this.options.legendTemplate,this); }, destroy : function(){ this.clear(); unbindEvents(this, this.events); var canvas = this.chart.canvas; // Reset canvas height/width attributes starts a fresh with the canvas context canvas.width = this.chart.width; canvas.height = this.chart.height; // < IE9 doesn't support removeProperty if (canvas.style.removeProperty) { canvas.style.removeProperty('width'); canvas.style.removeProperty('height'); } else { canvas.style.removeAttribute('width'); canvas.style.removeAttribute('height'); } delete Chart.instances[this.id]; }, showTooltip : function(ChartElements, forceRedraw){ // Only redraw the chart if we've actually changed what we're hovering on. if (typeof this.activeElements === 'undefined') this.activeElements = []; var isChanged = (function(Elements){ var changed = false; if (Elements.length !== this.activeElements.length){ changed = true; return changed; } each(Elements, function(element, index){ if (element !== this.activeElements[index]){ changed = true; } }, this); return changed; }).call(this, ChartElements); if (!isChanged && !forceRedraw){ return; } else{ this.activeElements = ChartElements; } this.draw(); if(this.options.customTooltips){ this.options.customTooltips(false); } if (ChartElements.length > 0){ // If we have multiple datasets, show a MultiTooltip for all of the data points at that index if (this.datasets && this.datasets.length > 1) { var dataArray, dataIndex; for (var i = this.datasets.length - 1; i >= 0; i--) { dataArray = this.datasets[i].points || this.datasets[i].bars || this.datasets[i].segments; dataIndex = indexOf(dataArray, ChartElements[0]); if (dataIndex !== -1){ break; } } var tooltipLabels = [], tooltipColors = [], medianPosition = (function(index) { // Get all the points at that particular index var Elements = [], dataCollection, xPositions = [], yPositions = [], xMax, yMax, xMin, yMin; helpers.each(this.datasets, function(dataset){ dataCollection = dataset.points || dataset.bars || dataset.segments; if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){ Elements.push(dataCollection[dataIndex]); } }); helpers.each(Elements, function(element) { xPositions.push(element.x); yPositions.push(element.y); //Include any colour information about the element tooltipLabels.push(helpers.template(this.options.multiTooltipTemplate, element)); tooltipColors.push({ fill: element._saved.fillColor || element.fillColor, stroke: element._saved.strokeColor || element.strokeColor }); }, this); yMin = min(yPositions); yMax = max(yPositions); xMin = min(xPositions); xMax = max(xPositions); return { x: (xMin > this.chart.width/2) ? xMin : xMax, y: (yMin + yMax)/2 }; }).call(this, dataIndex); new Chart.MultiTooltip({ x: medianPosition.x, y: medianPosition.y, xPadding: this.options.tooltipXPadding, yPadding: this.options.tooltipYPadding, xOffset: this.options.tooltipXOffset, fillColor: this.options.tooltipFillColor, textColor: this.options.tooltipFontColor, fontFamily: this.options.tooltipFontFamily, fontStyle: this.options.tooltipFontStyle, fontSize: this.options.tooltipFontSize, titleTextColor: this.options.tooltipTitleFontColor, titleFontFamily: this.options.tooltipTitleFontFamily, titleFontStyle: this.options.tooltipTitleFontStyle, titleFontSize: this.options.tooltipTitleFontSize, cornerRadius: this.options.tooltipCornerRadius, labels: tooltipLabels, legendColors: tooltipColors, legendColorBackground : this.options.multiTooltipKeyBackground, title: ChartElements[0].label, chart: this.chart, ctx: this.chart.ctx, custom: this.options.customTooltips }).draw(); } else { each(ChartElements, function(Element) { var tooltipPosition = Element.tooltipPosition(); new Chart.Tooltip({ x: Math.round(tooltipPosition.x), y: Math.round(tooltipPosition.y), xPadding: this.options.tooltipXPadding, yPadding: this.options.tooltipYPadding, fillColor: this.options.tooltipFillColor, textColor: this.options.tooltipFontColor, fontFamily: this.options.tooltipFontFamily, fontStyle: this.options.tooltipFontStyle, fontSize: this.options.tooltipFontSize, caretHeight: this.options.tooltipCaretSize, cornerRadius: this.options.tooltipCornerRadius, text: template(this.options.tooltipTemplate, Element), chart: this.chart, custom: this.options.customTooltips }).draw(); }, this); } } return this; }, toBase64Image : function(){ return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments); } }); Chart.Type.extend = function(extensions){ var parent = this; var ChartType = function(){ return parent.apply(this,arguments); }; //Copy the prototype object of the this class ChartType.prototype = clone(parent.prototype); //Now overwrite some of the properties in the base class with the new extensions extend(ChartType.prototype, extensions); ChartType.extend = Chart.Type.extend; if (extensions.name || parent.prototype.name){ var chartName = extensions.name || parent.prototype.name; //Assign any potential default values of the new chart type //If none are defined, we'll use a clone of the chart type this is being extended from. //I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart //doesn't define some defaults of their own. var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {}; Chart.defaults[chartName] = extend(baseDefaults,extensions.defaults); Chart.types[chartName] = ChartType; //Register this new chart type in the Chart prototype Chart.prototype[chartName] = function(data,options){ var config = merge(Chart.defaults.global, Chart.defaults[chartName], options || {}); return new ChartType(data,config,this); }; } else{ warn("Name not provided for this chart, so it hasn't been registered"); } return parent; }; Chart.Element = function(configuration){ extend(this,configuration); this.initialize.apply(this,arguments); this.save(); }; extend(Chart.Element.prototype,{ initialize : function(){}, restore : function(props){ if (!props){ extend(this,this._saved); } else { each(props,function(key){ this[key] = this._saved[key]; },this); } return this; }, save : function(){ this._saved = clone(this); delete this._saved._saved; return this; }, update : function(newProps){ each(newProps,function(value,key){ this._saved[key] = this[key]; this[key] = value; },this); return this; }, transition : function(props,ease){ each(props,function(value,key){ this[key] = ((value - this._saved[key]) * ease) + this._saved[key]; },this); return this; }, tooltipPosition : function(){ return { x : this.x, y : this.y }; }, hasValue: function(){ return isNumber(this.value); } }); Chart.Element.extend = inherits; Chart.Point = Chart.Element.extend({ display: true, inRange: function(chartX,chartY){ var hitDetectionRange = this.hitDetectionRadius + this.radius; return ((Math.pow(chartX-this.x, 2)+Math.pow(chartY-this.y, 2)) < Math.pow(hitDetectionRange,2)); }, draw : function(){ if (this.display){ var ctx = this.ctx; ctx.beginPath(); ctx.arc(this.x, this.y, this.radius, 0, Math.PI*2); ctx.closePath(); ctx.strokeStyle = this.strokeColor; ctx.lineWidth = this.strokeWidth; ctx.fillStyle = this.fillColor; ctx.fill(); ctx.stroke(); } //Quick debug for bezier curve splining //Highlights control points and the line between them. //Handy for dev - stripped in the min version. // ctx.save(); // ctx.fillStyle = "black"; // ctx.strokeStyle = "black" // ctx.beginPath(); // ctx.arc(this.controlPoints.inner.x,this.controlPoints.inner.y, 2, 0, Math.PI*2); // ctx.fill(); // ctx.beginPath(); // ctx.arc(this.controlPoints.outer.x,this.controlPoints.outer.y, 2, 0, Math.PI*2); // ctx.fill(); // ctx.moveTo(this.controlPoints.inner.x,this.controlPoints.inner.y); // ctx.lineTo(this.x, this.y); // ctx.lineTo(this.controlPoints.outer.x,this.controlPoints.outer.y); // ctx.stroke(); // ctx.restore(); } }); Chart.Arc = Chart.Element.extend({ inRange : function(chartX,chartY){ var pointRelativePosition = helpers.getAngleFromPoint(this, { x: chartX, y: chartY }); //Check if within the range of the open/close angle var betweenAngles = (pointRelativePosition.angle >= this.startAngle && pointRelativePosition.angle <= this.endAngle), withinRadius = (pointRelativePosition.distance >= this.innerRadius && pointRelativePosition.distance <= this.outerRadius); return (betweenAngles && withinRadius); //Ensure within the outside of the arc centre, but inside arc outer }, tooltipPosition : function(){ var centreAngle = this.startAngle + ((this.endAngle - this.startAngle) / 2), rangeFromCentre = (this.outerRadius - this.innerRadius) / 2 + this.innerRadius; return { x : this.x + (Math.cos(centreAngle) * rangeFromCentre), y : this.y + (Math.sin(centreAngle) * rangeFromCentre) }; }, draw : function(animationPercent){ var easingDecimal = animationPercent || 1; var ctx = this.ctx; ctx.beginPath(); ctx.arc(this.x, this.y, this.outerRadius, this.startAngle, this.endAngle); ctx.arc(this.x, this.y, this.innerRadius, this.endAngle, this.startAngle, true); ctx.closePath(); ctx.strokeStyle = this.strokeColor; ctx.lineWidth = this.strokeWidth; ctx.fillStyle = this.fillColor; ctx.fill(); ctx.lineJoin = 'bevel'; if (this.showStroke){ ctx.stroke(); } } }); Chart.Rectangle = Chart.Element.extend({ draw : function(){ var ctx = this.ctx, halfWidth = this.width/2, leftX = this.x - halfWidth, rightX = this.x + halfWidth, top = this.base - (this.base - this.y), halfStroke = this.strokeWidth / 2; // Canvas doesn't allow us to stroke inside the width so we can // adjust the sizes to fit if we're setting a stroke on the line if (this.showStroke){ leftX += halfStroke; rightX -= halfStroke; top += halfStroke; } ctx.beginPath(); ctx.fillStyle = this.fillColor; ctx.strokeStyle = this.strokeColor; ctx.lineWidth = this.strokeWidth; // It'd be nice to keep this class totally generic to any rectangle // and simply specify which border to miss out. ctx.moveTo(leftX, this.base); ctx.lineTo(leftX, top); ctx.lineTo(rightX, top); ctx.lineTo(rightX, this.base); ctx.fill(); if (this.showStroke){ ctx.stroke(); } }, height : function(){ return this.base - this.y; }, inRange : function(chartX,chartY){ return (chartX >= this.x - this.width/2 && chartX <= this.x + this.width/2) && (chartY >= this.y && chartY <= this.base); } }); Chart.Tooltip = Chart.Element.extend({ draw : function(){ var ctx = this.chart.ctx; ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily); this.xAlign = "center"; this.yAlign = "above"; //Distance between the actual element.y position and the start of the tooltip caret var caretPadding = this.caretPadding = 2; var tooltipWidth = ctx.measureText(this.text).width + 2*this.xPadding, tooltipRectHeight = this.fontSize + 2*this.yPadding, tooltipHeight = tooltipRectHeight + this.caretHeight + caretPadding; if (this.x + tooltipWidth/2 >this.chart.width){ this.xAlign = "left"; } else if (this.x - tooltipWidth/2 < 0){ this.xAlign = "right"; } if (this.y - tooltipHeight < 0){ this.yAlign = "below"; } var tooltipX = this.x - tooltipWidth/2, tooltipY = this.y - tooltipHeight; ctx.fillStyle = this.fillColor; // Custom Tooltips if(this.custom){ this.custom(this); } else{ switch(this.yAlign) { case "above": //Draw a caret above the x/y ctx.beginPath(); ctx.moveTo(this.x,this.y - caretPadding); ctx.lineTo(this.x + this.caretHeight, this.y - (caretPadding + this.caretHeight)); ctx.lineTo(this.x - this.caretHeight, this.y - (caretPadding + this.caretHeight)); ctx.closePath(); ctx.fill(); break; case "below": tooltipY = this.y + caretPadding + this.caretHeight; //Draw a caret below the x/y ctx.beginPath(); ctx.moveTo(this.x, this.y + caretPadding); ctx.lineTo(this.x + this.caretHeight, this.y + caretPadding + this.caretHeight); ctx.lineTo(this.x - this.caretHeight, this.y + caretPadding + this.caretHeight); ctx.closePath(); ctx.fill(); break; } switch(this.xAlign) { case "left": tooltipX = this.x - tooltipWidth + (this.cornerRadius + this.caretHeight); break; case "right": tooltipX = this.x - (this.cornerRadius + this.caretHeight); break; } drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,this.cornerRadius); ctx.fill(); ctx.fillStyle = this.textColor; ctx.textAlign = "center"; ctx.textBaseline = "middle"; ctx.fillText(this.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2); } } }); Chart.MultiTooltip = Chart.Element.extend({ initialize : function(){ this.font = fontString(this.fontSize,this.fontStyle,this.fontFamily); this.titleFont = fontString(this.titleFontSize,this.titleFontStyle,this.titleFontFamily); this.height = (this.labels.length * this.fontSize) + ((this.labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5; this.ctx.font = this.titleFont; var titleWidth = this.ctx.measureText(this.title).width, //Label has a legend square as well so account for this. labelWidth = longestText(this.ctx,this.font,this.labels) + this.fontSize + 3, longestTextWidth = max([labelWidth,titleWidth]); this.width = longestTextWidth + (this.xPadding*2); var halfHeight = this.height/2; //Check to ensure the height will fit on the canvas if (this.y - halfHeight < 0 ){ this.y = halfHeight; } else if (this.y + halfHeight > this.chart.height){ this.y = this.chart.height - halfHeight; } //Decide whether to align left or right based on position on canvas if (this.x > this.chart.width/2){ this.x -= this.xOffset + this.width; } else { this.x += this.xOffset; } }, getLineHeight : function(index){ var baseLineHeight = this.y - (this.height/2) + this.yPadding, afterTitleIndex = index-1; //If the index is zero, we're getting the title if (index === 0){ return baseLineHeight + this.titleFontSize/2; } else{ return baseLineHeight + ((this.fontSize*1.5*afterTitleIndex) + this.fontSize/2) + this.titleFontSize * 1.5; } }, draw : function(){ // Custom Tooltips if(this.custom){ this.custom(this); } else{ drawRoundedRectangle(this.ctx,this.x,this.y - this.height/2,this.width,this.height,this.cornerRadius); var ctx = this.ctx; ctx.fillStyle = this.fillColor; ctx.fill(); ctx.closePath(); ctx.textAlign = "left"; ctx.textBaseline = "middle"; ctx.fillStyle = this.titleTextColor; ctx.font = this.titleFont; ctx.fillText(this.title,this.x + this.xPadding, this.getLineHeight(0)); ctx.font = this.font; helpers.each(this.labels,function(label,index){ ctx.fillStyle = this.textColor; ctx.fillText(label,this.x + this.xPadding + this.fontSize + 3, this.getLineHeight(index + 1)); //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas) //ctx.clearRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize); //Instead we'll make a white filled block to put the legendColour palette over. ctx.fillStyle = this.legendColorBackground; ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize); ctx.fillStyle = this.legendColors[index].fill; ctx.fillRect(this.x + this.xPadding, this.getLineHeight(index + 1) - this.fontSize/2, this.fontSize, this.fontSize); },this); } } }); Chart.Scale = Chart.Element.extend({ initialize : function(){ this.fit(); }, buildYLabels : function(){ this.yLabels = []; var stepDecimalPlaces = getDecimalPlaces(this.stepValue); for (var i=0; i<=this.steps; i++){ this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)})); } this.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx,this.font,this.yLabels) : 0; }, addXLabel : function(label){ this.xLabels.push(label); this.valuesCount++; this.fit(); }, removeXLabel : function(){ this.xLabels.shift(); this.valuesCount--; this.fit(); }, // Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use fit: function(){ // First we need the width of the yLabels, assuming the xLabels aren't rotated // To do that we need the base line at the top and base of the chart, assuming there is no x label rotation this.startPoint = (this.display) ? this.fontSize : 0; this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels // Apply padding settings to the start and end point. this.startPoint += this.padding; this.endPoint -= this.padding; // Cache the starting height, so can determine if we need to recalculate the scale yAxis var cachedHeight = this.endPoint - this.startPoint, cachedYLabelWidth; // Build the current yLabels so we have an idea of what size they'll be to start /* * This sets what is returned from calculateScaleRange as static properties of this class: * this.steps; this.stepValue; this.min; this.max; * */ this.calculateYRange(cachedHeight); // With these properties set we can now build the array of yLabels // and also the width of the largest yLabel this.buildYLabels(); this.calculateXLabelRotation(); while((cachedHeight > this.endPoint - this.startPoint)){ cachedHeight = this.endPoint - this.startPoint; cachedYLabelWidth = this.yLabelWidth; this.calculateYRange(cachedHeight); this.buildYLabels(); // Only go through the xLabel loop again if the yLabel width has changed if (cachedYLabelWidth < this.yLabelWidth){ this.calculateXLabelRotation(); } } }, calculateXLabelRotation : function(){ //Get the width of each grid by calculating the difference //between x offsets between 0 and 1. this.ctx.font = this.font; var firstWidth = this.ctx.measureText(this.xLabels[0]).width, lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width, firstRotated, lastRotated; this.xScalePaddingRight = lastWidth/2 + 3; this.xScalePaddingLeft = (firstWidth/2 > this.yLabelWidth + 10) ? firstWidth/2 : this.yLabelWidth + 10; this.xLabelRotation = 0; if (this.display){ var originalLabelWidth = longestText(this.ctx,this.font,this.xLabels), cosRotation, firstRotatedWidth; this.xLabelWidth = originalLabelWidth; //Allow 3 pixels x2 padding either side for label readability var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6; //Max label rotate should be 90 - also act as a loop counter while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)){ cosRotation = Math.cos(toRadians(this.xLabelRotation)); firstRotated = cosRotation * firstWidth; lastRotated = cosRotation * lastWidth; // We're right aligning the text now. if (firstRotated + this.fontSize / 2 > this.yLabelWidth + 8){ this.xScalePaddingLeft = firstRotated + this.fontSize / 2; } this.xScalePaddingRight = this.fontSize/2; this.xLabelRotation++; this.xLabelWidth = cosRotation * originalLabelWidth; } if (this.xLabelRotation > 0){ this.endPoint -= Math.sin(toRadians(this.xLabelRotation))*originalLabelWidth + 3; } } else{ this.xLabelWidth = 0; this.xScalePaddingRight = this.padding; this.xScalePaddingLeft = this.padding; } }, // Needs to be overidden in each Chart type // Otherwise we need to pass all the data into the scale class calculateYRange: noop, drawingArea: function(){ return this.startPoint - this.endPoint; }, calculateY : function(value){ var scalingFactor = this.drawingArea() / (this.min - this.max); return this.endPoint - (scalingFactor * (value - this.min)); }, calculateX : function(index){ var isRotated = (this.xLabelRotation > 0), // innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding, innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight), valueWidth = innerWidth/Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1), valueOffset = (valueWidth * index) + this.xScalePaddingLeft; if (this.offsetGridLines){ valueOffset += (valueWidth/2); } return Math.round(valueOffset); }, update : function(newProps){ helpers.extend(this, newProps); this.fit(); }, draw : function(){ var ctx = this.ctx, yLabelGap = (this.endPoint - this.startPoint) / this.steps, xStart = Math.round(this.xScalePaddingLeft); if (this.display){ ctx.fillStyle = this.textColor; ctx.font = this.font; each(this.yLabels,function(labelString,index){ var yLabelCenter = this.endPoint - (yLabelGap * index), linePositionY = Math.round(yLabelCenter), drawHorizontalLine = this.showHorizontalLines; ctx.textAlign = "right"; ctx.textBaseline = "middle"; if (this.showLabels){ ctx.fillText(labelString,xStart - 10,yLabelCenter); } // This is X axis, so draw it if (index === 0 && !drawHorizontalLine){ drawHorizontalLine = true; } if (drawHorizontalLine){ ctx.beginPath(); } if (index > 0){ // This is a grid line in the centre, so drop that ctx.lineWidth = this.gridLineWidth; ctx.strokeStyle = this.gridLineColor; } else { // This is the first line on the scale ctx.lineWidth = this.lineWidth; ctx.strokeStyle = this.lineColor; } linePositionY += helpers.aliasPixel(ctx.lineWidth); if(drawHorizontalLine){ ctx.moveTo(xStart, linePositionY); ctx.lineTo(this.width, linePositionY); ctx.stroke(); ctx.closePath(); } ctx.lineWidth = this.lineWidth; ctx.strokeStyle = this.lineColor; ctx.beginPath(); ctx.moveTo(xStart - 5, linePositionY); ctx.lineTo(xStart, linePositionY); ctx.stroke(); ctx.closePath(); },this); each(this.xLabels,function(label,index){ var xPos = this.calculateX(index) + aliasPixel(this.lineWidth), // Check to see if line/bar here and decide where to place the line linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth), isRotated = (this.xLabelRotation > 0), drawVerticalLine = this.showVerticalLines; // This is Y axis, so draw it if (index === 0 && !drawVerticalLine){ drawVerticalLine = true; } if (drawVerticalLine){ ctx.beginPath(); } if (index > 0){ // This is a grid line in the centre, so drop that ctx.lineWidth = this.gridLineWidth; ctx.strokeStyle = this.gridLineColor; } else { // This is the first line on the scale ctx.lineWidth = this.lineWidth; ctx.strokeStyle = this.lineColor; } if (drawVerticalLine){ ctx.moveTo(linePos,this.endPoint); ctx.lineTo(linePos,this.startPoint - 3); ctx.stroke(); ctx.closePath(); } ctx.lineWidth = this.lineWidth; ctx.strokeStyle = this.lineColor; // Small lines at the bottom of the base grid line ctx.beginPath(); ctx.moveTo(linePos,this.endPoint); ctx.lineTo(linePos,this.endPoint + 5); ctx.stroke(); ctx.closePath(); ctx.save(); ctx.translate(xPos,(isRotated) ? this.endPoint + 12 : this.endPoint + 8); ctx.rotate(toRadians(this.xLabelRotation)*-1); ctx.font = this.font; ctx.textAlign = (isRotated) ? "right" : "center"; ctx.textBaseline = (isRotated) ? "middle" : "top"; ctx.fillText(label, 0, 0); ctx.restore(); },this); } } }); Chart.RadialScale = Chart.Element.extend({ initialize: function(){ this.size = min([this.height, this.width]); this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2); }, calculateCenterOffset: function(value){ // Take into account half font size + the yPadding of the top value var scalingFactor = this.drawingArea / (this.max - this.min); return (value - this.min) * scalingFactor; }, update : function(){ if (!this.lineArc){ this.setScaleSize(); } else { this.drawingArea = (this.display) ? (this.size/2) - (this.fontSize/2 + this.backdropPaddingY) : (this.size/2); } this.buildYLabels(); }, buildYLabels: function(){ this.yLabels = []; var stepDecimalPlaces = getDecimalPlaces(this.stepValue); for (var i=0; i<=this.steps; i++){ this.yLabels.push(template(this.templateString,{value:(this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces)})); } }, getCircumference : function(){ return ((Math.PI*2) / this.valuesCount); }, setScaleSize: function(){ /* * Right, this is really confusing and there is a lot of maths going on here * The gist of the problem is here: https://gist.github.com/nnnick/696cc9c55f4b0beb8fe9 * * Reaction: https://dl.dropboxusercontent.com/u/34601363/toomuchscience.gif * * Solution: * * We assume the radius of the polygon is half the size of the canvas at first * at each index we check if the text overlaps. * * Where it does, we store that angle and that index. * * After finding the largest index and angle we calculate how much we need to remove * from the shape radius to move the point inwards by that x. * * We average the left and right distances to get the maximum shape radius that can fit in the box * along with labels. * * Once we have that, we can find the centre point for the chart, by taking the x text protrusion * on each side, removing that from the size, halving it and adding the left x protrusion width. * * This will mean we have a shape fitted to the canvas, as large as it can be with the labels * and position it in the most space efficient manner * * https://dl.dropboxusercontent.com/u/34601363/yeahscience.gif */ // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points var largestPossibleRadius = min([(this.height/2 - this.pointLabelFontSize - 5), this.width/2]), pointPosition, i, textWidth, halfTextWidth, furthestRight = this.width, furthestRightIndex, furthestRightAngle, furthestLeft = 0, furthestLeftIndex, furthestLeftAngle, xProtrusionLeft, xProtrusionRight, radiusReductionRight, radiusReductionLeft, maxWidthRadius; this.ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily); for (i=0;i<this.valuesCount;i++){ // 5px to space the text slightly out - similar to what we do in the draw function. pointPosition = this.getPointPosition(i, largestPossibleRadius); textWidth = this.ctx.measureText(template(this.templateString, { value: this.labels[i] })).width + 5; if (i === 0 || i === this.valuesCount/2){ // If we're at index zero, or exactly the middle, we're at exactly the top/bottom // of the radar chart, so text will be aligned centrally, so we'll half it and compare // w/left and right text sizes halfTextWidth = textWidth/2; if (pointPosition.x + halfTextWidth > furthestRight) { furthestRight = pointPosition.x + halfTextWidth; furthestRightIndex = i; } if (pointPosition.x - halfTextWidth < furthestLeft) { furthestLeft = pointPosition.x - halfTextWidth; furthestLeftIndex = i; } } else if (i < this.valuesCount/2) { // Less than half the values means we'll left align the text if (pointPosition.x + textWidth > furthestRight) { furthestRight = pointPosition.x + textWidth; furthestRightIndex = i; } } else if (i > this.valuesCount/2){ // More than half the values means we'll right align the text if (pointPosition.x - textWidth < furthestLeft) { furthestLeft = pointPosition.x - textWidth; furthestLeftIndex = i; } } } xProtrusionLeft = furthestLeft; xProtrusionRight = Math.ceil(furthestRight - this.width); furthestRightAngle = this.getIndexAngle(furthestRightIndex); furthestLeftAngle = this.getIndexAngle(furthestLeftIndex); radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI/2); radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI/2); // Ensure we actually need to reduce the size of the chart radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0; radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0; this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight)/2; //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2]) this.setCenterPoint(radiusReductionLeft, radiusReductionRight); }, setCenterPoint: function(leftMovement, rightMovement){ var maxRight = this.width - rightMovement - this.drawingArea, maxLeft = leftMovement + this.drawingArea; this.xCenter = (maxLeft + maxRight)/2; // Always vertically in the centre as the text height doesn't change this.yCenter = (this.height/2); }, getIndexAngle : function(index){ var angleMultiplier = (Math.PI * 2) / this.valuesCount; // Start from the top instead of right, so remove a quarter of the circle return index * angleMultiplier - (Math.PI/2); }, getPointPosition : function(index, distanceFromCenter){ var thisAngle = this.getIndexAngle(index); return { x : (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter, y : (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter }; }, draw: function(){ if (this.display){ var ctx = this.ctx; each(this.yLabels, function(label, index){ // Don't draw a centre value if (index > 0){ var yCenterOffset = index * (this.drawingArea/this.steps), yHeight = this.yCenter - yCenterOffset, pointPosition; // Draw circular lines around the scale if (this.lineWidth > 0){ ctx.strokeStyle = this.lineColor; ctx.lineWidth = this.lineWidth; if(this.lineArc){ ctx.beginPath(); ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI*2); ctx.closePath(); ctx.stroke(); } else{ ctx.beginPath(); for (var i=0;i<this.valuesCount;i++) { pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue))); if (i === 0){ ctx.moveTo(pointPosition.x, pointPosition.y); } else { ctx.lineTo(pointPosition.x, pointPosition.y); } } ctx.closePath(); ctx.stroke(); } } if(this.showLabels){ ctx.font = fontString(this.fontSize,this.fontStyle,this.fontFamily); if (this.showLabelBackdrop){ var labelWidth = ctx.measureText(label).width; ctx.fillStyle = this.backdropColor; ctx.fillRect( this.xCenter - labelWidth/2 - this.backdropPaddingX, yHeight - this.fontSize/2 - this.backdropPaddingY, labelWidth + this.backdropPaddingX*2, this.fontSize + this.backdropPaddingY*2 ); } ctx.textAlign = 'center'; ctx.textBaseline = "middle"; ctx.fillStyle = this.fontColor; ctx.fillText(label, this.xCenter, yHeight); } } }, this); if (!this.lineArc){ ctx.lineWidth = this.angleLineWidth; ctx.strokeStyle = this.angleLineColor; for (var i = this.valuesCount - 1; i >= 0; i--) { if (this.angleLineWidth > 0){ var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max)); ctx.beginPath(); ctx.moveTo(this.xCenter, this.yCenter); ctx.lineTo(outerPosition.x, outerPosition.y); ctx.stroke(); ctx.closePath(); } // Extra 3px out for some label spacing var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5); ctx.font = fontString(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily); ctx.fillStyle = this.pointLabelFontColor; var labelsCount = this.labels.length, halfLabelsCount = this.labels.length/2, quarterLabelsCount = halfLabelsCount/2, upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount), exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount); if (i === 0){ ctx.textAlign = 'center'; } else if(i === halfLabelsCount){ ctx.textAlign = 'center'; } else if (i < halfLabelsCount){ ctx.textAlign = 'left'; } else { ctx.textAlign = 'right'; } // Set the correct text baseline based on outer positioning if (exactQuarter){ ctx.textBaseline = 'middle'; } else if (upperHalf){ ctx.textBaseline = 'bottom'; } else { ctx.textBaseline = 'top'; } ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y); } } } } }); // Attach global event to resize each chart instance when the browser resizes helpers.addEvent(window, "resize", (function(){ // Basic debounce of resize function so it doesn't hurt performance when resizing browser. var timeout; return function(){ clearTimeout(timeout); timeout = setTimeout(function(){ each(Chart.instances,function(instance){ // If the responsive flag is set in the chart instance config // Cascade the resize event down to the chart. if (instance.options.responsive){ instance.resize(instance.render, true); } }); }, 50); }; })()); if (amd) { define(function(){ return Chart; }); } else if (typeof module === 'object' && module.exports) { module.exports = Chart; } root.Chart = Chart; Chart.noConflict = function(){ root.Chart = previous; return Chart; }; }).call(this); (function(){ "use strict"; var root = this, Chart = root.Chart, helpers = Chart.helpers; var defaultConfig = { //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value scaleBeginAtZero : true, //Boolean - Whether grid lines are shown across the chart scaleShowGridLines : true, //String - Colour of the grid lines scaleGridLineColor : "rgba(0,0,0,.05)", //Number - Width of the grid lines scaleGridLineWidth : 1, //Boolean - Whether to show horizontal lines (except X axis) scaleShowHorizontalLines: true, //Boolean - Whether to show vertical lines (except Y axis) scaleShowVerticalLines: true, //Boolean - If there is a stroke on each bar barShowStroke : true, //Number - Pixel width of the bar stroke barStrokeWidth : 2, //Number - Spacing between each of the X value sets barValueSpacing : 5, //Number - Spacing between data sets within X values barDatasetSpacing : 1, //String - A legend template legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].fillColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>" }; Chart.Type.extend({ name: "Bar", defaults : defaultConfig, initialize: function(data){ //Expose options as a scope variable here so we can access it in the ScaleClass var options = this.options; this.ScaleClass = Chart.Scale.extend({ offsetGridLines : true, calculateBarX : function(datasetCount, datasetIndex, barIndex){ //Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar var xWidth = this.calculateBaseWidth(), xAbsolute = this.calculateX(barIndex) - (xWidth/2), barWidth = this.calculateBarWidth(datasetCount); return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth/2; }, calculateBaseWidth : function(){ return (this.calculateX(1) - this.calculateX(0)) - (2*options.barValueSpacing); }, calculateBarWidth : function(datasetCount){ //The padding between datasets is to the right of each bar, providing that there are more than 1 dataset var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing); return (baseWidth / datasetCount); } }); this.datasets = []; //Set up tooltip events on the chart if (this.options.showTooltips){ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ var activeBars = (evt.type !== 'mouseout') ? this.getBarsAtEvent(evt) : []; this.eachBars(function(bar){ bar.restore(['fillColor', 'strokeColor']); }); helpers.each(activeBars, function(activeBar){ activeBar.fillColor = activeBar.highlightFill; activeBar.strokeColor = activeBar.highlightStroke; }); this.showTooltip(activeBars); }); } //Declare the extension of the default point, to cater for the options passed in to the constructor this.BarClass = Chart.Rectangle.extend({ strokeWidth : this.options.barStrokeWidth, showStroke : this.options.barShowStroke, ctx : this.chart.ctx }); //Iterate through each of the datasets, and build this into a property of the chart helpers.each(data.datasets,function(dataset,datasetIndex){ var datasetObject = { label : dataset.label || null, fillColor : dataset.fillColor, strokeColor : dataset.strokeColor, bars : [] }; this.datasets.push(datasetObject); helpers.each(dataset.data,function(dataPoint,index){ //Add a new point for each piece of data, passing any required data to draw. datasetObject.bars.push(new this.BarClass({ value : dataPoint, label : data.labels[index], datasetLabel: dataset.label, strokeColor : dataset.strokeColor, fillColor : dataset.fillColor, highlightFill : dataset.highlightFill || dataset.fillColor, highlightStroke : dataset.highlightStroke || dataset.strokeColor })); },this); },this); this.buildScale(data.labels); this.BarClass.prototype.base = this.scale.endPoint; this.eachBars(function(bar, index, datasetIndex){ helpers.extend(bar, { width : this.scale.calculateBarWidth(this.datasets.length), x: this.scale.calculateBarX(this.datasets.length, datasetIndex, index), y: this.scale.endPoint }); bar.save(); }, this); this.render(); }, update : function(){ this.scale.update(); // Reset any highlight colours before updating. helpers.each(this.activeElements, function(activeElement){ activeElement.restore(['fillColor', 'strokeColor']); }); this.eachBars(function(bar){ bar.save(); }); this.render(); }, eachBars : function(callback){ helpers.each(this.datasets,function(dataset, datasetIndex){ helpers.each(dataset.bars, callback, this, datasetIndex); },this); }, getBarsAtEvent : function(e){ var barsArray = [], eventPosition = helpers.getRelativePosition(e), datasetIterator = function(dataset){ barsArray.push(dataset.bars[barIndex]); }, barIndex; for (var datasetIndex = 0; datasetIndex < this.datasets.length; datasetIndex++) { for (barIndex = 0; barIndex < this.datasets[datasetIndex].bars.length; barIndex++) { if (this.datasets[datasetIndex].bars[barIndex].inRange(eventPosition.x,eventPosition.y)){ helpers.each(this.datasets, datasetIterator); return barsArray; } } } return barsArray; }, buildScale : function(labels){ var self = this; var dataTotal = function(){ var values = []; self.eachBars(function(bar){ values.push(bar.value); }); return values; }; var scaleOptions = { templateString : this.options.scaleLabel, height : this.chart.height, width : this.chart.width, ctx : this.chart.ctx, textColor : this.options.scaleFontColor, fontSize : this.options.scaleFontSize, fontStyle : this.options.scaleFontStyle, fontFamily : this.options.scaleFontFamily, valuesCount : labels.length, beginAtZero : this.options.scaleBeginAtZero, integersOnly : this.options.scaleIntegersOnly, calculateYRange: function(currentHeight){ var updatedRanges = helpers.calculateScaleRange( dataTotal(), currentHeight, this.fontSize, this.beginAtZero, this.integersOnly ); helpers.extend(this, updatedRanges); }, xLabels : labels, font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily), lineWidth : this.options.scaleLineWidth, lineColor : this.options.scaleLineColor, showHorizontalLines : this.options.scaleShowHorizontalLines, showVerticalLines : this.options.scaleShowVerticalLines, gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0, gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)", padding : (this.options.showScale) ? 0 : (this.options.barShowStroke) ? this.options.barStrokeWidth : 0, showLabels : this.options.scaleShowLabels, display : this.options.showScale }; if (this.options.scaleOverride){ helpers.extend(scaleOptions, { calculateYRange: helpers.noop, steps: this.options.scaleSteps, stepValue: this.options.scaleStepWidth, min: this.options.scaleStartValue, max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) }); } this.scale = new this.ScaleClass(scaleOptions); }, addData : function(valuesArray,label){ //Map the values array for each of the datasets helpers.each(valuesArray,function(value,datasetIndex){ //Add a new point for each piece of data, passing any required data to draw. this.datasets[datasetIndex].bars.push(new this.BarClass({ value : value, label : label, x: this.scale.calculateBarX(this.datasets.length, datasetIndex, this.scale.valuesCount+1), y: this.scale.endPoint, width : this.scale.calculateBarWidth(this.datasets.length), base : this.scale.endPoint, strokeColor : this.datasets[datasetIndex].strokeColor, fillColor : this.datasets[datasetIndex].fillColor })); },this); this.scale.addXLabel(label); //Then re-render the chart. this.update(); }, removeData : function(){ this.scale.removeXLabel(); //Then re-render the chart. helpers.each(this.datasets,function(dataset){ dataset.bars.shift(); },this); this.update(); }, reflow : function(){ helpers.extend(this.BarClass.prototype,{ y: this.scale.endPoint, base : this.scale.endPoint }); var newScaleProps = helpers.extend({ height : this.chart.height, width : this.chart.width }); this.scale.update(newScaleProps); }, draw : function(ease){ var easingDecimal = ease || 1; this.clear(); var ctx = this.chart.ctx; this.scale.draw(easingDecimal); //Draw all the bars for each dataset helpers.each(this.datasets,function(dataset,datasetIndex){ helpers.each(dataset.bars,function(bar,index){ if (bar.hasValue()){ bar.base = this.scale.endPoint; //Transition then draw bar.transition({ x : this.scale.calculateBarX(this.datasets.length, datasetIndex, index), y : this.scale.calculateY(bar.value), width : this.scale.calculateBarWidth(this.datasets.length) }, easingDecimal).draw(); } },this); },this); } }); }).call(this); (function(){ "use strict"; var root = this, Chart = root.Chart, //Cache a local reference to Chart.helpers helpers = Chart.helpers; var defaultConfig = { //Boolean - Whether we should show a stroke on each segment segmentShowStroke : true, //String - The colour of each segment stroke segmentStrokeColor : "#fff", //Number - The width of each segment stroke segmentStrokeWidth : 2, //The percentage of the chart that we cut out of the middle. percentageInnerCutout : 50, //Number - Amount of animation steps animationSteps : 100, //String - Animation easing effect animationEasing : "easeOutBounce", //Boolean - Whether we animate the rotation of the Doughnut animateRotate : true, //Boolean - Whether we animate scaling the Doughnut from the centre animateScale : false, //String - A legend template legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>" }; Chart.Type.extend({ //Passing in a name registers this chart in the Chart namespace name: "Doughnut", //Providing a defaults will also register the deafults in the chart namespace defaults : defaultConfig, //Initialize is fired when the chart is initialized - Data is passed in as a parameter //Config is automatically merged by the core of Chart.js, and is available at this.options initialize: function(data){ //Declare segments as a static property to prevent inheriting across the Chart type prototype this.segments = []; this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2; this.SegmentArc = Chart.Arc.extend({ ctx : this.chart.ctx, x : this.chart.width/2, y : this.chart.height/2 }); //Set up tooltip events on the chart if (this.options.showTooltips){ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : []; helpers.each(this.segments,function(segment){ segment.restore(["fillColor"]); }); helpers.each(activeSegments,function(activeSegment){ activeSegment.fillColor = activeSegment.highlightColor; }); this.showTooltip(activeSegments); }); } this.calculateTotal(data); helpers.each(data,function(datapoint, index){ this.addData(datapoint, index, true); },this); this.render(); }, getSegmentsAtEvent : function(e){ var segmentsArray = []; var location = helpers.getRelativePosition(e); helpers.each(this.segments,function(segment){ if (segment.inRange(location.x,location.y)) segmentsArray.push(segment); },this); return segmentsArray; }, addData : function(segment, atIndex, silent){ var index = atIndex || this.segments.length; this.segments.splice(index, 0, new this.SegmentArc({ value : segment.value, outerRadius : (this.options.animateScale) ? 0 : this.outerRadius, innerRadius : (this.options.animateScale) ? 0 : (this.outerRadius/100) * this.options.percentageInnerCutout, fillColor : segment.color, highlightColor : segment.highlight || segment.color, showStroke : this.options.segmentShowStroke, strokeWidth : this.options.segmentStrokeWidth, strokeColor : this.options.segmentStrokeColor, startAngle : Math.PI * 1.5, circumference : (this.options.animateRotate) ? 0 : this.calculateCircumference(segment.value), label : segment.label })); if (!silent){ this.reflow(); this.update(); } }, calculateCircumference : function(value){ return (Math.PI*2)*(Math.abs(value) / this.total); }, calculateTotal : function(data){ this.total = 0; helpers.each(data,function(segment){ this.total += Math.abs(segment.value); },this); }, update : function(){ this.calculateTotal(this.segments); // Reset any highlight colours before updating. helpers.each(this.activeElements, function(activeElement){ activeElement.restore(['fillColor']); }); helpers.each(this.segments,function(segment){ segment.save(); }); this.render(); }, removeData: function(atIndex){ var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1; this.segments.splice(indexToDelete, 1); this.reflow(); this.update(); }, reflow : function(){ helpers.extend(this.SegmentArc.prototype,{ x : this.chart.width/2, y : this.chart.height/2 }); this.outerRadius = (helpers.min([this.chart.width,this.chart.height]) - this.options.segmentStrokeWidth/2)/2; helpers.each(this.segments, function(segment){ segment.update({ outerRadius : this.outerRadius, innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout }); }, this); }, draw : function(easeDecimal){ var animDecimal = (easeDecimal) ? easeDecimal : 1; this.clear(); helpers.each(this.segments,function(segment,index){ segment.transition({ circumference : this.calculateCircumference(segment.value), outerRadius : this.outerRadius, innerRadius : (this.outerRadius/100) * this.options.percentageInnerCutout },animDecimal); segment.endAngle = segment.startAngle + segment.circumference; segment.draw(); if (index === 0){ segment.startAngle = Math.PI * 1.5; } //Check to see if it's the last segment, if not get the next and update the start angle if (index < this.segments.length-1){ this.segments[index+1].startAngle = segment.endAngle; } },this); } }); Chart.types.Doughnut.extend({ name : "Pie", defaults : helpers.merge(defaultConfig,{percentageInnerCutout : 0}) }); }).call(this); (function(){ "use strict"; var root = this, Chart = root.Chart, helpers = Chart.helpers; var defaultConfig = { ///Boolean - Whether grid lines are shown across the chart scaleShowGridLines : true, //String - Colour of the grid lines scaleGridLineColor : "rgba(0,0,0,.05)", //Number - Width of the grid lines scaleGridLineWidth : 1, //Boolean - Whether to show horizontal lines (except X axis) scaleShowHorizontalLines: true, //Boolean - Whether to show vertical lines (except Y axis) scaleShowVerticalLines: true, //Boolean - Whether the line is curved between points bezierCurve : true, //Number - Tension of the bezier curve between points bezierCurveTension : 0.4, //Boolean - Whether to show a dot for each point pointDot : true, //Number - Radius of each point dot in pixels pointDotRadius : 4, //Number - Pixel width of point dot stroke pointDotStrokeWidth : 1, //Number - amount extra to add to the radius to cater for hit detection outside the drawn point pointHitDetectionRadius : 20, //Boolean - Whether to show a stroke for datasets datasetStroke : true, //Number - Pixel width of dataset stroke datasetStrokeWidth : 2, //Boolean - Whether to fill the dataset with a colour datasetFill : true, //String - A legend template legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>" }; Chart.Type.extend({ name: "Line", defaults : defaultConfig, initialize: function(data){ //Declare the extension of the default point, to cater for the options passed in to the constructor this.PointClass = Chart.Point.extend({ strokeWidth : this.options.pointDotStrokeWidth, radius : this.options.pointDotRadius, display: this.options.pointDot, hitDetectionRadius : this.options.pointHitDetectionRadius, ctx : this.chart.ctx, inRange : function(mouseX){ return (Math.pow(mouseX-this.x, 2) < Math.pow(this.radius + this.hitDetectionRadius,2)); } }); this.datasets = []; //Set up tooltip events on the chart if (this.options.showTooltips){ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ var activePoints = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : []; this.eachPoints(function(point){ point.restore(['fillColor', 'strokeColor']); }); helpers.each(activePoints, function(activePoint){ activePoint.fillColor = activePoint.highlightFill; activePoint.strokeColor = activePoint.highlightStroke; }); this.showTooltip(activePoints); }); } //Iterate through each of the datasets, and build this into a property of the chart helpers.each(data.datasets,function(dataset){ var datasetObject = { label : dataset.label || null, fillColor : dataset.fillColor, strokeColor : dataset.strokeColor, pointColor : dataset.pointColor, pointStrokeColor : dataset.pointStrokeColor, points : [] }; this.datasets.push(datasetObject); helpers.each(dataset.data,function(dataPoint,index){ //Add a new point for each piece of data, passing any required data to draw. datasetObject.points.push(new this.PointClass({ value : dataPoint, label : data.labels[index], datasetLabel: dataset.label, strokeColor : dataset.pointStrokeColor, fillColor : dataset.pointColor, highlightFill : dataset.pointHighlightFill || dataset.pointColor, highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor })); },this); this.buildScale(data.labels); this.eachPoints(function(point, index){ helpers.extend(point, { x: this.scale.calculateX(index), y: this.scale.endPoint }); point.save(); }, this); },this); this.render(); }, update : function(){ this.scale.update(); // Reset any highlight colours before updating. helpers.each(this.activeElements, function(activeElement){ activeElement.restore(['fillColor', 'strokeColor']); }); this.eachPoints(function(point){ point.save(); }); this.render(); }, eachPoints : function(callback){ helpers.each(this.datasets,function(dataset){ helpers.each(dataset.points,callback,this); },this); }, getPointsAtEvent : function(e){ var pointsArray = [], eventPosition = helpers.getRelativePosition(e); helpers.each(this.datasets,function(dataset){ helpers.each(dataset.points,function(point){ if (point.inRange(eventPosition.x,eventPosition.y)) pointsArray.push(point); }); },this); return pointsArray; }, buildScale : function(labels){ var self = this; var dataTotal = function(){ var values = []; self.eachPoints(function(point){ values.push(point.value); }); return values; }; var scaleOptions = { templateString : this.options.scaleLabel, height : this.chart.height, width : this.chart.width, ctx : this.chart.ctx, textColor : this.options.scaleFontColor, fontSize : this.options.scaleFontSize, fontStyle : this.options.scaleFontStyle, fontFamily : this.options.scaleFontFamily, valuesCount : labels.length, beginAtZero : this.options.scaleBeginAtZero, integersOnly : this.options.scaleIntegersOnly, calculateYRange : function(currentHeight){ var updatedRanges = helpers.calculateScaleRange( dataTotal(), currentHeight, this.fontSize, this.beginAtZero, this.integersOnly ); helpers.extend(this, updatedRanges); }, xLabels : labels, font : helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily), lineWidth : this.options.scaleLineWidth, lineColor : this.options.scaleLineColor, showHorizontalLines : this.options.scaleShowHorizontalLines, showVerticalLines : this.options.scaleShowVerticalLines, gridLineWidth : (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0, gridLineColor : (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)", padding: (this.options.showScale) ? 0 : this.options.pointDotRadius + this.options.pointDotStrokeWidth, showLabels : this.options.scaleShowLabels, display : this.options.showScale }; if (this.options.scaleOverride){ helpers.extend(scaleOptions, { calculateYRange: helpers.noop, steps: this.options.scaleSteps, stepValue: this.options.scaleStepWidth, min: this.options.scaleStartValue, max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) }); } this.scale = new Chart.Scale(scaleOptions); }, addData : function(valuesArray,label){ //Map the values array for each of the datasets helpers.each(valuesArray,function(value,datasetIndex){ //Add a new point for each piece of data, passing any required data to draw. this.datasets[datasetIndex].points.push(new this.PointClass({ value : value, label : label, x: this.scale.calculateX(this.scale.valuesCount+1), y: this.scale.endPoint, strokeColor : this.datasets[datasetIndex].pointStrokeColor, fillColor : this.datasets[datasetIndex].pointColor })); },this); this.scale.addXLabel(label); //Then re-render the chart. this.update(); }, removeData : function(){ this.scale.removeXLabel(); //Then re-render the chart. helpers.each(this.datasets,function(dataset){ dataset.points.shift(); },this); this.update(); }, reflow : function(){ var newScaleProps = helpers.extend({ height : this.chart.height, width : this.chart.width }); this.scale.update(newScaleProps); }, draw : function(ease){ var easingDecimal = ease || 1; this.clear(); var ctx = this.chart.ctx; // Some helper methods for getting the next/prev points var hasValue = function(item){ return item.value !== null; }, nextPoint = function(point, collection, index){ return helpers.findNextWhere(collection, hasValue, index) || point; }, previousPoint = function(point, collection, index){ return helpers.findPreviousWhere(collection, hasValue, index) || point; }; this.scale.draw(easingDecimal); helpers.each(this.datasets,function(dataset){ var pointsWithValues = helpers.where(dataset.points, hasValue); //Transition each point first so that the line and point drawing isn't out of sync //We can use this extra loop to calculate the control points of this dataset also in this loop helpers.each(dataset.points, function(point, index){ if (point.hasValue()){ point.transition({ y : this.scale.calculateY(point.value), x : this.scale.calculateX(index) }, easingDecimal); } },this); // Control points need to be calculated in a seperate loop, because we need to know the current x/y of the point // This would cause issues when there is no animation, because the y of the next point would be 0, so beziers would be skewed if (this.options.bezierCurve){ helpers.each(pointsWithValues, function(point, index){ var tension = (index > 0 && index < pointsWithValues.length - 1) ? this.options.bezierCurveTension : 0; point.controlPoints = helpers.splineCurve( previousPoint(point, pointsWithValues, index), point, nextPoint(point, pointsWithValues, index), tension ); // Prevent the bezier going outside of the bounds of the graph // Cap puter bezier handles to the upper/lower scale bounds if (point.controlPoints.outer.y > this.scale.endPoint){ point.controlPoints.outer.y = this.scale.endPoint; } else if (point.controlPoints.outer.y < this.scale.startPoint){ point.controlPoints.outer.y = this.scale.startPoint; } // Cap inner bezier handles to the upper/lower scale bounds if (point.controlPoints.inner.y > this.scale.endPoint){ point.controlPoints.inner.y = this.scale.endPoint; } else if (point.controlPoints.inner.y < this.scale.startPoint){ point.controlPoints.inner.y = this.scale.startPoint; } },this); } //Draw the line between all the points ctx.lineWidth = this.options.datasetStrokeWidth; ctx.strokeStyle = dataset.strokeColor; ctx.beginPath(); helpers.each(pointsWithValues, function(point, index){ if (index === 0){ ctx.moveTo(point.x, point.y); } else{ if(this.options.bezierCurve){ var previous = previousPoint(point, pointsWithValues, index); ctx.bezierCurveTo( previous.controlPoints.outer.x, previous.controlPoints.outer.y, point.controlPoints.inner.x, point.controlPoints.inner.y, point.x, point.y ); } else{ ctx.lineTo(point.x,point.y); } } }, this); ctx.stroke(); if (this.options.datasetFill && pointsWithValues.length > 0){ //Round off the line by going to the base of the chart, back to the start, then fill. ctx.lineTo(pointsWithValues[pointsWithValues.length - 1].x, this.scale.endPoint); ctx.lineTo(pointsWithValues[0].x, this.scale.endPoint); ctx.fillStyle = dataset.fillColor; ctx.closePath(); ctx.fill(); } //Now draw the points over the line //A little inefficient double looping, but better than the line //lagging behind the point positions helpers.each(pointsWithValues,function(point){ point.draw(); }); },this); } }); }).call(this); (function(){ "use strict"; var root = this, Chart = root.Chart, //Cache a local reference to Chart.helpers helpers = Chart.helpers; var defaultConfig = { //Boolean - Show a backdrop to the scale label scaleShowLabelBackdrop : true, //String - The colour of the label backdrop scaleBackdropColor : "rgba(255,255,255,0.75)", // Boolean - Whether the scale should begin at zero scaleBeginAtZero : true, //Number - The backdrop padding above & below the label in pixels scaleBackdropPaddingY : 2, //Number - The backdrop padding to the side of the label in pixels scaleBackdropPaddingX : 2, //Boolean - Show line for each value in the scale scaleShowLine : true, //Boolean - Stroke a line around each segment in the chart segmentShowStroke : true, //String - The colour of the stroke on each segement. segmentStrokeColor : "#fff", //Number - The width of the stroke value in pixels segmentStrokeWidth : 2, //Number - Amount of animation steps animationSteps : 100, //String - Animation easing effect. animationEasing : "easeOutBounce", //Boolean - Whether to animate the rotation of the chart animateRotate : true, //Boolean - Whether to animate scaling the chart from the centre animateScale : false, //String - A legend template legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<segments.length; i++){%><li><span style=\"background-color:<%=segments[i].fillColor%>\"></span><%if(segments[i].label){%><%=segments[i].label%><%}%></li><%}%></ul>" }; Chart.Type.extend({ //Passing in a name registers this chart in the Chart namespace name: "PolarArea", //Providing a defaults will also register the deafults in the chart namespace defaults : defaultConfig, //Initialize is fired when the chart is initialized - Data is passed in as a parameter //Config is automatically merged by the core of Chart.js, and is available at this.options initialize: function(data){ this.segments = []; //Declare segment class as a chart instance specific class, so it can share props for this instance this.SegmentArc = Chart.Arc.extend({ showStroke : this.options.segmentShowStroke, strokeWidth : this.options.segmentStrokeWidth, strokeColor : this.options.segmentStrokeColor, ctx : this.chart.ctx, innerRadius : 0, x : this.chart.width/2, y : this.chart.height/2 }); this.scale = new Chart.RadialScale({ display: this.options.showScale, fontStyle: this.options.scaleFontStyle, fontSize: this.options.scaleFontSize, fontFamily: this.options.scaleFontFamily, fontColor: this.options.scaleFontColor, showLabels: this.options.scaleShowLabels, showLabelBackdrop: this.options.scaleShowLabelBackdrop, backdropColor: this.options.scaleBackdropColor, backdropPaddingY : this.options.scaleBackdropPaddingY, backdropPaddingX: this.options.scaleBackdropPaddingX, lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0, lineColor: this.options.scaleLineColor, lineArc: true, width: this.chart.width, height: this.chart.height, xCenter: this.chart.width/2, yCenter: this.chart.height/2, ctx : this.chart.ctx, templateString: this.options.scaleLabel, valuesCount: data.length }); this.updateScaleRange(data); this.scale.update(); helpers.each(data,function(segment,index){ this.addData(segment,index,true); },this); //Set up tooltip events on the chart if (this.options.showTooltips){ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : []; helpers.each(this.segments,function(segment){ segment.restore(["fillColor"]); }); helpers.each(activeSegments,function(activeSegment){ activeSegment.fillColor = activeSegment.highlightColor; }); this.showTooltip(activeSegments); }); } this.render(); }, getSegmentsAtEvent : function(e){ var segmentsArray = []; var location = helpers.getRelativePosition(e); helpers.each(this.segments,function(segment){ if (segment.inRange(location.x,location.y)) segmentsArray.push(segment); },this); return segmentsArray; }, addData : function(segment, atIndex, silent){ var index = atIndex || this.segments.length; this.segments.splice(index, 0, new this.SegmentArc({ fillColor: segment.color, highlightColor: segment.highlight || segment.color, label: segment.label, value: segment.value, outerRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value), circumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(), startAngle: Math.PI * 1.5 })); if (!silent){ this.reflow(); this.update(); } }, removeData: function(atIndex){ var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1; this.segments.splice(indexToDelete, 1); this.reflow(); this.update(); }, calculateTotal: function(data){ this.total = 0; helpers.each(data,function(segment){ this.total += segment.value; },this); this.scale.valuesCount = this.segments.length; }, updateScaleRange: function(datapoints){ var valuesArray = []; helpers.each(datapoints,function(segment){ valuesArray.push(segment.value); }); var scaleSizes = (this.options.scaleOverride) ? { steps: this.options.scaleSteps, stepValue: this.options.scaleStepWidth, min: this.options.scaleStartValue, max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) } : helpers.calculateScaleRange( valuesArray, helpers.min([this.chart.width, this.chart.height])/2, this.options.scaleFontSize, this.options.scaleBeginAtZero, this.options.scaleIntegersOnly ); helpers.extend( this.scale, scaleSizes, { size: helpers.min([this.chart.width, this.chart.height]), xCenter: this.chart.width/2, yCenter: this.chart.height/2 } ); }, update : function(){ this.calculateTotal(this.segments); helpers.each(this.segments,function(segment){ segment.save(); }); this.reflow(); this.render(); }, reflow : function(){ helpers.extend(this.SegmentArc.prototype,{ x : this.chart.width/2, y : this.chart.height/2 }); this.updateScaleRange(this.segments); this.scale.update(); helpers.extend(this.scale,{ xCenter: this.chart.width/2, yCenter: this.chart.height/2 }); helpers.each(this.segments, function(segment){ segment.update({ outerRadius : this.scale.calculateCenterOffset(segment.value) }); }, this); }, draw : function(ease){ var easingDecimal = ease || 1; //Clear & draw the canvas this.clear(); helpers.each(this.segments,function(segment, index){ segment.transition({ circumference : this.scale.getCircumference(), outerRadius : this.scale.calculateCenterOffset(segment.value) },easingDecimal); segment.endAngle = segment.startAngle + segment.circumference; // If we've removed the first segment we need to set the first one to // start at the top. if (index === 0){ segment.startAngle = Math.PI * 1.5; } //Check to see if it's the last segment, if not get the next and update the start angle if (index < this.segments.length - 1){ this.segments[index+1].startAngle = segment.endAngle; } segment.draw(); }, this); this.scale.draw(); } }); }).call(this); (function(){ "use strict"; var root = this, Chart = root.Chart, helpers = Chart.helpers; Chart.Type.extend({ name: "Radar", defaults:{ //Boolean - Whether to show lines for each scale point scaleShowLine : true, //Boolean - Whether we show the angle lines out of the radar angleShowLineOut : true, //Boolean - Whether to show labels on the scale scaleShowLabels : false, // Boolean - Whether the scale should begin at zero scaleBeginAtZero : true, //String - Colour of the angle line angleLineColor : "rgba(0,0,0,.1)", //Number - Pixel width of the angle line angleLineWidth : 1, //String - Point label font declaration pointLabelFontFamily : "'Arial'", //String - Point label font weight pointLabelFontStyle : "normal", //Number - Point label font size in pixels pointLabelFontSize : 10, //String - Point label font colour pointLabelFontColor : "#666", //Boolean - Whether to show a dot for each point pointDot : true, //Number - Radius of each point dot in pixels pointDotRadius : 3, //Number - Pixel width of point dot stroke pointDotStrokeWidth : 1, //Number - amount extra to add to the radius to cater for hit detection outside the drawn point pointHitDetectionRadius : 20, //Boolean - Whether to show a stroke for datasets datasetStroke : true, //Number - Pixel width of dataset stroke datasetStrokeWidth : 2, //Boolean - Whether to fill the dataset with a colour datasetFill : true, //String - A legend template legendTemplate : "<ul class=\"<%=name.toLowerCase()%>-legend\"><% for (var i=0; i<datasets.length; i++){%><li><span style=\"background-color:<%=datasets[i].strokeColor%>\"></span><%if(datasets[i].label){%><%=datasets[i].label%><%}%></li><%}%></ul>" }, initialize: function(data){ this.PointClass = Chart.Point.extend({ strokeWidth : this.options.pointDotStrokeWidth, radius : this.options.pointDotRadius, display: this.options.pointDot, hitDetectionRadius : this.options.pointHitDetectionRadius, ctx : this.chart.ctx }); this.datasets = []; this.buildScale(data); //Set up tooltip events on the chart if (this.options.showTooltips){ helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : []; this.eachPoints(function(point){ point.restore(['fillColor', 'strokeColor']); }); helpers.each(activePointsCollection, function(activePoint){ activePoint.fillColor = activePoint.highlightFill; activePoint.strokeColor = activePoint.highlightStroke; }); this.showTooltip(activePointsCollection); }); } //Iterate through each of the datasets, and build this into a property of the chart helpers.each(data.datasets,function(dataset){ var datasetObject = { label: dataset.label || null, fillColor : dataset.fillColor, strokeColor : dataset.strokeColor, pointColor : dataset.pointColor, pointStrokeColor : dataset.pointStrokeColor, points : [] }; this.datasets.push(datasetObject); helpers.each(dataset.data,function(dataPoint,index){ //Add a new point for each piece of data, passing any required data to draw. var pointPosition; if (!this.scale.animation){ pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint)); } datasetObject.points.push(new this.PointClass({ value : dataPoint, label : data.labels[index], datasetLabel: dataset.label, x: (this.options.animation) ? this.scale.xCenter : pointPosition.x, y: (this.options.animation) ? this.scale.yCenter : pointPosition.y, strokeColor : dataset.pointStrokeColor, fillColor : dataset.pointColor, highlightFill : dataset.pointHighlightFill || dataset.pointColor, highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor })); },this); },this); this.render(); }, eachPoints : function(callback){ helpers.each(this.datasets,function(dataset){ helpers.each(dataset.points,callback,this); },this); }, getPointsAtEvent : function(evt){ var mousePosition = helpers.getRelativePosition(evt), fromCenter = helpers.getAngleFromPoint({ x: this.scale.xCenter, y: this.scale.yCenter }, mousePosition); var anglePerIndex = (Math.PI * 2) /this.scale.valuesCount, pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex), activePointsCollection = []; // If we're at the top, make the pointIndex 0 to get the first of the array. if (pointIndex >= this.scale.valuesCount || pointIndex < 0){ pointIndex = 0; } if (fromCenter.distance <= this.scale.drawingArea){ helpers.each(this.datasets, function(dataset){ activePointsCollection.push(dataset.points[pointIndex]); }); } return activePointsCollection; }, buildScale : function(data){ this.scale = new Chart.RadialScale({ display: this.options.showScale, fontStyle: this.options.scaleFontStyle, fontSize: this.options.scaleFontSize, fontFamily: this.options.scaleFontFamily, fontColor: this.options.scaleFontColor, showLabels: this.options.scaleShowLabels, showLabelBackdrop: this.options.scaleShowLabelBackdrop, backdropColor: this.options.scaleBackdropColor, backdropPaddingY : this.options.scaleBackdropPaddingY, backdropPaddingX: this.options.scaleBackdropPaddingX, lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0, lineColor: this.options.scaleLineColor, angleLineColor : this.options.angleLineColor, angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0, // Point labels at the edge of each line pointLabelFontColor : this.options.pointLabelFontColor, pointLabelFontSize : this.options.pointLabelFontSize, pointLabelFontFamily : this.options.pointLabelFontFamily, pointLabelFontStyle : this.options.pointLabelFontStyle, height : this.chart.height, width: this.chart.width, xCenter: this.chart.width/2, yCenter: this.chart.height/2, ctx : this.chart.ctx, templateString: this.options.scaleLabel, labels: data.labels, valuesCount: data.datasets[0].data.length }); this.scale.setScaleSize(); this.updateScaleRange(data.datasets); this.scale.buildYLabels(); }, updateScaleRange: function(datasets){ var valuesArray = (function(){ var totalDataArray = []; helpers.each(datasets,function(dataset){ if (dataset.data){ totalDataArray = totalDataArray.concat(dataset.data); } else { helpers.each(dataset.points, function(point){ totalDataArray.push(point.value); }); } }); return totalDataArray; })(); var scaleSizes = (this.options.scaleOverride) ? { steps: this.options.scaleSteps, stepValue: this.options.scaleStepWidth, min: this.options.scaleStartValue, max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) } : helpers.calculateScaleRange( valuesArray, helpers.min([this.chart.width, this.chart.height])/2, this.options.scaleFontSize, this.options.scaleBeginAtZero, this.options.scaleIntegersOnly ); helpers.extend( this.scale, scaleSizes ); }, addData : function(valuesArray,label){ //Map the values array for each of the datasets this.scale.valuesCount++; helpers.each(valuesArray,function(value,datasetIndex){ var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value)); this.datasets[datasetIndex].points.push(new this.PointClass({ value : value, label : label, x: pointPosition.x, y: pointPosition.y, strokeColor : this.datasets[datasetIndex].pointStrokeColor, fillColor : this.datasets[datasetIndex].pointColor })); },this); this.scale.labels.push(label); this.reflow(); this.update(); }, removeData : function(){ this.scale.valuesCount--; this.scale.labels.shift(); helpers.each(this.datasets,function(dataset){ dataset.points.shift(); },this); this.reflow(); this.update(); }, update : function(){ this.eachPoints(function(point){ point.save(); }); this.reflow(); this.render(); }, reflow: function(){ helpers.extend(this.scale, { width : this.chart.width, height: this.chart.height, size : helpers.min([this.chart.width, this.chart.height]), xCenter: this.chart.width/2, yCenter: this.chart.height/2 }); this.updateScaleRange(this.datasets); this.scale.setScaleSize(); this.scale.buildYLabels(); }, draw : function(ease){ var easeDecimal = ease || 1, ctx = this.chart.ctx; this.clear(); this.scale.draw(); helpers.each(this.datasets,function(dataset){ //Transition each point first so that the line and point drawing isn't out of sync helpers.each(dataset.points,function(point,index){ if (point.hasValue()){ point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal); } },this); //Draw the line between all the points ctx.lineWidth = this.options.datasetStrokeWidth; ctx.strokeStyle = dataset.strokeColor; ctx.beginPath(); helpers.each(dataset.points,function(point,index){ if (index === 0){ ctx.moveTo(point.x,point.y); } else{ ctx.lineTo(point.x,point.y); } },this); ctx.closePath(); ctx.stroke(); ctx.fillStyle = dataset.fillColor; ctx.fill(); //Now draw the points over the line //A little inefficient double looping, but better than the line //lagging behind the point positions helpers.each(dataset.points,function(point){ if (point.hasValue()){ point.draw(); } }); },this); } }); }).call(this);





The below is my html code in aspx page:





The below is my html code in aspx page:

<div class="widget">
                            <div class="widget-header ">
                                <span class="widget-caption">Doughnut Chart</span>
                                <div class="widget-buttons">
                                    <a href="#" data-toggle="collapse">
                                        <i class="fa fa-minus"></i>
                                    </a>
                                    <a href="#" data-toggle="dispose">
                                        <i class="fa fa-times"></i>
                                    </a>
                                </div>
                            </div>
                            <div class="widget-body">

                                <div class="chartcontainer">
                              <canvas id="doughnut" height="300"></canvas>
                                </div>
                            </div>
                        </div>







This works perfect but can any one say how can i add legend to the side of the Doughnut Chart pls help me.



What I have tried:



I do not know how to edit the jquery. I Can bring the data in json format. But Iam unable to know how to apply this on charts. The till now is a template where we purchased.




This works perfect but can any one say how can i add legend to the side of the Doughnut Chart pls help me.

What I have tried:

I do not know how to edit the jquery. I Can bring the data in json format. But Iam unable to know how to apply this on charts. The till now is a template where we purchased.


这篇关于如何在givin js文件中添加图例到HTML5圆环图的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆