diff --git a/Chart.js b/Chart.js index acb14e30c..5a1408d4f 100644 --- a/Chart.js +++ b/Chart.js @@ -9,1620 +9,1719 @@ */ -(function(){ +(function() { - "use strict"; + "use strict"; - //Declare root variable - window in the browser, global on the server - var root = this, - previous = root.Chart; + //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; + //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; + 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); - } - }; + //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') || context.canvas.width; - var height = this.height = computeDimension(context.canvas,'Height') || context.canvas.height; + var width = this.width = computeDimension(context.canvas, 'Width') || context.canvas.width; + var height = this.height = computeDimension(context.canvas, 'Height') || context.canvas.height; - // Firefox requires this to work correctly - context.canvas.width = width; - context.canvas.height = height; + // Firefox requires this to work correctly + context.canvas.width = width; + context.canvas.height = height; - width = this.width = context.canvas.width; - 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); + width = this.width = context.canvas.width; + 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, + 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 - animationDuration: 1000, + // Number - Number of animation steps + animationDuration: 1000, - // String - Animation easing effect - animationEasing: "easeOutQuart", + // String - Animation easing effect + animationEasing: "easeOutQuart", - // Boolean - If we should show the scale at all - showScale: true, + // Boolean - If we should show the scale at all + showScale: true, - // Boolean - If we want to override with a hard coded scale - scaleOverride: false, + // 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, + // ** 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)", + // String - Colour of the scale line + scaleLineColor: "rgba(0,0,0,.1)", - // Number - Pixel width of the scale line - scaleLineWidth: 1, + // Number - Pixel width of the scale line + scaleLineWidth: 1, - // Boolean - Whether to show labels on the scale - scaleShowLabels: true, + // Boolean - Whether to show labels on the scale + scaleShowLabels: true, - // Interpolated JS string - can access value - scaleLabel: "<%=value%>", + // 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 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, + // 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", + // 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, + // Number - Scale label font size in pixels + scaleFontSize: 12, - // String - Scale label font weight style - scaleFontStyle: "normal", + // String - Scale label font weight style + scaleFontStyle: "normal", - // String - Scale label font colour - scaleFontColor: "#666", + // 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 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 - 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, + //String / Boolean - Hover mode for events. + hoverMode: 'single', // 'label', 'dataset', 'false' - // 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 - tooltipBackgroundColor: "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: "<%if (datasetLabel){%><%=datasetLabel%>: <%}%><%= 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= 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)){ - var s = num.toString(); - if(s.indexOf("e-") < 0){ - // no exponent, e.g. 0.01 - return s.split(".")[1].length; - } - else if(s.indexOf(".") < 0) { - // no decimal point, e.g. 1e-9 - return parseInt(s.split("e-")[1]); - } - else { - // exponent and decimal point, e.g. 1.23e-9 - var parts = s.split(".")[1].split("e-"); - return parts[0].length + parseInt(parts[1]); - } - } - 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 (templateString){ - 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, - padding = parseInt(getStyle(container, 'padding-left')) + parseInt(getStyle(container, 'padding-right')); - // TODO = check cross browser stuff with this. - return container.clientWidth - padding; - }, - getMaximumHeight = helpers.getMaximumHeight = function(domNode){ - var container = domNode.parentNode, - padding = parseInt(getStyle(container, 'padding-bottom')) + parseInt(getStyle(container, 'padding-top')); - // TODO = check cross browser stuff with this. - return container.clientHeight - padding; - }, - getStyle = helpers.getStyle = function (el, property) { - return el.currentStyle ? - el.currentStyle[property] : - document.defaultView.getComputedStyle(el, null).getPropertyValue(property); - }, - 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(); - }, - color = helpers.color = function(color){ - if(!window.Color){ - console.log('Color.js not found!'); - return color; - } - return window.Color(color); - }, - isArray = helpers.isArray = function(obj){ - if (!Array.isArray) { - return Object.prototype.toString.call(arg) === '[object Array]'; - } - return Array.isArray(obj); - }; - - //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 - Chart.animationService.cancelAnimation(this); - 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, customDuration){ - if (reflow){ - this.reflow(); - } - - if (this.options.animation && !reflow){ - var animation = new Chart.Animation(); - animation.numSteps = (customDuration || this.options.animationDuration) / 16.66; //60 fps - animation.easing = this.options.animationEasing; - - // render function - animation.render = function(chartInstance, animationObject) { - var easingFunction = helpers.easingEffects[animationObject.easing]; - var stepDecimal = animationObject.currentStep / animationObject.numSteps; - var easeDecimal = easingFunction(stepDecimal); - - chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep); - }; - - // user events - animation.onAnimationProgress = this.options.onAnimationProgress; - animation.onAnimationComplete = this.options.onAnimationComplete; - - Chart.animationService.addAnimation(this, animation, customDuration); - } - 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]; - }, - 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,{ - _vm: {}, - }); - extend(this,configuration); - this.initialize.apply(this,arguments); - }; - extend(Chart.Element.prototype,{ - initialize : function(){}, - save: function(){ - this._vm = clone(this); - delete this._vm._vm; - delete this._vm._start; - return this; - }, - pivot: function(){ - if(this._start){ - this._start = clone(this); - helpers.extend(this._start, this._vm); - } - return this; - }, - transition : function(ease){ - if(!this._start){ - this._start = clone(this._vm); - } - - each(this,function(value, key){ - - // Only non-vm properties - if(key[0] === '_' || !this.hasOwnProperty(key)){ - return; - } - - // Init if doesn't exist - if(!this._vm[key]){ - this._vm[key] = value || null; - return; - } - - // No unnecessary computations - if(this[key] === this._vm[key]){ - return; - } - - // Color transitions if possible - if(typeof value === 'string'){ - try{ - var color = helpers.color(this._start[key]).mix(helpers.color(this[key]), ease); - this._vm[key] = color.rgbString(); - } catch(err){ - this._vm[key] = value; - } - } - // Number transitions - else if(typeof value === 'number'){ - this._vm[key] = ((this[key] - this._start[key]) * ease) + this._start[key]; - } - // Non-transitionals or strings - else{ - this._vm[key] = value; - } - - },this); - if(ease === 1){ - delete this._start; - } - 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.borderColor; - ctx.lineWidth = this.borderWidth; - - ctx.fillStyle = this.backgroundColor; - - 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.borderColor; - ctx.lineWidth = this.borderWidth; - - ctx.fillStyle = this.backgroundColor; - - ctx.fill(); - ctx.lineJoin = 'bevel'; - - if (this.showBorder){ - ctx.stroke(); - } - } - }); - - Chart.Rectangle = Chart.Element.extend({ - draw : function(){ - - var vm = this._vm; - - var ctx = this.ctx, - halfWidth = vm.width/2, - leftX = vm.x - halfWidth, - rightX = vm.x + halfWidth, - top = vm.base - (vm.base - vm.y), - halfStroke = vm.borderWidth / 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 (vm.borderWidth){ - leftX += halfStroke; - rightX -= halfStroke; - top += halfStroke; - } - - ctx.beginPath(); - - ctx.fillStyle = vm.backgroundColor; - ctx.strokeStyle = vm.borderColor; - ctx.lineWidth = vm.borderWidth; - - // It'd be nice to keep this class totally generic to any rectangle - // and simply specify which border to miss out. - ctx.moveTo(leftX, vm.base); - ctx.lineTo(leftX, top); - ctx.lineTo(rightX, top); - ctx.lineTo(rightX, vm.base); - ctx.fill(); - if (vm.borderWidth){ - ctx.stroke(); - } - }, - height : function(){ - var vm = this._vm; - return vm.base - vm.y; - }, - inRange : function(chartX,chartY){ - var vm = this._vm; - if (vm.y < vm.base){ - return (chartX >= vm.x - vm.width/2 && chartX <= vm.x + vm.width/2) && (chartY >= vm.y && chartY <= vm.base); - } else{ - return (chartX >= vm.x - vm.width / 2 && chartX <= vm.x + vm.width / 2) && (chartY >= vm.base && chartY <= vm.y); - } - }, - tooltipPosition : function(){ - var vm = this._vm; - if (vm.y < vm.base){ - return { - x : vm.x, - y : vm.y - }; - } - else{ - return { - x : vm.x, - y : vm.base - }; - } - }, - }); - - Chart.Animation = Chart.Element.extend({ - currentStep: null, // the current animation step - numSteps: 60, // default number of steps - easing: "", // the easing to use for this animation - render: null, // render function used by the animation service - - onAnimationProgress: null, // user specified callback to fire on each step of the animation - onAnimationComplete: null, // user specified callback to fire when the animation finishes - }); - - Chart.Tooltip = Chart.Element.extend({ - initialize : function(){ - var options = this._options; - extend(this, { - opacity:0, - xPadding: options.tooltipXPadding, - yPadding: options.tooltipYPadding, - xOffset: options.tooltipXOffset, - backgroundColor: options.tooltipBackgroundColor, - textColor: options.tooltipFontColor, - _fontFamily: options.tooltipFontFamily, - _fontStyle: options.tooltipFontStyle, - fontSize: options.tooltipFontSize, - titleTextColor: options.tooltipTitleFontColor, - _titleFontFamily: options.tooltipTitleFontFamily, - _titleFontStyle: options.tooltipTitleFontStyle, - titleFontSize: options.tooltipTitleFontSize, - caretHeight: options.tooltipCaretSize, - cornerRadius: options.tooltipCornerRadius, - legendColorBackground : options.multiTooltipKeyBackground, - labels: [], - colors: [], - }); - }, - update: function(){ - - var ctx = this._chart.ctx; - - switch(this._options.hoverMode){ - case 'single': - helpers.extend(this, { - text: template(this._options.tooltipTemplate, this._active), - }); - var tooltipPosition = this._active[0].tooltipPosition(); - helpers.extend(this.tooltip, { - x: Math.round(tooltipPosition.x), - y: Math.round(tooltipPosition.y), - }); - break; - - case 'label': - - // Tooltip Content - - var dataArray, - dataIndex; - - var labels = [], - colors = []; - - for (var i = this._data.datasets.length - 1; i >= 0; i--) { - dataArray = this._data.datasets[i].metaData; - dataIndex = indexOf(dataArray, this._active[0]); - if (dataIndex !== -1){ - break; - } - } - - var medianPosition = (function(index) { - // Get all the points at that particular index - var elements = [], - dataCollection, - xPositions = [], - yPositions = [], - xMax, - yMax, - xMin, - yMin; - helpers.each(this._data.datasets, function(dataset){ - dataCollection = dataset.metaData; - if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()){ - elements.push(dataCollection[dataIndex]); - } - }); - - helpers.each(elements, function(element) { - xPositions.push(element._vm.x); - yPositions.push(element._vm.y); - - //Include any colour information about the element - labels.push(helpers.template(this._options.multiTooltipTemplate, element)); - colors.push({ - fill: element._vm.backgroundColor, - stroke: element._vm.borderColor - }); - - }, 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); - - // Apply for now - helpers.extend(this, { - x: medianPosition.x, - y: medianPosition.y, - labels: labels, - title: this._active.length ? this._active[0].label : '', - legendColors: colors, - legendBackgroundColor : this._options.multiTooltipKeyBackground, - }); - - - // Calculate Appearance Tweaks - - this.height = (labels.length * this.fontSize) + ((labels.length-1) * (this.fontSize/2)) + (this.yPadding*2) + this.titleFontSize *1.5; - - var titleWidth = ctx.measureText(this.title).width, - //Label has a legend square as well so account for this. - labelWidth = longestText(ctx,this.font,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; - } - break; - } - - return this; - }, - draw : function(){ - - var ctx = this._chart.ctx; - var vm = this._vm; - - switch(this._options.hoverMode){ - case 'single': - - ctx.font = fontString(vm.fontSize,vm._fontStyle,vm._fontFamily); - - vm.xAlign = "center"; - vm.yAlign = "above"; - - //Distance between the actual element.y position and the start of the tooltip caret - var caretPadding = vm.caretPadding = 2; - - var tooltipWidth = ctx.measureText(vm.text).width + 2*vm.xPadding, - tooltipRectHeight = vm.fontSize + 2*vm.yPadding, - tooltipHeight = tooltipRectHeight + vm.caretHeight + caretPadding; - - if (vm.x + tooltipWidth/2 >this._chart.width){ - vm.xAlign = "left"; - } else if (vm.x - tooltipWidth/2 < 0){ - vm.xAlign = "right"; - } - - if (vm.y - tooltipHeight < 0){ - vm.yAlign = "below"; - } - - var tooltipX = vm.x - tooltipWidth/2, - tooltipY = vm.y - tooltipHeight; - - ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString(); - - // Custom Tooltips - if(this._custom){ - this._custom(this._vm); - } - else{ - switch(vm.yAlign){ - case "above": - //Draw a caret above the x/y - ctx.beginPath(); - ctx.moveTo(vm.x,vm.y - caretPadding); - ctx.lineTo(vm.x + vm.caretHeight, vm.y - (caretPadding + vm.caretHeight)); - ctx.lineTo(vm.x - vm.caretHeight, vm.y - (caretPadding + vm.caretHeight)); - ctx.closePath(); - ctx.fill(); - break; - case "below": - tooltipY = vm.y + caretPadding + vm.caretHeight; - //Draw a caret below the x/y - ctx.beginPath(); - ctx.moveTo(vm.x, vm.y + caretPadding); - ctx.lineTo(vm.x + vm.caretHeight, vm.y + caretPadding + vm.caretHeight); - ctx.lineTo(vm.x - vm.caretHeight, vm.y + caretPadding + vm.caretHeight); - ctx.closePath(); - ctx.fill(); - break; - } - - switch(vm.xAlign){ - case "left": - tooltipX = vm.x - tooltipWidth + (vm.cornerRadius + vm.caretHeight); - break; - case "right": - tooltipX = vm.x - (vm.cornerRadius + vm.caretHeight); - break; - } - - drawRoundedRectangle(ctx,tooltipX,tooltipY,tooltipWidth,tooltipRectHeight,vm.cornerRadius); - - ctx.fill(); - - ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString(); - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - ctx.fillText(vm.text, tooltipX + tooltipWidth/2, tooltipY + tooltipRectHeight/2); - - } - break; - case 'label': - - drawRoundedRectangle(ctx, vm.x, vm.y - vm.height/2, vm.width, vm.height, vm.cornerRadius); - ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString(); - ctx.fill(); - ctx.closePath(); - - ctx.textAlign = "left"; - ctx.textBaseline = "middle"; - ctx.fillStyle = helpers.color(vm.titleTextColor).alpha(vm.opacity).rgbString(); - ctx.font = fontString(vm.fontSize, vm._titleFontStyle, vm._titleFontFamily); - ctx.fillText(vm.title, vm.x + vm.xPadding, this.getLineHeight(0)); - - ctx.font = fontString(vm.fontSize, vm._fontStyle, vm._fontFamily); - helpers.each(vm.labels,function(label,index){ - ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString(); - ctx.fillText(label,vm.x + vm.xPadding + vm.fontSize + 3, this.getLineHeight(index + 1)); - - //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas) - //ctx.clearRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize/2, vm.fontSize, vm.fontSize); - //Instead we'll make a white filled block to put the legendColour palette over. - - ctx.fillStyle = helpers.color(vm.legendBackgroundColor).alpha(vm.opacity).rgbString(); - ctx.fillRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize / 2, vm.fontSize, vm.fontSize); - - ctx.fillStyle = helpers.color(vm.legendColors[index].fill).alpha(vm.opacity).rgbString(); - ctx.fillRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize / 2, vm.fontSize, vm.fontSize); - - - },this); - break; - } - }, - getLineHeight : function(index){ - var baseLineHeight = this._vm.y - (this._vm.height/2) + this._vm.yPadding, - afterTitleIndex = index-1; - - //If the index is zero, we're getting the title - if (index === 0){ - return baseLineHeight + this._vm.titleFontSize/2; - } else{ - return baseLineHeight + ((this._vm.fontSize*1.5*afterTitleIndex) + this._vm.fontSize/2) + this._vm.titleFontSize * 1.5; - } - - }, - }); - - 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) + 10 : 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 endpoint, excluding the space for x labels - var cachedEndPoint = this.endPoint; - - // 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 - /* + //Function - Custom hover handler + onHover: null, + + //Function - Custom hover handler + hoverAnimationDuration: 400, + + // 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 + tooltipBackgroundColor: "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: "<%if (datasetLabel){%><%=datasetLabel%>: <%}%><%= 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() {}, + + // Color String - Used for undefined Colros + colorFallback: 'rgba(0,0,0,0.1)', + + } + }; + + //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)) { + var s = num.toString(); + if (s.indexOf("e-") < 0) { + // no exponent, e.g. 0.01 + return s.split(".")[1].length; + } else if (s.indexOf(".") < 0) { + // no decimal point, e.g. 1e-9 + return parseInt(s.split("e-")[1]); + } else { + // exponent and decimal point, e.g. 1.23e-9 + var parts = s.split(".")[1].split("e-"); + return parts[0].length + parseInt(parts[1]); + } + } 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 { + next: { + x: MiddlePoint.x - fa * (AfterPoint.x - FirstPoint.x), + y: MiddlePoint.y - fa * (AfterPoint.y - FirstPoint.y) + }, + previous: { + 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 (templateString) { + 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, + padding = parseInt(getStyle(container, 'padding-left')) + parseInt(getStyle(container, 'padding-right')); + // TODO = check cross browser stuff with this. + return container.clientWidth - padding; + }, + getMaximumHeight = helpers.getMaximumHeight = function(domNode) { + var container = domNode.parentNode, + padding = parseInt(getStyle(container, 'padding-bottom')) + parseInt(getStyle(container, 'padding-top')); + // TODO = check cross browser stuff with this. + return container.clientHeight - padding; + }, + getStyle = helpers.getStyle = function(el, property) { + return el.currentStyle ? + el.currentStyle[property] : + document.defaultView.getComputedStyle(el, null).getPropertyValue(property); + }, + 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(); + }, + color = helpers.color = function(color) { + if (!window.Color) { + console.log('Color.js not found!'); + return color; + } + return window.Color(color); + }, + isArray = helpers.isArray = function(obj) { + if (!Array.isArray) { + return Object.prototype.toString.call(arg) === '[object Array]'; + } + return Array.isArray(obj); + }; + + //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 + Chart.animationService.cancelAnimation(this); + 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; + }, + redraw: noop, + render: function(duration) { + + if (this.options.animation) { + var animation = new Chart.Animation(); + animation.numSteps = (duration || this.options.animationDuration) / 16.66; //60 fps + animation.easing = this.options.animationEasing; + + // render function + animation.render = function(chartInstance, animationObject) { + var easingFunction = helpers.easingEffects[animationObject.easing]; + var stepDecimal = animationObject.currentStep / animationObject.numSteps; + var easeDecimal = easingFunction(stepDecimal); + + chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep); + }; + + // user events + animation.onAnimationProgress = this.options.onAnimationProgress; + animation.onAnimationComplete = this.options.onAnimationComplete; + + Chart.animationService.addAnimation(this, animation, duration); + } else { + this.draw(); + this.options.onAnimationComplete.call(this); + } + return this; + }, + eachElement: function(callback) { + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + helpers.each(dataset.metaData, callback, this, dataset.metaData, datasetIndex); + }, this); + }, + eachValue: function(callback) { + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + helpers.each(dataset.data, callback, this, datasetIndex); + }, this); + }, + eachDataset: function(callback) { + helpers.each(this.data.datasets, callback, this); + }, + getElementsAtEvent: function(e) { + var elementsArray = [], + eventPosition = helpers.getRelativePosition(e), + datasetIterator = function(dataset) { + elementsArray.push(dataset.metaData[elementIndex]); + }, + elementIndex; + + for (var datasetIndex = 0; datasetIndex < this.data.datasets.length; datasetIndex++) { + for (elementIndex = 0; elementIndex < this.data.datasets[datasetIndex].metaData.length; elementIndex++) { + if (this.data.datasets[datasetIndex].metaData[elementIndex].inGroupRange(eventPosition.x, eventPosition.y)) { + helpers.each(this.data.datasets, datasetIterator); + } + } + } + + return elementsArray.length ? elementsArray : []; + }, + // Get the single element that was clicked on + // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was drawn + getElementAtEvent: function(e) { + var element = []; + var eventPosition = helpers.getRelativePosition(e); + + for (var datasetIndex = 0; datasetIndex < this.data.datasets.length; ++datasetIndex) { + for (var elementIndex = 0; elementIndex < this.data.datasets[datasetIndex].metaData.length; ++elementIndex) { + if (this.data.datasets[datasetIndex].metaData[elementIndex].inRange(eventPosition.x, eventPosition.y)) { + element.push(this.data.datasets[datasetIndex].metaData[elementIndex]); + return element; + } + } + } + + return []; + }, + 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]; + }, + 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, { + _vm: {}, + }); + extend(this, configuration); + this.initialize.apply(this, arguments); + }; + extend(Chart.Element.prototype, { + initialize: function() {}, + save: function() { + this._vm = clone(this); + delete this._vm._vm; + delete this._vm._start; + return this; + }, + pivot: function() { + if (this._start) { + this._start = clone(this); + helpers.extend(this._start, this._vm); + } + return this; + }, + transition: function(ease) { + if (!this._start) { + if (!this._vm) { + this.save(); + } + this._start = clone(this._vm); + } + + each(this, function(value, key) { + + if (key[0] === '_' || !this.hasOwnProperty(key)) { + // Only non-underscored properties + } + + // Init if doesn't exist + else if (!this._vm[key]) { + this._vm[key] = value || null; + } + + // No unnecessary computations + else if (this[key] === this._vm[key]) { + // It's the same! Woohoo! + } + + // Color transitions if possible + else if (typeof value === 'string') { + try { + var color = helpers.color(this._start[key]).mix(helpers.color(this[key]), ease); + this._vm[key] = color.rgbString(); + } catch (err) { + this._vm[key] = value; + } + } + // Number transitions + else if (typeof value === 'number') { + + this._vm[key] = ((this[key] - this._start[key]) * ease) + this._start[key]; + } else { + // Everything else + this._vm[key] = value; + } + + }, this); + + if (ease === 1) { + delete this._start; + } + 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({ + inRange: function(mouseX, mouseY) { + var vm = this._vm; + var hoverRange = vm.hoverRadius + vm.radius; + return ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(hoverRange, 2)); + }, + inGroupRange: function(mouseX) { + var vm = this._vm; + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + this.hoverRadius, 2)); + }, + tooltipPosition: function() { + var vm = this._vm; + return { + x: vm.x, + y: vm.y + }; + }, + draw: function() { + + var vm = this._vm; + var ctx = this._chart.ctx; + + if (vm.radius > 0 || vm.borderWidth > 0) { + + ctx.beginPath(); + + ctx.arc(vm.x, vm.y, vm.radius, 0, Math.PI * 2); + ctx.closePath(); + + ctx.strokeStyle = vm.borderColor || Chart.defaults.global.colorFallback; + ctx.lineWidth = vm.borderWidth || Chart.defaults.global.colorFallback; + + ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.colorFallback; + + ctx.fill(); + ctx.stroke(); + } + } + }); + + + Chart.Line = Chart.Element.extend({ + draw: function() { + + var vm = this._vm; + var ctx = this._chart.ctx; + + //Draw the line between all the points + ctx.lineWidth = vm.borderWidth || Chart.defaults.global.colorFallback; + ctx.strokeStyle = vm.borderColor || Chart.defaults.global.colorFallback; + ctx.beginPath(); + + helpers.each(vm._points, function(point, index) { + if (index === 0) { + ctx.moveTo(point._vm.x, point._vm.y); + } else { + if (vm._tension > 0 || 1) { + var previous = this.previousPoint(point, vm._points, index); + + ctx.bezierCurveTo( + previous._vm.controlPointNextX, + previous._vm.controlPointNextY, + point._vm.controlPointPreviousX, + point._vm.controlPointPreviousY, + point._vm.x, + point._vm.y + ); + } else { + ctx.lineTo(point._vm.x, point._vm.y); + } + } + }, this); + + ctx.stroke(); + + if (vm._points.length > 0) { + //Round off the line by going to the base of the chart, back to the start, then fill. + ctx.lineTo(vm._points[vm._points.length - 1].x, vm.scaleBottom); + ctx.lineTo(vm._points[0].x, vm.scaleBottom); + ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.colorFallback; + ctx.closePath(); + ctx.fill(); + } + }, + previousPoint: function(point, collection, index) { + return helpers.findPreviousWhere(collection, function() { + return true; + }, index) || point; + }, + }); + + 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() { + + var ctx = this._chart.ctx; + var vm = this._vm; + + ctx.beginPath(); + + ctx.arc(vm.x, vm.y, vm.outerRadius, vm.startAngle, vm.endAngle); + + ctx.arc(vm.x, vm.y, vm.innerRadius, vm.endAngle, vm.startAngle, true); + + ctx.closePath(); + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = vm.borderWidth; + + ctx.fillStyle = vm.backgroundColor; + + ctx.fill(); + ctx.lineJoin = 'bevel'; + + if (vm.borderWidth) { + ctx.stroke(); + } + } + }); + + Chart.Rectangle = Chart.Element.extend({ + draw: function() { + + var vm = this._vm; + + var ctx = this.ctx, + halfWidth = vm.width / 2, + leftX = vm.x - halfWidth, + rightX = vm.x + halfWidth, + top = vm.base - (vm.base - vm.y), + halfStroke = vm.borderWidth / 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 (vm.borderWidth) { + leftX += halfStroke; + rightX -= halfStroke; + top += halfStroke; + } + + ctx.beginPath(); + + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = vm.borderWidth; + + // It'd be nice to keep this class totally generic to any rectangle + // and simply specify which border to miss out. + ctx.moveTo(leftX, vm.base); + ctx.lineTo(leftX, top); + ctx.lineTo(rightX, top); + ctx.lineTo(rightX, vm.base); + ctx.fill(); + if (vm.borderWidth) { + ctx.stroke(); + } + }, + height: function() { + var vm = this._vm; + return vm.base - vm.y; + }, + inRange: function(mouseX, mouseY) { + var vm = this._vm; + if (vm.y < vm.base) { + return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base); + } else { + return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y); + } + }, + inGroupRange: function(mouseX) { + var vm = this._vm; + return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2); + }, + tooltipPosition: function() { + var vm = this._vm; + if (vm.y < vm.base) { + return { + x: vm.x, + y: vm.y + }; + } else { + return { + x: vm.x, + y: vm.base + }; + } + }, + }); + + Chart.Animation = Chart.Element.extend({ + currentStep: null, // the current animation step + numSteps: 60, // default number of steps + easing: "", // the easing to use for this animation + render: null, // render function used by the animation service + + onAnimationProgress: null, // user specified callback to fire on each step of the animation + onAnimationComplete: null, // user specified callback to fire when the animation finishes + }); + + Chart.Tooltip = Chart.Element.extend({ + initialize: function() { + var options = this._options; + extend(this, { + opacity: 0, + xPadding: options.tooltipXPadding, + yPadding: options.tooltipYPadding, + xOffset: options.tooltipXOffset, + backgroundColor: options.tooltipBackgroundColor, + textColor: options.tooltipFontColor, + _fontFamily: options.tooltipFontFamily, + _fontStyle: options.tooltipFontStyle, + fontSize: options.tooltipFontSize, + titleTextColor: options.tooltipTitleFontColor, + _titleFontFamily: options.tooltipTitleFontFamily, + _titleFontStyle: options.tooltipTitleFontStyle, + titleFontSize: options.tooltipTitleFontSize, + caretHeight: options.tooltipCaretSize, + cornerRadius: options.tooltipCornerRadius, + legendColorBackground: options.multiTooltipKeyBackground, + labels: [], + colors: [], + }); + }, + update: function() { + + var ctx = this._chart.ctx; + + switch (this._options.hoverMode) { + case 'single': + helpers.extend(this, { + text: template(this._options.tooltipTemplate, this._active[0]), + }); + var tooltipPosition = this._active[0].tooltipPosition(); + helpers.extend(this, { + x: Math.round(tooltipPosition.x), + y: Math.round(tooltipPosition.y), + }); + break; + + case 'label': + + // Tooltip Content + + var dataArray, + dataIndex; + + var labels = [], + colors = []; + + for (var i = this._data.datasets.length - 1; i >= 0; i--) { + dataArray = this._data.datasets[i].metaData; + dataIndex = indexOf(dataArray, this._active[0]); + if (dataIndex !== -1) { + break; + } + } + + var medianPosition = (function(index) { + // Get all the points at that particular index + var elements = [], + dataCollection, + xPositions = [], + yPositions = [], + xMax, + yMax, + xMin, + yMin; + helpers.each(this._data.datasets, function(dataset) { + dataCollection = dataset.metaData; + if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()) { + elements.push(dataCollection[dataIndex]); + } + }); + + helpers.each(elements, function(element) { + xPositions.push(element._vm.x); + yPositions.push(element._vm.y); + + //Include any colour information about the element + labels.push(helpers.template(this._options.multiTooltipTemplate, element)); + colors.push({ + fill: element._vm.backgroundColor, + stroke: element._vm.borderColor + }); + + }, 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); + + // Apply for now + helpers.extend(this, { + x: medianPosition.x, + y: medianPosition.y, + labels: labels, + title: this._active.length ? this._active[0].label : '', + legendColors: colors, + legendBackgroundColor: this._options.multiTooltipKeyBackground, + }); + + + // Calculate Appearance Tweaks + + this.height = (labels.length * this.fontSize) + ((labels.length - 1) * (this.fontSize / 2)) + (this.yPadding * 2) + this.titleFontSize * 1.5; + + var titleWidth = ctx.measureText(this.title).width, + //Label has a legend square as well so account for this. + labelWidth = longestText(ctx, this.font, 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; + } + break; + } + + return this; + }, + draw: function() { + + var ctx = this._chart.ctx; + var vm = this._vm; + + switch (this._options.hoverMode) { + case 'single': + + ctx.font = fontString(vm.fontSize, vm._fontStyle, vm._fontFamily); + + vm.xAlign = "center"; + vm.yAlign = "above"; + + //Distance between the actual element.y position and the start of the tooltip caret + var caretPadding = vm.caretPadding = 2; + + var tooltipWidth = ctx.measureText(vm.text).width + 2 * vm.xPadding, + tooltipRectHeight = vm.fontSize + 2 * vm.yPadding, + tooltipHeight = tooltipRectHeight + vm.caretHeight + caretPadding; + + if (vm.x + tooltipWidth / 2 > this._chart.width) { + vm.xAlign = "left"; + } else if (vm.x - tooltipWidth / 2 < 0) { + vm.xAlign = "right"; + } + + if (vm.y - tooltipHeight < 0) { + vm.yAlign = "below"; + } + + var tooltipX = vm.x - tooltipWidth / 2, + tooltipY = vm.y - tooltipHeight; + + ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString(); + + // Custom Tooltips + if (this._custom) { + this._custom(this._vm); + } else { + switch (vm.yAlign) { + case "above": + //Draw a caret above the x/y + ctx.beginPath(); + ctx.moveTo(vm.x, vm.y - caretPadding); + ctx.lineTo(vm.x + vm.caretHeight, vm.y - (caretPadding + vm.caretHeight)); + ctx.lineTo(vm.x - vm.caretHeight, vm.y - (caretPadding + vm.caretHeight)); + ctx.closePath(); + ctx.fill(); + break; + case "below": + tooltipY = vm.y + caretPadding + vm.caretHeight; + //Draw a caret below the x/y + ctx.beginPath(); + ctx.moveTo(vm.x, vm.y + caretPadding); + ctx.lineTo(vm.x + vm.caretHeight, vm.y + caretPadding + vm.caretHeight); + ctx.lineTo(vm.x - vm.caretHeight, vm.y + caretPadding + vm.caretHeight); + ctx.closePath(); + ctx.fill(); + break; + } + + switch (vm.xAlign) { + case "left": + tooltipX = vm.x - tooltipWidth + (vm.cornerRadius + vm.caretHeight); + break; + case "right": + tooltipX = vm.x - (vm.cornerRadius + vm.caretHeight); + break; + } + + drawRoundedRectangle(ctx, tooltipX, tooltipY, tooltipWidth, tooltipRectHeight, vm.cornerRadius); + + ctx.fill(); + + ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString(); + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText(vm.text, tooltipX + tooltipWidth / 2, tooltipY + tooltipRectHeight / 2); + + } + break; + case 'label': + + drawRoundedRectangle(ctx, vm.x, vm.y - vm.height / 2, vm.width, vm.height, vm.cornerRadius); + ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString(); + ctx.fill(); + ctx.closePath(); + + ctx.textAlign = "left"; + ctx.textBaseline = "middle"; + ctx.fillStyle = helpers.color(vm.titleTextColor).alpha(vm.opacity).rgbString(); + ctx.font = fontString(vm.fontSize, vm._titleFontStyle, vm._titleFontFamily); + ctx.fillText(vm.title, vm.x + vm.xPadding, this.getLineHeight(0)); + + ctx.font = fontString(vm.fontSize, vm._fontStyle, vm._fontFamily); + helpers.each(vm.labels, function(label, index) { + ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString(); + ctx.fillText(label, vm.x + vm.xPadding + vm.fontSize + 3, this.getLineHeight(index + 1)); + + //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas) + //ctx.clearRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize/2, vm.fontSize, vm.fontSize); + //Instead we'll make a white filled block to put the legendColour palette over. + + ctx.fillStyle = helpers.color(vm.legendBackgroundColor).alpha(vm.opacity).rgbString(); + ctx.fillRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize / 2, vm.fontSize, vm.fontSize); + + ctx.fillStyle = helpers.color(vm.legendColors[index].fill).alpha(vm.opacity).rgbString(); + ctx.fillRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize / 2, vm.fontSize, vm.fontSize); + + + }, this); + break; + } + }, + getLineHeight: function(index) { + var baseLineHeight = this._vm.y - (this._vm.height / 2) + this._vm.yPadding, + afterTitleIndex = index - 1; + + //If the index is zero, we're getting the title + if (index === 0) { + return baseLineHeight + this._vm.titleFontSize / 2; + } else { + return baseLineHeight + ((this._vm.fontSize * 1.5 * afterTitleIndex) + this._vm.fontSize / 2) + this._vm.titleFontSize * 1.5; + } + + }, + }); + + 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) + 10 : 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 endpoint, excluding the space for x labels + var cachedEndPoint = this.endPoint; + + // 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; @@ -1631,1675 +1730,1659 @@ 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.endPoint = cachedEndPoint; - 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) ? firstWidth/2 : this.yLabelWidth; - - 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){ - 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 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= 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); - } - } - } - } - }); - - Chart.animationService = { - frameDuration: 17, - animations: [], - dropFrames: 0, - addAnimation: function(chartInstance, animationObject, customDuration) { - - if(!customDuration){ - chartInstance.animating = true; - } - - for (var index = 0; index < this.animations.length; ++ index){ - if (this.animations[index].chartInstance === chartInstance){ - // replacing an in progress animation - this.animations[index].animationObject = animationObject; - return; - } - } - - this.animations.push({ - chartInstance: chartInstance, - animationObject: animationObject - }); - - // If there are no animations queued, manually kickstart a digest, for lack of a better word - if (this.animations.length == 1) { - helpers.requestAnimFrame.call(window, this.digestWrapper); - } - }, - // Cancel the animation for a given chart instance - cancelAnimation: function(chartInstance) { - var index = helpers.findNextWhere(this.animations, function(animationWrapper) { - return animationWrapper.chartInstance === chartInstance; - }); - - if (index){ - this.animations.splice(index, 1); - chartInstance.animating = false; - } - }, - // calls startDigest with the proper context - digestWrapper: function() { - Chart.animationService.startDigest.call(Chart.animationService); - }, - startDigest: function() { - - var startTime = Date.now(); - var framesToDrop = 0; - - if(this.dropFrames > 1){ - framesToDrop = Math.floor(this.dropFrames); - this.dropFrames -= framesToDrop; - } - - for (var i = 0; i < this.animations.length; i++) { - - if (this.animations[i].animationObject.currentStep === null){ - this.animations[i].animationObject.currentStep = 0; - } - - this.animations[i].animationObject.currentStep += 1 + framesToDrop; - if(this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps){ - this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps; - } - - this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject); - - if (this.animations[i].animationObject.currentStep == this.animations[i].animationObject.numSteps){ - // executed the last frame. Remove the animation. - this.animations[i].chartInstance.animating = false; - this.animations.splice(i, 1); - // Keep the index in place to offset the splice - i--; - } - } - - var endTime = Date.now(); - var delay = endTime - startTime - this.frameDuration; - var frameDelay = delay / this.frameDuration; - - if(frameDelay > 1){ - this.dropFrames += frameDelay; - } - - // Do we have more stuff to animate? - if (this.animations.length > 0){ - helpers.requestAnimFrame.call(window, this.digestWrapper); - } - } - }; - - // 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; - }; + 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.endPoint = cachedEndPoint; + 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) ? firstWidth / 2 : this.yLabelWidth; + + 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) { + 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); + } + } + } + } + }); + + Chart.animationService = { + frameDuration: 17, + animations: [], + dropFrames: 0, + addAnimation: function(chartInstance, animationObject, duration) { + + if (!duration) { + chartInstance.animating = true; + } + + for (var index = 0; index < this.animations.length; ++index) { + if (this.animations[index].chartInstance === chartInstance) { + // replacing an in progress animation + this.animations[index].animationObject = animationObject; + return; + } + } + + this.animations.push({ + chartInstance: chartInstance, + animationObject: animationObject + }); + + // If there are no animations queued, manually kickstart a digest, for lack of a better word + if (this.animations.length == 1) { + helpers.requestAnimFrame.call(window, this.digestWrapper); + } + }, + // Cancel the animation for a given chart instance + cancelAnimation: function(chartInstance) { + var index = helpers.findNextWhere(this.animations, function(animationWrapper) { + return animationWrapper.chartInstance === chartInstance; + }); + + if (index) { + this.animations.splice(index, 1); + chartInstance.animating = false; + } + }, + // calls startDigest with the proper context + digestWrapper: function() { + Chart.animationService.startDigest.call(Chart.animationService); + }, + startDigest: function() { + + var startTime = Date.now(); + var framesToDrop = 0; + + if (this.dropFrames > 1) { + framesToDrop = Math.floor(this.dropFrames); + this.dropFrames -= framesToDrop; + } + + for (var i = 0; i < this.animations.length; i++) { + + if (this.animations[i].animationObject.currentStep === null) { + this.animations[i].animationObject.currentStep = 0; + } + + this.animations[i].animationObject.currentStep += 1 + framesToDrop; + if (this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps) { + this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps; + } + + this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject); + + if (this.animations[i].animationObject.currentStep == this.animations[i].animationObject.numSteps) { + // executed the last frame. Remove the animation. + this.animations[i].chartInstance.animating = false; + this.animations.splice(i, 1); + // Keep the index in place to offset the splice + i--; + } + } + + var endTime = Date.now(); + var delay = endTime - startTime - this.frameDuration; + var frameDelay = delay / this.frameDuration; + + if (frameDelay > 1) { + this.dropFrames += frameDelay; + } + + // Do we have more stuff to animate? + if (this.animations.length > 0) { + helpers.requestAnimFrame.call(window, this.digestWrapper); + } + } + }; + + // 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"; +(function() { + "use strict"; - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; + 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, + 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, + //Boolean - Whether grid lines are shown across the chart + scaleShowGridLines: true, - //String - Colour of the grid lines - scaleGridLineColor : "rgba(0,0,0,.05)", + //String - Colour of the grid lines + scaleGridLineColor: "rgba(0,0,0,.05)", - //Number - Width of the grid lines - scaleGridLineWidth : 1, + //Number - Width of the grid lines + scaleGridLineWidth: 1, - //Boolean - Whether to show horizontal lines (except X axis) - scaleShowHorizontalLines: true, + //Boolean - Whether to show horizontal lines (except X axis) + scaleShowHorizontalLines: true, - //Boolean - Whether to show vertical lines (except Y axis) - scaleShowVerticalLines: true, + //Boolean - Whether to show vertical lines (except Y axis) + scaleShowVerticalLines: true, - //Number - Pixel width of the bar border - barBorderWidth : 2, + //Number - Pixel width of the bar border + barBorderWidth: 2, - //Number - Spacing between each of the X value sets - barValueSpacing : 5, + //Number - Spacing between each of the X value sets + barValueSpacing: 5, - //Number - Spacing between data sets within X values - barDatasetSpacing : 1, + //Number - Spacing between data sets within X values + barDatasetSpacing: 1, - //String / Boolean - Hover mode for events. - hoverMode : 'single', // 'label', 'dataset', 'false' + //String - A legend template + legendTemplate: "
    -legend\"><% for (var i=0; i
  • \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
" - //Function - Custom hover handler - onHover : null, - - //Function - Custom hover handler - hoverAnimationDuration : 400, - - //String - A legend template - legendTemplate : "
    -legend\"><% for (var i=0; i
  • \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
" - - }; + }; - Chart.Type.extend({ - name: "Bar", - defaults : defaultConfig, - initialize: function(data){ + Chart.Type.extend({ + name: "Bar", + defaults: defaultConfig, + initialize: function(data) { - // Save data as a source for updating of values & methods - this.data = data; + // Save data as a source for updating of values & methods + this.data = data; - //Expose options as a scope variable here so we can access it in the ScaleClass - var options = this.options; + var options = this.options; - this.ScaleClass = Chart.Scale.extend({ - offsetGridLines : true, - calculateBarX : function(datasetCount, datasetIndex, elementIndex){ - //Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar - var xWidth = this.calculateBaseWidth(), - xAbsolute = this.calculateX(elementIndex) - (xWidth/2), - barWidth = this.calculateBarWidth(datasetCount); + // Custom Scale Methods and Options + this.ScaleClass = Chart.Scale.extend({ + offsetGridLines: true, + calculateBarX: function(datasetCount, datasetIndex, elementIndex) { + //Reusable method for calculating the xPosition of a given bar based on datasetIndex & width of the bar + var xWidth = this.calculateBaseWidth(), + xAbsolute = this.calculateX(elementIndex) - (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 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); - } - }); + return (baseWidth / datasetCount); + } + }); - // Events - helpers.bindEvents(this, this.options.tooltipEvents, this.onHover); - - //Declare the extension of the default point, to cater for the options passed in to the constructor - this.BarClass = Chart.Rectangle.extend({ - ctx : this.chart.ctx, - }); + // Events + helpers.bindEvents(this, this.options.tooltipEvents, this.onHover); - // Build Scale - this.buildScale(data.labels); + //Declare the extension of the default point, to cater for the options passed in to the constructor + this.BarClass = Chart.Rectangle.extend({ + ctx: this.chart.ctx, + }); - //Create a new bar for each piece of data - helpers.each(this.data.datasets,function(dataset,datasetIndex){ - dataset.metaData = []; - helpers.each(dataset.data,function(dataPoint,index){ - dataset.metaData.push(new this.BarClass()); - },this); - },this); + // Build Scale + this.buildScale(this.data.labels); - // Set defaults for bars - this.eachBars(function(bar, index, datasetIndex){ - helpers.extend(bar, { - width : this.scale.calculateBarWidth(this.data.datasets.length), - x: this.scale.calculateBarX(this.data.datasets.length, datasetIndex, index), - y: this.calculateBarBase(), - _datasetIndex: datasetIndex, - _index: index, - }); - // Copy to view model - bar.save(); - }, this); + //Create a new bar for each piece of data + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + dataset.metaData = []; + helpers.each(dataset.data, function(dataPoint, index) { + dataset.metaData.push(new this.BarClass()); + }, this); + }, this); - // Create tooltip instance exclusively for this chart with some defaults. - this.tooltip = new Chart.Tooltip({ - _chart: this.chart, - _data: this.data, - _options: this.options, - }, this); + // Set defaults for bars + this.eachElement(function(bar, index, datasetIndex) { + helpers.extend(bar, { + width: this.scale.calculateBarWidth(this.data.datasets.length), + x: this.scale.calculateBarX(this.data.datasets.length, datasetIndex, index), + y: this.calculateBaseY(), + _datasetIndex: datasetIndex, + _index: index, + }); + // Copy to view model + bar.save(); + }, this); - // Update the chart with the latest data. - this.update(); - }, - onHover: function(e){ + // Create tooltip instance exclusively for this chart with some defaults. + this.tooltip = new Chart.Tooltip({ + _chart: this.chart, + _data: this.data, + _options: this.options, + }, this); + + // Update the chart with the latest data. + this.update(); + }, + onHover: function(e) { - // If exiting chart - if(e.type == 'mouseout'){ - return false; - } + // If exiting chart + if (e.type == 'mouseout') { + return this; + } - this.lastActive = this.lastActive || []; + this.lastActive = this.lastActive || []; - // Find Active Elements - this.active = function(){ - switch(this.options.hoverMode){ - case 'single': - return this.getBarAtEvent(e); - case 'label': - return this.getBarsAtEvent(e); - case 'dataset': - return this.getDatasetAtEvent(e); - default: - return e; - } - }.call(this); + // Find Active Elements + this.active = function() { + switch (this.options.hoverMode) { + case 'single': + return this.getElementAtEvent(e); + case 'label': + return this.getElementsAtEvent(e); + case 'dataset': + return this.getDatasetAtEvent(e); + default: + return e; + } + }.call(this); - // On Hover hook - if(this.options.onHover){ - this.options.onHover.call(this, this.active); - } + // On Hover hook + if (this.options.onHover) { + this.options.onHover.call(this, this.active); + } - // Remove styling for last active (even if it may still be active) - if(this.lastActive){ - switch(this.options.hoverMode){ - case 'single': - this.lastActive[0].backgroundColor = this.data.datasets[this.lastActive[0]._datasetIndex].backgroundColor; - this.lastActive[0].borderColor = this.data.datasets[this.lastActive[0]._datasetIndex].borderColor; - this.lastActive[0].borderWidth = 0; - break; - case 'label': - for (var i = 0; i < this.lastActive.length; i++) { - this.lastActive[i].backgroundColor = this.data.datasets[this.lastActive[i]._datasetIndex].backgroundColor; - this.lastActive[i].borderColor = this.data.datasets[this.lastActive[i]._datasetIndex].borderColor; - this.lastActive[i].borderWidth = 0; - } - break; - case 'dataset': - break; - default: - // do nothing - } - } - - // Built in hover actions - if(this.active.length && this.options.hoverMode){ - switch(this.options.hoverMode){ - case 'single': - this.active[0].backgroundColor = this.data.datasets[this.active[0]._datasetIndex].hoverBackgroundColor || helpers.color(this.active[0].backgroundColor).saturate(0.5).darken(0.35).rgbString(); - this.active[0].borderColor = this.data.datasets[this.active[0]._datasetIndex].hoverBorderColor || helpers.color(this.active[0].borderColor).saturate(0.5).darken(0.35).rgbString(); - break; - case 'label': - for (var i = 0; i < this.active.length; i++) { - this.active[i].backgroundColor = this.data.datasets[this.active[i]._datasetIndex].hoverBackgroundColor || helpers.color(this.active[i].backgroundColor).saturate(0.5).darken(0.35).rgbString(); - this.active[i].borderColor = this.data.datasets[this.active[i]._datasetIndex].hoverBorderColor || helpers.color(this.active[i].borderColor).saturate(0.5).darken(0.35).rgbString(); - } - break; - case 'dataset': - break; - default: - // do nothing - } - } + // Remove styling for last active (even if it may still be active) + if (this.lastActive.length) { + switch (this.options.hoverMode) { + case 'single': + this.lastActive[0].backgroundColor = this.data.datasets[this.lastActive[0]._datasetIndex].backgroundColor; + this.lastActive[0].borderColor = this.data.datasets[this.lastActive[0]._datasetIndex].borderColor; + break; + case 'label': + for (var i = 0; i < this.lastActive.length; i++) { + this.lastActive[i].backgroundColor = this.data.datasets[this.lastActive[i]._datasetIndex].backgroundColor; + this.lastActive[i].borderColor = this.data.datasets[this.lastActive[i]._datasetIndex].borderColor; + } + break; + case 'dataset': + break; + default: + // Don't change anything + } + } + + // Built in hover styling + if (this.active.length && this.options.hoverMode) { + switch (this.options.hoverMode) { + case 'single': + this.active[0].backgroundColor = this.data.datasets[this.active[0]._datasetIndex].hoverBackgroundColor || helpers.color(this.active[0].backgroundColor).saturate(0.5).darken(0.35).rgbString(); + this.active[0].borderColor = this.data.datasets[this.active[0]._datasetIndex].hoverBorderColor || helpers.color(this.active[0].borderColor).saturate(0.5).darken(0.35).rgbString(); + break; + case 'label': + for (var i = 0; i < this.active.length; i++) { + this.active[i].backgroundColor = this.data.datasets[this.active[i]._datasetIndex].hoverBackgroundColor || helpers.color(this.active[i].backgroundColor).saturate(0.5).darken(0.35).rgbString(); + this.active[i].borderColor = this.data.datasets[this.active[i]._datasetIndex].hoverBorderColor || helpers.color(this.active[i].borderColor).saturate(0.5).darken(0.35).rgbString(); + } + break; + case 'dataset': + break; + default: + // Don't change anything + } + } - // Built in Tooltips - if(this.options.showTooltips){ + // Built in Tooltips + if (this.options.showTooltips) { - // The usual updates - this.tooltip.initialize(); + // The usual updates + this.tooltip.initialize(); - // Active - if(this.active.length){ - helpers.extend(this.tooltip, { - opacity: 1, - _active: this.active, - }); + // Active + if (this.active.length) { + helpers.extend(this.tooltip, { + opacity: 1, + _active: this.active, + }); - this.tooltip.update(); - } - else{ - // Inactive - helpers.extend(this.tooltip, { - opacity: 0, - }); - } - } + this.tooltip.update(); + } else { + // Inactive + helpers.extend(this.tooltip, { + opacity: 0, + }); + } + } - if(!this.animating){ + this.tooltip.pivot(); - // If entering - if(!this.lastActive.length && this.active.length){ - console.log('entering'); - this.tooltip.pivot(); - this.stop(); - this.render(false, this.options.hoverAnimationDuration); - } + // Hover animations + if (!this.animating) { + var changed; - var changed; - - helpers.each(this.active, function(element, index){ - if (element !== this.lastActive[index]){ - changed = true; - } - }, this); + helpers.each(this.active, function(element, index) { + if (element !== this.lastActive[index]) { + changed = true; + } + }, this); - // If different element - if(this.lastActive.length && this.active.length && changed){ - console.log('changing'); - this.tooltip.pivot(); - this.stop(); - this.render(false, this.options.hoverAnimationDuration); - } + // If entering, leaving, or changing elements, animate the change via pivot + if ((!this.lastActive.length && this.active.length) || + (this.lastActive.length && !this.active.length) || + (this.lastActive.length && this.active.length && changed)) { - // if Leaving - if (this.lastActive.length && !this.active.length){ - console.log('leaving'); - this.tooltip.pivot(); - this.stop(); - this.render(false, this.options.hoverAnimationDuration); - } + this.stop(); + this.render(this.options.hoverAnimationDuration); + } + } - } + // Remember Last Active + this.lastActive = this.active; + return this; + }, + // Calculate the base point for the bar. + calculateBaseY: function() { + var base = this.scale.endPoint; - // Remember Last Active - - this.lastActive = this.active; - }, - // Calculate the base point for the bar. - // If the scale has a 0 point, use that as the base - // If the scale min and max are both positive, use the bottom as a base - // If the scale min and max are both negative, use the top as a base - calculateBarBase: function() { - var base = this.scale.endPoint; - - if (this.scale.beginAtZero || ((this.scale.min <= 0 && this.scale.max >= 0) || (this.scale.min >= 0 && this.scale.max <= 0))) - { - base = this.scale.calculateY(0); - base += this.options.scaleGridLineWidth; - } - else if (this.scale.min < 0 && this.scale.max < 0) - { - // All values are negative. Use the top as the base - base = this.scale.startPoint; - } - - return base; - }, - update : function(){ + if (this.scale.beginAtZero || ((this.scale.min <= 0 && this.scale.max >= 0) || (this.scale.min >= 0 && this.scale.max <= 0))) { + base = this.scale.calculateY(0); + base += this.options.scaleGridLineWidth; + } else if (this.scale.min < 0 && this.scale.max < 0) { + // All values are negative. Use the top as the base + base = this.scale.startPoint; + } - this.scale.update(); + return base; + }, + update: function() { - this.eachBars(function(bar, index, datasetIndex){ - helpers.extend(bar, { - width : this.scale.calculateBarWidth(this.data.datasets.length), - x: this.scale.calculateBarX(this.data.datasets.length, datasetIndex, index), - y: this.scale.calculateY(this.data.datasets[datasetIndex].data[index]), - value : this.data.datasets[datasetIndex].data[index], - label : this.data.labels[index], - datasetLabel: this.data.datasets[datasetIndex].label, - borderColor : this.data.datasets[datasetIndex].borderColor, - borderWidth : this.data.datasets[datasetIndex].borderWidth, - backgroundColor : this.data.datasets[datasetIndex].backgroundColor, - _datasetIndex: datasetIndex, - _index: index, - _start: undefined - }); - }, this); + this.scale.update(); - this.render(); - }, - eachBars : function(callback){ - helpers.each(this.data.datasets,function(dataset, datasetIndex){ - helpers.each(dataset.metaData, callback, this, datasetIndex); - },this); - }, - eachValue : function(callback){ - helpers.each(this.data.datasets,function(dataset, datasetIndex){ - helpers.each(dataset.data, callback, this, datasetIndex); - },this); - }, - getBarsAtEvent : function(e){ - var barsArray = [], - eventPosition = helpers.getRelativePosition(e), - datasetIterator = function(dataset){ - barsArray.push(dataset.metaData[elementIndex]); - }, - elementIndex; + this.eachElement(function(bar, index, dataset, datasetIndex) { + helpers.extend(bar, { + width: this.scale.calculateBarWidth(this.data.datasets.length), + x: this.scale.calculateBarX(this.data.datasets.length, datasetIndex, index), + y: this.scale.calculateY(this.data.datasets[datasetIndex].data[index]), + value: this.data.datasets[datasetIndex].data[index], + label: this.data.labels[index], + datasetLabel: this.data.datasets[datasetIndex].label, + borderColor: this.data.datasets[datasetIndex].borderColor, + borderWidth: this.data.datasets[datasetIndex].borderWidth, + backgroundColor: this.data.datasets[datasetIndex].backgroundColor, + _datasetIndex: datasetIndex, + _index: index, + }); + bar.pivot(); + }, this); - for (var datasetIndex = 0; datasetIndex < this.data.datasets.length; datasetIndex++) { - for (elementIndex = 0; elementIndex < this.data.datasets[datasetIndex].metaData.length; elementIndex++) { - if (this.data.datasets[datasetIndex].metaData[elementIndex].inRange(eventPosition.x,eventPosition.y)){ - helpers.each(this.data.datasets, datasetIterator); - } - } - } + this.render(); + }, + buildScale: function(labels) { + var self = this; - return barsArray.length ? barsArray : []; - }, - // Get the single bar that was clicked on - // @return : An object containing the dataset index and bar index of the matching bar. Also contains the rectangle that was drawn - getBarAtEvent : function(e) { - var bar = []; - var eventPosition = helpers.getRelativePosition(e); - - for (var datasetIndex = 0; datasetIndex < this.data.datasets.length; ++datasetIndex) { - for (var elementIndex = 0; elementIndex < this.data.datasets[datasetIndex].metaData.length; ++elementIndex) { - if (this.data.datasets[datasetIndex].metaData[elementIndex].inRange(eventPosition.x, eventPosition.y)) { - bar.push(this.data.datasets[datasetIndex].metaData[elementIndex]); - return bar; - } - } - } - - return []; - }, - buildScale : function(labels){ - var self = this; + var dataTotal = function() { + var values = []; + self.eachValue(function(value) { + values.push(value); + }); + return values; + }; - var dataTotal = function(){ - var values = []; - self.eachValue(function(value){ - values.push(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.borderWidth, + showLabels: this.options.scaleShowLabels, + display: this.options.showScale + }; - 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.borderWidth, - 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) + }); + } - 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); + }, + // This should be incorportated into the init as something like a default value. "Reflow" seems like a weird word for a fredraw function + redraw: function() { + var base = this.calculateBaseY(); + this.eachElement(function(element, index, datasetIndex) { + helpers.extend(element, { + y: base, + base: base + }); + }); + this.render(); + }, + draw: function(ease) { - this.scale = new this.ScaleClass(scaleOptions); - }, - // This should be incorportated into the init as something like a default value. "Reflow" seems like a weird word for a fredraw function - /*reflow : function(){ - helpers.extend(this.BarClass.prototype,{ - y: this.calculateBarBase(), // so that we animate from the baseline - base : this.calculateBarBase() - }); - 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 easingDecimal = ease || 1; - this.clear(); + this.scale.draw(easingDecimal); - this.scale.draw(easingDecimal); + //Draw all the bars for each dataset + this.eachElement(function(bar, index, datasetIndex) { + if (bar.hasValue()) { + // Update the bar basepoint + bar.base = this.calculateBaseY(); + //Transition + bar.transition(easingDecimal).draw(); + } + }, this); - //Draw all the bars for each dataset - this.eachBars(function(bar, index, datasetIndex){ - if (bar.hasValue()){ - // Update the bar basepoint - bar.base = this.calculateBarBase(); - //Transition - bar.transition(easingDecimal).draw(); - } - }, this); - - // Finally draw the tooltip - this.tooltip.transition(easingDecimal).draw(); - } - }); + // Finally draw the tooltip + this.tooltip.transition(easingDecimal).draw(); + } + }); }).call(this); -(function(){ - "use strict"; +(function() { + "use strict"; - var root = this, - Chart = root.Chart, - //Cache a local reference to Chart.helpers - helpers = Chart.helpers; + 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, + var defaultConfig = { + //Boolean - Whether we should show a stroke on each segment + segmentShowStroke: true, - //String - The colour of each segment stroke - segmentStrokeColor : "#fff", + //String - The colour of each segment stroke + segmentStrokeColor: "#fff", - //Number - The width of each segment stroke - segmentStrokeWidth : 2, + //Number - The width of each segment stroke + borderWidth: 2, - //The percentage of the chart that we cut out of the middle. - percentageInnerCutout : 50, + //The percentage of the chart that we cut out of the middle. + cutoutPercentage: 50, - //Number - Amount of animation steps - animationSteps : 100, + // The duration of animations triggered by hover events + hoverAnimationDuration: 400, - //String - Animation easing effect - animationEasing : "easeOutBounce", + //String - Animation easing effect + animationEasing: "easeOutQuart", - //Boolean - Whether we animate the rotation of the Doughnut - animateRotate : true, + //Boolean - Whether we animate the rotation of the Doughnut + animateRotate: true, - //Boolean - Whether we animate scaling the Doughnut from the centre - animateScale : false, + //Boolean - Whether we animate scaling the Doughnut from the centre + animateScale: false, - //String - A legend template - legendTemplate : "
    -legend\"><% for (var i=0; i
  • \"><%if(segments[i].label){%><%=segments[i].label%><%}%>
  • <%}%>
" + //String - A legend template + legendTemplate: "
    -legend\"><% for (var i=0; i
  • \"><%if(segments[i].label){%><%=segments[i].label%><%}%>
  • <%}%>
" - }; + }; - 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){ + 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) { - // Save data as a source for updating of values & methods - this.data = data; + // Save data as a source for updating of values & methods + this.data = 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; + // Slice Type and defaults + this.Slice = Chart.Arc.extend({ + _chart: this.chart, + x: this.chart.width / 2, + y: this.chart.height / 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, this.onHover); + } - //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) : []; + // Create new slice for each piece of data + this.data.metaData = []; + helpers.each(this.data.data, function(slice, index) { + var metaSlice = new this.Slice(); + if (typeof slice == 'number') { + helpers.extend(metaSlice, { + value: slice + }); + } else { + helpers.extend(metaSlice, slice); + } + helpers.extend(metaSlice, { + startAngle: Math.PI * 1.5, + circumference: (this.options.animateRotate) ? 0 : this.calculateCircumference(metaSlice.value), + outerRadius: (this.options.animateScale) ? 0 : this.outerRadius, + innerRadius: (this.options.animateScale) ? 0 : (this.outerRadius / 100) * this.options.percentageInnerCutout, + }); + if (!metaSlice.backgroundColor) { + slice.backgroundColor = 'hsl(' + (360 * index / data.length) + ', 100%, 50%)'; + } + metaSlice.save(); + this.data.metaData.push(metaSlice); + }, this); - helpers.each(this.segments,function(segment){ - segment.restore(["fillColor"]); - }); - helpers.each(activeSegments,function(activeSegment){ - activeSegment.fillColor = activeSegment.highlightColor; - }); - this.showTooltip(activeSegments); - }); - } - this.calculateTotal(data); + // Create tooltip instance exclusively for this chart with some defaults. + this.tooltip = new Chart.Tooltip({ + _chart: this.chart, + _data: this.data, + _options: this.options, + }, this); - helpers.each(data,function(datapoint, index){ - if (!datapoint.color) { - datapoint.color = 'hsl(' + (360 * index / data.length) + ', 100%, 50%)'; - } - this.addData(datapoint, index, true); - },this); + this.update(); + }, + onHover: function(e) { - this.render(); - }, - getSegmentsAtEvent : function(e){ - var segmentsArray = []; + // If exiting chart + if (e.type == 'mouseout') { + return this; + } - var location = helpers.getRelativePosition(e); + this.lastActive = this.lastActive || []; - 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) { - if ( this.total > 0 ) { - return (Math.PI*2)*(value / this.total); - } else { - return 0; - } - }, - calculateTotal : function(data){ - this.total = 0; - helpers.each(data,function(segment){ - this.total += Math.abs(segment.value); - },this); - }, - update : function(){ + // Find Active Elements + this.active = this.getSliceAtEvent(e); - // Map new data to data points - if(this.data.length == this.segments.length){ - helpers.each(this.data, function(segment, i){ - helpers.extend(this.segments[i], { - value : segment.value, - fillColor : segment.color, - highlightColor : segment.highlight || segment.color, - showStroke : this.options.segmentShowStroke, - strokeWidth : this.options.segmentStrokeWidth, - strokeColor : this.options.segmentStrokeColor, - label : segment.label - }); - }, this); - } else{ - // Data size changed without properly inserting, just redraw the chart - this.initialize(this.data); - } + // On Hover hook + if (this.options.onHover) { + this.options.onHover.call(this, this.active); + } - this.calculateTotal(this.segments); + // Remove styling for last active (even if it may still be active) + if (this.lastActive.length) { + this.lastActive[0].backgroundColor = this.data.data[this.lastActive[0]._index].backgroundColor; + } - // Reset any highlight colours before updating. - helpers.each(this.activeElements, function(activeElement){ - activeElement.restore(['fillColor']); - }); + // Built in hover styling + if (this.active.length && this.options.hoverMode) { + this.active[0].backgroundColor = this.data.data[this.active[0]._index].hoverBackgroundColor || helpers.color(this.data.data[this.active[0]._index].backgroundColor).saturate(0.5).darken(0.35).rgbString(); + } - helpers.each(this.segments,function(segment){ - segment.save(); - }); - this.render(); - }, + // Built in Tooltips + if (this.options.showTooltips) { - removeData: function(atIndex){ - var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1; - this.segments.splice(indexToDelete, 1); - this.reflow(); - this.update(); - }, + // The usual updates + this.tooltip.initialize(); - 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); + // Active + if (this.active.length) { + helpers.extend(this.tooltip, { + opacity: 1, + _active: this.active, + }); - segment.endAngle = segment.startAngle + segment.circumference; + this.tooltip.update(); + } else { + // Inactive + helpers.extend(this.tooltip, { + opacity: 0, + }); + } + } - 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); - } - }); + // Hover animations + this.tooltip.pivot(); - Chart.types.Doughnut.extend({ - name : "Pie", - defaults : helpers.merge(defaultConfig,{percentageInnerCutout : 0}) - }); + if (!this.animating) { + var changed; + + helpers.each(this.active, function(element, index) { + if (element !== this.lastActive[index]) { + changed = true; + } + }, this); + + // If entering, leaving, or changing elements, animate the change via pivot + if ((!this.lastActive.length && this.active.length) || + (this.lastActive.length && !this.active.length) || + (this.lastActive.length && this.active.length && changed)) { + + this.stop(); + this.render(this.options.hoverAnimationDuration); + } + } + + // Remember Last Active + this.lastActive = this.active; + return this; + + }, + getSliceAtEvent: function(e) { + var elements = []; + + var location = helpers.getRelativePosition(e); + + helpers.each(this.data.metaData, function(slice, index) { + if (slice.inRange(location.x, location.y)) elements.push(slice); + }, this); + return elements; + }, + calculateCircumference: function(value) { + if (this.total > 0) { + return (Math.PI * 2) * (value / this.total); + } else { + return 0; + } + }, + update: function() { + + // Calc Total + this.total = 0; + helpers.each(this.data.data, function(slice) { + this.total += Math.abs(slice.value); + }, this); + + this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.borderWidth / 2) / 2; + + // Map new data to data points + helpers.each(this.data.metaData, function(slice, index) { + + var datapoint = this.data.data[index]; + + helpers.extend(slice, { + _index: index, + x: this.chart.width / 2, + y: this.chart.height / 2, + value: datapoint.value, + label: datapoint.label, + circumference: this.calculateCircumference(datapoint.value), + outerRadius: this.outerRadius, + innerRadius: (this.outerRadius / 100) * this.options.cutoutPercentage, + + backgroundColor: datapoint.backgroundColor, + hoverBackgroundColor: datapoint.hoverBackgroundColor || datapoint.backgroundColor, + borderWidth: this.options.borderWidth, + borderColor: this.options.segmentStrokeColor, + }); + + helpers.extend(slice, { + endAngle: slice.startAngle + slice.circumference, + }); + + if (index === 0) { + slice.startAngle = Math.PI * 1.5; + } + + //Check to see if it's the last slice, if not get the next and update its start angle + if (index < this.data.data.length - 1) { + this.data.metaData[index + 1].startAngle = slice.endAngle; + } + + slice.pivot(); + + }, this); + + this.render(); + }, + draw: function(easeDecimal) { + easeDecimal = easeDecimal || 1; + this.clear(); + + helpers.each(this.data.metaData, function(slice, index) { + slice.transition(easeDecimal).draw(); + }, this); + + this.tooltip.transition(easeDecimal).draw(); + } + }); + + Chart.types.Doughnut.extend({ + name: "Pie", + defaults: helpers.merge(defaultConfig, { + cutoutPercentage: 0 + }) + }); }).call(this); -(function(){ - "use strict"; +(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 : "
    -legend\"><% for (var i=0; i
  • \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
", - - //Boolean - Whether to horizontally center the label and point dot inside the grid - offsetGridLines : false - - }; - - - Chart.Type.extend({ - name: "Line", - defaults : defaultConfig, - initialize: function(data){ - // Save data as a source for updating of values & methods - this.data = data; - - //Declare the extension of the default point, to cater for the options passed in to the constructor - this.PointClass = Chart.Point.extend({ - offsetGridLines : this.options.offsetGridLines, - 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(){ - //Iterate through each of the datasets, and build this into a property of the chart - helpers.each(this.data.datasets,function(dataset,datasetIndex){ - - helpers.extend(this.datasets[datasetIndex], { - label : dataset.label || null, - fillColor : dataset.fillColor, - strokeColor : dataset.strokeColor, - pointColor : dataset.pointColor, - pointStrokeColor : dataset.pointStrokeColor, - }); - - helpers.each(dataset.data,function(dataPoint,index){ - helpers.extend(this.datasets[datasetIndex].points[index], { - value : dataPoint, - label : this.data.labels[index], - datasetLabel: dataset.label, - strokeColor : dataset.pointStrokeColor, - fillColor : dataset.pointColor, - highlightFill : dataset.pointHighlightFill || dataset.pointColor, - highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor - }); - },this); - - },this); - - 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, - offsetGridLines : this.options.offsetGridLines, - 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, - datasetLabel: this.datasets[datasetIndex].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); - } - }); + 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, + + //Number - Tension of the bezier curve between points + tension: 0.4, + + //Number - Radius of each point dot in pixels + pointRadius: 4, + + //Number - Pixel width of point dot border + pointBorderWidth: 1, + + //Number - amount extra to add to the radius to cater for hit detection outside the drawn point + pointHoverRadius: 20, + + //Number - Pixel width of dataset border + borderWidth: 2, + + //String - A legend template + legendTemplate: "
    -legend\"><% for (var i=0; i
  • \"><%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
", + + //Boolean - Whether to horizontally center the label and point dot inside the grid + offsetGridLines: false + + }; + + + Chart.Type.extend({ + name: "Line", + defaults: defaultConfig, + initialize: function(data) { + // Save data as a source for updating of values & methods + this.data = data; + + //Custom Point Defaults + this.PointClass = Chart.Point.extend({ + _chart: this.chart, + offsetGridLines: this.options.offsetGridLines, + borderWidth: this.options.pointBorderWidth, + radius: this.options.pointRadius, + hoverRadius: this.options.pointHoverRadius, + }); + + // Events + helpers.bindEvents(this, this.options.tooltipEvents, this.onHover); + + // Build Scale + this.buildScale(this.data.labels); + + + //Create a new line and its points for each dataset and piece of data + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + dataset.metaDataset = new Chart.Line(); + dataset.metaData = []; + helpers.each(dataset.data, function(dataPoint, index) { + dataset.metaData.push(new this.PointClass()); + }, this); + }, this); + + // Set defaults for lines + this.eachDataset(function(dataset, datasetIndex) { + dataset = helpers.merge(this.options, dataset); + helpers.extend(dataset.metaDataset, { + _points: dataset.metaData, + _datasetIndex: datasetIndex, + _chart: this.chart, + }); + // Copy to view model + dataset.metaDataset.save(); + }, this); + + // Set defaults for points + this.eachElement(function(point, index, dataset, datasetIndex) { + helpers.extend(point, { + x: this.scale.calculateX(index), + y: this.scale.endPoint, + _datasetIndex: datasetIndex, + _index: index, + _chart: this.chart + }); + + // Default bezier control points + helpers.extend(point, { + controlPointPreviousX: this.previousPoint(dataset, index).x, + controlPointPreviousY: this.nextPoint(dataset, index).y, + controlPointNextX: this.previousPoint(dataset, index).x, + controlPointNextY: this.nextPoint(dataset, index).y, + }); + // Copy to view model + point.save(); + }, this); + + // Create tooltip instance exclusively for this chart with some defaults. + this.tooltip = new Chart.Tooltip({ + _chart: this.chart, + _data: this.data, + _options: this.options, + }, this); + + this.update(); + }, + nextPoint: function(collection, index) { + return collection[index - 1] || collection[index]; + }, + previousPoint: function(collection, index) { + return collection[index + 1] || collection[index]; + }, + onHover: function(e) { + + + // If exiting chart + if (e.type == 'mouseout') { + return this; + } + + this.lastActive = this.lastActive || []; + + // Find Active Elements + this.active = function() { + switch (this.options.hoverMode) { + case 'single': + return this.getElementAtEvent(e); + case 'label': + return this.getElementsAtEvent(e); + case 'dataset': + return this.getDatasetAtEvent(e); + default: + return e; + } + }.call(this); + + // On Hover hook + if (this.options.onHover) { + this.options.onHover.call(this, this.active); + } + + // Remove styling for last active (even if it may still be active) + if (this.lastActive.length) { + switch (this.options.hoverMode) { + case 'single': + this.lastActive[0].backgroundColor = this.data.datasets[this.lastActive[0]._datasetIndex].pointBackgroundColor; + this.lastActive[0].borderColor = this.data.datasets[this.lastActive[0]._datasetIndex].pointBorderColor; + this.lastActive[0].borderWidth = this.data.datasets[this.lastActive[0]._datasetIndex].pointBorderWidth; + break; + case 'label': + for (var i = 0; i < this.lastActive.length; i++) { + this.lastActive[i].backgroundColor = this.data.datasets[this.lastActive[i]._datasetIndex].pointBackgroundColor; + this.lastActive[i].borderColor = this.data.datasets[this.lastActive[i]._datasetIndex].pointBorderColor; + this.lastActive[i].borderWidth = this.data.datasets[this.lastActive[0]._datasetIndex].pointBorderWidth; + } + break; + case 'dataset': + break; + default: + // Don't change anything + } + } + + // Built in hover styling + if (this.active.length && this.options.hoverMode) { + switch (this.options.hoverMode) { + case 'single': + this.active[0].backgroundColor = this.data.datasets[this.active[0]._datasetIndex].hoverBackgroundColor || helpers.color(this.active[0].backgroundColor).saturate(0.5).darken(0.35).rgbString(); + this.active[0].borderColor = this.data.datasets[this.active[0]._datasetIndex].hoverBorderColor || helpers.color(this.active[0].borderColor).saturate(0.5).darken(0.35).rgbString(); + this.active[0].borderWidth = this.data.datasets[this.active[0]._datasetIndex].borderWidth + 10; + break; + case 'label': + for (var i = 0; i < this.active.length; i++) { + this.active[i].backgroundColor = this.data.datasets[this.active[i]._datasetIndex].hoverBackgroundColor || helpers.color(this.active[i].backgroundColor).saturate(0.5).darken(0.35).rgbString(); + this.active[i].borderColor = this.data.datasets[this.active[i]._datasetIndex].hoverBorderColor || helpers.color(this.active[i].borderColor).saturate(0.5).darken(0.35).rgbString(); + this.active[i].borderWidth = this.data.datasets[this.active[i]._datasetIndex].borderWidth + 2; + } + break; + case 'dataset': + break; + default: + // Don't change anything + } + } + + + // Built in Tooltips + if (this.options.showTooltips) { + + // The usual updates + this.tooltip.initialize(); + + // Active + if (this.active.length) { + helpers.extend(this.tooltip, { + opacity: 1, + _active: this.active, + }); + + this.tooltip.update(); + } else { + // Inactive + helpers.extend(this.tooltip, { + opacity: 0, + }); + } + } + + + // Hover animations + this.tooltip.pivot(); + + if (!this.animating) { + var changed; + + helpers.each(this.active, function(element, index) { + if (element !== this.lastActive[index]) { + changed = true; + } + }, this); + + // If entering, leaving, or changing elements, animate the change via pivot + if ((!this.lastActive.length && this.active.length) || + (this.lastActive.length && !this.active.length) || + (this.lastActive.length && this.active.length && changed)) { + + this.stop(); + this.render(this.options.hoverAnimationDuration); + } + } + + // Remember Last Active + this.lastActive = this.active; + return this; + + }, + update: function() { + + this.scale.update(); + + // Update the lines + this.eachDataset(function(dataset, datasetIndex) { + helpers.extend(dataset.metaDataset, { + backgroundColor: dataset.backgroundColor || this.options.backgroundColor, + borderWidth: dataset.borderWidth || this.options.borderWidth, + borderColor: dataset.borderColor || this.options.borderColor, + tension: dataset.tension || this.options.tension, + scaleTop: this.scale.startPoint, + scaleBottom: this.scale.endPoint, + _points: dataset.metaData, + _datasetIndex: datasetIndex, + }); + dataset.metaDataset.pivot(); + }); + + // Update the points + this.eachElement(function(point, index, dataset, datasetIndex) { + helpers.extend(point, { + x: this.scale.calculateX(index), + y: this.scale.calculateY(this.data.datasets[datasetIndex].data[index]), + value: this.data.datasets[datasetIndex].data[index], + label: this.data.labels[index], + datasetLabel: this.data.datasets[datasetIndex].label, + // Appearance + hoverBackgroundColor: this.data.datasets[datasetIndex].pointHoverBackgroundColor || this.options.pointHoverBackgroundColor, + hoverBorderColor: this.data.datasets[datasetIndex].pointHoverBorderColor || this.options.pointHoverBorderColor, + hoverRadius: this.data.datasets[datasetIndex].pointHoverRadius || this.options.pointHoverRadius, + radius: this.data.datasets[datasetIndex].pointRadius || this.options.pointRadius, + borderWidth: this.data.datasets[datasetIndex].pointBorderWidth || this.options.pointBorderWidth, + borderColor: this.data.datasets[datasetIndex].pointBorderColor || this.options.pointBorderColor, + backgroundColor: this.data.datasets[datasetIndex].pointBackgroundColor || this.options.pointBackgroundColor, + tension: this.data.datasets[datasetIndex].metaDataset.tension, + _datasetIndex: datasetIndex, + _index: index, + }); + }, this); + + // Update control points for the bezier curve + this.eachElement(function(point, index, dataset, datasetIndex) { + var controlPoints = helpers.splineCurve( + this.previousPoint(dataset, index), + point, + this.nextPoint(dataset, index), + point.tension + ); + + point.controlPointPreviousX = controlPoints.previous.x; + point.controlPointNextX = controlPoints.next.x; + + // Prevent the bezier going outside of the bounds of the graph + + // Cap puter bezier handles to the upper/lower scale bounds + if (controlPoints.next.y > this.scale.endPoint) { + point.controlPointNextY = this.scale.endPoint; + } else if (controlPoints.next.y < this.scale.startPoint) { + point.controlPointNextY = this.scale.startPoint; + } else { + point.controlPointNextY = controlPoints.next.y; + } + + // Cap inner bezier handles to the upper/lower scale bounds + if (controlPoints.previous.y > this.scale.endPoint) { + point.controlPointPreviousY = this.scale.endPoint; + } else if (controlPoints.previous.y < this.scale.startPoint) { + point.controlPointPreviousY = this.scale.startPoint; + } else { + point.controlPointPreviousY = controlPoints.previous.y; + } + // Now pivot the point for animation + point.pivot(); + }, this); + + this.render(); + }, + buildScale: function(labels) { + var self = this; + + var dataTotal = function() { + var values = []; + self.eachValue(function(value) { + values.push(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, + offsetGridLines: this.options.offsetGridLines, + 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: this.data.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.pointRadius + this.options.pointBorderWidth, + 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); + }, + redraw: function() { + + }, + draw: function(ease) { + + var easingDecimal = ease || 1; + this.clear(); + + this.scale.draw(easingDecimal); + + this.eachDataset(function(dataset, datasetIndex) { + + // Transition Point Locations + helpers.each(dataset.metaData, function(point, index) { + point.transition(easingDecimal); + }, this); + + // Transition and Draw the line + dataset.metaDataset.transition(easingDecimal).draw(); + + // Draw the points + helpers.each(dataset.metaData, function(point) { + point.draw(); + }); + }, this); + + // Finally draw the tooltip + this.tooltip.transition(easingDecimal).draw(); + } + }); }).call(this); diff --git a/Chart.min.js b/Chart.min.js index 0b03b1c44..a83c08996 100644 --- a/Chart.min.js +++ b/Chart.min.js @@ -7,6 +7,6 @@ * Released under the MIT license * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md */ -(function(){"use strict";var t=this,e=t.Chart,i=function(t){this.canvas=t.canvas,this.ctx=t;var e=function(t,e){return t["offset"+e]?t["offset"+e]:document.defaultView.getComputedStyle(t).getPropertyValue(e)},i=this.width=e(t.canvas,"Width")||t.canvas.width,s=this.height=e(t.canvas,"Height")||t.canvas.height;return t.canvas.width=i,t.canvas.height=s,i=this.width=t.canvas.width,s=this.height=t.canvas.height,this.aspectRatio=this.width/this.height,n.retinaScale(this),this};i.defaults={global:{animation:!0,animationDuration:1e3,animationEasing:"easeOutQuart",showScale:!0,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleIntegersOnly:!0,scaleBeginAtZero:!1,scaleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",responsive:!1,maintainAspectRatio:!0,showTooltips:!0,customTooltips:!1,tooltipEvents:["mousemove","touchstart","touchmove","mouseout"],tooltipBackgroundColor:"rgba(0,0,0,0.8)",tooltipFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipFontSize:14,tooltipFontStyle:"normal",tooltipFontColor:"#fff",tooltipTitleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipTitleFontSize:14,tooltipTitleFontStyle:"bold",tooltipTitleFontColor:"#fff",tooltipYPadding:6,tooltipXPadding:6,tooltipCaretSize:8,tooltipCornerRadius:6,tooltipXOffset:10,tooltipTemplate:"<%if (label){%><%=label%>: <%}%><%= value %>",multiTooltipTemplate:"<%if (datasetLabel){%><%=datasetLabel%>: <%}%><%= value %>",multiTooltipKeyBackground:"#fff",onAnimationProgress:function(){},onAnimationComplete:function(){}}},i.types={};var n=i.helpers={},s=n.each=function(t,e,i){var n=Array.prototype.slice.call(arguments,3);if(t)if(t.length===+t.length){var s;for(s=0;s=0;n--){var s=t[n];if(e(s))return s}},n.inherits=function(t){var e=this,i=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return e.apply(this,arguments)},n=function(){this.constructor=i};return n.prototype=e.prototype,i.prototype=new n,i.extend=l,t&&o(i.prototype,t),i.__super__=e.prototype,i}),c=n.noop=function(){},u=n.uid=function(){var t=0;return function(){return"chart-"+t++}}(),d=n.warn=function(t){window.console&&"function"==typeof window.console.warn&&console.warn(t)},p=n.amd="function"==typeof define&&define.amd,g=n.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},f=n.max=function(t){return Math.max.apply(Math,t)},v=n.min=function(t){return Math.min.apply(Math,t)},m=(n.cap=function(t,e,i){if(g(e)){if(t>e)return e}else if(g(i)&&i>t)return i;return t},n.getDecimalPlaces=function(t){if(t%1!==0&&g(t)){var e=t.toString();if(e.indexOf("e-")<0)return e.split(".")[1].length;if(e.indexOf(".")<0)return parseInt(e.split("e-")[1]);var i=e.split(".")[1].split("e-");return i[0].length+parseInt(i[1])}return 0}),b=n.radians=function(t){return t*(Math.PI/180)},y=(n.getAngleFromPoint=function(t,e){var i=e.x-t.x,n=e.y-t.y,s=Math.sqrt(i*i+n*n),a=2*Math.PI+Math.atan2(n,i);return 0>i&&0>n&&(a+=2*Math.PI),{angle:a,distance:s}},n.aliasPixel=function(t){return t%2===0?0:.5}),w=(n.splineCurve=function(t,e,i,n){var s=Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2)),a=Math.sqrt(Math.pow(i.x-e.x,2)+Math.pow(i.y-e.y,2)),o=n*s/(s+a),r=n*a/(s+a);return{inner:{x:e.x-o*(i.x-t.x),y:e.y-o*(i.y-t.y)},outer:{x:e.x+r*(i.x-t.x),y:e.y+r*(i.y-t.y)}}},n.calculateOrderOfMagnitude=function(t){return Math.floor(Math.log(t)/Math.LN10)}),S=(n.calculateScaleRange=function(t,e,i,n,s){var a=2,o=Math.floor(e/(1.5*i)),r=a>=o,h=f(t),l=v(t);h===l&&(h+=.5,l>=.5&&!n?l-=.5:h+=.5);for(var c=Math.abs(h-l),u=w(c),d=Math.ceil(h/(1*Math.pow(10,u)))*Math.pow(10,u),p=n?0:Math.floor(l/(1*Math.pow(10,u)))*Math.pow(10,u),g=d-p,m=Math.pow(10,u),b=Math.round(g/m);(b>o||o>2*b)&&!r;)if(b>o)m*=2,b=Math.round(g/m),b%1!==0&&(r=!0);else if(s&&u>=0){if(m/2%1!==0)break;m/=2,b=Math.round(g/m)}else m/=2,b=Math.round(g/m);return r&&(b=a,m=g/b),{steps:b,stepValue:m,min:p,max:p+b*m}},n.template=function(t,e){function i(t,e){var i=/\W/.test(t)?new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+t.replace(/[\r\t\n]/g," ").split("<%").join(" ").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split(" ").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');"):n[t]=n[t];return e?i(e):i}if(t instanceof Function)return t(e);var n={};return i(t,e)}),x=(n.generateLabels=function(t,e,i,n){var a=new Array(e);return t&&s(a,function(e,s){a[s]=S(t,{value:i+n*(s+1)})}),a},n.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-0.5*(--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){return(t/=.5)<1?.5*t*t*t:.5*((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){return(t/=.5)<1?.5*t*t*t*t:-0.5*((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){return(t/=.5)<1?.5*t*t*t*t*t:.5*((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-0.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-0.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,i=0,n=1;return 0===t?0:1==(t/=1)?1:(i||(i=.3),nt?-.5*n*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-e)*Math.PI/i):n*Math.pow(2,-10*(t-=1))*Math.sin(2*(1*t-e)*Math.PI/i)*.5+1)},easeInBack:function(t){var e=1.70158;return 1*(t/=1)*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return 1*((t=t/1-1)*t*((e+1)*t+e)+1)},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?.5*t*t*(((e*=1.525)+1)*t-e):.5*((t-=2)*t*(((e*=1.525)+1)*t+e)+2)},easeInBounce:function(t){return 1-x.easeOutBounce(1-t)},easeOutBounce:function(t){return(t/=1)<1/2.75?7.5625*t*t:2/2.75>t?1*(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1*(7.5625*(t-=2.25/2.75)*t+.9375):1*(7.5625*(t-=2.625/2.75)*t+.984375)},easeInOutBounce:function(t){return.5>t?.5*x.easeInBounce(2*t):.5*x.easeOutBounce(2*t-1)+.5}}),C=n.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),k=(n.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),n.animationLoop=function(t,e,i,n,s,a){var o=0,r=x[i]||x.linear,h=function(){o++;var i=o/e,l=r(i);t.call(a,l,i,o),n.call(a,l,i),e>o?a.animationFrame=C(h):s.apply(a)};C(h)},n.getRelativePosition=function(t){var e,i,n=t.originalEvent||t,s=t.currentTarget||t.srcElement,a=s.getBoundingClientRect();return n.touches?(e=n.touches[0].clientX-a.left,i=n.touches[0].clientY-a.top):(e=n.clientX-a.left,i=n.clientY-a.top),{x:e,y:i}},n.addEvent=function(t,e,i){t.addEventListener?t.addEventListener(e,i):t.attachEvent?t.attachEvent("on"+e,i):t["on"+e]=i}),P=n.removeEvent=function(t,e,i){t.removeEventListener?t.removeEventListener(e,i,!1):t.detachEvent?t.detachEvent("on"+e,i):t["on"+e]=c},L=(n.bindEvents=function(t,e,i){t.events||(t.events={}),s(e,function(e){t.events[e]=function(){i.apply(t,arguments)},k(t.chart.canvas,e,t.events[e])})},n.unbindEvents=function(t,e){s(e,function(e,i){P(t.chart.canvas,i,e)})}),A=n.getMaximumWidth=function(t){var e=t.parentNode,i=parseInt(F(e,"padding-left"))+parseInt(F(e,"padding-right"));return e.clientWidth-i},M=n.getMaximumHeight=function(t){var e=t.parentNode,i=parseInt(F(e,"padding-bottom"))+parseInt(F(e,"padding-top"));return e.clientHeight-i},F=n.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},R=(n.getMaximumSize=n.getMaximumWidth,n.retinaScale=function(t){var e=t.ctx,i=t.canvas.width,n=t.canvas.height;window.devicePixelRatio&&(e.canvas.style.width=i+"px",e.canvas.style.height=n+"px",e.canvas.height=n*window.devicePixelRatio,e.canvas.width=i*window.devicePixelRatio,e.scale(window.devicePixelRatio,window.devicePixelRatio))}),W=n.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},z=n.fontString=function(t,e,i){return e+" "+t+"px "+i},T=n.longestText=function(t,e,i){t.font=e;var n=0;return s(i,function(e){var i=t.measureText(e).width;n=i>n?i:n}),n},_=n.drawRoundedRectangle=function(t,e,i,n,s,a){t.beginPath(),t.moveTo(e+a,i),t.lineTo(e+n-a,i),t.quadraticCurveTo(e+n,i,e+n,i+a),t.lineTo(e+n,i+s-a),t.quadraticCurveTo(e+n,i+s,e+n-a,i+s),t.lineTo(e+a,i+s),t.quadraticCurveTo(e,i+s,e,i+s-a),t.lineTo(e,i+a),t.quadraticCurveTo(e,i,e+a,i),t.closePath()};n.color=function(t){return window.Color?window.Color(t):(console.log("Color.js not found!"),t)},n.isArray=function(t){return Array.isArray?Array.isArray(t):"[object Array]"===Object.prototype.toString.call(arg)};i.instances={},i.Type=function(t,e,n){this.options=e,this.chart=n,this.id=u(),i.instances[this.id]=this,e.responsive&&this.resize(),this.initialize.call(this,t)},o(i.Type.prototype,{initialize:function(){return this},clear:function(){return W(this.chart),this},stop:function(){return i.animationService.cancelAnimation(this),this},resize:function(t){this.stop();var e=this.chart.canvas,i=A(this.chart.canvas),n=this.options.maintainAspectRatio?i/this.chart.aspectRatio:M(this.chart.canvas);return e.width=this.chart.width=i,e.height=this.chart.height=n,R(this.chart),"function"==typeof t&&t.apply(this,Array.prototype.slice.call(arguments,1)),this},reflow:c,render:function(t,e){if(t&&this.reflow(),this.options.animation&&!t){var s=new i.Animation;s.numSteps=(e||this.options.animationDuration)/16.66,s.easing=this.options.animationEasing,s.render=function(t,e){var i=n.easingEffects[e.easing],s=e.currentStep/e.numSteps,a=i(s);t.draw(a,s,e.currentStep)},s.onAnimationProgress=this.options.onAnimationProgress,s.onAnimationComplete=this.options.onAnimationComplete,i.animationService.addAnimation(this,s,e)}else this.draw(),this.options.onAnimationComplete.call(this);return this},generateLegend:function(){return S(this.options.legendTemplate,this)},destroy:function(){this.clear(),L(this,this.events);var t=this.chart.canvas;t.width=this.chart.width,t.height=this.chart.height,t.style.removeProperty?(t.style.removeProperty("width"),t.style.removeProperty("height")):(t.style.removeAttribute("width"),t.style.removeAttribute("height")),delete i.instances[this.id]},toBase64Image:function(){return this.chart.canvas.toDataURL.apply(this.chart.canvas,arguments)}}),i.Type.extend=function(t){var e=this,n=function(){return e.apply(this,arguments)};if(n.prototype=a(e.prototype),o(n.prototype,t),n.extend=i.Type.extend,t.name||e.prototype.name){var s=t.name||e.prototype.name,h=i.defaults[e.prototype.name]?a(i.defaults[e.prototype.name]):{};i.defaults[s]=o(h,t.defaults),i.types[s]=n,i.prototype[s]=function(t,e){var a=r(i.defaults.global,i.defaults[s],e||{});return new n(t,a,this)}}else d("Name not provided for this chart, so it hasn't been registered");return e},i.Element=function(t){o(this,{_vm:{}}),o(this,t),this.initialize.apply(this,arguments)},o(i.Element.prototype,{initialize:function(){},save:function(){return this._vm=a(this),delete this._vm._vm,delete this._vm._start,this},pivot:function(){return this._start&&(this._start=a(this),n.extend(this._start,this._vm)),this},transition:function(t){return this._start||(this._start=a(this._vm)),s(this,function(e,i){if("_"!==i[0]&&this.hasOwnProperty(i)){if(!this._vm[i])return void(this._vm[i]=e||null);if(this[i]!==this._vm[i])if("string"==typeof e)try{var s=n.color(this._start[i]).mix(n.color(this[i]),t);this._vm[i]=s.rgbString()}catch(a){this._vm[i]=e}else"number"==typeof e?this._vm[i]=(this[i]-this._start[i])*t+this._start[i]:this._vm[i]=e}},this),1===t&&delete this._start,this},tooltipPosition:function(){return{x:this.x,y:this.y}},hasValue:function(){return g(this.value)}}),i.Element.extend=l,i.Point=i.Element.extend({display:!0,inRange:function(t,e){var i=this.hitDetectionRadius+this.radius;return Math.pow(t-this.x,2)+Math.pow(e-this.y,2)=this.startAngle&&i.angle<=this.endAngle,a=i.distance>=this.innerRadius&&i.distance<=this.outerRadius;return s&&a},tooltipPosition:function(){var t=this.startAngle+(this.endAngle-this.startAngle)/2,e=(this.outerRadius-this.innerRadius)/2+this.innerRadius;return{x:this.x+Math.cos(t)*e,y:this.y+Math.sin(t)*e}},draw:function(t){var e=this.ctx;e.beginPath(),e.arc(this.x,this.y,this.outerRadius,this.startAngle,this.endAngle),e.arc(this.x,this.y,this.innerRadius,this.endAngle,this.startAngle,!0),e.closePath(),e.strokeStyle=this.borderColor,e.lineWidth=this.borderWidth,e.fillStyle=this.backgroundColor,e.fill(),e.lineJoin="bevel",this.showBorder&&e.stroke()}}),i.Rectangle=i.Element.extend({draw:function(){var t=this._vm,e=this.ctx,i=t.width/2,n=t.x-i,s=t.x+i,a=t.base-(t.base-t.y),o=t.borderWidth/2;t.borderWidth&&(n+=o,s-=o,a+=o),e.beginPath(),e.fillStyle=t.backgroundColor,e.strokeStyle=t.borderColor,e.lineWidth=t.borderWidth,e.moveTo(n,t.base),e.lineTo(n,a),e.lineTo(s,a),e.lineTo(s,t.base),e.fill(),t.borderWidth&&e.stroke()},height:function(){var t=this._vm;return t.base-t.y},inRange:function(t,e){var i=this._vm;return i.y=i.x-i.width/2&&t<=i.x+i.width/2&&e>=i.y&&e<=i.base:t>=i.x-i.width/2&&t<=i.x+i.width/2&&e>=i.base&&e<=i.y},tooltipPosition:function(){var t=this._vm;return t.y=0&&(i=this._data.datasets[r].metaData,s=h(i,this._active[0]),-1===s);r--);var l=function(t){var e,i,r,h,l,c=[],u=[],d=[];return n.each(this._data.datasets,function(t){e=t.metaData,e[s]&&e[s].hasValue()&&c.push(e[s])}),n.each(c,function(t){u.push(t._vm.x),d.push(t._vm.y),a.push(n.template(this._options.multiTooltipTemplate,t)),o.push({fill:t._vm.backgroundColor,stroke:t._vm.borderColor})},this),l=v(d),r=f(d),h=v(u),i=f(u),{x:h>this._chart.width/2?h:i,y:(l+r)/2}}.call(this,s);n.extend(this,{x:l.x,y:l.y,labels:a,title:this._active.length?this._active[0].label:"",legendColors:o,legendBackgroundColor:this._options.multiTooltipKeyBackground}),this.height=a.length*this.fontSize+(a.length-1)*(this.fontSize/2)+2*this.yPadding+1.5*this.titleFontSize;var c=t.measureText(this.title).width,u=T(t,this.font,a)+this.fontSize+3,d=f([u,c]);this.width=d+2*this.xPadding;var p=this.height/2;this.y-p<0?this.y=p:this.y+p>this._chart.height&&(this.y=this._chart.height-p),this.x>this._chart.width/2?this.x-=this.xOffset+this.width:this.x+=this.xOffset}return this},draw:function(){var t=this._chart.ctx,e=this._vm;switch(this._options.hoverMode){case"single":t.font=z(e.fontSize,e._fontStyle,e._fontFamily),e.xAlign="center",e.yAlign="above";var i=e.caretPadding=2,s=t.measureText(e.text).width+2*e.xPadding,a=e.fontSize+2*e.yPadding,o=a+e.caretHeight+i;e.x+s/2>this._chart.width?e.xAlign="left":e.x-s/2<0&&(e.xAlign="right"),e.y-o<0&&(e.yAlign="below");var r=e.x-s/2,h=e.y-o;if(t.fillStyle=n.color(e.backgroundColor).alpha(e.opacity).rgbString(),this._custom)this._custom(this._vm);else{switch(e.yAlign){case"above":t.beginPath(),t.moveTo(e.x,e.y-i),t.lineTo(e.x+e.caretHeight,e.y-(i+e.caretHeight)),t.lineTo(e.x-e.caretHeight,e.y-(i+e.caretHeight)),t.closePath(),t.fill();break;case"below":h=e.y+i+e.caretHeight,t.beginPath(),t.moveTo(e.x,e.y+i),t.lineTo(e.x+e.caretHeight,e.y+i+e.caretHeight),t.lineTo(e.x-e.caretHeight,e.y+i+e.caretHeight),t.closePath(),t.fill()}switch(e.xAlign){case"left":r=e.x-s+(e.cornerRadius+e.caretHeight);break;case"right":r=e.x-(e.cornerRadius+e.caretHeight)}_(t,r,h,s,a,e.cornerRadius),t.fill(),t.fillStyle=n.color(e.textColor).alpha(e.opacity).rgbString(),t.textAlign="center",t.textBaseline="middle",t.fillText(e.text,r+s/2,h+a/2)}break;case"label":_(t,e.x,e.y-e.height/2,e.width,e.height,e.cornerRadius),t.fillStyle=n.color(e.backgroundColor).alpha(e.opacity).rgbString(),t.fill(),t.closePath(),t.textAlign="left",t.textBaseline="middle",t.fillStyle=n.color(e.titleTextColor).alpha(e.opacity).rgbString(),t.font=z(e.fontSize,e._titleFontStyle,e._titleFontFamily),t.fillText(e.title,e.x+e.xPadding,this.getLineHeight(0)),t.font=z(e.fontSize,e._fontStyle,e._fontFamily),n.each(e.labels,function(i,s){t.fillStyle=n.color(e.textColor).alpha(e.opacity).rgbString(),t.fillText(i,e.x+e.xPadding+e.fontSize+3,this.getLineHeight(s+1)),t.fillStyle=n.color(e.legendBackgroundColor).alpha(e.opacity).rgbString(),t.fillRect(e.x+e.xPadding,this.getLineHeight(s+1)-e.fontSize/2,e.fontSize,e.fontSize),t.fillStyle=n.color(e.legendColors[s].fill).alpha(e.opacity).rgbString(),t.fillRect(e.x+e.xPadding,this.getLineHeight(s+1)-e.fontSize/2,e.fontSize,e.fontSize)},this)}},getLineHeight:function(t){var e=this._vm.y-this._vm.height/2+this._vm.yPadding,i=t-1;return 0===t?e+this._vm.titleFontSize/2:e+(1.5*this._vm.fontSize*i+this._vm.fontSize/2)+1.5*this._vm.titleFontSize}}),i.Scale=i.Element.extend({initialize:function(){this.fit()},buildYLabels:function(){this.yLabels=[];for(var t=m(this.stepValue),e=0;e<=this.steps;e++)this.yLabels.push(S(this.templateString,{value:(this.min+e*this.stepValue).toFixed(t)}));this.yLabelWidth=this.display&&this.showLabels?T(this.ctx,this.font,this.yLabels)+10:0},addXLabel:function(t){this.xLabels.push(t),this.valuesCount++,this.fit()},removeXLabel:function(){this.xLabels.shift(),this.valuesCount--,this.fit()},fit:function(){this.startPoint=this.display?this.fontSize:0,this.endPoint=this.display?this.height-1.5*this.fontSize-5:this.height,this.startPoint+=this.padding,this.endPoint-=this.padding;var t,e=this.endPoint,i=this.endPoint-this.startPoint;for(this.calculateYRange(i),this.buildYLabels(),this.calculateXLabelRotation();i>this.endPoint-this.startPoint;)i=this.endPoint-this.startPoint,t=this.yLabelWidth,this.calculateYRange(i),this.buildYLabels(),tthis.yLabelWidth?i/2:this.yLabelWidth,this.xLabelRotation=0,this.display){var s,a=T(this.ctx,this.font,this.xLabels);this.xLabelWidth=a;for(var o=Math.floor(this.calculateX(1)-this.calculateX(0))-6;this.xLabelWidth>o&&0===this.xLabelRotation||this.xLabelWidth>o&&this.xLabelRotation<=90&&this.xLabelRotation>0;)s=Math.cos(b(this.xLabelRotation)),t=s*i,e=s*n,t+this.fontSize/2>this.yLabelWidth&&(this.xScalePaddingLeft=t+this.fontSize/2),this.xScalePaddingRight=this.fontSize/2,this.xLabelRotation++,this.xLabelWidth=s*a;this.xLabelRotation>0&&(this.endPoint-=Math.sin(b(this.xLabelRotation))*a+3)}else this.xLabelWidth=0,this.xScalePaddingRight=this.padding,this.xScalePaddingLeft=this.padding},calculateYRange:c,drawingArea:function(){return this.startPoint-this.endPoint},calculateY:function(t){var e=this.drawingArea()/(this.min-this.max);return this.endPoint-e*(t-this.min)},calculateX:function(t){var e=(this.xLabelRotation>0,this.width-(this.xScalePaddingLeft+this.xScalePaddingRight)),i=e/Math.max(this.valuesCount-(this.offsetGridLines?0:1),1),n=i*t+this.xScalePaddingLeft;return this.offsetGridLines&&(n+=i/2),Math.round(n)},update:function(t){n.extend(this,t),this.fit()},draw:function(){var t=this.ctx,e=(this.endPoint-this.startPoint)/this.steps,i=Math.round(this.xScalePaddingLeft);this.display&&(t.fillStyle=this.textColor,t.font=this.font,s(this.yLabels,function(s,a){var o=this.endPoint-e*a,r=Math.round(o),h=this.showHorizontalLines;t.textAlign="right",t.textBaseline="middle",this.showLabels&&t.fillText(s,i-10,o),0!==a||h||(h=!0),h&&t.beginPath(),a>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),r+=n.aliasPixel(t.lineWidth),h&&(t.moveTo(i,r),t.lineTo(this.width,r),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(i-5,r),t.lineTo(i,r),t.stroke(),t.closePath()},this),s(this.xLabels,function(e,i){var n=this.calculateX(i)+y(this.lineWidth),s=this.calculateX(i-(this.offsetGridLines?.5:0))+y(this.lineWidth),a=this.xLabelRotation>0,o=this.showVerticalLines;0!==i||o||(o=!0),o&&t.beginPath(),i>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),o&&(t.moveTo(s,this.endPoint),t.lineTo(s,this.startPoint-3),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(s,this.endPoint),t.lineTo(s,this.endPoint+5),t.stroke(),t.closePath(),t.save(),t.translate(n,a?this.endPoint+12:this.endPoint+8),t.rotate(-1*b(this.xLabelRotation)),t.font=this.font,t.textAlign=a?"right":"center",t.textBaseline=a?"middle":"top",t.fillText(e,0,0),t.restore()},this))}}),i.RadialScale=i.Element.extend({initialize:function(){this.size=v([this.height,this.width]),this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2},calculateCenterOffset:function(t){var e=this.drawingArea/(this.max-this.min);return(t-this.min)*e},update:function(){this.lineArc?this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2:this.setScaleSize(),this.buildYLabels()},buildYLabels:function(){this.yLabels=[];for(var t=m(this.stepValue),e=0;e<=this.steps;e++)this.yLabels.push(S(this.templateString,{value:(this.min+e*this.stepValue).toFixed(t)}))},getCircumference:function(){return 2*Math.PI/this.valuesCount},setScaleSize:function(){var t,e,i,n,s,a,o,r,h,l,c,u,d=v([this.height/2-this.pointLabelFontSize-5,this.width/2]),p=this.width,f=0;for(this.ctx.font=z(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),e=0;ep&&(p=t.x+n,s=e),t.x-np&&(p=t.x+i,s=e):e>this.valuesCount/2&&t.x-i0){var n,s=i*(this.drawingArea/this.steps),a=this.yCenter-s;if(this.lineWidth>0)if(t.strokeStyle=this.lineColor,t.lineWidth=this.lineWidth,this.lineArc)t.beginPath(),t.arc(this.xCenter,this.yCenter,s,0,2*Math.PI),t.closePath(),t.stroke();else{t.beginPath();for(var o=0;o=0;e--){if(this.angleLineWidth>0){var i=this.getPointPosition(e,this.calculateCenterOffset(this.max));t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(i.x,i.y),t.stroke(),t.closePath()}var n=this.getPointPosition(e,this.calculateCenterOffset(this.max)+5);t.font=z(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),t.fillStyle=this.pointLabelFontColor;var a=this.labels.length,o=this.labels.length/2,r=o/2,h=r>e||e>a-r,l=e===r||e===a-r;0===e?t.textAlign="center":e===o?t.textAlign="center":o>e?t.textAlign="left":t.textAlign="right",l?t.textBaseline="middle":h?t.textBaseline="bottom":t.textBaseline="top",t.fillText(this.labels[e],n.x,n.y)}}}}}),i.animationService={frameDuration:17,animations:[],dropFrames:0,addAnimation:function(t,e,i){i||(t.animating=!0);for(var s=0;s1&&(e=Math.floor(this.dropFrames),this.dropFrames-=e);for(var i=0;ithis.animations[i].animationObject.numSteps&&(this.animations[i].animationObject.currentStep=this.animations[i].animationObject.numSteps),this.animations[i].animationObject.render(this.animations[i].chartInstance,this.animations[i].animationObject),this.animations[i].animationObject.currentStep==this.animations[i].animationObject.numSteps&&(this.animations[i].chartInstance.animating=!1,this.animations.splice(i,1),i--);var s=Date.now(),a=s-t-this.frameDuration,o=a/this.frameDuration;o>1&&(this.dropFrames+=o),this.animations.length>0&&n.requestAnimFrame.call(window,this.digestWrapper)}},n.addEvent(window,"resize",function(){var t;return function(){clearTimeout(t),t=setTimeout(function(){s(i.instances,function(t){t.options.responsive&&t.resize(t.render,!0)})},50)}}()),p?define(function(){return i}):"object"==typeof module&&module.exports&&(module.exports=i),t.Chart=i,i.noConflict=function(){return t.Chart=e,i}}).call(this),function(){"use strict";var t=this,e=t.Chart,i=e.helpers,n={scaleBeginAtZero:!0,scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,scaleShowHorizontalLines:!0,scaleShowVerticalLines:!0,barBorderWidth:2,barValueSpacing:5,barDatasetSpacing:1,hoverMode:"single",onHover:null,hoverAnimationDuration:400,legendTemplate:'
    <% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
'};e.Type.extend({name:"Bar",defaults:n,initialize:function(t){this.data=t;var n=this.options;this.ScaleClass=e.Scale.extend({offsetGridLines:!0,calculateBarX:function(t,e,i){var s=this.calculateBaseWidth(),a=this.calculateX(i)-s/2,o=this.calculateBarWidth(t);return a+o*e+e*n.barDatasetSpacing+o/2},calculateBaseWidth:function(){return this.calculateX(1)-this.calculateX(0)-2*n.barValueSpacing},calculateBarWidth:function(t){var e=this.calculateBaseWidth()-(t-1)*n.barDatasetSpacing;return e/t}}),i.bindEvents(this,this.options.tooltipEvents,this.onHover),this.BarClass=e.Rectangle.extend({ctx:this.chart.ctx}),this.buildScale(t.labels),i.each(this.data.datasets,function(t,e){t.metaData=[],i.each(t.data,function(e,i){t.metaData.push(new this.BarClass)},this)},this),this.eachBars(function(t,e,n){i.extend(t,{width:this.scale.calculateBarWidth(this.data.datasets.length),x:this.scale.calculateBarX(this.data.datasets.length,n,e),y:this.calculateBarBase(),_datasetIndex:n,_index:e}),t.save()},this),this.tooltip=new e.Tooltip({_chart:this.chart,_data:this.data,_options:this.options},this),this.update()},onHover:function(t){if("mouseout"==t.type)return!1;if(this.lastActive=this.lastActive||[],this.active=function(){switch(this.options.hoverMode){case"single":return this.getBarAtEvent(t);case"label":return this.getBarsAtEvent(t);case"dataset":return this.getDatasetAtEvent(t);default:return t}}.call(this),this.options.onHover&&this.options.onHover.call(this,this.active),this.lastActive)switch(this.options.hoverMode){case"single":this.lastActive[0].backgroundColor=this.data.datasets[this.lastActive[0]._datasetIndex].backgroundColor,this.lastActive[0].borderColor=this.data.datasets[this.lastActive[0]._datasetIndex].borderColor, -this.lastActive[0].borderWidth=0;break;case"label":for(var e=0;e=0||this.scale.min>=0&&this.scale.max<=0?(t=this.scale.calculateY(0),t+=this.options.scaleGridLineWidth):this.scale.min<0&&this.scale.max<0&&(t=this.scale.startPoint),t},update:function(){this.scale.update(),this.eachBars(function(t,e,n){i.extend(t,{width:this.scale.calculateBarWidth(this.data.datasets.length),x:this.scale.calculateBarX(this.data.datasets.length,n,e),y:this.scale.calculateY(this.data.datasets[n].data[e]),value:this.data.datasets[n].data[e],label:this.data.labels[e],datasetLabel:this.data.datasets[n].label,borderColor:this.data.datasets[n].borderColor,borderWidth:this.data.datasets[n].borderWidth,backgroundColor:this.data.datasets[n].backgroundColor,_datasetIndex:n,_index:e,_start:void 0})},this),this.render()},eachBars:function(t){i.each(this.data.datasets,function(e,n){i.each(e.metaData,t,this,n)},this)},eachValue:function(t){i.each(this.data.datasets,function(e,n){i.each(e.data,t,this,n)},this)},getBarsAtEvent:function(t){for(var e,n=[],s=i.getRelativePosition(t),a=function(t){n.push(t.metaData[e])},o=0;o<% for (var i=0; i
  • <%if(segments[i].label){%><%=segments[i].label%><%}%>
  • <%}%>'};e.Type.extend({name:"Doughnut",defaults:n,initialize:function(t){this.data=t,this.segments=[],this.outerRadius=(i.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,this.SegmentArc=e.Arc.extend({ctx:this.chart.ctx,x:this.chart.width/2,y:this.chart.height/2}),this.options.showTooltips&&i.bindEvents(this,this.options.tooltipEvents,function(t){var e="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];i.each(this.segments,function(t){t.restore(["fillColor"])}),i.each(e,function(t){t.fillColor=t.highlightColor}),this.showTooltip(e)}),this.calculateTotal(t),i.each(t,function(e,i){e.color||(e.color="hsl("+360*i/t.length+", 100%, 50%)"),this.addData(e,i,!0)},this),this.render()},getSegmentsAtEvent:function(t){var e=[],n=i.getRelativePosition(t);return i.each(this.segments,function(t){t.inRange(n.x,n.y)&&e.push(t)},this),e},addData:function(t,e,i){var n=e||this.segments.length;this.segments.splice(n,0,new this.SegmentArc({value:t.value,outerRadius:this.options.animateScale?0:this.outerRadius,innerRadius:this.options.animateScale?0:this.outerRadius/100*this.options.percentageInnerCutout,fillColor:t.color,highlightColor:t.highlight||t.color,showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,startAngle:1.5*Math.PI,circumference:this.options.animateRotate?0:this.calculateCircumference(t.value),label:t.label})),i||(this.reflow(),this.update())},calculateCircumference:function(t){return this.total>0?2*Math.PI*(t/this.total):0},calculateTotal:function(t){this.total=0,i.each(t,function(t){this.total+=Math.abs(t.value)},this)},update:function(){this.data.length==this.segments.length?i.each(this.data,function(t,e){i.extend(this.segments[e],{value:t.value,fillColor:t.color,highlightColor:t.highlight||t.color,showStroke:this.options.segmentShowStroke,strokeWidth:this.options.segmentStrokeWidth,strokeColor:this.options.segmentStrokeColor,label:t.label})},this):this.initialize(this.data),this.calculateTotal(this.segments),i.each(this.activeElements,function(t){t.restore(["fillColor"])}),i.each(this.segments,function(t){t.save()}),this.render()},removeData:function(t){var e=i.isNumber(t)?t:this.segments.length-1;this.segments.splice(e,1),this.reflow(),this.update()},reflow:function(){i.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.outerRadius=(i.min([this.chart.width,this.chart.height])-this.options.segmentStrokeWidth/2)/2,i.each(this.segments,function(t){t.update({outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout})},this)},draw:function(t){var e=t?t:1;this.clear(),i.each(this.segments,function(t,i){t.transition({circumference:this.calculateCircumference(t.value),outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.percentageInnerCutout},e),t.endAngle=t.startAngle+t.circumference,t.draw(),0===i&&(t.startAngle=1.5*Math.PI),i<% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>',offsetGridLines:!1};e.Type.extend({name:"Line",defaults:n,initialize:function(t){this.data=t,this.PointClass=e.Point.extend({offsetGridLines:this.options.offsetGridLines,strokeWidth:this.options.pointDotStrokeWidth,radius:this.options.pointDotRadius,display:this.options.pointDot,hitDetectionRadius:this.options.pointHitDetectionRadius,ctx:this.chart.ctx,inRange:function(t){return Math.pow(t-this.x,2)0&&ethis.scale.endPoint?t.controlPoints.outer.y=this.scale.endPoint:t.controlPoints.outer.ythis.scale.endPoint?t.controlPoints.inner.y=this.scale.endPoint:t.controlPoints.inner.y0&&(n.lineTo(r[r.length-1].x,this.scale.endPoint),n.lineTo(r[0].x,this.scale.endPoint),n.fillStyle=t.fillColor,n.closePath(),n.fill()),i.each(r,function(t){t.draw()})},this)}})}.call(this),function(){"use strict";var t=this,e=t.Chart,i=e.helpers,n={scaleShowLabelBackdrop:!0,scaleBackdropColor:"rgba(255,255,255,0.75)",scaleBeginAtZero:!0,scaleBackdropPaddingY:2,scaleBackdropPaddingX:2,scaleShowLine:!0,segmentShowStroke:!0,segmentStrokeColor:"#fff",segmentStrokeWidth:2,animationSteps:100,animationEasing:"easeOutBounce",animateRotate:!0,animateScale:!1,legendTemplate:'
      <% for (var i=0; i
    • <%if(segments[i].label){%><%=segments[i].label%><%}%>
    • <%}%>
    '};e.Type.extend({name:"PolarArea",defaults:n,initialize:function(t){this.data=t,this.segments=[],this.SegmentArc=e.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 e.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:!0,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:t.length}),this.updateScaleRange(t),this.scale.update(),i.each(t,function(t,e){this.addData(t,e,!0)},this),this.options.showTooltips&&i.bindEvents(this,this.options.tooltipEvents,function(t){var e="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];i.each(this.segments,function(t){t.restore(["fillColor"])}),i.each(e,function(t){t.fillColor=t.highlightColor}),this.showTooltip(e)}),this.render()},getSegmentsAtEvent:function(t){var e=[],n=i.getRelativePosition(t);return i.each(this.segments,function(t){t.inRange(n.x,n.y)&&e.push(t)},this),e},addData:function(t,e,i){var n=e||this.segments.length;this.segments.splice(n,0,new this.SegmentArc({fillColor:t.color,highlightColor:t.highlight||t.color,label:t.label,value:t.value,outerRadius:this.options.animateScale?0:this.scale.calculateCenterOffset(t.value),circumference:this.options.animateRotate?0:this.scale.getCircumference(),startAngle:1.5*Math.PI})),i||(this.reflow(),this.update())},removeData:function(t){var e=i.isNumber(t)?t:this.segments.length-1;this.segments.splice(e,1),this.reflow(),this.update()},calculateTotal:function(t){this.total=0,i.each(t,function(t){this.total+=t.value},this),this.scale.valuesCount=this.segments.length},updateScaleRange:function(t){var e=[];i.each(t,function(t){e.push(t.value)});var n=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}:i.calculateScaleRange(e,i.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);i.extend(this.scale,n,{size:i.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2})},update:function(){this.data.length==this.segments.length?i.each(this.data,function(t,e){i.extend(this.segments[e],{fillColor:t.color,highlightColor:t.highlight||t.color,label:t.label,value:t.value})},this):this.initialize(this.data),this.calculateTotal(this.segments),i.each(this.segments,function(t){t.save()}),this.reflow(),this.render()},reflow:function(){i.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.updateScaleRange(this.segments),this.scale.update(),i.extend(this.scale,{xCenter:this.chart.width/2,yCenter:this.chart.height/2}),i.each(this.segments,function(t){t.update({outerRadius:this.scale.calculateCenterOffset(t.value)})},this)},draw:function(t){var e=t||1;this.clear(),i.each(this.segments,function(t,i){t.transition({circumference:this.scale.getCircumference(),outerRadius:this.scale.calculateCenterOffset(t.value)},e),t.endAngle=t.startAngle+t.circumference,0===i&&(t.startAngle=1.5*Math.PI),i<% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>'},initialize:function(t){this.data=t,this.PointClass=e.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(t),this.options.showTooltips&&i.bindEvents(this,this.options.tooltipEvents,function(t){var e="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),i.each(e,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(e)}),i.each(t.datasets,function(e){var n={label:e.label||null,fillColor:e.fillColor,strokeColor:e.strokeColor,pointColor:e.pointColor,pointStrokeColor:e.pointStrokeColor,points:[]};this.datasets.push(n),i.each(e.data,function(i,s){var a;this.scale.animation||(a=this.scale.getPointPosition(s,this.scale.calculateCenterOffset(i))),n.points.push(new this.PointClass({value:i,label:t.labels[s],datasetLabel:e.label,x:this.options.animation?this.scale.xCenter:a.x,y:this.options.animation?this.scale.yCenter:a.y,strokeColor:e.pointStrokeColor,fillColor:e.pointColor,highlightFill:e.pointHighlightFill||e.pointColor,highlightStroke:e.pointHighlightStroke||e.pointStrokeColor}))},this)},this),this.render()},eachPoints:function(t){i.each(this.datasets,function(e){i.each(e.points,t,this)},this)},getPointsAtEvent:function(t){var e=i.getRelativePosition(t),n=i.getAngleFromPoint({x:this.scale.xCenter,y:this.scale.yCenter},e),s=2*Math.PI/this.scale.valuesCount,a=Math.round((n.angle-1.5*Math.PI)/s),o=[];return(a>=this.scale.valuesCount||0>a)&&(a=0),n.distance<=this.scale.drawingArea&&i.each(this.datasets,function(t){o.push(t.points[a])}),o},buildScale:function(t){this.scale=new e.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,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:t.labels,valuesCount:t.datasets[0].data.length}),this.scale.setScaleSize(),this.updateScaleRange(t.datasets),this.scale.buildYLabels()},updateScaleRange:function(t){var e=function(){var e=[];return i.each(t,function(t){t.data?e=e.concat(t.data):i.each(t.points,function(t){e.push(t.value)})}),e}(),n=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}:i.calculateScaleRange(e,i.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);i.extend(this.scale,n)},addData:function(t,e){this.scale.valuesCount++,i.each(t,function(t,i){var n=this.scale.getPointPosition(this.scale.valuesCount,this.scale.calculateCenterOffset(t));this.datasets[i].points.push(new this.PointClass({value:t,label:e,datasetLabel:this.datasets[i].label,x:n.x,y:n.y,strokeColor:this.datasets[i].pointStrokeColor,fillColor:this.datasets[i].pointColor}))},this),this.scale.labels.push(e),this.reflow(),this.update()},removeData:function(){this.scale.valuesCount--,this.scale.labels.shift(),i.each(this.datasets,function(t){t.points.shift()},this),this.reflow(),this.update()},update:function(){i.each(this.data.datasets,function(t,e){i.extend(this.datasets[e],{label:t.label||null,fillColor:t.fillColor,strokeColor:t.strokeColor,pointColor:t.pointColor,pointStrokeColor:t.pointStrokeColor}),i.each(t.data,function(n,s){i.extend(this.datasets[e].points[s],{value:n,label:this.data.labels[s],datasetLabel:t.label,strokeColor:t.pointStrokeColor,fillColor:t.pointColor,highlightFill:t.pointHighlightFill||t.pointColor,highlightStroke:t.pointHighlightStroke||t.pointStrokeColor})},this)},this),this.eachPoints(function(t){t.save()}),this.reflow(),this.render()},reflow:function(){i.extend(this.scale,{width:this.chart.width,height:this.chart.height,size:i.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(t){var e=t||1,n=this.chart.ctx;this.clear(),this.scale.draw(),i.each(this.datasets,function(t){i.each(t.points,function(t,i){t.hasValue()&&t.transition(this.scale.getPointPosition(i,this.scale.calculateCenterOffset(t.value)),e)},this),n.lineWidth=this.options.datasetStrokeWidth,n.strokeStyle=t.strokeColor,n.beginPath(),i.each(t.points,function(t,e){0===e?n.moveTo(t.x,t.y):n.lineTo(t.x,t.y)},this),n.closePath(),n.stroke(),n.fillStyle=t.fillColor,n.fill(),i.each(t.points,function(t){t.hasValue()&&t.draw()})},this)}})}.call(this),!function t(e,i,n){function s(o,r){if(!i[o]){if(!e[o]){var h="function"==typeof require&&require;if(!r&&h)return h(o,!0);if(a)return a(o,!0);var l=new Error("Cannot find module '"+o+"'");throw l.code="MODULE_NOT_FOUND",l}var c=i[o]={exports:{}};e[o][0].call(c.exports,function(t){var i=e[o][1][t];return s(i?i:t)},c,c.exports,t,e,i,n)}return i[o].exports}for(var a="function"==typeof require&&require,o=0;o=n?n/12.92:Math.pow((n+.055)/1.055,2.4)}return.2126*e[0]+.7152*e[1]+.0722*e[2]},contrast:function(t){var e=this.luminosity(),i=t.luminosity();return e>i?(e+.05)/(i+.05):(i+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb,e=(299*t[0]+587*t[1]+114*t[2])/1e3;return 128>e},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;3>e;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){return this.values.hsl[2]+=this.values.hsl[2]*t,this.setValues("hsl",this.values.hsl),this},darken:function(t){return this.values.hsl[2]-=this.values.hsl[2]*t,this.setValues("hsl",this.values.hsl),this},saturate:function(t){return this.values.hsl[1]+=this.values.hsl[1]*t,this.setValues("hsl",this.values.hsl),this},desaturate:function(t){return this.values.hsl[1]-=this.values.hsl[1]*t,this.setValues("hsl",this.values.hsl),this},whiten:function(t){return this.values.hwb[1]+=this.values.hwb[1]*t,this.setValues("hwb",this.values.hwb),this},blacken:function(t){return this.values.hwb[2]+=this.values.hwb[2]*t,this.setValues("hwb",this.values.hwb),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){return this.setValues("alpha",this.values.alpha-this.values.alpha*t),this},opaquer:function(t){return this.setValues("alpha",this.values.alpha+this.values.alpha*t),this},rotate:function(t){var e=this.values.hsl[0];return e=(e+t)%360,e=0>e?360+e:e,this.values.hsl[0]=e,this.setValues("hsl",this.values.hsl),this},mix:function(t,e){e=1-(null==e?.5:e);for(var i=2*e-1,n=this.alpha()-t.alpha(),s=((i*n==-1?i:(i+n)/(1+i*n))+1)/2,a=1-s,o=this.rgbArray(),r=t.rgbArray(),h=0;he&&(e+=360),n=(r+h)/2,i=h==r?0:.5>=n?l/(h+r):l/(2-h-r),[e,100*i,100*n]}function s(t){var e,i,n,s=t[0],a=t[1],o=t[2],r=Math.min(s,a,o),h=Math.max(s,a,o),l=h-r;return i=0==h?0:l/h*1e3/10,h==r?e=0:s==h?e=(a-o)/l:a==h?e=2+(o-s)/l:o==h&&(e=4+(s-a)/l),e=Math.min(60*e,360),0>e&&(e+=360),n=h/255*1e3/10,[e,i,n]}function a(t){var e=t[0],i=t[1],s=t[2],a=n(t)[0],o=1/255*Math.min(e,Math.min(i,s)),s=1-1/255*Math.max(e,Math.max(i,s));return[a,100*o,100*s]}function o(t){var e,i,n,s,a=t[0]/255,o=t[1]/255,r=t[2]/255;return s=Math.min(1-a,1-o,1-r),e=(1-a-s)/(1-s)||0,i=(1-o-s)/(1-s)||0,n=(1-r-s)/(1-s)||0,[100*e,100*i,100*n,100*s]}function h(t){return $[JSON.stringify(t)]}function l(t){var e=t[0]/255,i=t[1]/255,n=t[2]/255;e=e>.04045?Math.pow((e+.055)/1.055,2.4):e/12.92,i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92,n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92;var s=.4124*e+.3576*i+.1805*n,a=.2126*e+.7152*i+.0722*n,o=.0193*e+.1192*i+.9505*n;return[100*s,100*a,100*o]}function c(t){var e,i,n,s=l(t),a=s[0],o=s[1],r=s[2];return a/=95.047,o/=100,r/=108.883,a=a>.008856?Math.pow(a,1/3):7.787*a+16/116,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,e=116*o-16,i=500*(a-o),n=200*(o-r),[e,i,n]}function u(t){return V(c(t))}function d(t){var e,i,n,s,a,o=t[0]/360,r=t[1]/100,h=t[2]/100;if(0==r)return a=255*h,[a,a,a];i=.5>h?h*(1+r):h+r-h*r,e=2*h-i,s=[0,0,0];for(var l=0;3>l;l++)n=o+1/3*-(l-1),0>n&&n++,n>1&&n--,a=1>6*n?e+6*(i-e)*n:1>2*n?i:2>3*n?e+(i-e)*(2/3-n)*6:e,s[l]=255*a;return s}function p(t){var e,i,n=t[0],s=t[1]/100,a=t[2]/100;return a*=2,s*=1>=a?a:2-a,i=(a+s)/2,e=2*s/(a+s),[n,100*e,100*i]}function f(t){return a(d(t))}function v(t){return o(d(t))}function m(t){return h(d(t))}function y(t){var e=t[0]/60,i=t[1]/100,n=t[2]/100,s=Math.floor(e)%6,a=e-Math.floor(e),o=255*n*(1-i),r=255*n*(1-i*a),h=255*n*(1-i*(1-a)),n=255*n;switch(s){case 0:return[n,h,o];case 1:return[r,n,o];case 2:return[o,n,h];case 3:return[o,r,n];case 4:return[h,o,n];case 5:return[n,o,r]}}function w(t){var e,i,n=t[0],s=t[1]/100,a=t[2]/100;return i=(2-s)*a,e=s*a,e/=1>=i?i:2-i,e=e||0,i/=2,[n,100*e,100*i]}function S(t){return a(y(t))}function x(t){return o(y(t))}function C(t){return h(y(t))}function k(t){var e,i,n,s,a=t[0]/360,o=t[1]/100,h=t[2]/100,l=o+h;switch(l>1&&(o/=l,h/=l),e=Math.floor(6*a),i=1-h,n=6*a-e,0!=(1&e)&&(n=1-n),s=o+n*(i-o),e){default:case 6:case 0:r=i,g=s,b=o;break;case 1:r=s,g=i,b=o;break;case 2:r=o,g=i,b=s;break;case 3:r=o,g=s,b=i;break;case 4:r=s,g=o,b=i;break;case 5:r=i,g=o,b=s}return[255*r,255*g,255*b]}function P(t){return n(k(t))}function L(t){return s(k(t))}function A(t){return o(k(t))}function M(t){return h(k(t))}function F(t){var e,i,n,s=t[0]/100,a=t[1]/100,o=t[2]/100,r=t[3]/100;return e=1-Math.min(1,s*(1-r)+r),i=1-Math.min(1,a*(1-r)+r),n=1-Math.min(1,o*(1-r)+r),[255*e,255*i,255*n]}function R(t){return n(F(t))}function W(t){return s(F(t))}function z(t){return a(F(t))}function T(t){return h(F(t))}function _(t){var e,i,n,s=t[0]/100,a=t[1]/100,o=t[2]/100;return e=3.2406*s+-1.5372*a+o*-.4986,i=s*-.9689+1.8758*a+.0415*o,n=.0557*s+a*-.204+1.057*o,e=e>.0031308?1.055*Math.pow(e,1/2.4)-.055:e=12.92*e,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:i=12.92*i,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:n=12.92*n,e=Math.min(Math.max(0,e),1),i=Math.min(Math.max(0,i),1),n=Math.min(Math.max(0,n),1),[255*e,255*i,255*n]}function I(t){var e,i,n,s=t[0],a=t[1],o=t[2];return s/=95.047,a/=100,o/=108.883,s=s>.008856?Math.pow(s,1/3):7.787*s+16/116,a=a>.008856?Math.pow(a,1/3):7.787*a+16/116,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,e=116*a-16,i=500*(s-a),n=200*(a-o),[e,i,n]}function O(t){return V(I(t))}function B(t){var e,i,n,s,a=t[0],o=t[1],r=t[2];return 8>=a?(i=100*a/903.3,s=7.787*(i/100)+16/116):(i=100*Math.pow((a+16)/116,3),s=Math.pow(i/100,1/3)),e=.008856>=e/95.047?e=95.047*(o/500+s-16/116)/7.787:95.047*Math.pow(o/500+s,3),n=.008859>=n/108.883?n=108.883*(s-r/200-16/116)/7.787:108.883*Math.pow(s-r/200,3),[e,i,n]}function V(t){var e,i,n,s=t[0],a=t[1],o=t[2];return e=Math.atan2(o,a),i=360*e/2/Math.PI,0>i&&(i+=360),n=Math.sqrt(a*a+o*o),[s,n,i]}function E(t){return _(B(t))}function D(t){var e,i,n,s=t[0],a=t[1],o=t[2];return n=o/360*2*Math.PI,e=a*Math.cos(n),i=a*Math.sin(n),[s,e,i]}function H(t){return B(D(t))}function q(t){return E(D(t))}function X(t){return U[t]}function Y(t){return n(X(t))}function j(t){return s(X(t))}function N(t){return a(X(t))}function G(t){return o(X(t))}function Z(t){return c(X(t))}function Q(t){return l(X(t))}e.exports={rgb2hsl:n,rgb2hsv:s,rgb2hwb:a,rgb2cmyk:o,rgb2keyword:h,rgb2xyz:l,rgb2lab:c,rgb2lch:u,hsl2rgb:d,hsl2hsv:p,hsl2hwb:f,hsl2cmyk:v,hsl2keyword:m,hsv2rgb:y,hsv2hsl:w,hsv2hwb:S,hsv2cmyk:x,hsv2keyword:C,hwb2rgb:k,hwb2hsl:P,hwb2hsv:L,hwb2cmyk:A,hwb2keyword:M,cmyk2rgb:F,cmyk2hsl:R,cmyk2hsv:W,cmyk2hwb:z,cmyk2keyword:T,keyword2rgb:X,keyword2hsl:Y,keyword2hsv:j,keyword2hwb:N,keyword2cmyk:G,keyword2lab:Z,keyword2xyz:Q,xyz2rgb:_,xyz2lab:I,xyz2lch:O,lab2xyz:B,lab2rgb:E,lab2lch:V,lch2lab:D,lch2xyz:H,lch2rgb:q};var U={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},$={};for(var J in U)$[JSON.stringify(U[J])]=J},{}],3:[function(t,e,i){var n=t("./conversions"),s=function(){return new l};for(var a in n){s[a+"Raw"]=function(t){return function(e){return"number"==typeof e&&(e=Array.prototype.slice.call(arguments)),n[t](e)}}(a);var o=/(\w+)2(\w+)/.exec(a),r=o[1],h=o[2];s[r]=s[r]||{},s[r][h]=s[a]=function(t){return function(e){"number"==typeof e&&(e=Array.prototype.slice.call(arguments));var i=n[t](e);if("string"==typeof i||void 0===i)return i;for(var s=0;se||t[3]&&t[3]<1?u(t,e):"rgb("+t[0]+", "+t[1]+", "+t[2]+")"}function u(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"rgba("+t[0]+", "+t[1]+", "+t[2]+", "+e+")"}function d(t,e){if(1>e||t[3]&&t[3]<1)return p(t,e);var i=Math.round(t[0]/255*100),n=Math.round(t[1]/255*100),s=Math.round(t[2]/255*100);return"rgb("+i+"%, "+n+"%, "+s+"%)"}function p(t,e){var i=Math.round(t[0]/255*100),n=Math.round(t[1]/255*100),s=Math.round(t[2]/255*100);return"rgba("+i+"%, "+n+"%, "+s+"%, "+(e||t[3]||1)+")"}function g(t,e){return 1>e||t[3]&&t[3]<1?f(t,e):"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"}function f(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hsla("+t[0]+", "+t[1]+"%, "+t[2]+"%, "+e+")"}function v(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==e&&1!==e?", "+e:"")+")"}function m(t){return S[t.slice(0,3)]}function b(t,e,i){return Math.min(Math.max(e,t),i)}function y(t){var e=t.toString(16).toUpperCase();return e.length<2?"0"+e:e}var w=t("color-name");e.exports={getRgba:n,getHsla:s,getRgb:o,getHsl:r,getHwb:a,getAlpha:h,hexString:l,rgbString:c,rgbaString:u,percentString:d,percentaString:p,hslString:g,hslaString:f,hwbString:v,keyword:m};var S={};for(var x in w)S[w[x]]=x},{"color-name":5}],5:[function(t,e,i){e.exports={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}},{}]},{},[1]); \ No newline at end of file +(function(){"use strict";var t=this,e=t.Chart,i=function(t){this.canvas=t.canvas,this.ctx=t;var e=function(t,e){return t["offset"+e]?t["offset"+e]:document.defaultView.getComputedStyle(t).getPropertyValue(e)},i=this.width=e(t.canvas,"Width")||t.canvas.width,n=this.height=e(t.canvas,"Height")||t.canvas.height;return t.canvas.width=i,t.canvas.height=n,i=this.width=t.canvas.width,n=this.height=t.canvas.height,this.aspectRatio=this.width/this.height,a.retinaScale(this),this};i.defaults={global:{animation:!0,animationDuration:1e3,animationEasing:"easeOutQuart",showScale:!0,scaleOverride:!1,scaleSteps:null,scaleStepWidth:null,scaleStartValue:null,scaleLineColor:"rgba(0,0,0,.1)",scaleLineWidth:1,scaleShowLabels:!0,scaleLabel:"<%=value%>",scaleIntegersOnly:!0,scaleBeginAtZero:!1,scaleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",scaleFontSize:12,scaleFontStyle:"normal",scaleFontColor:"#666",responsive:!1,maintainAspectRatio:!0,hoverMode:"single",onHover:null,hoverAnimationDuration:400,showTooltips:!0,customTooltips:!1,tooltipEvents:["mousemove","touchstart","touchmove","mouseout"],tooltipBackgroundColor:"rgba(0,0,0,0.8)",tooltipFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipFontSize:14,tooltipFontStyle:"normal",tooltipFontColor:"#fff",tooltipTitleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",tooltipTitleFontSize:14,tooltipTitleFontStyle:"bold",tooltipTitleFontColor:"#fff",tooltipYPadding:6,tooltipXPadding:6,tooltipCaretSize:8,tooltipCornerRadius:6,tooltipXOffset:10,tooltipTemplate:"<%if (label){%><%=label%>: <%}%><%= value %>",multiTooltipTemplate:"<%if (datasetLabel){%><%=datasetLabel%>: <%}%><%= value %>",multiTooltipKeyBackground:"#fff",onAnimationProgress:function(){},onAnimationComplete:function(){},colorFallback:"rgba(0,0,0,0.1)"}},i.types={};var a=i.helpers={},n=a.each=function(t,e,i){var a=Array.prototype.slice.call(arguments,3);if(t)if(t.length===+t.length){var n;for(n=0;n=0;a--){var n=t[a];if(e(n))return n}},a.inherits=function(t){var e=this,i=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return e.apply(this,arguments)},a=function(){this.constructor=i};return a.prototype=e.prototype,i.prototype=new a,i.extend=l,t&&o(i.prototype,t),i.__super__=e.prototype,i}),c=a.noop=function(){},u=a.uid=function(){var t=0;return function(){return"chart-"+t++}}(),d=a.warn=function(t){window.console&&"function"==typeof window.console.warn&&console.warn(t)},p=a.amd="function"==typeof define&&define.amd,g=a.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},f=a.max=function(t){return Math.max.apply(Math,t)},v=a.min=function(t){return Math.min.apply(Math,t)},m=(a.cap=function(t,e,i){if(g(e)){if(t>e)return e}else if(g(i)&&i>t)return i;return t},a.getDecimalPlaces=function(t){if(t%1!==0&&g(t)){var e=t.toString();if(e.indexOf("e-")<0)return e.split(".")[1].length;if(e.indexOf(".")<0)return parseInt(e.split("e-")[1]);var i=e.split(".")[1].split("e-");return i[0].length+parseInt(i[1])}return 0}),b=a.radians=function(t){return t*(Math.PI/180)},y=(a.getAngleFromPoint=function(t,e){var i=e.x-t.x,a=e.y-t.y,n=Math.sqrt(i*i+a*a),s=2*Math.PI+Math.atan2(a,i);return 0>i&&0>a&&(s+=2*Math.PI),{angle:s,distance:n}},a.aliasPixel=function(t){return t%2===0?0:.5}),w=(a.splineCurve=function(t,e,i,a){var n=Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2)),s=Math.sqrt(Math.pow(i.x-e.x,2)+Math.pow(i.y-e.y,2)),o=a*n/(n+s),r=a*s/(n+s);return{next:{x:e.x-o*(i.x-t.x),y:e.y-o*(i.y-t.y)},previous:{x:e.x+r*(i.x-t.x),y:e.y+r*(i.y-t.y)}}},a.calculateOrderOfMagnitude=function(t){return Math.floor(Math.log(t)/Math.LN10)}),x=(a.calculateScaleRange=function(t,e,i,a,n){var s=2,o=Math.floor(e/(1.5*i)),r=s>=o,h=f(t),l=v(t);h===l&&(h+=.5,l>=.5&&!a?l-=.5:h+=.5);for(var c=Math.abs(h-l),u=w(c),d=Math.ceil(h/(1*Math.pow(10,u)))*Math.pow(10,u),p=a?0:Math.floor(l/(1*Math.pow(10,u)))*Math.pow(10,u),g=d-p,m=Math.pow(10,u),b=Math.round(g/m);(b>o||o>2*b)&&!r;)if(b>o)m*=2,b=Math.round(g/m),b%1!==0&&(r=!0);else if(n&&u>=0){if(m/2%1!==0)break;m/=2,b=Math.round(g/m)}else m/=2,b=Math.round(g/m);return r&&(b=s,m=g/b),{steps:b,stepValue:m,min:p,max:p+b*m}},a.template=function(t,e){function i(t,e){var i=/\W/.test(t)?new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+t.replace(/[\r\t\n]/g," ").split("<%").join(" ").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split(" ").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');"):a[t]=a[t];return e?i(e):i}if(t instanceof Function)return t(e);var a={};return i(t,e)}),S=(a.generateLabels=function(t,e,i,a){var s=new Array(e);return t&&n(s,function(e,n){s[n]=x(t,{value:i+a*(n+1)})}),s},a.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-0.5*(--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){return(t/=.5)<1?.5*t*t*t:.5*((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){return(t/=.5)<1?.5*t*t*t*t:-0.5*((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){return(t/=.5)<1?.5*t*t*t*t*t:.5*((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-0.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-0.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var e=1.70158,i=0,a=1;return 0===t?0:1==(t/=1)?1:(i||(i=.3),at?-.5*a*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-e)*Math.PI/i):a*Math.pow(2,-10*(t-=1))*Math.sin(2*(1*t-e)*Math.PI/i)*.5+1)},easeInBack:function(t){var e=1.70158;return 1*(t/=1)*t*((e+1)*t-e)},easeOutBack:function(t){var e=1.70158;return 1*((t=t/1-1)*t*((e+1)*t+e)+1)},easeInOutBack:function(t){var e=1.70158;return(t/=.5)<1?.5*t*t*(((e*=1.525)+1)*t-e):.5*((t-=2)*t*(((e*=1.525)+1)*t+e)+2)},easeInBounce:function(t){return 1-S.easeOutBounce(1-t)},easeOutBounce:function(t){return(t/=1)<1/2.75?7.5625*t*t:2/2.75>t?1*(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1*(7.5625*(t-=2.25/2.75)*t+.9375):1*(7.5625*(t-=2.625/2.75)*t+.984375)},easeInOutBounce:function(t){return.5>t?.5*S.easeInBounce(2*t):.5*S.easeOutBounce(2*t-1)+.5}}),C=a.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),k=(a.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),a.animationLoop=function(t,e,i,a,n,s){var o=0,r=S[i]||S.linear,h=function(){o++;var i=o/e,l=r(i);t.call(s,l,i,o),a.call(s,l,i),e>o?s.animationFrame=C(h):n.apply(s)};C(h)},a.getRelativePosition=function(t){var e,i,a=t.originalEvent||t,n=t.currentTarget||t.srcElement,s=n.getBoundingClientRect();return a.touches?(e=a.touches[0].clientX-s.left,i=a.touches[0].clientY-s.top):(e=a.clientX-s.left,i=a.clientY-s.top),{x:e,y:i}},a.addEvent=function(t,e,i){t.addEventListener?t.addEventListener(e,i):t.attachEvent?t.attachEvent("on"+e,i):t["on"+e]=i}),P=a.removeEvent=function(t,e,i){t.removeEventListener?t.removeEventListener(e,i,!1):t.detachEvent?t.detachEvent("on"+e,i):t["on"+e]=c},A=(a.bindEvents=function(t,e,i){t.events||(t.events={}),n(e,function(e){t.events[e]=function(){i.apply(t,arguments)},k(t.chart.canvas,e,t.events[e])})},a.unbindEvents=function(t,e){n(e,function(e,i){P(t.chart.canvas,i,e)})}),L=a.getMaximumWidth=function(t){var e=t.parentNode,i=parseInt(_(e,"padding-left"))+parseInt(_(e,"padding-right"));return e.clientWidth-i},M=a.getMaximumHeight=function(t){var e=t.parentNode,i=parseInt(_(e,"padding-bottom"))+parseInt(_(e,"padding-top"));return e.clientHeight-i},_=a.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},F=(a.getMaximumSize=a.getMaximumWidth,a.retinaScale=function(t){var e=t.ctx,i=t.canvas.width,a=t.canvas.height;window.devicePixelRatio&&(e.canvas.style.width=i+"px",e.canvas.style.height=a+"px",e.canvas.height=a*window.devicePixelRatio,e.canvas.width=i*window.devicePixelRatio,e.scale(window.devicePixelRatio,window.devicePixelRatio))}),R=a.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},W=a.fontString=function(t,e,i){return e+" "+t+"px "+i},T=a.longestText=function(t,e,i){t.font=e;var a=0;return n(i,function(e){var i=t.measureText(e).width;a=i>a?i:a}),a},z=a.drawRoundedRectangle=function(t,e,i,a,n,s){t.beginPath(),t.moveTo(e+s,i),t.lineTo(e+a-s,i),t.quadraticCurveTo(e+a,i,e+a,i+s),t.lineTo(e+a,i+n-s),t.quadraticCurveTo(e+a,i+n,e+a-s,i+n),t.lineTo(e+s,i+n),t.quadraticCurveTo(e,i+n,e,i+n-s),t.lineTo(e,i+s),t.quadraticCurveTo(e,i,e+s,i),t.closePath()};a.color=function(t){return window.Color?window.Color(t):(console.log("Color.js not found!"),t)},a.isArray=function(t){return Array.isArray?Array.isArray(t):"[object Array]"===Object.prototype.toString.call(arg)};i.instances={},i.Type=function(t,e,a){this.options=e,this.chart=a,this.id=u(),i.instances[this.id]=this,e.responsive&&this.resize(),this.initialize.call(this,t)},o(i.Type.prototype,{initialize:function(){return this},clear:function(){return R(this.chart),this},stop:function(){return i.animationService.cancelAnimation(this),this},resize:function(t){this.stop();var e=this.chart.canvas,i=L(this.chart.canvas),a=this.options.maintainAspectRatio?i/this.chart.aspectRatio:M(this.chart.canvas);return e.width=this.chart.width=i,e.height=this.chart.height=a,F(this.chart),"function"==typeof t&&t.apply(this,Array.prototype.slice.call(arguments,1)),this},redraw:c,render:function(t){if(this.options.animation){var e=new i.Animation;e.numSteps=(t||this.options.animationDuration)/16.66,e.easing=this.options.animationEasing,e.render=function(t,e){var i=a.easingEffects[e.easing],n=e.currentStep/e.numSteps,s=i(n);t.draw(s,n,e.currentStep)},e.onAnimationProgress=this.options.onAnimationProgress,e.onAnimationComplete=this.options.onAnimationComplete,i.animationService.addAnimation(this,e,t)}else this.draw(),this.options.onAnimationComplete.call(this);return this},eachElement:function(t){a.each(this.data.datasets,function(e,i){a.each(e.metaData,t,this,e.metaData,i)},this)},eachValue:function(t){a.each(this.data.datasets,function(e,i){a.each(e.data,t,this,i)},this)},eachDataset:function(t){a.each(this.data.datasets,t,this)},getElementsAtEvent:function(t){for(var e,i=[],n=a.getRelativePosition(t),s=function(t){i.push(t.metaData[e])},o=0;o0||t.borderWidth>0)&&(e.beginPath(),e.arc(t.x,t.y,t.radius,0,2*Math.PI),e.closePath(),e.strokeStyle=t.borderColor||i.defaults.global.colorFallback,e.lineWidth=t.borderWidth||i.defaults.global.colorFallback,e.fillStyle=t.backgroundColor||i.defaults.global.colorFallback,e.fill(),e.stroke())}}),i.Line=i.Element.extend({draw:function(){var t=this._vm,e=this._chart.ctx;e.lineWidth=t.borderWidth||i.defaults.global.colorFallback,e.strokeStyle=t.borderColor||i.defaults.global.colorFallback,e.beginPath(),a.each(t._points,function(i,a){if(0===a)e.moveTo(i._vm.x,i._vm.y);else if(t._tension>0,0)e.lineTo(i._vm.x,i._vm.y);else{var n=this.previousPoint(i,t._points,a);e.bezierCurveTo(n._vm.controlPointNextX,n._vm.controlPointNextY,i._vm.controlPointPreviousX,i._vm.controlPointPreviousY,i._vm.x,i._vm.y)}},this),e.stroke(),t._points.length>0&&(e.lineTo(t._points[t._points.length-1].x,t.scaleBottom),e.lineTo(t._points[0].x,t.scaleBottom),e.fillStyle=t.backgroundColor||i.defaults.global.colorFallback,e.closePath(),e.fill())},previousPoint:function(t,e,i){return a.findPreviousWhere(e,function(){return!0},i)||t}}),i.Arc=i.Element.extend({inRange:function(t,e){var i=a.getAngleFromPoint(this,{x:t,y:e}),n=i.angle>=this.startAngle&&i.angle<=this.endAngle,s=i.distance>=this.innerRadius&&i.distance<=this.outerRadius;return n&&s},tooltipPosition:function(){var t=this.startAngle+(this.endAngle-this.startAngle)/2,e=(this.outerRadius-this.innerRadius)/2+this.innerRadius;return{x:this.x+Math.cos(t)*e,y:this.y+Math.sin(t)*e}},draw:function(){var t=this._chart.ctx,e=this._vm;t.beginPath(),t.arc(e.x,e.y,e.outerRadius,e.startAngle,e.endAngle),t.arc(e.x,e.y,e.innerRadius,e.endAngle,e.startAngle,!0),t.closePath(),t.strokeStyle=e.borderColor,t.lineWidth=e.borderWidth,t.fillStyle=e.backgroundColor,t.fill(),t.lineJoin="bevel",e.borderWidth&&t.stroke()}}),i.Rectangle=i.Element.extend({draw:function(){var t=this._vm,e=this.ctx,i=t.width/2,a=t.x-i,n=t.x+i,s=t.base-(t.base-t.y),o=t.borderWidth/2;t.borderWidth&&(a+=o,n-=o,s+=o),e.beginPath(),e.fillStyle=t.backgroundColor,e.strokeStyle=t.borderColor,e.lineWidth=t.borderWidth,e.moveTo(a,t.base),e.lineTo(a,s),e.lineTo(n,s),e.lineTo(n,t.base),e.fill(),t.borderWidth&&e.stroke()},height:function(){var t=this._vm;return t.base-t.y},inRange:function(t,e){var i=this._vm;return i.y=i.x-i.width/2&&t<=i.x+i.width/2&&e>=i.y&&e<=i.base:t>=i.x-i.width/2&&t<=i.x+i.width/2&&e>=i.base&&e<=i.y},inGroupRange:function(t){var e=this._vm;return t>=e.x-e.width/2&&t<=e.x+e.width/2},tooltipPosition:function(){var t=this._vm;return t.y=0&&(i=this._data.datasets[r].metaData,n=h(i,this._active[0]),-1===n);r--);var l=function(t){var e,i,r,h,l,c=[],u=[],d=[];return a.each(this._data.datasets,function(t){e=t.metaData,e[n]&&e[n].hasValue()&&c.push(e[n])}),a.each(c,function(t){u.push(t._vm.x),d.push(t._vm.y),s.push(a.template(this._options.multiTooltipTemplate,t)),o.push({fill:t._vm.backgroundColor,stroke:t._vm.borderColor})},this),l=v(d),r=f(d),h=v(u),i=f(u),{x:h>this._chart.width/2?h:i,y:(l+r)/2}}.call(this,n);a.extend(this,{x:l.x,y:l.y,labels:s,title:this._active.length?this._active[0].label:"",legendColors:o,legendBackgroundColor:this._options.multiTooltipKeyBackground}),this.height=s.length*this.fontSize+(s.length-1)*(this.fontSize/2)+2*this.yPadding+1.5*this.titleFontSize;var c=t.measureText(this.title).width,u=T(t,this.font,s)+this.fontSize+3,d=f([u,c]);this.width=d+2*this.xPadding;var p=this.height/2;this.y-p<0?this.y=p:this.y+p>this._chart.height&&(this.y=this._chart.height-p),this.x>this._chart.width/2?this.x-=this.xOffset+this.width:this.x+=this.xOffset}return this},draw:function(){var t=this._chart.ctx,e=this._vm;switch(this._options.hoverMode){case"single":t.font=W(e.fontSize,e._fontStyle,e._fontFamily),e.xAlign="center",e.yAlign="above";var i=e.caretPadding=2,n=t.measureText(e.text).width+2*e.xPadding,s=e.fontSize+2*e.yPadding,o=s+e.caretHeight+i;e.x+n/2>this._chart.width?e.xAlign="left":e.x-n/2<0&&(e.xAlign="right"),e.y-o<0&&(e.yAlign="below");var r=e.x-n/2,h=e.y-o;if(t.fillStyle=a.color(e.backgroundColor).alpha(e.opacity).rgbString(),this._custom)this._custom(this._vm);else{switch(e.yAlign){case"above":t.beginPath(),t.moveTo(e.x,e.y-i),t.lineTo(e.x+e.caretHeight,e.y-(i+e.caretHeight)),t.lineTo(e.x-e.caretHeight,e.y-(i+e.caretHeight)),t.closePath(),t.fill();break;case"below":h=e.y+i+e.caretHeight,t.beginPath(),t.moveTo(e.x,e.y+i),t.lineTo(e.x+e.caretHeight,e.y+i+e.caretHeight),t.lineTo(e.x-e.caretHeight,e.y+i+e.caretHeight),t.closePath(),t.fill()}switch(e.xAlign){case"left":r=e.x-n+(e.cornerRadius+e.caretHeight);break;case"right":r=e.x-(e.cornerRadius+e.caretHeight)}z(t,r,h,n,s,e.cornerRadius),t.fill(),t.fillStyle=a.color(e.textColor).alpha(e.opacity).rgbString(),t.textAlign="center",t.textBaseline="middle",t.fillText(e.text,r+n/2,h+s/2)}break;case"label":z(t,e.x,e.y-e.height/2,e.width,e.height,e.cornerRadius),t.fillStyle=a.color(e.backgroundColor).alpha(e.opacity).rgbString(),t.fill(),t.closePath(),t.textAlign="left",t.textBaseline="middle",t.fillStyle=a.color(e.titleTextColor).alpha(e.opacity).rgbString(),t.font=W(e.fontSize,e._titleFontStyle,e._titleFontFamily),t.fillText(e.title,e.x+e.xPadding,this.getLineHeight(0)),t.font=W(e.fontSize,e._fontStyle,e._fontFamily),a.each(e.labels,function(i,n){t.fillStyle=a.color(e.textColor).alpha(e.opacity).rgbString(),t.fillText(i,e.x+e.xPadding+e.fontSize+3,this.getLineHeight(n+1)),t.fillStyle=a.color(e.legendBackgroundColor).alpha(e.opacity).rgbString(),t.fillRect(e.x+e.xPadding,this.getLineHeight(n+1)-e.fontSize/2,e.fontSize,e.fontSize),t.fillStyle=a.color(e.legendColors[n].fill).alpha(e.opacity).rgbString(),t.fillRect(e.x+e.xPadding,this.getLineHeight(n+1)-e.fontSize/2,e.fontSize,e.fontSize)},this)}},getLineHeight:function(t){var e=this._vm.y-this._vm.height/2+this._vm.yPadding,i=t-1;return 0===t?e+this._vm.titleFontSize/2:e+(1.5*this._vm.fontSize*i+this._vm.fontSize/2)+1.5*this._vm.titleFontSize}}),i.Scale=i.Element.extend({initialize:function(){this.fit()},buildYLabels:function(){this.yLabels=[];for(var t=m(this.stepValue),e=0;e<=this.steps;e++)this.yLabels.push(x(this.templateString,{value:(this.min+e*this.stepValue).toFixed(t)}));this.yLabelWidth=this.display&&this.showLabels?T(this.ctx,this.font,this.yLabels)+10:0},addXLabel:function(t){this.xLabels.push(t),this.valuesCount++,this.fit()},removeXLabel:function(){this.xLabels.shift(),this.valuesCount--,this.fit()},fit:function(){this.startPoint=this.display?this.fontSize:0,this.endPoint=this.display?this.height-1.5*this.fontSize-5:this.height,this.startPoint+=this.padding,this.endPoint-=this.padding;var t,e=this.endPoint,i=this.endPoint-this.startPoint;for(this.calculateYRange(i),this.buildYLabels(),this.calculateXLabelRotation();i>this.endPoint-this.startPoint;)i=this.endPoint-this.startPoint,t=this.yLabelWidth,this.calculateYRange(i),this.buildYLabels(),tthis.yLabelWidth?i/2:this.yLabelWidth,this.xLabelRotation=0,this.display){var n,s=T(this.ctx,this.font,this.xLabels);this.xLabelWidth=s;for(var o=Math.floor(this.calculateX(1)-this.calculateX(0))-6;this.xLabelWidth>o&&0===this.xLabelRotation||this.xLabelWidth>o&&this.xLabelRotation<=90&&this.xLabelRotation>0;)n=Math.cos(b(this.xLabelRotation)),t=n*i,e=n*a,t+this.fontSize/2>this.yLabelWidth&&(this.xScalePaddingLeft=t+this.fontSize/2),this.xScalePaddingRight=this.fontSize/2,this.xLabelRotation++,this.xLabelWidth=n*s;this.xLabelRotation>0&&(this.endPoint-=Math.sin(b(this.xLabelRotation))*s+3)}else this.xLabelWidth=0,this.xScalePaddingRight=this.padding,this.xScalePaddingLeft=this.padding},calculateYRange:c,drawingArea:function(){return this.startPoint-this.endPoint},calculateY:function(t){var e=this.drawingArea()/(this.min-this.max);return this.endPoint-e*(t-this.min)},calculateX:function(t){var e=(this.xLabelRotation>0,this.width-(this.xScalePaddingLeft+this.xScalePaddingRight)),i=e/Math.max(this.valuesCount-(this.offsetGridLines?0:1),1),a=i*t+this.xScalePaddingLeft;return this.offsetGridLines&&(a+=i/2),Math.round(a)},update:function(t){a.extend(this,t),this.fit()},draw:function(){var t=this.ctx,e=(this.endPoint-this.startPoint)/this.steps,i=Math.round(this.xScalePaddingLeft);this.display&&(t.fillStyle=this.textColor,t.font=this.font,n(this.yLabels,function(n,s){var o=this.endPoint-e*s,r=Math.round(o),h=this.showHorizontalLines;t.textAlign="right",t.textBaseline="middle",this.showLabels&&t.fillText(n,i-10,o),0!==s||h||(h=!0),h&&t.beginPath(),s>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),r+=a.aliasPixel(t.lineWidth),h&&(t.moveTo(i,r),t.lineTo(this.width,r),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(i-5,r),t.lineTo(i,r),t.stroke(),t.closePath()},this),n(this.xLabels,function(e,i){var a=this.calculateX(i)+y(this.lineWidth),n=this.calculateX(i-(this.offsetGridLines?.5:0))+y(this.lineWidth),s=this.xLabelRotation>0,o=this.showVerticalLines;0!==i||o||(o=!0),o&&t.beginPath(),i>0?(t.lineWidth=this.gridLineWidth,t.strokeStyle=this.gridLineColor):(t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor),o&&(t.moveTo(n,this.endPoint),t.lineTo(n,this.startPoint-3),t.stroke(),t.closePath()),t.lineWidth=this.lineWidth,t.strokeStyle=this.lineColor,t.beginPath(),t.moveTo(n,this.endPoint),t.lineTo(n,this.endPoint+5),t.stroke(),t.closePath(),t.save(),t.translate(a,s?this.endPoint+12:this.endPoint+8),t.rotate(-1*b(this.xLabelRotation)),t.font=this.font,t.textAlign=s?"right":"center",t.textBaseline=s?"middle":"top",t.fillText(e,0,0),t.restore()},this))}}),i.RadialScale=i.Element.extend({initialize:function(){this.size=v([this.height,this.width]),this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2},calculateCenterOffset:function(t){var e=this.drawingArea/(this.max-this.min);return(t-this.min)*e},update:function(){this.lineArc?this.drawingArea=this.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2:this.setScaleSize(),this.buildYLabels()},buildYLabels:function(){this.yLabels=[];for(var t=m(this.stepValue),e=0;e<=this.steps;e++)this.yLabels.push(x(this.templateString,{value:(this.min+e*this.stepValue).toFixed(t)}))},getCircumference:function(){return 2*Math.PI/this.valuesCount},setScaleSize:function(){var t,e,i,a,n,s,o,r,h,l,c,u,d=v([this.height/2-this.pointLabelFontSize-5,this.width/2]),p=this.width,f=0;for(this.ctx.font=W(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),e=0;ep&&(p=t.x+a,n=e),t.x-ap&&(p=t.x+i,n=e):e>this.valuesCount/2&&t.x-i0){var a,n=i*(this.drawingArea/this.steps),s=this.yCenter-n;if(this.lineWidth>0)if(t.strokeStyle=this.lineColor,t.lineWidth=this.lineWidth,this.lineArc)t.beginPath(),t.arc(this.xCenter,this.yCenter,n,0,2*Math.PI),t.closePath(),t.stroke();else{t.beginPath();for(var o=0;o=0;e--){if(this.angleLineWidth>0){var i=this.getPointPosition(e,this.calculateCenterOffset(this.max));t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(i.x,i.y),t.stroke(),t.closePath()}var a=this.getPointPosition(e,this.calculateCenterOffset(this.max)+5);t.font=W(this.pointLabelFontSize,this.pointLabelFontStyle,this.pointLabelFontFamily),t.fillStyle=this.pointLabelFontColor;var s=this.labels.length,o=this.labels.length/2,r=o/2,h=r>e||e>s-r,l=e===r||e===s-r;0===e?t.textAlign="center":e===o?t.textAlign="center":o>e?t.textAlign="left":t.textAlign="right",l?t.textBaseline="middle":h?t.textBaseline="bottom":t.textBaseline="top",t.fillText(this.labels[e],a.x,a.y)}}}}}),i.animationService={frameDuration:17,animations:[],dropFrames:0,addAnimation:function(t,e,i){i||(t.animating=!0);for(var n=0;n1&&(e=Math.floor(this.dropFrames),this.dropFrames-=e);for(var i=0;ithis.animations[i].animationObject.numSteps&&(this.animations[i].animationObject.currentStep=this.animations[i].animationObject.numSteps),this.animations[i].animationObject.render(this.animations[i].chartInstance,this.animations[i].animationObject),this.animations[i].animationObject.currentStep==this.animations[i].animationObject.numSteps&&(this.animations[i].chartInstance.animating=!1,this.animations.splice(i,1),i--);var n=Date.now(),s=n-t-this.frameDuration,o=s/this.frameDuration;o>1&&(this.dropFrames+=o),this.animations.length>0&&a.requestAnimFrame.call(window,this.digestWrapper)}},a.addEvent(window,"resize",function(){var t;return function(){clearTimeout(t),t=setTimeout(function(){n(i.instances,function(t){t.options.responsive&&t.resize(t.render,!0)})},50)}}()),p?define(function(){return i}):"object"==typeof module&&module.exports&&(module.exports=i),t.Chart=i,i.noConflict=function(){return t.Chart=e,i}}).call(this),function(){"use strict";var t=this,e=t.Chart,i=e.helpers,a={scaleBeginAtZero:!0,scaleShowGridLines:!0,scaleGridLineColor:"rgba(0,0,0,.05)",scaleGridLineWidth:1,scaleShowHorizontalLines:!0, +scaleShowVerticalLines:!0,barBorderWidth:2,barValueSpacing:5,barDatasetSpacing:1,legendTemplate:'
      <% for (var i=0; i
    • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
    • <%}%>
    '};e.Type.extend({name:"Bar",defaults:a,initialize:function(t){this.data=t;var a=this.options;this.ScaleClass=e.Scale.extend({offsetGridLines:!0,calculateBarX:function(t,e,i){var n=this.calculateBaseWidth(),s=this.calculateX(i)-n/2,o=this.calculateBarWidth(t);return s+o*e+e*a.barDatasetSpacing+o/2},calculateBaseWidth:function(){return this.calculateX(1)-this.calculateX(0)-2*a.barValueSpacing},calculateBarWidth:function(t){var e=this.calculateBaseWidth()-(t-1)*a.barDatasetSpacing;return e/t}}),i.bindEvents(this,this.options.tooltipEvents,this.onHover),this.BarClass=e.Rectangle.extend({ctx:this.chart.ctx}),this.buildScale(this.data.labels),i.each(this.data.datasets,function(t,e){t.metaData=[],i.each(t.data,function(e,i){t.metaData.push(new this.BarClass)},this)},this),this.eachElement(function(t,e,a){i.extend(t,{width:this.scale.calculateBarWidth(this.data.datasets.length),x:this.scale.calculateBarX(this.data.datasets.length,a,e),y:this.calculateBaseY(),_datasetIndex:a,_index:e}),t.save()},this),this.tooltip=new e.Tooltip({_chart:this.chart,_data:this.data,_options:this.options},this),this.update()},onHover:function(t){if("mouseout"==t.type)return this;if(this.lastActive=this.lastActive||[],this.active=function(){switch(this.options.hoverMode){case"single":return this.getElementAtEvent(t);case"label":return this.getElementsAtEvent(t);case"dataset":return this.getDatasetAtEvent(t);default:return t}}.call(this),this.options.onHover&&this.options.onHover.call(this,this.active),this.lastActive.length)switch(this.options.hoverMode){case"single":this.lastActive[0].backgroundColor=this.data.datasets[this.lastActive[0]._datasetIndex].backgroundColor,this.lastActive[0].borderColor=this.data.datasets[this.lastActive[0]._datasetIndex].borderColor;break;case"label":for(var e=0;e=0||this.scale.min>=0&&this.scale.max<=0?(t=this.scale.calculateY(0),t+=this.options.scaleGridLineWidth):this.scale.min<0&&this.scale.max<0&&(t=this.scale.startPoint),t},update:function(){this.scale.update(),this.eachElement(function(t,e,a,n){i.extend(t,{width:this.scale.calculateBarWidth(this.data.datasets.length),x:this.scale.calculateBarX(this.data.datasets.length,n,e),y:this.scale.calculateY(this.data.datasets[n].data[e]),value:this.data.datasets[n].data[e],label:this.data.labels[e],datasetLabel:this.data.datasets[n].label,borderColor:this.data.datasets[n].borderColor,borderWidth:this.data.datasets[n].borderWidth,backgroundColor:this.data.datasets[n].backgroundColor,_datasetIndex:n,_index:e}),t.pivot()},this),this.render()},buildScale:function(t){var e=this,a=function(){var t=[];return e.eachValue(function(e){t.push(e)}),t},n={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:t.length,beginAtZero:this.options.scaleBeginAtZero,integersOnly:this.options.scaleIntegersOnly,calculateYRange:function(t){var e=i.calculateScaleRange(a(),t,this.fontSize,this.beginAtZero,this.integersOnly);i.extend(this,e)},xLabels:t,font:i.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.borderWidth,showLabels:this.options.scaleShowLabels,display:this.options.showScale};this.options.scaleOverride&&i.extend(n,{calculateYRange:i.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(n)},redraw:function(){var t=this.calculateBaseY();this.eachElement(function(e,a,n){i.extend(e,{y:t,base:t})}),this.render()},draw:function(t){var e=t||1;this.clear(),this.scale.draw(e),this.eachElement(function(t,i,a){t.hasValue()&&(t.base=this.calculateBaseY(),t.transition(e).draw())},this),this.tooltip.transition(e).draw()}})}.call(this),function(){"use strict";var t=this,e=t.Chart,i=e.helpers,a={segmentShowStroke:!0,segmentStrokeColor:"#fff",borderWidth:2,cutoutPercentage:50,hoverAnimationDuration:400,animationEasing:"easeOutQuart",animateRotate:!0,animateScale:!1,legendTemplate:'
      <% for (var i=0; i
    • <%if(segments[i].label){%><%=segments[i].label%><%}%>
    • <%}%>
    '};e.Type.extend({name:"Doughnut",defaults:a,initialize:function(t){this.data=t,this.Slice=e.Arc.extend({_chart:this.chart,x:this.chart.width/2,y:this.chart.height/2}),this.options.showTooltips&&i.bindEvents(this,this.options.tooltipEvents,this.onHover),this.data.metaData=[],i.each(this.data.data,function(e,a){var n=new this.Slice;"number"==typeof e?i.extend(n,{value:e}):i.extend(n,e),i.extend(n,{startAngle:1.5*Math.PI,circumference:this.options.animateRotate?0:this.calculateCircumference(n.value),outerRadius:this.options.animateScale?0:this.outerRadius,innerRadius:this.options.animateScale?0:this.outerRadius/100*this.options.percentageInnerCutout}),n.backgroundColor||(e.backgroundColor="hsl("+360*a/t.length+", 100%, 50%)"),n.save(),this.data.metaData.push(n)},this),this.tooltip=new e.Tooltip({_chart:this.chart,_data:this.data,_options:this.options},this),this.update()},onHover:function(t){if("mouseout"==t.type)return this;if(this.lastActive=this.lastActive||[],this.active=this.getSliceAtEvent(t),this.options.onHover&&this.options.onHover.call(this,this.active),this.lastActive.length&&(this.lastActive[0].backgroundColor=this.data.data[this.lastActive[0]._index].backgroundColor),this.active.length&&this.options.hoverMode&&(this.active[0].backgroundColor=this.data.data[this.active[0]._index].hoverBackgroundColor||i.color(this.data.data[this.active[0]._index].backgroundColor).saturate(.5).darken(.35).rgbString()),this.options.showTooltips&&(this.tooltip.initialize(),this.active.length?(i.extend(this.tooltip,{opacity:1,_active:this.active}),this.tooltip.update()):i.extend(this.tooltip,{opacity:0})),this.tooltip.pivot(),!this.animating){var e;i.each(this.active,function(t,i){t!==this.lastActive[i]&&(e=!0)},this),(!this.lastActive.length&&this.active.length||this.lastActive.length&&!this.active.length||this.lastActive.length&&this.active.length&&e)&&(this.stop(),this.render(this.options.hoverAnimationDuration))}return this.lastActive=this.active,this},getSliceAtEvent:function(t){var e=[],a=i.getRelativePosition(t);return i.each(this.data.metaData,function(t,i){t.inRange(a.x,a.y)&&e.push(t)},this),e},calculateCircumference:function(t){return this.total>0?2*Math.PI*(t/this.total):0},update:function(){this.total=0,i.each(this.data.data,function(t){this.total+=Math.abs(t.value)},this),this.outerRadius=(i.min([this.chart.width,this.chart.height])-this.options.borderWidth/2)/2,i.each(this.data.metaData,function(t,e){var a=this.data.data[e];i.extend(t,{_index:e,x:this.chart.width/2,y:this.chart.height/2,value:a.value,label:a.label,circumference:this.calculateCircumference(a.value),outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.cutoutPercentage,backgroundColor:a.backgroundColor,hoverBackgroundColor:a.hoverBackgroundColor||a.backgroundColor,borderWidth:this.options.borderWidth,borderColor:this.options.segmentStrokeColor}),i.extend(t,{endAngle:t.startAngle+t.circumference}),0===e&&(t.startAngle=1.5*Math.PI),e<% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>',offsetGridLines:!1};e.Type.extend({name:"Line",defaults:a,initialize:function(t){this.data=t,this.PointClass=e.Point.extend({_chart:this.chart,offsetGridLines:this.options.offsetGridLines,borderWidth:this.options.pointBorderWidth,radius:this.options.pointRadius,hoverRadius:this.options.pointHoverRadius}),i.bindEvents(this,this.options.tooltipEvents,this.onHover),this.buildScale(this.data.labels),i.each(this.data.datasets,function(t,a){t.metaDataset=new e.Line,t.metaData=[],i.each(t.data,function(e,i){t.metaData.push(new this.PointClass)},this)},this),this.eachDataset(function(t,e){t=i.merge(this.options,t),i.extend(t.metaDataset,{_points:t.metaData,_datasetIndex:e,_chart:this.chart}),t.metaDataset.save()},this),this.eachElement(function(t,e,a,n){i.extend(t,{x:this.scale.calculateX(e),y:this.scale.endPoint,_datasetIndex:n,_index:e,_chart:this.chart}),i.extend(t,{controlPointPreviousX:this.previousPoint(a,e).x,controlPointPreviousY:this.nextPoint(a,e).y,controlPointNextX:this.previousPoint(a,e).x,controlPointNextY:this.nextPoint(a,e).y}),t.save()},this),this.tooltip=new e.Tooltip({_chart:this.chart,_data:this.data,_options:this.options},this),this.update()},nextPoint:function(t,e){return t[e-1]||t[e]},previousPoint:function(t,e){return t[e+1]||t[e]},onHover:function(t){if("mouseout"==t.type)return this;if(this.lastActive=this.lastActive||[],this.active=function(){switch(this.options.hoverMode){case"single":return this.getElementAtEvent(t);case"label":return this.getElementsAtEvent(t);case"dataset":return this.getDatasetAtEvent(t);default:return t}}.call(this),this.options.onHover&&this.options.onHover.call(this,this.active),this.lastActive.length)switch(this.options.hoverMode){case"single":this.lastActive[0].backgroundColor=this.data.datasets[this.lastActive[0]._datasetIndex].pointBackgroundColor,this.lastActive[0].borderColor=this.data.datasets[this.lastActive[0]._datasetIndex].pointBorderColor,this.lastActive[0].borderWidth=this.data.datasets[this.lastActive[0]._datasetIndex].pointBorderWidth;break;case"label":for(var e=0;ethis.scale.endPoint?t.controlPointNextY=this.scale.endPoint:s.next.ythis.scale.endPoint?t.controlPointPreviousY=this.scale.endPoint:s.previous.y<% for (var i=0; i
  • <%if(segments[i].label){%><%=segments[i].label%><%}%>
  • <%}%>'};e.Type.extend({name:"PolarArea",defaults:a,initialize:function(t){this.data=t,this.segments=[],this.SegmentArc=e.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 e.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:!0,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:t.length}),this.updateScaleRange(t),this.scale.update(),i.each(t,function(t,e){this.addData(t,e,!0)},this),this.options.showTooltips&&i.bindEvents(this,this.options.tooltipEvents,function(t){var e="mouseout"!==t.type?this.getSegmentsAtEvent(t):[];i.each(this.segments,function(t){t.restore(["fillColor"])}),i.each(e,function(t){t.fillColor=t.highlightColor}),this.showTooltip(e)}),this.render()},getSegmentsAtEvent:function(t){var e=[],a=i.getRelativePosition(t);return i.each(this.segments,function(t){t.inRange(a.x,a.y)&&e.push(t)},this),e},addData:function(t,e,i){var a=e||this.segments.length;this.segments.splice(a,0,new this.SegmentArc({fillColor:t.color,highlightColor:t.highlight||t.color,label:t.label,value:t.value,outerRadius:this.options.animateScale?0:this.scale.calculateCenterOffset(t.value),circumference:this.options.animateRotate?0:this.scale.getCircumference(),startAngle:1.5*Math.PI})),i||(this.reflow(),this.update())},removeData:function(t){var e=i.isNumber(t)?t:this.segments.length-1;this.segments.splice(e,1),this.reflow(),this.update()},calculateTotal:function(t){this.total=0,i.each(t,function(t){this.total+=t.value},this),this.scale.valuesCount=this.segments.length},updateScaleRange:function(t){var e=[];i.each(t,function(t){e.push(t.value)});var a=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}:i.calculateScaleRange(e,i.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);i.extend(this.scale,a,{size:i.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2})},update:function(){this.data.length==this.segments.length?i.each(this.data,function(t,e){i.extend(this.segments[e],{fillColor:t.color,highlightColor:t.highlight||t.color,label:t.label,value:t.value})},this):this.initialize(this.data),this.calculateTotal(this.segments),i.each(this.segments,function(t){t.save()}),this.reflow(),this.render()},reflow:function(){i.extend(this.SegmentArc.prototype,{x:this.chart.width/2,y:this.chart.height/2}),this.updateScaleRange(this.segments),this.scale.update(),i.extend(this.scale,{xCenter:this.chart.width/2,yCenter:this.chart.height/2}),i.each(this.segments,function(t){t.update({outerRadius:this.scale.calculateCenterOffset(t.value)})},this)},draw:function(t){var e=t||1;this.clear(),i.each(this.segments,function(t,i){t.transition({circumference:this.scale.getCircumference(),outerRadius:this.scale.calculateCenterOffset(t.value)},e),t.endAngle=t.startAngle+t.circumference,0===i&&(t.startAngle=1.5*Math.PI),i<% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>'},initialize:function(t){this.data=t,this.PointClass=e.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(t),this.options.showTooltips&&i.bindEvents(this,this.options.tooltipEvents,function(t){var e="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),i.each(e,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(e)}),i.each(t.datasets,function(e){var a={label:e.label||null,fillColor:e.fillColor,strokeColor:e.strokeColor,pointColor:e.pointColor,pointStrokeColor:e.pointStrokeColor,points:[]};this.datasets.push(a),i.each(e.data,function(i,n){var s;this.scale.animation||(s=this.scale.getPointPosition(n,this.scale.calculateCenterOffset(i))),a.points.push(new this.PointClass({value:i,label:t.labels[n],datasetLabel:e.label,x:this.options.animation?this.scale.xCenter:s.x,y:this.options.animation?this.scale.yCenter:s.y,strokeColor:e.pointStrokeColor,fillColor:e.pointColor,highlightFill:e.pointHighlightFill||e.pointColor,highlightStroke:e.pointHighlightStroke||e.pointStrokeColor}))},this)},this),this.render()},eachPoints:function(t){i.each(this.datasets,function(e){i.each(e.points,t,this)},this)},getPointsAtEvent:function(t){var e=i.getRelativePosition(t),a=i.getAngleFromPoint({x:this.scale.xCenter,y:this.scale.yCenter},e),n=2*Math.PI/this.scale.valuesCount,s=Math.round((a.angle-1.5*Math.PI)/n),o=[];return(s>=this.scale.valuesCount||0>s)&&(s=0),a.distance<=this.scale.drawingArea&&i.each(this.datasets,function(t){o.push(t.points[s])}),o},buildScale:function(t){this.scale=new e.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,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:t.labels,valuesCount:t.datasets[0].data.length}),this.scale.setScaleSize(),this.updateScaleRange(t.datasets),this.scale.buildYLabels()},updateScaleRange:function(t){var e=function(){var e=[];return i.each(t,function(t){t.data?e=e.concat(t.data):i.each(t.points,function(t){e.push(t.value)})}),e}(),a=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}:i.calculateScaleRange(e,i.min([this.chart.width,this.chart.height])/2,this.options.scaleFontSize,this.options.scaleBeginAtZero,this.options.scaleIntegersOnly);i.extend(this.scale,a)},addData:function(t,e){this.scale.valuesCount++,i.each(t,function(t,i){var a=this.scale.getPointPosition(this.scale.valuesCount,this.scale.calculateCenterOffset(t));this.datasets[i].points.push(new this.PointClass({value:t,label:e,datasetLabel:this.datasets[i].label,x:a.x,y:a.y,strokeColor:this.datasets[i].pointStrokeColor,fillColor:this.datasets[i].pointColor}))},this),this.scale.labels.push(e),this.reflow(),this.update()},removeData:function(){this.scale.valuesCount--,this.scale.labels.shift(),i.each(this.datasets,function(t){t.points.shift()},this),this.reflow(),this.update()},update:function(){i.each(this.data.datasets,function(t,e){i.extend(this.datasets[e],{label:t.label||null,fillColor:t.fillColor,strokeColor:t.strokeColor,pointColor:t.pointColor,pointStrokeColor:t.pointStrokeColor}),i.each(t.data,function(a,n){i.extend(this.datasets[e].points[n],{value:a,label:this.data.labels[n],datasetLabel:t.label,strokeColor:t.pointStrokeColor,fillColor:t.pointColor,highlightFill:t.pointHighlightFill||t.pointColor,highlightStroke:t.pointHighlightStroke||t.pointStrokeColor})},this)},this),this.eachPoints(function(t){t.save()}),this.reflow(),this.render()},reflow:function(){i.extend(this.scale,{width:this.chart.width,height:this.chart.height,size:i.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(t){var e=t||1,a=this.chart.ctx;this.clear(),this.scale.draw(),i.each(this.datasets,function(t){i.each(t.points,function(t,i){t.hasValue()&&t.transition(this.scale.getPointPosition(i,this.scale.calculateCenterOffset(t.value)),e)},this),a.lineWidth=this.options.datasetStrokeWidth,a.strokeStyle=t.strokeColor,a.beginPath(),i.each(t.points,function(t,e){0===e?a.moveTo(t.x,t.y):a.lineTo(t.x,t.y)},this),a.closePath(),a.stroke(),a.fillStyle=t.fillColor,a.fill(),i.each(t.points,function(t){t.hasValue()&&t.draw()})},this)}})}.call(this),!function t(e,i,a){function n(o,r){if(!i[o]){if(!e[o]){var h="function"==typeof require&&require;if(!r&&h)return h(o,!0);if(s)return s(o,!0);var l=new Error("Cannot find module '"+o+"'");throw l.code="MODULE_NOT_FOUND",l}var c=i[o]={exports:{}};e[o][0].call(c.exports,function(t){var i=e[o][1][t];return n(i?i:t)},c,c.exports,t,e,i,a)}return i[o].exports}for(var s="function"==typeof require&&require,o=0;o=a?a/12.92:Math.pow((a+.055)/1.055,2.4)}return.2126*e[0]+.7152*e[1]+.0722*e[2]},contrast:function(t){var e=this.luminosity(),i=t.luminosity();return e>i?(e+.05)/(i+.05):(i+.05)/(e+.05)},level:function(t){var e=this.contrast(t);return e>=7.1?"AAA":e>=4.5?"AA":""},dark:function(){var t=this.values.rgb,e=(299*t[0]+587*t[1]+114*t[2])/1e3;return 128>e},light:function(){return!this.dark()},negate:function(){for(var t=[],e=0;3>e;e++)t[e]=255-this.values.rgb[e];return this.setValues("rgb",t),this},lighten:function(t){return this.values.hsl[2]+=this.values.hsl[2]*t,this.setValues("hsl",this.values.hsl),this},darken:function(t){return this.values.hsl[2]-=this.values.hsl[2]*t,this.setValues("hsl",this.values.hsl),this},saturate:function(t){return this.values.hsl[1]+=this.values.hsl[1]*t,this.setValues("hsl",this.values.hsl),this},desaturate:function(t){return this.values.hsl[1]-=this.values.hsl[1]*t,this.setValues("hsl",this.values.hsl),this},whiten:function(t){return this.values.hwb[1]+=this.values.hwb[1]*t,this.setValues("hwb",this.values.hwb),this},blacken:function(t){return this.values.hwb[2]+=this.values.hwb[2]*t,this.setValues("hwb",this.values.hwb),this},greyscale:function(){var t=this.values.rgb,e=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[e,e,e]),this},clearer:function(t){return this.setValues("alpha",this.values.alpha-this.values.alpha*t),this},opaquer:function(t){return this.setValues("alpha",this.values.alpha+this.values.alpha*t),this},rotate:function(t){var e=this.values.hsl[0];return e=(e+t)%360,e=0>e?360+e:e,this.values.hsl[0]=e,this.setValues("hsl",this.values.hsl),this},mix:function(t,e){e=1-(null==e?.5:e);for(var i=2*e-1,a=this.alpha()-t.alpha(),n=((i*a==-1?i:(i+a)/(1+i*a))+1)/2,s=1-n,o=this.rgbArray(),r=t.rgbArray(),h=0;he&&(e+=360),a=(r+h)/2,i=h==r?0:.5>=a?l/(h+r):l/(2-h-r),[e,100*i,100*a]}function n(t){var e,i,a,n=t[0],s=t[1],o=t[2],r=Math.min(n,s,o),h=Math.max(n,s,o),l=h-r;return i=0==h?0:l/h*1e3/10,h==r?e=0:n==h?e=(s-o)/l:s==h?e=2+(o-n)/l:o==h&&(e=4+(n-s)/l),e=Math.min(60*e,360),0>e&&(e+=360),a=h/255*1e3/10,[e,i,a]}function s(t){var e=t[0],i=t[1],n=t[2],s=a(t)[0],o=1/255*Math.min(e,Math.min(i,n)),n=1-1/255*Math.max(e,Math.max(i,n));return[s,100*o,100*n]}function o(t){var e,i,a,n,s=t[0]/255,o=t[1]/255,r=t[2]/255;return n=Math.min(1-s,1-o,1-r),e=(1-s-n)/(1-n)||0,i=(1-o-n)/(1-n)||0,a=(1-r-n)/(1-n)||0,[100*e,100*i,100*a,100*n]}function h(t){return $[JSON.stringify(t)]}function l(t){var e=t[0]/255,i=t[1]/255,a=t[2]/255;e=e>.04045?Math.pow((e+.055)/1.055,2.4):e/12.92,i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92,a=a>.04045?Math.pow((a+.055)/1.055,2.4):a/12.92;var n=.4124*e+.3576*i+.1805*a,s=.2126*e+.7152*i+.0722*a,o=.0193*e+.1192*i+.9505*a;return[100*n,100*s,100*o]}function c(t){var e,i,a,n=l(t),s=n[0],o=n[1],r=n[2];return s/=95.047,o/=100,r/=108.883,s=s>.008856?Math.pow(s,1/3):7.787*s+16/116,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,e=116*o-16,i=500*(s-o),a=200*(o-r),[e,i,a]}function u(t){return E(c(t))}function d(t){var e,i,a,n,s,o=t[0]/360,r=t[1]/100,h=t[2]/100;if(0==r)return s=255*h,[s,s,s];i=.5>h?h*(1+r):h+r-h*r,e=2*h-i,n=[0,0,0];for(var l=0;3>l;l++)a=o+1/3*-(l-1),0>a&&a++,a>1&&a--,s=1>6*a?e+6*(i-e)*a:1>2*a?i:2>3*a?e+(i-e)*(2/3-a)*6:e,n[l]=255*s;return n}function p(t){var e,i,a=t[0],n=t[1]/100,s=t[2]/100;return s*=2,n*=1>=s?s:2-s,i=(s+n)/2,e=2*n/(s+n),[a,100*e,100*i]}function f(t){return s(d(t))}function v(t){return o(d(t))}function m(t){return h(d(t))}function y(t){var e=t[0]/60,i=t[1]/100,a=t[2]/100,n=Math.floor(e)%6,s=e-Math.floor(e),o=255*a*(1-i),r=255*a*(1-i*s),h=255*a*(1-i*(1-s)),a=255*a;switch(n){case 0:return[a,h,o];case 1:return[r,a,o];case 2:return[o,a,h];case 3:return[o,r,a];case 4:return[h,o,a];case 5:return[a,o,r]}}function w(t){var e,i,a=t[0],n=t[1]/100,s=t[2]/100;return i=(2-n)*s,e=n*s,e/=1>=i?i:2-i,e=e||0,i/=2,[a,100*e,100*i]}function x(t){return s(y(t))}function S(t){return o(y(t))}function C(t){return h(y(t))}function k(t){var e,i,a,n,s=t[0]/360,o=t[1]/100,h=t[2]/100,l=o+h;switch(l>1&&(o/=l,h/=l),e=Math.floor(6*s),i=1-h,a=6*s-e,0!=(1&e)&&(a=1-a),n=o+a*(i-o),e){default:case 6:case 0:r=i,g=n,b=o;break;case 1:r=n,g=i,b=o;break;case 2:r=o,g=i,b=n;break;case 3:r=o,g=n,b=i;break;case 4:r=n,g=o,b=i;break;case 5:r=i,g=o,b=n}return[255*r,255*g,255*b]}function P(t){return a(k(t))}function A(t){return n(k(t))}function L(t){return o(k(t))}function M(t){return h(k(t))}function _(t){var e,i,a,n=t[0]/100,s=t[1]/100,o=t[2]/100,r=t[3]/100;return e=1-Math.min(1,n*(1-r)+r),i=1-Math.min(1,s*(1-r)+r),a=1-Math.min(1,o*(1-r)+r),[255*e,255*i,255*a]}function F(t){return a(_(t))}function R(t){return n(_(t))}function W(t){return s(_(t))}function T(t){return h(_(t))}function z(t){var e,i,a,n=t[0]/100,s=t[1]/100,o=t[2]/100;return e=3.2406*n+-1.5372*s+o*-.4986,i=n*-.9689+1.8758*s+.0415*o,a=.0557*n+s*-.204+1.057*o,e=e>.0031308?1.055*Math.pow(e,1/2.4)-.055:e=12.92*e,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:i=12.92*i,a=a>.0031308?1.055*Math.pow(a,1/2.4)-.055:a=12.92*a,e=Math.min(Math.max(0,e),1),i=Math.min(Math.max(0,i),1),a=Math.min(Math.max(0,a),1),[255*e,255*i,255*a]}function B(t){var e,i,a,n=t[0],s=t[1],o=t[2];return n/=95.047,s/=100,o/=108.883,n=n>.008856?Math.pow(n,1/3):7.787*n+16/116,s=s>.008856?Math.pow(s,1/3):7.787*s+16/116,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,e=116*s-16,i=500*(n-s),a=200*(s-o),[e,i,a]}function I(t){return E(B(t))}function O(t){var e,i,a,n,s=t[0],o=t[1],r=t[2];return 8>=s?(i=100*s/903.3,n=7.787*(i/100)+16/116):(i=100*Math.pow((s+16)/116,3),n=Math.pow(i/100,1/3)),e=.008856>=e/95.047?e=95.047*(o/500+n-16/116)/7.787:95.047*Math.pow(o/500+n,3),a=.008859>=a/108.883?a=108.883*(n-r/200-16/116)/7.787:108.883*Math.pow(n-r/200,3),[e,i,a]}function E(t){var e,i,a,n=t[0],s=t[1],o=t[2];return e=Math.atan2(o,s),i=360*e/2/Math.PI,0>i&&(i+=360),a=Math.sqrt(s*s+o*o),[n,a,i]}function D(t){return z(O(t))}function V(t){var e,i,a,n=t[0],s=t[1],o=t[2];return a=o/360*2*Math.PI,e=s*Math.cos(a),i=s*Math.sin(a),[n,e,i]}function H(t){return O(V(t))}function Y(t){return D(V(t))}function q(t){return U[t]}function X(t){return a(q(t))}function N(t){return n(q(t))}function j(t){return s(q(t))}function G(t){return o(q(t))}function Z(t){return c(q(t))}function Q(t){return l(q(t))}e.exports={rgb2hsl:a,rgb2hsv:n,rgb2hwb:s,rgb2cmyk:o,rgb2keyword:h,rgb2xyz:l,rgb2lab:c,rgb2lch:u,hsl2rgb:d,hsl2hsv:p,hsl2hwb:f,hsl2cmyk:v,hsl2keyword:m,hsv2rgb:y,hsv2hsl:w,hsv2hwb:x,hsv2cmyk:S,hsv2keyword:C,hwb2rgb:k,hwb2hsl:P,hwb2hsv:A,hwb2cmyk:L,hwb2keyword:M,cmyk2rgb:_,cmyk2hsl:F,cmyk2hsv:R,cmyk2hwb:W,cmyk2keyword:T,keyword2rgb:q,keyword2hsl:X,keyword2hsv:N,keyword2hwb:j,keyword2cmyk:G,keyword2lab:Z,keyword2xyz:Q,xyz2rgb:z,xyz2lab:B,xyz2lch:I,lab2xyz:O,lab2rgb:D,lab2lch:E,lch2lab:V,lch2xyz:H,lch2rgb:Y};var U={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]},$={};for(var J in U)$[JSON.stringify(U[J])]=J},{}],3:[function(t,e,i){var a=t("./conversions"),n=function(){return new l};for(var s in a){n[s+"Raw"]=function(t){return function(e){return"number"==typeof e&&(e=Array.prototype.slice.call(arguments)),a[t](e)}}(s);var o=/(\w+)2(\w+)/.exec(s),r=o[1],h=o[2];n[r]=n[r]||{},n[r][h]=n[s]=function(t){return function(e){"number"==typeof e&&(e=Array.prototype.slice.call(arguments));var i=a[t](e);if("string"==typeof i||void 0===i)return i;for(var n=0;ne||t[3]&&t[3]<1?u(t,e):"rgb("+t[0]+", "+t[1]+", "+t[2]+")"}function u(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"rgba("+t[0]+", "+t[1]+", "+t[2]+", "+e+")"}function d(t,e){if(1>e||t[3]&&t[3]<1)return p(t,e);var i=Math.round(t[0]/255*100),a=Math.round(t[1]/255*100),n=Math.round(t[2]/255*100);return"rgb("+i+"%, "+a+"%, "+n+"%)"}function p(t,e){var i=Math.round(t[0]/255*100),a=Math.round(t[1]/255*100),n=Math.round(t[2]/255*100);return"rgba("+i+"%, "+a+"%, "+n+"%, "+(e||t[3]||1)+")"}function g(t,e){return 1>e||t[3]&&t[3]<1?f(t,e):"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"}function f(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hsla("+t[0]+", "+t[1]+"%, "+t[2]+"%, "+e+")"}function v(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==e&&1!==e?", "+e:"")+")"}function m(t){return x[t.slice(0,3)]}function b(t,e,i){return Math.min(Math.max(e,t),i)}function y(t){var e=t.toString(16).toUpperCase();return e.length<2?"0"+e:e}var w=t("color-name");e.exports={getRgba:a,getHsla:n,getRgb:o,getHsl:r,getHwb:s,getAlpha:h,hexString:l,rgbString:c,rgbaString:u,percentString:d,percentaString:p,hslString:g,hslaString:f,hwbString:v,keyword:m};var x={};for(var S in w)x[w[S]]=S},{"color-name":5}],5:[function(t,e,i){e.exports={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}},{}]},{},[1]); \ No newline at end of file diff --git a/samples/doughnut.html b/samples/doughnut.html index 145ff450a..19c6a6d0c 100644 --- a/samples/doughnut.html +++ b/samples/doughnut.html @@ -34,27 +34,22 @@ data: [{ value: randomScalingFactor(), backgroundColor: "#F7464A", - hoverBackgroundColor: "#FF5A5E", label: "Red" }, { value: randomScalingFactor(), backgroundColor: "#46BFBD", - hoverBackgroundColor: "#5AD3D1", label: "Green" }, { value: randomScalingFactor(), backgroundColor: "#FDB45C", - hoverBackgroundColor: "#FFC870", label: "Yellow" }, { value: randomScalingFactor(), backgroundColor: "#949FB1", - hoverBackgroundColor: "#A8B3C5", label: "Grey" }, { value: randomScalingFactor(), backgroundColor: "#4D5360", - hoverBackgroundColor: "#616774", label: "Dark Grey" } diff --git a/src/Chart.Doughnut.js b/src/Chart.Doughnut.js index 64e3634ab..1fc05581c 100644 --- a/src/Chart.Doughnut.js +++ b/src/Chart.Doughnut.js @@ -117,7 +117,7 @@ // Built in hover styling if (this.active.length && this.options.hoverMode) { - this.active[0].backgroundColor = this.data.data[this.active[0]._index].hoverBackgroundColor || helpers.color(this.active[0].backgroundColor).saturate(0.5).darken(0.35).rgbString(); + this.active[0].backgroundColor = this.data.data[this.active[0]._index].hoverBackgroundColor || helpers.color(this.data.data[this.active[0]._index].backgroundColor).saturate(0.5).darken(0.35).rgbString(); } // Built in Tooltips