From 257bdb2dbbb769da36d0703e2d3b91a920cb2bee Mon Sep 17 00:00:00 2001 From: Tanner Linsley Date: Mon, 15 Jun 2015 15:27:56 -0600 Subject: [PATCH] Hybrid Line and Bar chart - Line Now drawing --- Chart.js | 10776 ++++++++++++--------------- Chart.min.js | 38 +- samples/combo-bar-line.html | 63 + src/controllers/controller.bar.js | 72 +- src/controllers/controller.line.js | 371 + src/core/core.controller.js | 6 + src/core/core.helpers.js | 12 + src/elements/element.line.js | 22 +- src/elements/element.point.js | 2 +- 9 files changed, 5085 insertions(+), 6277 deletions(-) create mode 100644 samples/combo-bar-line.html create mode 100644 src/controllers/controller.line.js diff --git a/Chart.js b/Chart.js index 88b54859a..4ab301d15 100644 --- a/Chart.js +++ b/Chart.js @@ -11,2900 +11,4334 @@ (function() { - "use strict"; - - //Declare root variable - window in the browser, global on the server - var root = this, - previous = root.Chart; - - //Occupy the global variable of Chart, and create a simple base class - var Chart = function(context) { - var chart = this; - this.canvas = context.canvas; - - this.ctx = context; - - //Variables global to the chart - var computeDimension = function(element, dimension) { - if (element['offset' + dimension]) { - return element['offset' + dimension]; - } else { - return document.defaultView.getComputedStyle(element).getPropertyValue(dimension); - } - }; - - var width = this.width = computeDimension(context.canvas, 'Width') || 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; - - 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; - }; - - var defaultColor = 'rgba(0,0,0,0.1)'; - - //Globally expose the defaults to allow for user updating/changing - Chart.defaults = { - global: { - responsive: true, - maintainAspectRatio: true, - events: ["mousemove", "mouseout", "click", "touchstart", "touchmove", "touchend"], - hover: { - onHover: null, - mode: 'single', - animationDuration: 400, - }, - onClick: null, - defaultColor: defaultColor, - - // Element defaults defined in element extensions - elements: {} - }, - }; - - //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); - }, - // Need a special merge function to chart configs since they are now grouped - configMerge = helpers.configMerge = function(_base) { - var base = clone(_base); - helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) { - helpers.each(extension, function(value, key) { - if (extension.hasOwnProperty(key)) { - if (base.hasOwnProperty(key) && helpers.isArray(base[key]) && helpers.isArray(value)) { - // In this case we have an array of objects replacing another array. Rather than doing a strict replace, - // merge. This allows easy scale option merging - var baseArray = base[key]; - - helpers.each(value, function(valueObj, index) { - if (index < baseArray.length) { - baseArray[index] = helpers.configMerge(baseArray[index], valueObj); - } else { - baseArray.push(valueObj); // nothing to merge - } - }); - } else if (base.hasOwnProperty(key) && typeof base[key] == "object" && base[key] !== null && typeof value == "object") { - // If we are overwriting an object with an object, do a merge of the properties. - base[key] = helpers.configMerge(base[key], value); - } else { - // can just overwrite the value in this case - base[key] = value; - } - } - }); - }); - - return base; - }, - getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) { - if (!value) { - return defaultValue; - } - - if (helpers.isArray(value) && index < value.length) { - return value[index]; - } - - return value; - }, - 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); - }, - sign = helpers.sign = function(x) { - if (Math.sign) { - return Math.sign(x); - } else { - x = +x; // convert to a number - if (x === 0 || isNaN(x)) { - return x; - } - return x > 0 ? 1 : -1; - } - }, - log10 = helpers.log10 = function(x) { - if (Math.log10) { - return Math.log10(x) - } else { - return Math.log(x) / Math.LN10; - } - }, - 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.toRadians = function(degrees) { - return degrees * (Math.PI / 180); - }, - toDegrees = helpers.toDegrees = function(radians) { - return radians * (180 / Math.PI); - }, - // 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.atan2(distanceFromYCenter, distanceFromXCenter); - - if (angle < (-0.5 * Math.PI)) { - angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/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 { - previous: { - x: MiddlePoint.x - fa * (AfterPoint.x - FirstPoint.x), - y: MiddlePoint.y - fa * (AfterPoint.y - FirstPoint.y) - }, - next: { - 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) - }; - - }, - // Implementation of the nice number algorithm used in determining where axis labels will go - niceNum = helpers.niceNum = function(range, round) { - var exponent = Math.floor(helpers.log10(range)); - var fraction = range / Math.pow(10, exponent); - var niceFraction; - - if (round) { - if (fraction < 1.5) { - niceFraction = 1; - } else if (fraction < 3) { - niceFraction = 2; - } else if (fraction < 7) { - niceFraction = 5; - } else { - niceFraction = 10; - } - } else { - if (fraction <= 1.0) { - niceFraction = 1; - } else if (fraction <= 2) { - niceFraction = 2; - } else if (fraction <= 5) { - niceFraction = 5; - } else { - niceFraction = 10; - } - } - - return niceFraction * Math.pow(10, exponent); - }, - /* 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(config, instance) { - this.data = config.data; - this.options = config.options; - this.chart = instance; - 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 (this.options.responsive) { - this.resize(); - } - this.initialize.call(this); - }; - - //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() { - 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); - - return this; - }, - redraw: noop, - render: function(duration) { - - if (this.options.animation.duration !== 0 || duration) { - var animation = new Chart.Animation(); - animation.numSteps = (duration || this.options.animation.duration) / 16.66; //60 fps - animation.easing = this.options.animation.easing; - - // 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] = helpers.configMerge(baseDefaults, extensions.defaults); - - Chart.types[chartName] = ChartType; - - //Register this new chart type in the Chart prototype - Chart.prototype[chartName] = function(config) { - config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[chartName], config.options || {}); - return new ChartType(config, this); - }; - } else { - warn("Name not provided for this chart, so it hasn't been registered"); - } - return parent; - }; - - Chart.Element = function(configuration) { - extend(this, configuration); - this.initialize.apply(this, arguments); - }; - extend(Chart.Element.prototype, { - initialize: function() {}, - pivot: function() { - if (!this._view) { - this._view = clone(this._model); - } - this._start = clone(this._view); - return this; - }, - transition: function(ease) { - if (!this._view) { - this._view = clone(this._model); - } - if (!this._start) { - this.pivot(); - } - - each(this._model, function(value, key) { - - if (key[0] === '_' || !this._model.hasOwnProperty(key)) { - // Only non-underscored properties - } - - // Init if doesn't exist - else if (!this._view[key]) { - if (typeof value === 'number') { - this._view[key] = value * ease; - } else { - this._view[key] = value || null; - } - } - - // No unnecessary computations - else if (this._model[key] === this._view[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._model[key]), ease); - this._view[key] = color.rgbString(); - } catch (err) { - this._view[key] = value; - } - } - // Number transitions - else if (typeof value === 'number') { - var startVal = this._start[key] !== undefined ? this._start[key] : 0; - this._view[key] = ((this._model[key] - startVal) * ease) + startVal; - } - // Everything else - else { - this._view[key] = value; - } - - }, this); - - if (ease === 1) { - delete this._start; - } - return this; - }, - tooltipPosition: function() { - return { - x: this._model.x, - y: this._model.y - }; - }, - hasValue: function() { - return isNumber(this._model.x) && isNumber(this._model.y); - } - }); - - Chart.Element.extend = inherits; - - - // 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.update(); - instance.render(); - } - }); - }, 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); - -/*! - * Chart.js - * http://chartjs.org/ - * Version: 2.0.0-alpha - * - * Copyright 2015 Nick Downie - * Released under the MIT license - * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md - */ - - -(function() { - - "use strict"; - - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; - - Chart.defaults.global.animation = { - duration: 1000, - easing: "easeOutQuart", - onProgress: function() {}, - onComplete: function() {}, - }; - - 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.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); - } - } - }; + "use strict"; + + //Declare root variable - window in the browser, global on the server + var root = this, + previous = root.Chart; + + //Occupy the global variable of Chart, and create a simple base class + var Chart = function(context, config) { + var chart = this; + this.config = config; + + // Support a jQuery'd canvas element + if (context.length && context[0].getContext) { + context = context[0]; + } + + // Support a canvas domnode + if (context.getContext) { + context = context.getContext("2d"); + } + + this.canvas = context.canvas; + + this.ctx = context; + + //Variables global to the chart + var computeDimension = function(element, dimension) { + if (element['offset' + dimension]) { + return element['offset' + dimension]; + } else { + return document.defaultView.getComputedStyle(element).getPropertyValue(dimension); + } + }; + + var width = this.width = computeDimension(context.canvas, 'Width') || 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; + + 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. + Chart.helpers.retinaScale(this); + + if (config) { + this.controller = new Chart.Controller(this); + return this.controller; + } + + return this; + + }; + + //Globally expose the defaults to allow for user updating/changing + Chart.defaults = { + global: { + responsive: true, + maintainAspectRatio: true, + events: ["mousemove", "mouseout", "click", "touchstart", "touchmove", "touchend"], + hover: { + onHover: null, + mode: 'single', + animationDuration: 400, + }, + onClick: null, + defaultColor: 'rgba(0,0,0,0.1)', + + // Element defaults defined in element extensions + elements: {} + }, + }; + + if (typeof amd !== 'undefined') { + define(function() { + return Chart; + }); + } else if (typeof module === 'object' && module.exports) { + module.exports = Chart; + } + + root.Chart = Chart; + + Chart.noConflict = function() { + root.Chart = previous; + return Chart; + }; }).call(this); (function() { - "use strict"; - - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; - - // The scale service is used to resize charts along with all of their axes. We make this as - // a service where scales are registered with their respective charts so that changing the - // scales does not require - Chart.scaleService = { - // The interesting function - fitScalesForChart: function(chartInstance, width, height) { - var xPadding = width > 30 ? 5 : 2; - var yPadding = height > 30 ? 5 : 2; - - if (chartInstance) { - var leftScales = helpers.where(chartInstance.scales, function(scaleInstance) { - return scaleInstance.options.position == "left"; - }); - var rightScales = helpers.where(chartInstance.scales, function(scaleInstance) { - return scaleInstance.options.position == "right"; - }); - var topScales = helpers.where(chartInstance.scales, function(scaleInstance) { - return scaleInstance.options.position == "top"; - }); - var bottomScales = helpers.where(chartInstance.scales, function(scaleInstance) { - return scaleInstance.options.position == "bottom"; - }); - - var visibleLeftScales = helpers.where(chartInstance.scales, function(scaleInstance) { - return scaleInstance.options.position == "left"; - }); - var visibleRightScales = helpers.where(chartInstance.scales, function(scaleInstance) { - return scaleInstance.options.position == "right"; - }); - var visibleTopScales = helpers.where(chartInstance.scales, function(scaleInstance) { - return scaleInstance.options.position == "top"; - }); - var visibleBottomScales = helpers.where(chartInstance.scales, function(scaleInstance) { - return scaleInstance.options.position == "bottom"; - }); - - // // Adjust the padding to take into account displaying labels - // if (topScales.length === 0 || bottomScales.length === 0) { - // var maxFontHeight = 0; - - // var maxFontHeightFunction = function(scaleInstance) { - // if (scaleInstance.options.labels.show) { - // // Only consider font sizes for axes that actually show labels - // maxFontHeight = Math.max(maxFontHeight, scaleInstance.options.labels.fontSize); - // } - // }; - - // helpers.each(leftScales, maxFontHeightFunction); - // helpers.each(rightScales, maxFontHeightFunction); - - // if (topScales.length === 0) { - // // Add padding so that we can handle drawing the top nicely - // yPadding += 0.75 * maxFontHeight; // 0.75 since padding added on both sides - // } - - // if (bottomScales.length === 0) { - // // Add padding so that we can handle drawing the bottom nicely - // yPadding += 1.5 * maxFontHeight; - // } - // } - - // Essentially we now have any number of scales on each of the 4 sides. - // Our canvas looks like the following. - // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and - // B1 is the bottom axis - // |------------------------------------------------------| - // | | T1 | | - // |----|-----|-------------------------------------|-----| - // | | | | | - // | L1 | L2 | Chart area | R1 | - // | | | | | - // | | | | | - // |----|-----|-------------------------------------|-----| - // | | B1 | | - // | | | | - // |------------------------------------------------------| - - // What we do to find the best sizing, we do the following - // 1. Determine the minimum size of the chart area. - // 2. Split the remaining width equally between each vertical axis - // 3. Split the remaining height equally between each horizontal axis - // 4. Give each scale the maximum size it can be. The scale will return it's minimum size - // 5. Adjust the sizes of each axis based on it's minimum reported size. - // 6. Refit each axis - // 7. Position each axis in the final location - // 8. Tell the chart the final location of the chart area - - // Step 1 - var chartWidth = width / 2; // min 50% - var chartHeight = height / 2; // min 50% - - chartWidth -= (2 * xPadding); - chartHeight -= (2 * yPadding); - - - // Step 2 - var verticalScaleWidth = (width - chartWidth) / (leftScales.length + rightScales.length); - - // Step 3 - var horizontalScaleHeight = (height - chartHeight) / (topScales.length + bottomScales.length); - - // Step 4; - var minimumScaleSizes = []; - - var verticalScaleMinSizeFunction = function(scaleInstance) { - var minSize = scaleInstance.fit(verticalScaleWidth, chartHeight); - minimumScaleSizes.push({ - horizontal: false, - minSize: minSize, - scale: scaleInstance, - }); - }; - - var horizontalScaleMinSizeFunction = function(scaleInstance) { - var minSize = scaleInstance.fit(chartWidth, horizontalScaleHeight); - minimumScaleSizes.push({ - horizontal: true, - minSize: minSize, - scale: scaleInstance, - }); - }; - - // vertical scales - helpers.each(leftScales, verticalScaleMinSizeFunction); - helpers.each(rightScales, verticalScaleMinSizeFunction); - - // horizontal scales - helpers.each(topScales, horizontalScaleMinSizeFunction); - helpers.each(bottomScales, horizontalScaleMinSizeFunction); - - // Step 5 - var maxChartHeight = height - (2 * yPadding); - var maxChartWidth = width - (2 * xPadding); - - helpers.each(minimumScaleSizes, function(wrapper) { - if (wrapper.horizontal) { - maxChartHeight -= wrapper.minSize.height; - } else { - maxChartWidth -= wrapper.minSize.width; - } - }); - - // At this point, maxChartHeight and maxChartWidth are the size the chart area could - // be if the axes are drawn at their minimum sizes. - - // Step 6 - var verticalScaleFitFunction = function(scaleInstance) { - var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { - return wrapper.scale === scaleInstance; - }); - - if (wrapper) { - scaleInstance.fit(wrapper.minSize.width, maxChartHeight); - } - }; - - var horizontalScaleFitFunction = function(scaleInstance) { - var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { - return wrapper.scale === scaleInstance; - }); - - var scaleMargin = { - left: totalLeftWidth, - right: totalRightWidth, - top: 0, - bottom: 0, - }; - - if (wrapper) { - scaleInstance.fit(maxChartWidth, wrapper.minSize.height, scaleMargin); - } - }; - - var totalLeftWidth = xPadding; - var totalRightWidth = xPadding; - var totalTopHeight = yPadding; - var totalBottomHeight = yPadding; - - helpers.each(leftScales, verticalScaleFitFunction); - helpers.each(rightScales, verticalScaleFitFunction); - - // Figure out how much margin is on the left and right of the horizontal axes - helpers.each(leftScales, function(scaleInstance) { - totalLeftWidth += scaleInstance.width; - }); - - helpers.each(rightScales, function(scaleInstance) { - totalRightWidth += scaleInstance.width; - }); - - helpers.each(topScales, horizontalScaleFitFunction); - helpers.each(bottomScales, horizontalScaleFitFunction); - - helpers.each(topScales, function(scaleInstance) { - totalTopHeight += scaleInstance.height; - }); - helpers.each(bottomScales, function(scaleInstance) { - totalBottomHeight += scaleInstance.height; - }); - - // Let the left scale know the final margin - helpers.each(leftScales, function(scaleInstance) { - var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { - return wrapper.scale === scaleInstance; - }); - - var scaleMargin = { - left: 0, - right: 0, - top: totalTopHeight, - bottom: totalBottomHeight - }; - - if (wrapper) { - scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin); - } - }); - - helpers.each(rightScales, function(scaleInstance) { - var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { - return wrapper.scale === scaleInstance; - }); - - var scaleMargin = { - left: 0, - right: 0, - top: totalTopHeight, - bottom: totalBottomHeight - }; - - if (wrapper) { - scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin); - } - }); - - // Step 7 - // Position the scales - var left = xPadding; - var top = yPadding; - var right = 0; - var bottom = 0; - - var verticalScalePlacer = function(scaleInstance) { - scaleInstance.left = left; - scaleInstance.right = left + scaleInstance.width; - scaleInstance.top = totalTopHeight; - scaleInstance.bottom = totalTopHeight + maxChartHeight; - - // Move to next point - left = scaleInstance.right; - }; - - var horizontalScalePlacer = function(scaleInstance) { - scaleInstance.left = totalLeftWidth; - scaleInstance.right = totalLeftWidth + maxChartWidth; - scaleInstance.top = top; - scaleInstance.bottom = top + scaleInstance.height; - - // Move to next point - top = scaleInstance.bottom; - }; - - helpers.each(leftScales, verticalScalePlacer); - helpers.each(topScales, horizontalScalePlacer); - - // Account for chart width and height - left += maxChartWidth; - top += maxChartHeight; - - helpers.each(rightScales, verticalScalePlacer); - helpers.each(bottomScales, horizontalScalePlacer); - - // Step 8 - chartInstance.chartArea = { - left: totalLeftWidth, - top: totalTopHeight, - right: totalLeftWidth + maxChartWidth, - bottom: totalTopHeight + maxChartHeight, - }; - } - } - }; - - // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then - // use the new chart options to grab the correct scale - Chart.scales = { - constructors: {}, - // Use a registration function so that we can move to an ES6 map when we no longer need to support - // old browsers - registerScaleType: function(type, scaleConstructor) { - this.constructors[type] = scaleConstructor; - }, - getScaleConstructor: function(type) { - return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; - } - }; - -}).call(this); - -/*! - * Chart.js - * http://chartjs.org/ - * Version: 2.0.0-alpha - * - * Copyright 2015 Nick Downie - * Released under the MIT license - * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md - */ - - -(function() { - - "use strict"; - - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; - - Chart.defaults.global.tooltips = { - enabled: true, - custom: null, - backgroundColor: "rgba(0,0,0,0.8)", - fontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", - fontSize: 10, - fontStyle: "normal", - fontColor: "#fff", - titleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", - titleFontSize: 12, - titleFontStyle: "bold", - titleFontColor: "#fff", - yPadding: 6, - xPadding: 6, - caretSize: 8, - cornerRadius: 6, - xOffset: 10, - template: [ - '<% if(label){ %>', - '<%=label %>: ', - '<% } %>', - '<%=value %>', - ].join(''), - multiTemplate: [ - '<%if (datasetLabel){ %>', - '<%=datasetLabel %>: ', - '<% } %>', - '<%=value %>' - ].join(''), - multiKeyBackground: '#fff', - }; - - Chart.Tooltip = Chart.Element.extend({ - initialize: function() { - var options = this._options; - helpers.extend(this, { - _model: { - // Positioning - xPadding: options.tooltips.xPadding, - yPadding: options.tooltips.yPadding, - xOffset: options.tooltips.xOffset, - - // Labels - textColor: options.tooltips.fontColor, - _fontFamily: options.tooltips.fontFamily, - _fontStyle: options.tooltips.fontStyle, - fontSize: options.tooltips.fontSize, - - // Title - titleTextColor: options.tooltips.titleFontColor, - _titleFontFamily: options.tooltips.titleFontFamily, - _titleFontStyle: options.tooltips.titleFontStyle, - titleFontSize: options.tooltips.titleFontSize, - - // Appearance - caretHeight: options.tooltips.caretSize, - cornerRadius: options.tooltips.cornerRadius, - backgroundColor: options.tooltips.backgroundColor, - opacity: 0, - legendColorBackground: options.tooltips.multiKeyBackground, - }, - }); - }, - update: function() { - - var ctx = this._chart.ctx; - - switch (this._options.hover.mode) { - case 'single': - helpers.extend(this._model, { - text: helpers.template(this._options.tooltips.template, { - // These variables are available in the template function. Add others here - element: this._active[0], - value: this._data.datasets[this._active[0]._datasetIndex].data[this._active[0]._index], - label: this._data.labels ? this._data.labels[this._active[0]._index] : '', - }), - }); - - var tooltipPosition = this._active[0].tooltipPosition(); - helpers.extend(this._model, { - x: Math.round(tooltipPosition.x), - y: Math.round(tooltipPosition.y), - caretPadding: tooltipPosition.padding - }); - - 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 = helpers.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]); - } - }, this); - - // Reverse labels if stacked - helpers.each(this._options.stacked ? elements.reverse() : elements, function(element) { - xPositions.push(element._view.x); - yPositions.push(element._view.y); - - //Include any colour information about the element - labels.push(helpers.template(this._options.tooltips.multiTemplate, { - // These variables are available in the template function. Add others here - element: element, - datasetLabel: this._data.datasets[element._datasetIndex].label, - value: this._data.datasets[element._datasetIndex].data[element._index], - })); - colors.push({ - fill: element._view.backgroundColor, - stroke: element._view.borderColor - }); - - }, this); - - yMin = helpers.min(yPositions); - yMax = helpers.max(yPositions); - - xMin = helpers.min(xPositions); - xMax = helpers.max(xPositions); - - return { - x: (xMin > this._chart.width / 2) ? xMin : xMax, - y: (yMin + yMax) / 2, - }; - }).call(this, dataIndex); - - // Apply for now - helpers.extend(this._model, { - x: medianPosition.x, - y: medianPosition.y, - labels: labels, - title: this._data.labels && this._data.labels.length ? this._data.labels[this._active[0]._index] : '', - legendColors: colors, - legendBackgroundColor: this._options.tooltips.multiKeyBackground, - }); - - - // Calculate Appearance Tweaks - - this._model.height = (labels.length * this._model.fontSize) + ((labels.length - 1) * (this._model.fontSize / 2)) + (this._model.yPadding * 2) + this._model.titleFontSize * 1.5; - - var titleWidth = ctx.measureText(this.title).width, - //Label has a legend square as well so account for this. - labelWidth = helpers.longestText(ctx, this.font, labels) + this._model.fontSize + 3, - longestTextWidth = helpers.max([labelWidth, titleWidth]); - - this._model.width = longestTextWidth + (this._model.xPadding * 2); - - - var halfHeight = this._model.height / 2; - - //Check to ensure the height will fit on the canvas - if (this._model.y - halfHeight < 0) { - this._model.y = halfHeight; - } else if (this._model.y + halfHeight > this._chart.height) { - this._model.y = this._chart.height - halfHeight; - } - - //Decide whether to align left or right based on position on canvas - if (this._model.x > this._chart.width / 2) { - this._model.x -= this._model.xOffset + this._model.width; - } else { - this._model.x += this._model.xOffset; - } - break; - } - - return this; - }, - draw: function() { - - var ctx = this._chart.ctx; - var vm = this._view; - - switch (this._options.hover.mode) { - case 'single': - - ctx.font = helpers.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._view); - } 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; - } - - helpers.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': - - helpers.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 = helpers.fontString(vm.fontSize, vm._titleFontStyle, vm._titleFontFamily); - ctx.fillText(vm.title, vm.x + vm.xPadding, this.getLineHeight(0)); - - ctx.font = helpers.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.legendColors[index].stroke).alpha(vm.opacity).rgbString(); - ctx.fillRect(vm.x + vm.xPadding - 1, this.getLineHeight(index + 1) - vm.fontSize / 2 - 1, vm.fontSize + 2, vm.fontSize + 2); - - 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._view.y - (this._view.height / 2) + this._view.yPadding, - afterTitleIndex = index - 1; - - //If the index is zero, we're getting the title - if (index === 0) { - return baseLineHeight + this._view.titleFontSize / 2; - } else { - return baseLineHeight + ((this._view.fontSize * 1.5 * afterTitleIndex) + this._view.fontSize / 2) + this._view.titleFontSize * 1.5; - } - - }, - }); + + "use strict"; + + //Declare root variable - window in the browser, global on the server + var root = this, + previous = root.Chart; + + //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)) { + if (helpers.isArray(value)) { + objClone[key] = value.slice(0); + } else if (typeof value === 'object' && value !== null) { + objClone[key] = clone(value); + } else { + 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); + }, + // Need a special merge function to chart configs since they are now grouped + configMerge = helpers.configMerge = function(_base) { + var base = clone(_base); + helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) { + helpers.each(extension, function(value, key) { + if (extension.hasOwnProperty(key)) { + if (key === 'scales') { + // Scale config merging is complex. Add out own function here for that + base[key] = helpers.scaleMerge(base.hasOwnProperty(key) ? base[key] : {}, value); + + } else if (key === 'scale') { + // Used in polar area & radar charts since there is only one scale + base[key] = helpers.configMerge(base.hasOwnProperty(key) ? base[key] : {}, Chart.scaleService.getScaleDefaults(value.type), value); + } else if (base.hasOwnProperty(key) && helpers.isArray(base[key]) && helpers.isArray(value)) { + // In this case we have an array of objects replacing another array. Rather than doing a strict replace, + // merge. This allows easy scale option merging + var baseArray = base[key]; + + helpers.each(value, function(valueObj, index) { + + if (index < baseArray.length) { + baseArray[index] = helpers.configMerge(baseArray[index], valueObj); + } else { + baseArray.push(valueObj); // nothing to merge + } + }); + + } else if (base.hasOwnProperty(key) && typeof base[key] == "object" && base[key] !== null && typeof value == "object") { + // If we are overwriting an object with an object, do a merge of the properties. + base[key] = helpers.configMerge(base[key], value); + + } else { + // can just overwrite the value in this case + base[key] = value; + } + } + }); + }); + + return base; + }, + scaleMerge = helpers.scaleMerge = function(_base, extension) { + var base = clone(_base); + + helpers.each(extension, function(value, key) { + if (extension.hasOwnProperty(key)) { + if (key === 'xAxes' || key === 'yAxes') { + // These properties are arrays of items + if (base.hasOwnProperty(key)) { + helpers.each(value, function(valueObj, index) { + if (index >= base[key].length || !base[key][index].type) { + base[key].push(helpers.configMerge(valueObj.type ? Chart.scaleService.getScaleDefaults(valueObj.type) : {}, valueObj)); + } else if (valueObj.type !== base[key][index].type) { + // Type changed. Bring in the new defaults before we bring in valueObj so that valueObj can override the correct scale defaults + base[key][index] = helpers.configMerge(base[key][index], valueObj.type ? Chart.scaleService.getScaleDefaults(valueObj.type) : {}, valueObj) + } else { + // Type is the same + base[key][index] = helpers.configMerge(base[key][index], valueObj); + } + }); + } else { + base[key] = []; + helpers.each(value, function(valueObj) { + base[key].push(helpers.configMerge(valueObj.type ? Chart.scaleService.getScaleDefaults(valueObj.type) : {}, valueObj)); + }); + } + } else if (base.hasOwnProperty(key) && typeof base[key] == "object" && base[key] !== null && typeof value == "object") { + // If we are overwriting an object with an object, do a merge of the properties. + base[key] = helpers.configMerge(base[key], value); + + } else { + // can just overwrite the value in this case + base[key] = value; + } + } + }); + + return base; + }, + getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) { + if (!value) { + return defaultValue; + } + + if (helpers.isArray(value) && index < value.length) { + return value[index]; + } + + return value; + }, + 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); + }, + sign = helpers.sign = function(x) { + if (Math.sign) { + return Math.sign(x); + } else { + x = +x; // convert to a number + if (x === 0 || isNaN(x)) { + return x; + } + return x > 0 ? 1 : -1; + } + }, + log10 = helpers.log10 = function(x) { + if (Math.log10) { + return Math.log10(x) + } else { + return Math.log(x) / Math.LN10; + } + }, + 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.toRadians = function(degrees) { + return degrees * (Math.PI / 180); + }, + toDegrees = helpers.toDegrees = function(radians) { + return radians * (180 / Math.PI); + }, + // 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.atan2(distanceFromYCenter, distanceFromXCenter); + + if (angle < (-0.5 * Math.PI)) { + angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/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 { + previous: { + x: MiddlePoint.x - fa * (AfterPoint.x - FirstPoint.x), + y: MiddlePoint.y - fa * (AfterPoint.y - FirstPoint.y) + }, + next: { + x: MiddlePoint.x + fb * (AfterPoint.x - FirstPoint.x), + y: MiddlePoint.y + fb * (AfterPoint.y - FirstPoint.y) + } + }; + }, + nextItem = helpers.nextItem = function(collection, index, loop) { + if (loop) { + return collection[index + 1] || collection[0]; + } + return collection[index + 1] || collection[collection.length - 1]; + }, + previousItem = helpers.previousItem = function(collection, index, loop) { + if (loop) { + return collection[index - 1] || collection[collection.length - 1]; + } + return collection[index - 1] || collection[0]; + }, + 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) + }; + + }, + // Implementation of the nice number algorithm used in determining where axis labels will go + niceNum = helpers.niceNum = function(range, round) { + var exponent = Math.floor(helpers.log10(range)); + var fraction = range / Math.pow(10, exponent); + var niceFraction; + + if (round) { + if (fraction < 1.5) { + niceFraction = 1; + } else if (fraction < 3) { + niceFraction = 2; + } else if (fraction < 7) { + niceFraction = 5; + } else { + niceFraction = 10; + } + } else { + if (fraction <= 1.0) { + niceFraction = 1; + } else if (fraction <= 2) { + niceFraction = 2; + } else if (fraction <= 5) { + niceFraction = 5; + } else { + niceFraction = 10; + } + } + + return niceFraction * Math.pow(10, exponent); + }, + /* 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); + }; }).call(this); (function() { - "use strict"; - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; + "use strict"; - var DatasetScale = Chart.Element.extend({ - // overridden in the chart. Will set min and max as properties of the scale for later use. Min will always be 0 when using a dataset and max will be the number of items in the dataset - calculateRange: helpers.noop, - isHorizontal: function() { - return this.options.position == "top" || this.options.position == "bottom"; - }, - getPixelForValue: function(value, index, includeOffset) { - // This must be called after fit has been run so that - // this.left, this.top, this.right, and this.bottom have been defined - if (this.isHorizontal()) { - var isRotated = (this.labelRotation > 0); - var innerWidth = this.width - (this.paddingLeft + this.paddingRight); - var valueWidth = innerWidth / Math.max((this.max - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1); - var valueOffset = (valueWidth * index) + this.paddingLeft; + //Declare root variable - window in the browser, global on the server + var root = this, + previous = root.Chart, + helpers = Chart.helpers; - if (this.options.gridLines.offsetGridLines && includeOffset) { - valueOffset += (valueWidth / 2); - } + Chart.elements = {}; - return this.left + Math.round(valueOffset); - } else { - return this.top + (index * (this.height / this.max)); - } - }, - calculateLabelRotation: function(maxHeight, margins) { - //Get the width of each grid by calculating the difference - //between x offsets between 0 and 1. - var labelFont = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily); - this.ctx.font = labelFont; + Chart.Element = function(configuration) { + helpers.extend(this, configuration); + this.initialize.apply(this, arguments); + }; + helpers.extend(Chart.Element.prototype, { + initialize: function() {}, + pivot: function() { + if (!this._view) { + this._view = helpers.clone(this._model); + } + this._start = helpers.clone(this._view); + return this; + }, + transition: function(ease) { + if (!this._view) { + this._view = helpers.clone(this._model); + } + if (!this._start) { + this.pivot(); + } - var firstWidth = this.ctx.measureText(this.labels[0]).width; - var lastWidth = this.ctx.measureText(this.labels[this.labels.length - 1]).width; - var firstRotated; - var lastRotated; + helpers.each(this._model, function(value, key) { - this.paddingRight = lastWidth / 2 + 3; - this.paddingLeft = firstWidth / 2 + 3; + if (key[0] === '_' || !this._model.hasOwnProperty(key)) { + // Only non-underscored properties + } - this.labelRotation = 0; + // Init if doesn't exist + else if (!this._view[key]) { + if (typeof value === 'number') { + this._view[key] = value * ease; + } else { + this._view[key] = value || null; + } + } - if (this.options.display) { - var originalLabelWidth = helpers.longestText(this.ctx, labelFont, this.labels); - var cosRotation; - var sinRotation; - var firstRotatedWidth; + // No unnecessary computations + else if (this._model[key] === this._view[key]) { + // It's the same! Woohoo! + } - this.labelWidth = originalLabelWidth; + // Color transitions if possible + else if (typeof value === 'string') { + try { + var color = helpers.color(this._start[key]).mix(helpers.color(this._model[key]), ease); + this._view[key] = color.rgbString(); + } catch (err) { + this._view[key] = value; + } + } + // Number transitions + else if (typeof value === 'number') { + var startVal = this._start[key] !== undefined ? this._start[key] : 0; + this._view[key] = ((this._model[key] - startVal) * ease) + startVal; + } + // Everything else + else { + this._view[key] = value; + } - //Allow 3 pixels x2 padding either side for label readability - // only the index matters for a dataset scale, but we want a consistent interface between scales - var gridWidth = Math.floor(this.getPixelForValue(0, 1) - this.getPixelForValue(0, 0)) - 6; + }, this); - //Max label rotate should be 90 - also act as a loop counter - while ((this.labelWidth > gridWidth && this.labelRotation === 0) || (this.labelWidth > gridWidth && this.labelRotation <= 90 && this.labelRotation > 0)) { - cosRotation = Math.cos(helpers.toRadians(this.labelRotation)); - sinRotation = Math.sin(helpers.toRadians(this.labelRotation)); + if (ease === 1) { + delete this._start; + } + return this; + }, + tooltipPosition: function() { + return { + x: this._model.x, + y: this._model.y + }; + }, + hasValue: function() { + return helpers.isNumber(this._model.x) && helpers.isNumber(this._model.y); + } + }); - firstRotated = cosRotation * firstWidth; - lastRotated = cosRotation * lastWidth; + Chart.Element.extend = helpers.inherits; - // We're right aligning the text now. - if (firstRotated + this.options.labels.fontSize / 2 > this.yLabelWidth) { - this.paddingLeft = firstRotated + this.options.labels.fontSize / 2; - } +}).call(this); - this.paddingRight = this.options.labels.fontSize / 2; +(function() { - if (sinRotation * originalLabelWidth > maxHeight) { - // go back one step - this.labelRotation--; - break; - } + "use strict"; - this.labelRotation++; - this.labelWidth = cosRotation * originalLabelWidth; + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; - } - } else { - this.labelWidth = 0; - this.paddingRight = 0; - this.paddingLeft = 0; - } + Chart.defaults.global.animation = { + duration: 1000, + easing: "easeOutQuart", + onProgress: function() {}, + onComplete: function() {}, + }; - if (margins) { - this.paddingLeft -= margins.left; - this.paddingRight -= margins.right; + 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 - this.paddingLeft = Math.max(this.paddingLeft, 0); - this.paddingRight = Math.max(this.paddingRight, 0); - } + onAnimationProgress: null, // user specified callback to fire on each step of the animation + onAnimationComplete: null, // user specified callback to fire when the animation finishes + }); - }, - // Fit this axis to the given size - // @param {number} maxWidth : the max width the axis can be - // @param {number} maxHeight: the max height the axis can be - // @return {object} minSize : the minimum size needed to draw the axis - fit: function(maxWidth, maxHeight, margins) { - this.calculateRange(); - this.calculateLabelRotation(maxHeight, margins); + Chart.animationService = { + frameDuration: 17, + animations: [], + dropFrames: 0, + addAnimation: function(chartInstance, animationObject, duration) { - var minSize = { - width: 0, - height: 0, - }; + if (!duration) { + chartInstance.animating = true; + } - var labelFont = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily); - var longestLabelWidth = helpers.longestText(this.ctx, labelFont, this.labels); + 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; + } + } - // Width - if (this.isHorizontal()) { - minSize.width = maxWidth; - this.width = maxWidth; - } else if (this.options.display) { - minSize.width = Math.min(longestLabelWidth + 6, maxWidth); - } + this.animations.push({ + chartInstance: chartInstance, + animationObject: animationObject + }); - // Height - if (this.isHorizontal()) { - var labelHeight = (Math.cos(helpers.toRadians(this.labelRotation)) * longestLabelWidth) + 1.5 * this.options.labels.fontSize; - minSize.height = Math.min(labelHeight, maxHeight); - } else if (this.options.display) { - minSize.width = Math.min(longestLabelWidth + 6, maxWidth); - } + // 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; + }); - this.width = minSize.width; - this.height = minSize.height; - return minSize; - }, - // Actualy draw the scale on the canvas - // @param {rectangle} chartArea : the area of the chart to draw full grid lines on - draw: function(chartArea) { - if (this.options.display) { + 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 setContextLineSettings; + var startTime = Date.now(); + var framesToDrop = 0; - // Make sure we draw text in the correct color - this.ctx.fillStyle = this.options.labels.fontColor; + if (this.dropFrames > 1) { + framesToDrop = Math.floor(this.dropFrames); + this.dropFrames -= framesToDrop; + } - if (this.isHorizontal()) { - setContextLineSettings = true; - var yTickStart = this.options.position == "bottom" ? this.top : this.bottom - 10; - var yTickEnd = this.options.position == "bottom" ? this.top + 10 : this.bottom; - var isRotated = this.labelRotation !== 0; + for (var i = 0; i < this.animations.length; i++) { - helpers.each(this.labels, function(label, index) { - var xLineValue = this.getPixelForValue(label, index, false); // xvalues for grid lines - var xLabelValue = this.getPixelForValue(label, index, true); // x values for labels (need to consider offsetLabel option) + if (this.animations[i].animationObject.currentStep === null) { + this.animations[i].animationObject.currentStep = 0; + } - if (this.options.gridLines.show) { - if (index === 0) { - // Draw the first index specially - this.ctx.lineWidth = this.options.gridLines.zeroLineWidth; - this.ctx.strokeStyle = this.options.gridLines.zeroLineColor; - setContextLineSettings = true; // reset next time - } else if (setContextLineSettings) { - this.ctx.lineWidth = this.options.gridLines.lineWidth; - this.ctx.strokeStyle = this.options.gridLines.color; - setContextLineSettings = false; - } + 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; + } - xLineValue += helpers.aliasPixel(this.ctx.lineWidth); + this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject); - // Draw the label area - this.ctx.beginPath(); + 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--; + } + } - if (this.options.gridLines.drawTicks) { - this.ctx.moveTo(xLineValue, yTickStart); - this.ctx.lineTo(xLineValue, yTickEnd); - } + var endTime = Date.now(); + var delay = endTime - startTime - this.frameDuration; + var frameDelay = delay / this.frameDuration; - // Draw the chart area - if (this.options.gridLines.drawOnChartArea) { - this.ctx.moveTo(xLineValue, chartArea.top); - this.ctx.lineTo(xLineValue, chartArea.bottom); - } + if (frameDelay > 1) { + this.dropFrames += frameDelay; + } - // Need to stroke in the loop because we are potentially changing line widths & colours - this.ctx.stroke(); - } + // Do we have more stuff to animate? + if (this.animations.length > 0) { + helpers.requestAnimFrame.call(window, this.digestWrapper); + } + } + }; - if (this.options.labels.show) { - this.ctx.save(); - this.ctx.translate(xLabelValue, (isRotated) ? this.top + 12 : this.top + 8); - this.ctx.rotate(helpers.toRadians(this.labelRotation) * -1); - this.ctx.font = this.font; - this.ctx.textAlign = (isRotated) ? "right" : "center"; - this.ctx.textBaseline = (isRotated) ? "middle" : "top"; - this.ctx.fillText(label, 0, 0); - this.ctx.restore(); - } - }, this); - } else { - // Vertical - if (this.options.gridLines.show) {} +}).call(this); - if (this.options.labels.show) { - // Draw the labels - } - } - } - } - }); - Chart.scales.registerScaleType("category", DatasetScale); +(function() { + + "use strict"; + + //Declare root variable - window in the browser, global on the server + var root = this, + previous = root.Chart, + helpers = Chart.helpers; + + + //Create a dictionary of chart types, to allow for extension of existing types + Chart.types = {}; + + //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 = {}; + + // Controllers available for dataset visualization eg. bar, line, slice, etc. + Chart.controllers = {}; + + // The main controller of a chart + Chart.Controller = function(instance) { + + this.chart = instance; + this.config = instance.config; + this.data = this.config.data; + this.options = this.config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[this.config.type], this.config.options || {}); + this.id = helpers.uid(); + + //Add the chart instance to the global namespace + Chart.instances[this.id] = this; + + if (this.options.responsive) { + this.resize(); + } + + this.initialize.call(this); + + return this; + }; + + helpers.extend(Chart.Controller.prototype, { + + initialize: function initialize() { + + // TODO + // If BeforeInit(this) doesn't return false, proceed + + this.bindEvents(); + this.buildScales(); + this.buildControllers(); + this.resetElements(); + this.initToolTip(); + this.update(); + + // TODO + // If AfterInit(this) doesn't return false, proceed + + return this; + }, + + clear: function clear() { + helpers.clear(this.chart); + return this; + }, + + stop: function stop() { + // Stops any current animation loop occuring + Chart.animationService.cancelAnimation(this); + return this; + }, + + resize: function resize() { + this.stop(); + var canvas = this.chart.canvas, + newWidth = helpers.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; + + helpers.retinaScale(this.chart); + + return this; + }, + + buildScales: function buildScales() { + // Map of scale ID to scale object so we can lookup later + this.scales = {}; + + // Build the x axes + helpers.each(this.options.scales.xAxes, function(xAxisOptions) { + var ScaleClass = Chart.scaleService.getScaleConstructor(xAxisOptions.type); + var scale = new ScaleClass({ + ctx: this.chart.ctx, + options: xAxisOptions, + data: this.data, + id: xAxisOptions.id, + }); + + this.scales[scale.id] = scale; + }, this); + + // Build the y axes + helpers.each(this.options.scales.yAxes, function(yAxisOptions) { + var ScaleClass = Chart.scaleService.getScaleConstructor(yAxisOptions.type); + var scale = new ScaleClass({ + ctx: this.chart.ctx, + options: yAxisOptions, + data: this.data, + id: yAxisOptions.id, + }); + + this.scales[scale.id] = scale; + }, this); + + Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); + }, + + buildControllers: function() { + this.eachDataset(function(dataset, datasetIndex) { + var type = dataset.type || this.config.type; + if (dataset.controller) { + dataset.controller.updateIndex(datasetIndex); + return; + } + dataset.controller = new Chart.controllers[type](this, datasetIndex); + }); + }, + + resetElements: function resetElements() { + this.eachDataset(function(dataset, datasetIndex) { + dataset.controller.reset(); + }); + }, + + + update: function update(animationDuration) { + // This will loop through any data and do the appropriate element update for the type + Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); + this.eachDataset(function(dataset, datasetIndex) { + dataset.controller.update(); + }); + this.render(animationDuration); + }, + + render: function render(duration) { + + if (this.options.animation.duration !== 0 || duration) { + var animation = new Chart.Animation(); + animation.numSteps = (duration || this.options.animation.duration) / 16.66; //60 fps + animation.easing = this.options.animation.easing; + + // 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; + }, + + draw: function(ease) { + var easingDecimal = ease || 1; + this.clear(); + + // Draw all the scales + helpers.each(this.scales, function(scale) { + scale.draw(this.chartArea); + }, this); + + // Draw each dataset via its respective controller + // TODO: needs support for reverse stacking (line chart) + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + dataset.controller.draw(ease); + }, this); + + // Finally draw the tooltip + this.tooltip.transition(easingDecimal).draw(); + }, + + + + + + + + eachValue: function eachValue(callback) { + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + helpers.each(dataset.data, callback, this, datasetIndex); + }, this); + }, + + eachElement: function eachElement(callback) { + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + helpers.each(dataset.metaData, callback, this, dataset.metaData, datasetIndex); + }, this); + }, + + eachDataset: function eachDataset(callback) { + helpers.each(this.data.datasets, callback, this); + }, + + // 2 helper functions to get next/previous elements in datasets + nextElement: function nextElement(datasets, index, loop) { + if (this.loop) { + return dataset[index + 1] || dataset[0]; + } + return datasets[index + 1] || datasets[index]; + }, + previousElement: function previousElement(datasets, index, loop) { + if (this.loop) { + return dataset[index - 1] || dataset[dataset.length - 1]; + } + return datasets[index - 1] || datasets[index]; + }, + + // 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 draw + getElementAtEvent: function getElementAtEvent(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 []; + }, + + getElementsAtEvent: function getElementsAtEvent(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 : []; + }, + + getDatasetAtEvent: function getDatasetAtEvent(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 : []; + }, + + generateLegend: function generateLegend() { + return template(this.options.legendTemplate, this); + }, + + destroy: function destroy() { + this.clear(); + helpers.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 toBase64Image() { + return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments); + }, + + initToolTip: function initToolTip() { + this.tooltip = new Chart.Tooltip({ + _chart: this.chart, + _data: this.data, + _options: this.options, + }, this); + }, + + bindEvents: function bindEvents() { + helpers.bindEvents(this, this.options.events, function(evt) { + this.eventHandler(evt); + }); + }, + eventHandler: function eventHandler(e) { + this.lastActive = this.lastActive || []; + + // Find Active Elements + if (e.type == 'mouseout') { + this.active = []; + } else { + this.active = function() { + switch (this.options.hover.mode) { + case 'single': + return this.elementController.getElementAtEvent(e); + case 'label': + return this.elementController.getElementsAtEvent(e); + case 'dataset': + return this.elementController.getDatasetAtEvent(e); + default: + return e; + } + }.call(this); + } + + // On Hover hook + if (this.options.hover.onHover) { + this.options.hover.onHover.call(this, this.active); + } + + if (e.type == 'mouseup' || e.type == 'click') { + if (this.options.onClick) { + this.options.onClick.call(this, e, this.active); + } + } + + var dataset; + var index; + // Remove styling for last active (even if it may still be active) + if (this.lastActive.length) { + switch (this.options.hover.mode) { + case 'single': + this.elementController.resetElementAppearance(this.lastActive[0], this.lastActive[0]._datasetIndex, this.lastActive[0]._index); + break; + case 'label': + for (var i = 0; i < this.lastActive.length; i++) { + this.elementController.resetElementAppearance(this.lastActive[i], this.lastActive[i]._datasetIndex, this.lastActive[i]._index); + } + break; + case 'dataset': + break; + default: + // Don't change anything + } + } + + // Built in hover styling + if (this.active.length && this.options.hover.mode) { + switch (this.options.hover.mode) { + case 'single': + this.elementController.setElementHoverStyle(this.active[0]); + break; + case 'label': + for (var i = 0; i < this.active.length; i++) { + this.elementController.setElementHoverStyle(this.active[i]); + } + break; + case 'dataset': + break; + default: + // Don't change anything + } + } + + + // Built in Tooltips + if (this.options.tooltips.enabled) { + + // The usual updates + this.tooltip.initialize(); + + // Active + if (this.active.length) { + this.tooltip._model.opacity = 1; + + helpers.extend(this.tooltip, { + _active: this.active, + }); + + this.tooltip.update(); + } else { + // Inactive + this.tooltip._model.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.hover.animationDuration); + } + } + + // Remember Last Active + this.lastActive = this.active; + return this; + }, + }); + +}).call(this); + +(function() { + + "use strict"; + + //Declare root variable - window in the browser, global on the server + var root = this, + previous = root.Chart, + helpers = Chart.helpers; + + + // 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.update(); + } + }); + }, 50); + }; + })()); + +}).call(this); + +(function() { + "use strict"; + + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; + + // The scale service is used to resize charts along with all of their axes. We make this as + // a service where scales are registered with their respective charts so that changing the + // scales does not require + Chart.scaleService = { + // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then + // use the new chart options to grab the correct scale + constructors: {}, + // Use a registration function so that we can move to an ES6 map when we no longer need to support + // old browsers + // Scale config defaults + defaults: {}, + registerScaleType: function(type, scaleConstructor, defaults) { + this.constructors[type] = scaleConstructor; + this.defaults[type] = defaults; + }, + getScaleConstructor: function(type) { + return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; + }, + getScaleDefaults: function(type) { + return this.defaults.hasOwnProperty(type) ? this.defaults[type] : {}; + }, + // The interesting function + fitScalesForChart: function(chartInstance, width, height) { + var xPadding = width > 30 ? 5 : 2; + var yPadding = height > 30 ? 5 : 2; + + if (chartInstance) { + var leftScales = helpers.where(chartInstance.scales, function(scaleInstance) { + return scaleInstance.options.position == "left"; + }); + var rightScales = helpers.where(chartInstance.scales, function(scaleInstance) { + return scaleInstance.options.position == "right"; + }); + var topScales = helpers.where(chartInstance.scales, function(scaleInstance) { + return scaleInstance.options.position == "top"; + }); + var bottomScales = helpers.where(chartInstance.scales, function(scaleInstance) { + return scaleInstance.options.position == "bottom"; + }); + + // Essentially we now have any number of scales on each of the 4 sides. + // Our canvas looks like the following. + // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and + // B1 is the bottom axis + // |------------------------------------------------------| + // | | T1 | | + // |----|-----|-------------------------------------|-----| + // | | | | | + // | L1 | L2 | Chart area | R1 | + // | | | | | + // | | | | | + // |----|-----|-------------------------------------|-----| + // | | B1 | | + // | | | | + // |------------------------------------------------------| + + // What we do to find the best sizing, we do the following + // 1. Determine the minimum size of the chart area. + // 2. Split the remaining width equally between each vertical axis + // 3. Split the remaining height equally between each horizontal axis + // 4. Give each scale the maximum size it can be. The scale will return it's minimum size + // 5. Adjust the sizes of each axis based on it's minimum reported size. + // 6. Refit each axis + // 7. Position each axis in the final location + // 8. Tell the chart the final location of the chart area + + // Step 1 + var chartWidth = width / 2; // min 50% + var chartHeight = height / 2; // min 50% + + chartWidth -= (2 * xPadding); + chartHeight -= (2 * yPadding); + + + // Step 2 + var verticalScaleWidth = (width - chartWidth) / (leftScales.length + rightScales.length); + + // Step 3 + var horizontalScaleHeight = (height - chartHeight) / (topScales.length + bottomScales.length); + + // Step 4; + var minimumScaleSizes = []; + + var verticalScaleMinSizeFunction = function(scaleInstance) { + var minSize = scaleInstance.fit(verticalScaleWidth, chartHeight); + minimumScaleSizes.push({ + horizontal: false, + minSize: minSize, + scale: scaleInstance, + }); + }; + + var horizontalScaleMinSizeFunction = function(scaleInstance) { + var minSize = scaleInstance.fit(chartWidth, horizontalScaleHeight); + minimumScaleSizes.push({ + horizontal: true, + minSize: minSize, + scale: scaleInstance, + }); + }; + + // vertical scales + helpers.each(leftScales, verticalScaleMinSizeFunction); + helpers.each(rightScales, verticalScaleMinSizeFunction); + + // horizontal scales + helpers.each(topScales, horizontalScaleMinSizeFunction); + helpers.each(bottomScales, horizontalScaleMinSizeFunction); + + // Step 5 + var maxChartHeight = height - (2 * yPadding); + var maxChartWidth = width - (2 * xPadding); + + helpers.each(minimumScaleSizes, function(wrapper) { + if (wrapper.horizontal) { + maxChartHeight -= wrapper.minSize.height; + } else { + maxChartWidth -= wrapper.minSize.width; + } + }); + + // At this point, maxChartHeight and maxChartWidth are the size the chart area could + // be if the axes are drawn at their minimum sizes. + + // Step 6 + var verticalScaleFitFunction = function(scaleInstance) { + var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { + return wrapper.scale === scaleInstance; + }); + + if (wrapper) { + scaleInstance.fit(wrapper.minSize.width, maxChartHeight); + } + }; + + var horizontalScaleFitFunction = function(scaleInstance) { + var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { + return wrapper.scale === scaleInstance; + }); + + var scaleMargin = { + left: totalLeftWidth, + right: totalRightWidth, + top: 0, + bottom: 0, + }; + + if (wrapper) { + scaleInstance.fit(maxChartWidth, wrapper.minSize.height, scaleMargin); + } + }; + + var totalLeftWidth = xPadding; + var totalRightWidth = xPadding; + var totalTopHeight = yPadding; + var totalBottomHeight = yPadding; + + helpers.each(leftScales, verticalScaleFitFunction); + helpers.each(rightScales, verticalScaleFitFunction); + + // Figure out how much margin is on the left and right of the horizontal axes + helpers.each(leftScales, function(scaleInstance) { + totalLeftWidth += scaleInstance.width; + }); + + helpers.each(rightScales, function(scaleInstance) { + totalRightWidth += scaleInstance.width; + }); + + helpers.each(topScales, horizontalScaleFitFunction); + helpers.each(bottomScales, horizontalScaleFitFunction); + + helpers.each(topScales, function(scaleInstance) { + totalTopHeight += scaleInstance.height; + }); + helpers.each(bottomScales, function(scaleInstance) { + totalBottomHeight += scaleInstance.height; + }); + + // Let the left scale know the final margin + helpers.each(leftScales, function(scaleInstance) { + var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { + return wrapper.scale === scaleInstance; + }); + + var scaleMargin = { + left: 0, + right: 0, + top: totalTopHeight, + bottom: totalBottomHeight + }; + + if (wrapper) { + scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin); + } + }); + + helpers.each(rightScales, function(scaleInstance) { + var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { + return wrapper.scale === scaleInstance; + }); + + var scaleMargin = { + left: 0, + right: 0, + top: totalTopHeight, + bottom: totalBottomHeight + }; + + if (wrapper) { + scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin); + } + }); + + // Step 7 + // Position the scales + var left = xPadding; + var top = yPadding; + var right = 0; + var bottom = 0; + + var verticalScalePlacer = function(scaleInstance) { + scaleInstance.left = left; + scaleInstance.right = left + scaleInstance.width; + scaleInstance.top = totalTopHeight; + scaleInstance.bottom = totalTopHeight + maxChartHeight; + + // Move to next point + left = scaleInstance.right; + }; + + var horizontalScalePlacer = function(scaleInstance) { + scaleInstance.left = totalLeftWidth; + scaleInstance.right = totalLeftWidth + maxChartWidth; + scaleInstance.top = top; + scaleInstance.bottom = top + scaleInstance.height; + + // Move to next point + top = scaleInstance.bottom; + }; + + helpers.each(leftScales, verticalScalePlacer); + helpers.each(topScales, horizontalScalePlacer); + + // Account for chart width and height + left += maxChartWidth; + top += maxChartHeight; + + helpers.each(rightScales, verticalScalePlacer); + helpers.each(bottomScales, horizontalScalePlacer); + + // Step 8 + chartInstance.chartArea = { + left: totalLeftWidth, + top: totalTopHeight, + right: totalLeftWidth + maxChartWidth, + bottom: totalTopHeight + maxChartHeight, + }; + } + } + }; +}).call(this); + +(function() { + + "use strict"; + + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; + + Chart.defaults.global.tooltips = { + enabled: true, + custom: null, + backgroundColor: "rgba(0,0,0,0.8)", + fontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + fontSize: 10, + fontStyle: "normal", + fontColor: "#fff", + titleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + titleFontSize: 12, + titleFontStyle: "bold", + titleFontColor: "#fff", + yPadding: 6, + xPadding: 6, + caretSize: 8, + cornerRadius: 6, + xOffset: 10, + template: [ + '<% if(label){ %>', + '<%=label %>: ', + '<% } %>', + '<%=value %>', + ].join(''), + multiTemplate: [ + '<%if (datasetLabel){ %>', + '<%=datasetLabel %>: ', + '<% } %>', + '<%=value %>' + ].join(''), + multiKeyBackground: '#fff', + }; + + Chart.Tooltip = Chart.Element.extend({ + initialize: function() { + var options = this._options; + helpers.extend(this, { + _model: { + // Positioning + xPadding: options.tooltips.xPadding, + yPadding: options.tooltips.yPadding, + xOffset: options.tooltips.xOffset, + + // Labels + textColor: options.tooltips.fontColor, + _fontFamily: options.tooltips.fontFamily, + _fontStyle: options.tooltips.fontStyle, + fontSize: options.tooltips.fontSize, + + // Title + titleTextColor: options.tooltips.titleFontColor, + _titleFontFamily: options.tooltips.titleFontFamily, + _titleFontStyle: options.tooltips.titleFontStyle, + titleFontSize: options.tooltips.titleFontSize, + + // Appearance + caretHeight: options.tooltips.caretSize, + cornerRadius: options.tooltips.cornerRadius, + backgroundColor: options.tooltips.backgroundColor, + opacity: 0, + legendColorBackground: options.tooltips.multiKeyBackground, + }, + }); + }, + update: function() { + + var ctx = this._chart.ctx; + + switch (this._options.hover.mode) { + case 'single': + helpers.extend(this._model, { + text: helpers.template(this._options.tooltips.template, { + // These variables are available in the template function. Add others here + element: this._active[0], + value: this._data.datasets[this._active[0]._datasetIndex].data[this._active[0]._index], + label: this._data.labels ? this._data.labels[this._active[0]._index] : '', + }), + }); + + var tooltipPosition = this._active[0].tooltipPosition(); + helpers.extend(this._model, { + x: Math.round(tooltipPosition.x), + y: Math.round(tooltipPosition.y), + caretPadding: tooltipPosition.padding + }); + + 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 = helpers.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]); + } + }, this); + + // Reverse labels if stacked + helpers.each(this._options.stacked ? elements.reverse() : elements, function(element) { + xPositions.push(element._view.x); + yPositions.push(element._view.y); + + //Include any colour information about the element + labels.push(helpers.template(this._options.tooltips.multiTemplate, { + // These variables are available in the template function. Add others here + element: element, + datasetLabel: this._data.datasets[element._datasetIndex].label, + value: this._data.datasets[element._datasetIndex].data[element._index], + })); + colors.push({ + fill: element._view.backgroundColor, + stroke: element._view.borderColor + }); + + }, this); + + yMin = helpers.min(yPositions); + yMax = helpers.max(yPositions); + + xMin = helpers.min(xPositions); + xMax = helpers.max(xPositions); + + return { + x: (xMin > this._chart.width / 2) ? xMin : xMax, + y: (yMin + yMax) / 2, + }; + }).call(this, dataIndex); + + // Apply for now + helpers.extend(this._model, { + x: medianPosition.x, + y: medianPosition.y, + labels: labels, + title: this._data.labels && this._data.labels.length ? this._data.labels[this._active[0]._index] : '', + legendColors: colors, + legendBackgroundColor: this._options.tooltips.multiKeyBackground, + }); + + + // Calculate Appearance Tweaks + + this._model.height = (labels.length * this._model.fontSize) + ((labels.length - 1) * (this._model.fontSize / 2)) + (this._model.yPadding * 2) + this._model.titleFontSize * 1.5; + + var titleWidth = ctx.measureText(this.title).width, + //Label has a legend square as well so account for this. + labelWidth = helpers.longestText(ctx, this.font, labels) + this._model.fontSize + 3, + longestTextWidth = helpers.max([labelWidth, titleWidth]); + + this._model.width = longestTextWidth + (this._model.xPadding * 2); + + + var halfHeight = this._model.height / 2; + + //Check to ensure the height will fit on the canvas + if (this._model.y - halfHeight < 0) { + this._model.y = halfHeight; + } else if (this._model.y + halfHeight > this._chart.height) { + this._model.y = this._chart.height - halfHeight; + } + + //Decide whether to align left or right based on position on canvas + if (this._model.x > this._chart.width / 2) { + this._model.x -= this._model.xOffset + this._model.width; + } else { + this._model.x += this._model.xOffset; + } + break; + } + + return this; + }, + draw: function() { + + var ctx = this._chart.ctx; + var vm = this._view; + + switch (this._options.hover.mode) { + case 'single': + + ctx.font = helpers.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._view); + } 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; + } + + helpers.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': + + helpers.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 = helpers.fontString(vm.fontSize, vm._titleFontStyle, vm._titleFontFamily); + ctx.fillText(vm.title, vm.x + vm.xPadding, this.getLineHeight(0)); + + ctx.font = helpers.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.legendColors[index].stroke).alpha(vm.opacity).rgbString(); + ctx.fillRect(vm.x + vm.xPadding - 1, this.getLineHeight(index + 1) - vm.fontSize / 2 - 1, vm.fontSize + 2, vm.fontSize + 2); + + 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._view.y - (this._view.height / 2) + this._view.yPadding, + afterTitleIndex = index - 1; + + //If the index is zero, we're getting the title + if (index === 0) { + return baseLineHeight + this._view.titleFontSize / 2; + } else { + return baseLineHeight + ((this._view.fontSize * 1.5 * afterTitleIndex) + this._view.fontSize / 2) + this._view.titleFontSize * 1.5; + } + + }, + }); + +}).call(this); + +(function() { + + "use strict"; + + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; + + Chart.controllers.bar = function(chart, datasetIndex) { + this.initialize.call(this, chart, datasetIndex); + }; + + helpers.extend(Chart.controllers.bar.prototype, { + + initialize: function(chart, datasetIndex) { + this.chart = chart; + this.index = datasetIndex; + this.linkScales(); + this.addElements(); + }, + + linkScales: function() { + if (!this.getDataset().xAxisID) { + this.getDataset().xAxisID = this.chart.options.scales.xAxes[0].id; + } + + if (!this.getDataset().yAxisID) { + this.getDataset().yAxisID = this.chart.options.scales.yAxes[0].id; + } + }, + + getDataset: function() { + return this.chart.data.datasets[this.index]; + }, + + getScaleForId: function(scaleID) { + return this.chart.scales[scaleID]; + }, + + addElements: function() { + this.getDataset().metaData = this.getDataset().metaData || []; + helpers.each(this.getDataset().data, function(value, index) { + this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Rectangle({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _index: index, + }); + }, this); + }, + + reset: function() { + this.update(true); + }, + + update: function(reset) { + + var xScale = this.getScaleForId(this.getDataset().xAxisID); + var yScale = this.getScaleForId(this.getDataset().yAxisID); + helpers.each(this.getDataset().metaData, function(rectangle, index) { + + var yScalePoint; + + if (yScale.min < 0 && yScale.max < 0) { + // all less than 0. use the top + yScalePoint = yScale.getPixelForValue(yScale.max); + } else if (yScale.min > 0 && yScale.max > 0) { + yScalePoint = yScale.getPixelForValue(yScale.min); + } else { + yScalePoint = yScale.getPixelForValue(0); + } + + helpers.extend(rectangle, { + // Utility + _chart: this.chart.chart, + _xScale: xScale, + _yScale: yScale, + _datasetIndex: this.index, + _index: index, + + + // Desired view properties + _model: { + x: xScale.calculateBarX(this.chart.data.datasets.length, this.index, index), + y: reset ? yScalePoint : yScale.getPixelForValue(this.getDataset().data[index]), + + // Tooltip + label: this.chart.data.labels[index], + datasetLabel: this.getDataset().label, + + // Appearance + base: yScale.calculateBarBase(this.index, index), + width: xScale.calculateBarWidth(this.chart.data.datasets.length), + backgroundColor: rectangle.custom && rectangle.custom.backgroundColor ? rectangle.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.rectangle.backgroundColor), + borderColor: rectangle.custom && rectangle.custom.borderColor ? rectangle.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.rectangle.borderColor), + borderWidth: rectangle.custom && rectangle.custom.borderWidth ? rectangle.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.rectangle.borderWidth), + }, + }); + rectangle.pivot(); + }, this); + }, + + draw: function(ease) { + var easingDecimal = ease || 1; + helpers.each(this.getDataset().metaData, function(rectangle, index) { + rectangle.transition(easingDecimal).draw(); + }, this); + }, + + + + + + + + + // eachLine: function eachLine(callback) { + // helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) { + // if (dataset.metaDataset && dataset.metaDataset instanceof Chart.Line) { + // callback.call(this, dataset, datasetIndex); + // } + // }, this); + // }, + + // addLine: function addLine(dataset, datasetIndex) { + // if (dataset) { + // dataset.metaDataset = new Chart.Line({ + // _chart: this.chart.chart, + // _datasetIndex: datasetIndex, + // _points: dataset.metaData, + // }); + // } + // }, + + // addPoint: function addPoint(dataset, datasetIndex, index) { + // if (dataset) { + // dataset.metaData = dataset.metaData || new Array(this.chart.data.datasets[datasetIndex].data.length); + + // if (index < dataset.metaData.length) { + // dataset.metaData[index] = new Chart.Point({ + // _datasetIndex: datasetIndex, + // _index: index, + // _chart: this.chart.chart, + // _model: { + // x: 0, + // y: 0, + // }, + // }); + // } + // } + // }, + + + + // resetElements: function resetElements() { + // helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) { + // // All elements must be the same type for the given dataset so we are fine to check just the first one + // if (dataset.metaData[0] instanceof Chart.Point) { + // // Have points. Update all of them + // this.resetDatasetPoints(dataset, datasetIndex); + // } else if (dataset.metaData[0] instanceof Chart.Rectangle) { + // // Have rectangles (bars) + // this.resetDatasetRectangles(dataset, datasetIndex); + // } + // }, this); + // }, + + // resetDatasetPoints: function resetDatasetPoints(dataset, datasetIndex) { + // helpers.each(dataset.metaData, function(point, index) { + // var xScale = this.getScaleForId(this.chart.data.datasets[datasetIndex].xAxisID); + // var yScale = this.getScaleForId(this.chart.data.datasets[datasetIndex].yAxisID); + + // var yScalePoint; + + // if (yScale.min < 0 && yScale.max < 0) { + // // all less than 0. use the top + // yScalePoint = yScale.getPixelForValue(yScale.max); + // } else if (yScale.min > 0 && yScale.max > 0) { + // yScalePoint = yScale.getPixelForValue(yScale.min); + // } else { + // yScalePoint = yScale.getPixelForValue(0); + // } + + // helpers.extend(point, { + // // Utility + // _chart: this.chart.chart, //WTF + // _xScale: xScale, + // _yScale: yScale, + // _datasetIndex: datasetIndex, + // _index: index, + + // // Desired view properties + // _model: { + // x: xScale.getPointPixelForValue(this.chart.data.datasets[datasetIndex].data[index], index, datasetIndex), + // y: yScalePoint, + // }, + // }); + + // this.updatePointElementAppearance(point, datasetIndex, index); + // }, this); + + // this.updateBezierControlPoints(dataset); + // }, + + // resetDatasetRectangles: function resetDatasetRectangles(dataset, datasetIndex) { + + // }, + + // resetElementAppearance: function(element, datasetIndex, index) { + // if (element instanceof Chart.Point) { + // this.updatePointElementAppearance(element, datasetIndex, index); + // } else if (element instanceof Chart.Rectangle) { + // this.updateRectangleElementAppearance(element, datasetIndex, index); + // } + // }, + + // updateElements: function updateElements() { + // // Update the lines + // this.updateLines(); + + // helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) { + // // All elements must be the same type for the given dataset so we are fine to check just the first one + // if (dataset.metaData[0] instanceof Chart.Point) { + // // Have points. Update all of them + // this.updatePoints(dataset, datasetIndex); + // } else if (dataset.metaData[0] instanceof Chart.Rectangle) { + // // Have rectangles (bars) + // this.updateRectangles(dataset, datasetIndex); + // } + // }, this); + // }, + + // updateLines: function updateLines() { + // this.eachLine(function(dataset, datasetIndex) { + // var yScale = this.getScaleForId(dataset.yAxisID); + // var scaleBase; + + // if (yScale.min < 0 && yScale.max < 0) { + // scaleBase = yScale.getPixelForValue(yScale.max); + // } else if (yScale.min > 0 && yScale.max > 0) { + // scaleBase = yScale.getPixelForValue(yScale.min); + // } else { + // scaleBase = yScale.getPixelForValue(0); + // } + + // helpers.extend(dataset.metaDataset, { + // // Utility + // _scale: yScale, + // _datasetIndex: datasetIndex, + // // Data + // _children: dataset.metaData, + // // Model + // _model: { + // // Appearance + // tension: dataset.metaDataset.custom && dataset.metaDataset.custom.tension ? dataset.metaDataset.custom.tension : (dataset.tension || this.chart.options.elements.line.tension), + // backgroundColor: dataset.metaDataset.custom && dataset.metaDataset.custom.backgroundColor ? dataset.metaDataset.custom.backgroundColor : (dataset.backgroundColor || this.chart.options.elements.line.backgroundColor), + // borderWidth: dataset.metaDataset.custom && dataset.metaDataset.custom.borderWidth ? dataset.metaDataset.custom.borderWidth : (dataset.borderWidth || this.chart.options.elements.line.borderWidth), + // borderColor: dataset.metaDataset.custom && dataset.metaDataset.custom.borderColor ? dataset.metaDataset.custom.borderColor : (dataset.borderColor || this.chart.options.elements.line.borderColor), + // fill: dataset.metaDataset.custom && dataset.metaDataset.custom.fill ? dataset.metaDataset.custom.fill : (dataset.fill !== undefined ? dataset.fill : this.chart.options.elements.line.fill), + // skipNull: dataset.skipNull !== undefined ? dataset.skipNull : this.chart.options.elements.line.skipNull, + // drawNull: dataset.drawNull !== undefined ? dataset.drawNull : this.chart.options.elements.line.drawNull, + // // Scale + // scaleTop: yScale.top, + // scaleBottom: yScale.bottom, + // scaleZero: scaleBase, + // }, + // }); + + // dataset.metaDataset.pivot(); + // }); + // }, + + // updatePoints: function updatePoints(dataset, datasetIndex) { + // helpers.each(dataset.metaData, function(point, index) { + // var xScale = this.getScaleForId(this.chart.data.datasets[datasetIndex].xAxisID); + // var yScale = this.getScaleForId(this.chart.data.datasets[datasetIndex].yAxisID); + + // helpers.extend(point, { + // // Utility + // _chart: this.chart.chart, + // _xScale: xScale, + // _yScale: yScale, + // _datasetIndex: datasetIndex, + // _index: index, + + // // Desired view properties + // _model: { + // x: xScale.getPointPixelForValue(this.chart.data.datasets[datasetIndex].data[index], index, datasetIndex), + // y: yScale.getPointPixelForValue(this.chart.data.datasets[datasetIndex].data[index], index, datasetIndex), + // }, + // }); + + // this.updatePointElementAppearance(point, datasetIndex, index); + // }, this); + + // this.updateBezierControlPoints(dataset); + // }, + + // updatePointElementAppearance: function updatePointElementAppearance(point, datasetIndex, index) { + // helpers.extend(point._model, { + // // Appearance + // tension: point.custom && point.custom.tension ? point.custom.tension : this.chart.options.elements.line.tension, + // radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.chart.data.datasets[datasetIndex].radius, index, this.chart.options.elements.point.radius), + // backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.chart.data.datasets[datasetIndex].pointBackgroundColor, index, this.chart.options.elements.point.backgroundColor), + // borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.chart.data.datasets[datasetIndex].pointBorderColor, index, this.chart.options.elements.point.borderColor), + // borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.chart.data.datasets[datasetIndex].pointBorderWidth, index, this.chart.options.elements.point.borderWidth), + // skip: this.chart.data.datasets[datasetIndex].data[index] === null, + + // // Tooltip + // hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.chart.data.datasets[datasetIndex].hitRadius, index, this.chart.options.elements.point.hitRadius), + // }); + // }, + + + // setElementHoverStyle: function setElementHoverStyle(element) { + // if (element instanceof Chart.Point) { + // this.setPointHoverStyle(element); + // } else if (element instanceof Chart.Rectangle) { + // this.setRectangleHoverStyle(element); + // } + // }, + + // setPointHoverStyle: function setPointHoverStyle(point) { + // var dataset = this.chart.data.datasets[point._datasetIndex]; + // var index = point._index; + + // point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); + // point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(point._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); + // point._model.borderColor = point.custom && point.custom.hoverBorderColor ? point.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(point._model.borderColor).saturate(0.5).darken(0.1).rgbString()); + // point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, point._model.borderWidth); + // }, + + // setHoverStyle: function(rectangle) { + // var dataset = this.chart.data.datasets[rectangle._datasetIndex]; + // var index = rectangle._index; + + // rectangle._model.backgroundColor = rectangle.custom && rectangle.custom.hoverBackgroundColor ? rectangle.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(rectangle._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); + // rectangle._model.borderColor = rectangle.custom && rectangle.custom.hoverBorderColor ? rectangle.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(rectangle._model.borderColor).saturate(0.5).darken(0.1).rgbString()); + // rectangle._model.borderWidth = rectangle.custom && rectangle.custom.hoverBorderWidth ? rectangle.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangle._model.borderWidth); + // }, + + // updateBezierControlPoints: function updateBezierControlPoints(dataset) { + // // Update control points for the bezier curve + // helpers.each(dataset.metaData, function(point, index) { + // var controlPoints = helpers.splineCurve( + // this.previousPoint(dataset.metaData, index)._model, + // point._model, + // this.nextPoint(dataset.metaData, index)._model, + // point._model.tension + // ); + + // point._model.controlPointPreviousX = controlPoints.previous.x; + // point._model.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.chart.chartArea.bottom) { + // point._model.controlPointNextY = this.chart.chartArea.bottom; + // } else if (controlPoints.next.y < this.chart.chartArea.top) { + // point._model.controlPointNextY = this.chart.chartArea.top; + // } else { + // point._model.controlPointNextY = controlPoints.next.y; + // } + + // // Cap inner bezier handles to the upper/lower scale bounds + // if (controlPoints.previous.y > this.chart.chartArea.bottom) { + // point._model.controlPointPreviousY = this.chart.chartArea.bottom; + // } else if (controlPoints.previous.y < this.chart.chartArea.top) { + // point._model.controlPointPreviousY = this.chart.chartArea.top; + // } else { + // point._model.controlPointPreviousY = controlPoints.previous.y; + // } + + // // Now pivot the point for animation + // point.pivot(); + // }, 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.chart.data.datasets.length; datasetIndex++) { + for (elementIndex = 0; elementIndex < this.chart.data.datasets[datasetIndex].metaData.length; elementIndex++) { + if (this.chart.data.datasets[datasetIndex].metaData[elementIndex].inGroupRange(eventPosition.x, eventPosition.y)) { + helpers.each(this.chart.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.chart.data.datasets.length; ++datasetIndex) { + for (var elementIndex = 0; elementIndex < this.chart.data.datasets[datasetIndex].metaData.length; ++elementIndex) { + if (this.chart.data.datasets[datasetIndex].metaData[elementIndex].inRange(eventPosition.x, eventPosition.y)) { + element.push(this.chart.data.datasets[datasetIndex].metaData[elementIndex]); + return element; + } + } + } + + return []; + }, + }); }).call(this); (function() { - "use strict"; - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; + "use strict"; - var LinearScale = Chart.Element.extend({ - calculateRange: helpers.noop, // overridden in the chart. Will set min and max as properties of the scale for later use - isHorizontal: function() { - return this.options.position == "top" || this.options.position == "bottom"; - }, - generateTicks: function(width, height) { - // We need to decide how many ticks we are going to have. Each tick draws a grid line. - // There are two possibilities. The first is that the user has manually overridden the scale - // calculations in which case the job is easy. The other case is that we have to do it ourselves - // - // We assume at this point that the scale object has been updated with the following values - // by the chart. - // min: this is the minimum value of the scale - // max: this is the maximum value of the scale - // options: contains the options for the scale. This is referenced from the user settings - // rather than being cloned. This ensures that updates always propogate to a redraw + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; - // Reset the ticks array. Later on, we will draw a grid line at these positions - // The array simply contains the numerical value of the spots where ticks will be - this.ticks = []; + Chart.controllers.line = function(chart, datasetIndex) { + this.initialize.call(this, chart, datasetIndex); + }; - if (this.options.override) { - // The user has specified the manual override. We use <= instead of < so that - // we get the final line - for (var i = 0; i <= this.options.override.steps; ++i) { - var value = this.options.override.start + (i * this.options.override.stepWidth); - ticks.push(value); - } - } else { - // Figure out what the max number of ticks we can support it is based on the size of - // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 - // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on - // the graph + helpers.extend(Chart.controllers.line.prototype, { - var maxTicks; + initialize: function(chart, datasetIndex) { + this.chart = chart; + this.index = datasetIndex; + this.linkScales(); + this.addElements(); + }, - if (this.isHorizontal()) { - maxTicks = Math.min(11, Math.ceil(width / 50)); - } else { - // The factor of 2 used to scale the font size has been experimentally determined. - maxTicks = Math.min(11, Math.ceil(height / (2 * this.options.labels.fontSize))); - } + linkScales: function() { + if (!this.getDataset().xAxisID) { + this.getDataset().xAxisID = this.chart.options.scales.xAxes[0].id; + } - // Make sure we always have at least 2 ticks - maxTicks = Math.max(2, maxTicks); + if (!this.getDataset().yAxisID) { + this.getDataset().yAxisID = this.chart.options.scales.yAxes[0].id; + } + }, - // To get a "nice" value for the tick spacing, we will use the appropriately named - // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks - // for details. + getDataset: function() { + return this.chart.data.datasets[this.index]; + }, - // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, - // do nothing since that would make the chart weird. If the user really wants a weird chart - // axis, they can manually override it - if (this.options.beginAtZero) { - var minSign = helpers.sign(this.min); - var maxSign = helpers.sign(this.max); + getScaleForId: function(scaleID) { + return this.chart.scales[scaleID]; + }, - if (minSign < 0 && maxSign < 0) { - // move the top up to 0 - this.max = 0; - } else if (minSign > 0 && maxSign > 0) { - // move the botttom down to 0 - this.min = 0; - } - } + addElements: function() { - var niceRange = helpers.niceNum(this.max - this.min, false); - var spacing = helpers.niceNum(niceRange / (maxTicks - 1), true); - var niceMin = Math.floor(this.min / spacing) * spacing; - var niceMax = Math.ceil(this.max / spacing) * spacing; + this.getDataset().metaData = this.getDataset().metaData || []; - // Put the values into the ticks array - for (var j = niceMin; j <= niceMax; j += spacing) { - this.ticks.push(j); - } - } + this.getDataset().metaDataset = this.getDataset().metaDataset || new Chart.elements.Line({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _points: this.getDataset().metaData, + }); - if (this.options.position == "left" || this.options.position == "right") { - // We are in a vertical orientation. The top value is the highest. So reverse the array - this.ticks.reverse(); - } + helpers.each(this.getDataset().data, function(value, index) { + this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _index: index, + }); + }, this); + }, - // At this point, we need to update our max and min given the tick values since we have expanded the - // range of the scale - this.max = helpers.max(this.ticks); - this.min = helpers.min(this.ticks); - }, - buildLabels: function() { - // We assume that this has been run after ticks have been generated. We try to figure out - // a label for each tick. - this.labels = []; + reset: function() { + this.update(true); + }, - helpers.each(this.ticks, function(tick, index, ticks) { - var label; + update: function(reset) { - if (this.options.labels.userCallback) { - // If the user provided a callback for label generation, use that as first priority - label = this.options.lables.userCallback(tick, index, ticks); - } else if (this.options.labels.template) { - // else fall back to the template string - label = helpers.template(this.options.labels.template, { - value: tick - }); - } + var line = this.getDataset().metaDataset; + var points = this.getDataset().metaData; - this.labels.push(label ? label : ""); // empty string will not render so we're good - }, this); - }, - getPixelForValue: function(value) { - // This must be called after fit has been run so that - // this.left, this.top, this.right, and this.bottom have been defined - var pixel; - var range = this.max - this.min; + var yScale = this.getScaleForId(this.getDataset().yAxisID); + var xScale = this.getScaleForId(this.getDataset().xAxisID); + var scaleBase; - if (this.isHorizontal()) { - pixel = this.left + (this.width / range * (value - this.min)); - } else { - // Bottom - top since pixels increase downard on a screen - pixel = this.bottom - (this.height / range * (value - this.min)); - } - - return pixel; - }, - // Fit this axis to the given size - // @param {number} maxWidth : the max width the axis can be - // @param {number} maxHeight: the max height the axis can be - // @return {object} minSize : the minimum size needed to draw the axis - fit: function(maxWidth, maxHeight) { - this.calculateRange(); - this.generateTicks(maxWidth, maxHeight); - this.buildLabels(); - - var minSize = { - width: 0, - height: 0, - }; - - // In a horizontal axis, we need some room for the scale to be drawn - // - // ----------------------------------------------------- - // | | | | | - // - // In a vertical axis, we need some room for the scale to be drawn. - // The actual grid lines will be drawn on the chart area, however, we need to show - // ticks where the axis actually is. - // We will allocate 25px for this width - // | - // -| - // | - // | - // -| - // | - // | - // -| + if (yScale.min < 0 && yScale.max < 0) { + scaleBase = yScale.getPixelForValue(yScale.max); + } else if (yScale.min > 0 && yScale.max > 0) { + scaleBase = yScale.getPixelForValue(yScale.min); + } else { + scaleBase = yScale.getPixelForValue(0); + } - // Width - if (this.isHorizontal()) { - minSize.width = maxWidth; // fill all the width - } else { - minSize.width = this.options.gridLines.show && this.options.display ? 10 : 0; - } + // Update Line + helpers.extend(line, { + // Utility + _scale: yScale, + _datasetIndex: this.index, + // Data + _children: points, + // Model + _model: { + // Appearance + tension: line.custom && line.custom.tension ? line.custom.tension : (this.getDataset().tension || this.chart.options.elements.line.tension), + backgroundColor: line.custom && line.custom.backgroundColor ? line.custom.backgroundColor : (this.getDataset().backgroundColor || this.chart.options.elements.line.backgroundColor), + borderWidth: line.custom && line.custom.borderWidth ? line.custom.borderWidth : (this.getDataset().borderWidth || this.chart.options.elements.line.borderWidth), + borderColor: line.custom && line.custom.borderColor ? line.custom.borderColor : (this.getDataset().borderColor || this.chart.options.elements.line.borderColor), + fill: line.custom && line.custom.fill ? line.custom.fill : (this.getDataset().fill !== undefined ? this.getDataset().fill : this.chart.options.elements.line.fill), + skipNull: this.getDataset().skipNull !== undefined ? this.getDataset().skipNull : this.chart.options.elements.line.skipNull, + drawNull: this.getDataset().drawNull !== undefined ? this.getDataset().drawNull : this.chart.options.elements.line.drawNull, + // Scale + scaleTop: yScale.top, + scaleBottom: yScale.bottom, + scaleZero: scaleBase, + }, + }); + line.pivot(); - // height - if (this.isHorizontal()) { - minSize.height = this.options.gridLines.show && this.options.display ? 10 : 0; - } else { - minSize.height = maxHeight; // fill all the height - } + // Update Points + helpers.each(points, function(point, index) { + helpers.extend(point, { + // Utility + _chart: this.chart.chart, + _xScale: xScale, + _yScale: yScale, + _datasetIndex: this.index, + _index: index, + + // Desired view properties + _model: { + x: xScale.getPointPixelForValue(this.getDataset().data[index], index, this.index), + y: yScale.getPointPixelForValue(this.getDataset().data[index], index, this.index), + // Appearance + tension: point.custom && point.custom.tension ? point.custom.tension : this.chart.options.elements.line.tension, + radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius), + backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor, index, this.chart.options.elements.point.backgroundColor), + borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, this.chart.options.elements.point.borderColor), + borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth, index, this.chart.options.elements.point.borderWidth), + skip: this.getDataset().data[index] === null, + + // Tooltip + hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius), + }, + + }); + }, this); + + // Update bezier control points + helpers.each(this.getDataset().metaData, function(point, index) { + var controlPoints = helpers.splineCurve( + helpers.previousItem(this.getDataset().metaData, index)._model, + point._model, + helpers.nextItem(this.getDataset().metaData, index)._model, + point._model.tension + ); + + point._model.controlPointPreviousX = controlPoints.previous.x; + point._model.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.chart.chartArea.bottom) { + point._model.controlPointNextY = this.chart.chartArea.bottom; + } else if (controlPoints.next.y < this.chart.chartArea.top) { + point._model.controlPointNextY = this.chart.chartArea.top; + } else { + point._model.controlPointNextY = controlPoints.next.y; + } + + // Cap inner bezier handles to the upper/lower scale bounds + if (controlPoints.previous.y > this.chart.chartArea.bottom) { + point._model.controlPointPreviousY = this.chart.chartArea.bottom; + } else if (controlPoints.previous.y < this.chart.chartArea.top) { + point._model.controlPointPreviousY = this.chart.chartArea.top; + } else { + point._model.controlPointPreviousY = controlPoints.previous.y; + } + + // Now pivot the point for animation + point.pivot(); + }, this); + + }, + + draw: function(ease) { + var easingDecimal = ease || 1; + + // Transition Point Locations + helpers.each(this.getDataset().metaData, function(point, index) { + point.transition(easingDecimal); + }, this); + + // Transition and Draw the line + this.getDataset().metaDataset.transition(easingDecimal).draw(); + + // Draw the points + helpers.each(this.getDataset().metaData, function(point) { + point.draw(); + }); + }, - if (this.options.labels.show && this.options.display) { - // Don't bother fitting the labels if we are not showing them - var labelFont = helpers.fontString(this.options.labels.fontSize, - this.options.labels.fontStyle, this.options.labels.fontFamily); - if (this.isHorizontal()) { - // A horizontal axis is more constrained by the height. - var maxLabelHeight = maxHeight - minSize.height; - var labelHeight = 1.5 * this.options.labels.fontSize; - minSize.height = Math.min(maxHeight, minSize.height + labelHeight); - } else { - // A vertical axis is more constrained by the width. Labels are the dominant factor - // here, so get that length first - var maxLabelWidth = maxWidth - minSize.width; - var largestTextWidth = helpers.longestText(this.ctx, labelFont, this.labels); - if (largestTextWidth < maxLabelWidth) { - // We don't need all the room - minSize.width += largestTextWidth; - } else { - // Expand to max size - minSize.width = maxWidth; - } - } - } - this.width = minSize.width; - this.height = minSize.height; - return minSize; - }, - // Actualy draw the scale on the canvas - // @param {rectangle} chartArea : the area of the chart to draw full grid lines on - draw: function(chartArea) { - if (this.options.display) { - var setContextLineSettings; - var hasZero; - // Make sure we draw text in the correct color - this.ctx.fillStyle = this.options.labels.fontColor; + // eachLine: function eachLine(callback) { + // helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) { + // if (dataset.metaDataset && dataset.metaDataset instanceof Chart.Line) { + // callback.call(this, dataset, datasetIndex); + // } + // }, this); + // }, - if (this.isHorizontal()) { - if (this.options.gridLines.show) { - // Draw the horizontal line - setContextLineSettings = true; - hasZero = helpers.findNextWhere(this.ticks, function(tick) { - return tick === 0; - }) !== undefined; - var yTickStart = this.options.position == "bottom" ? this.top : this.bottom - 5; - var yTickEnd = this.options.position == "bottom" ? this.top + 5 : this.bottom; + // addPoint: function addPoint(dataset, datasetIndex, index) { + // if (dataset) { + // dataset.metaData = dataset.metaData || new Array(this.chart.data.datasets[datasetIndex].data.length); - helpers.each(this.ticks, function(tick, index) { - // Grid lines are vertical - var xValue = this.getPixelForValue(tick); + // if (index < dataset.metaData.length) { + // dataset.metaData[index] = new Chart.Point({ + // _datasetIndex: datasetIndex, + // _index: index, + // _chart: this.chart.chart, + // _model: { + // x: 0, + // y: 0, + // }, + // }); + // } + // } + // }, - if (tick === 0 || (!hasZero && index === 0)) { - // Draw the 0 point specially or the left if there is no 0 - this.ctx.lineWidth = this.options.gridLines.zeroLineWidth; - this.ctx.strokeStyle = this.options.gridLines.zeroLineColor; - setContextLineSettings = true; // reset next time - } else if (setContextLineSettings) { - this.ctx.lineWidth = this.options.gridLines.lineWidth; - this.ctx.strokeStyle = this.options.gridLines.color; - setContextLineSettings = false; - } - xValue += helpers.aliasPixel(this.ctx.lineWidth); - // Draw the label area - this.ctx.beginPath(); + // resetElements: function resetElements() { + // helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) { + // // All elements must be the same type for the given dataset so we are fine to check just the first one + // if (dataset.metaData[0] instanceof Chart.Point) { + // // Have points. Update all of them + // this.resetDatasetPoints(dataset, datasetIndex); + // } else if (dataset.metaData[0] instanceof Chart.Rectangle) { + // // Have rectangles (lines) + // this.resetDatasetRectangles(dataset, datasetIndex); + // } + // }, this); + // }, - if (this.options.gridLines.drawTicks) { - this.ctx.moveTo(xValue, yTickStart); - this.ctx.lineTo(xValue, yTickEnd); - } + // resetDatasetPoints: function resetDatasetPoints(dataset, datasetIndex) { + // helpers.each(dataset.metaData, function(point, index) { + // var xScale = this.getScaleForId(this.chart.data.datasets[datasetIndex].xAxisID); + // var yScale = this.getScaleForId(this.chart.data.datasets[datasetIndex].yAxisID); - // Draw the chart area - if (this.options.gridLines.drawOnChartArea) { - this.ctx.moveTo(xValue, chartArea.top); - this.ctx.lineTo(xValue, chartArea.bottom); - } + // var yScalePoint; - // Need to stroke in the loop because we are potentially changing line widths & colours - this.ctx.stroke(); - }, this); - } + // if (yScale.min < 0 && yScale.max < 0) { + // // all less than 0. use the top + // yScalePoint = yScale.getPixelForValue(yScale.max); + // } else if (yScale.min > 0 && yScale.max > 0) { + // yScalePoint = yScale.getPixelForValue(yScale.min); + // } else { + // yScalePoint = yScale.getPixelForValue(0); + // } - if (this.options.labels.show) { - // Draw the labels + // helpers.extend(point, { + // // Utility + // _chart: this.chart.chart, //WTF + // _xScale: xScale, + // _yScale: yScale, + // _datasetIndex: datasetIndex, + // _index: index, - var labelStartY; + // // Desired view properties + // _model: { + // x: xScale.getPointPixelForValue(this.chart.data.datasets[datasetIndex].data[index], index, datasetIndex), + // y: yScalePoint, + // }, + // }); - if (this.options.position == "top") { - labelStartY = this.bottom - 10; - this.ctx.textBaseline = "bottom"; - } else { - // bottom side - labelStartY = this.top + 10; - this.ctx.textBaseline = "top"; - } + // this.updatePointElementAppearance(point, datasetIndex, index); + // }, this); - this.ctx.textAlign = "center"; - this.ctx.font = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily); + // this.updateBezierControlPoints(dataset); + // }, - helpers.each(this.labels, function(label, index) { - var xValue = this.getPixelForValue(this.ticks[index]); - this.ctx.fillText(label, xValue, labelStartY); - }, this); - } - } else { - // Vertical - if (this.options.gridLines.show) { + // resetDatasetRectangles: function resetDatasetRectangles(dataset, datasetIndex) { - // Draw the vertical line - setContextLineSettings = true; - hasZero = helpers.findNextWhere(this.ticks, function(tick) { - return tick === 0; - }) !== undefined; - var xTickStart = this.options.position == "right" ? this.left : this.right - 5; - var xTickEnd = this.options.position == "right" ? this.left + 5 : this.right; + // }, - helpers.each(this.ticks, function(tick, index) { - // Grid lines are horizontal - var yValue = this.getPixelForValue(tick); + // resetElementAppearance: function(element, datasetIndex, index) { + // if (element instanceof Chart.Point) { + // this.updatePointElementAppearance(element, datasetIndex, index); + // } else if (element instanceof Chart.Rectangle) { + // this.updateRectangleElementAppearance(element, datasetIndex, index); + // } + // }, - if (tick === 0 || (!hasZero && index === 0)) { - // Draw the 0 point specially or the bottom if there is no 0 - this.ctx.lineWidth = this.options.gridLines.zeroLineWidth; - this.ctx.strokeStyle = this.options.gridLines.zeroLineColor; - setContextLineSettings = true; // reset next time - } else if (setContextLineSettings) { - this.ctx.lineWidth = this.options.gridLines.lineWidth; - this.ctx.strokeStyle = this.options.gridLines.color; - setContextLineSettings = false; // use boolean to indicate that we only want to do this once - } + // updateElements: function updateElements() { + // // Update the lines + // this.updateLines(); - yValue += helpers.aliasPixel(this.ctx.lineWidth); + // helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) { + // // All elements must be the same type for the given dataset so we are fine to check just the first one + // if (dataset.metaData[0] instanceof Chart.Point) { + // // Have points. Update all of them + // this.updatePoints(dataset, datasetIndex); + // } else if (dataset.metaData[0] instanceof Chart.Rectangle) { + // // Have rectangles (lines) + // this.updateRectangles(dataset, datasetIndex); + // } + // }, this); + // }, - // Draw the label area - this.ctx.beginPath(); - if (this.options.gridLines.drawTicks) { - this.ctx.moveTo(xTickStart, yValue); - this.ctx.lineTo(xTickEnd, yValue); - } + // setElementHoverStyle: function setElementHoverStyle(element) { + // if (element instanceof Chart.Point) { + // this.setPointHoverStyle(element); + // } else if (element instanceof Chart.Rectangle) { + // this.setRectangleHoverStyle(element); + // } + // }, - // Draw the chart area - if (this.options.gridLines.drawOnChartArea) { - this.ctx.moveTo(chartArea.left, yValue); - this.ctx.lineTo(chartArea.right, yValue); - } + // setPointHoverStyle: function setPointHoverStyle(point) { + // var dataset = this.chart.data.datasets[point._datasetIndex]; + // var index = point._index; - this.ctx.stroke(); - }, this); - } + // point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); + // point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(point._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); + // point._model.borderColor = point.custom && point.custom.hoverBorderColor ? point.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(point._model.borderColor).saturate(0.5).darken(0.1).rgbString()); + // point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, point._model.borderWidth); + // }, - if (this.options.labels.show) { - // Draw the labels + // setHoverStyle: function(rectangle) { + // var dataset = this.chart.data.datasets[rectangle._datasetIndex]; + // var index = rectangle._index; - var labelStartX; + // rectangle._model.backgroundColor = rectangle.custom && rectangle.custom.hoverBackgroundColor ? rectangle.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(rectangle._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); + // rectangle._model.borderColor = rectangle.custom && rectangle.custom.hoverBorderColor ? rectangle.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(rectangle._model.borderColor).saturate(0.5).darken(0.1).rgbString()); + // rectangle._model.borderWidth = rectangle.custom && rectangle.custom.hoverBorderWidth ? rectangle.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangle._model.borderWidth); + // }, - if (this.options.position == "left") { - labelStartX = this.right - 10; - this.ctx.textAlign = "right"; - } else { - // right side - labelStartX = this.left + 5; - this.ctx.textAlign = "left"; - } + getElementsAtEvent: function(e) { + var elementsArray = [], + eventPosition = helpers.getRelativePosition(e), + datasetIterator = function(dataset) { + elementsArray.push(dataset.metaData[elementIndex]); + }, + elementIndex; - this.ctx.textBaseline = "middle"; - this.ctx.font = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily); + for (var datasetIndex = 0; datasetIndex < this.chart.data.datasets.length; datasetIndex++) { + for (elementIndex = 0; elementIndex < this.chart.data.datasets[datasetIndex].metaData.length; elementIndex++) { + if (this.chart.data.datasets[datasetIndex].metaData[elementIndex].inGroupRange(eventPosition.x, eventPosition.y)) { + helpers.each(this.chart.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.chart.data.datasets.length; ++datasetIndex) { + for (var elementIndex = 0; elementIndex < this.chart.data.datasets[datasetIndex].metaData.length; ++elementIndex) { + if (this.chart.data.datasets[datasetIndex].metaData[elementIndex].inRange(eventPosition.x, eventPosition.y)) { + element.push(this.chart.data.datasets[datasetIndex].metaData[elementIndex]); + return element; + } + } + } + + return []; + }, + }); - helpers.each(this.labels, function(label, index) { - var yValue = this.getPixelForValue(this.ticks[index]); - this.ctx.fillText(label, labelStartX, yValue); - }, this); - } - } - } - } - }); - Chart.scales.registerScaleType("linear", LinearScale); }).call(this); (function() { - "use strict"; + "use strict"; - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; - var LinearRadialScale = Chart.Element.extend({ - initialize: function() { - this.size = helpers.min([this.height, this.width]); - this.drawingArea = (this.options.display) ? (this.size / 2) - (this.options.labels.fontSize / 2 + this.options.labels.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.options.lineArc) { - this.setScaleSize(); - } else { - this.drawingArea = (this.options.display) ? (this.size / 2) - (this.fontSize / 2 + this.backdropPaddingY) : (this.size / 2); - } + // Default config for a category scale + var defaultConfig = { + display: true, + position: "bottom", + id: "x-axis-1", // need an ID so datasets can reference the scale - this.buildYLabels(); - }, - calculateRange: helpers.noop, // overridden in chart - generateTicks: function() { - // We need to decide how many ticks we are going to have. Each tick draws a grid line. - // There are two possibilities. The first is that the user has manually overridden the scale - // calculations in which case the job is easy. The other case is that we have to do it ourselves - // - // We assume at this point that the scale object has been updated with the following values - // by the chart. - // min: this is the minimum value of the scale - // max: this is the maximum value of the scale - // options: contains the options for the scale. This is referenced from the user settings - // rather than being cloned. This ensures that updates always propogate to a redraw + // grid line settings + gridLines: { + show: true, + color: "rgba(0, 0, 0, 0.1)", + lineWidth: 1, + drawOnChartArea: true, + drawTicks: true, + zeroLineWidth: 1, + zeroLineColor: "rgba(0,0,0,0.25)", + offsetGridLines: false, + }, - // Reset the ticks array. Later on, we will draw a grid line at these positions - // The array simply contains the numerical value of the spots where ticks will be - this.ticks = []; + // label settings + labels: { + show: true, + template: "<%=value%>", + fontSize: 12, + fontStyle: "normal", + fontColor: "#666", + fontFamily: "Helvetica Neue", + }, + }; - if (this.options.override) { - // The user has specified the manual override. We use <= instead of < so that - // we get the final line - for (var i = 0; i <= this.options.override.steps; ++i) { - var value = this.options.override.start + (i * this.options.override.stepWidth); - ticks.push(value); - } - } else { - // Figure out what the max number of ticks we can support it is based on the size of - // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 - // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on - // the graph + var DatasetScale = Chart.Element.extend({ + isHorizontal: function() { + return this.options.position == "top" || this.options.position == "bottom"; + }, + getPixelForValue: function(value, index, datasetIndex, includeOffset) { + // This must be called after fit has been run so that + // this.left, this.top, this.right, and this.bottom have been defined + if (this.isHorizontal()) { + var isRotated = (this.labelRotation > 0); + var innerWidth = this.width - (this.paddingLeft + this.paddingRight); + var valueWidth = innerWidth / Math.max((this.data.labels.length - ((this.options.gridLines.offsetGridLines) ? 0 : 1)), 1); + var valueOffset = (valueWidth * index) + this.paddingLeft; - var maxTicks = Math.min(11, Math.ceil(this.drawingArea / (2 * this.options.labels.fontSize))); + if (this.options.gridLines.offsetGridLines && includeOffset) { + valueOffset += (valueWidth / 2); + } - // Make sure we always have at least 2 ticks - maxTicks = Math.max(2, maxTicks); + return this.left + Math.round(valueOffset); + } else { + return this.top + (index * (this.height / this.data.labels.length)); + } + }, + getPointPixelForValue: function(value, index, datasetIndex) { + return this.getPixelForValue(value, index, datasetIndex, true); + }, - // To get a "nice" value for the tick spacing, we will use the appropriately named - // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks - // for details. + // Functions needed for bar charts + calculateBaseWidth: function() { + return (this.getPixelForValue(null, 1, 0, true) - this.getPixelForValue(null, 0, 0, true)) - (2 * this.options.categorySpacing); + }, + 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) * this.options.spacing); - // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, - // do nothing since that would make the chart weird. If the user really wants a weird chart - // axis, they can manually override it - if (this.options.beginAtZero) { - var minSign = helpers.sign(this.min); - var maxSign = helpers.sign(this.max); + if (this.options.stacked) { + return baseWidth; + } + return (baseWidth / datasetCount); + }, + calculateBarX: function(datasetCount, datasetIndex, elementIndex) { + var xWidth = this.calculateBaseWidth(), + xAbsolute = this.getPixelForValue(null, elementIndex, datasetIndex, true) - (xWidth / 2), + barWidth = this.calculateBarWidth(datasetCount); - if (minSign < 0 && maxSign < 0) { - // move the top up to 0 - this.max = 0; - } else if (minSign > 0 && maxSign > 0) { - // move the botttom down to 0 - this.min = 0; - } - } + if (this.options.stacked) { + return xAbsolute + barWidth / 2; + } - var niceRange = helpers.niceNum(this.max - this.min, false); - var spacing = helpers.niceNum(niceRange / (maxTicks - 1), true); - var niceMin = Math.floor(this.min / spacing) * spacing; - var niceMax = Math.ceil(this.max / spacing) * spacing; + return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * this.options.spacing) + barWidth / 2; + }, - // Put the values into the ticks array - for (var j = niceMin; j <= niceMax; j += spacing) { - this.ticks.push(j); - } - } + calculateLabelRotation: function(maxHeight, margins) { + //Get the width of each grid by calculating the difference + //between x offsets between 0 and 1. + var labelFont = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily); + this.ctx.font = labelFont; - if (this.options.position == "left" || this.options.position == "right") { - // We are in a vertical orientation. The top value is the highest. So reverse the array - this.ticks.reverse(); - } + var firstWidth = this.ctx.measureText(this.data.labels[0]).width; + var lastWidth = this.ctx.measureText(this.data.labels[this.data.labels.length - 1]).width; + var firstRotated; + var lastRotated; - // At this point, we need to update our max and min given the tick values since we have expanded the - // range of the scale - this.max = helpers.max(this.ticks); - this.min = helpers.min(this.ticks); - }, - buildYLabels: function() { - this.yLabels = []; + this.paddingRight = lastWidth / 2 + 3; + this.paddingLeft = firstWidth / 2 + 3; - helpers.each(this.ticks, function(tick, index, ticks) { - var label; + this.labelRotation = 0; - if (this.options.labels.userCallback) { - // If the user provided a callback for label generation, use that as first priority - label = this.options.labels.userCallback(tick, index, ticks); - } else if (this.options.labels.template) { - // else fall back to the template string - label = helpers.template(this.options.labels.template, { - value: tick - }); - } + if (this.options.display) { + var originalLabelWidth = helpers.longestText(this.ctx, labelFont, this.data.labels); + var cosRotation; + var sinRotation; + var firstRotatedWidth; - this.yLabels.push(label ? label : ""); - }, this); - }, - 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 - */ + this.labelWidth = originalLabelWidth; + + //Allow 3 pixels x2 padding either side for label readability + // only the index matters for a dataset scale, but we want a consistent interface between scales + var gridWidth = Math.floor(this.getPixelForValue(0, 1) - this.getPixelForValue(0, 0)) - 6; + + //Max label rotate should be 90 - also act as a loop counter + while ((this.labelWidth > gridWidth && this.labelRotation === 0) || (this.labelWidth > gridWidth && this.labelRotation <= 90 && this.labelRotation > 0)) { + cosRotation = Math.cos(helpers.toRadians(this.labelRotation)); + sinRotation = Math.sin(helpers.toRadians(this.labelRotation)); + + firstRotated = cosRotation * firstWidth; + lastRotated = cosRotation * lastWidth; + + // We're right aligning the text now. + if (firstRotated + this.options.labels.fontSize / 2 > this.yLabelWidth) { + this.paddingLeft = firstRotated + this.options.labels.fontSize / 2; + } + + this.paddingRight = this.options.labels.fontSize / 2; + + if (sinRotation * originalLabelWidth > maxHeight) { + // go back one step + this.labelRotation--; + break; + } + + this.labelRotation++; + this.labelWidth = cosRotation * originalLabelWidth; + + } + } else { + this.labelWidth = 0; + this.paddingRight = 0; + this.paddingLeft = 0; + } + + if (margins) { + this.paddingLeft -= margins.left; + this.paddingRight -= margins.right; + + this.paddingLeft = Math.max(this.paddingLeft, 0); + this.paddingRight = Math.max(this.paddingRight, 0); + } + + }, + // Fit this axis to the given size + // @param {number} maxWidth : the max width the axis can be + // @param {number} maxHeight: the max height the axis can be + // @return {object} minSize : the minimum size needed to draw the axis + fit: function(maxWidth, maxHeight, margins) { + this.calculateLabelRotation(maxHeight, margins); + + var minSize = { + width: 0, + height: 0, + }; + + var labelFont = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily); + var longestLabelWidth = helpers.longestText(this.ctx, labelFont, this.data.labels); + + // Width + if (this.isHorizontal()) { + minSize.width = maxWidth; + this.width = maxWidth; + } else if (this.options.display) { + minSize.width = Math.min(longestLabelWidth + 6, maxWidth); + } + + // Height + if (this.isHorizontal() && this.options.display) { + var labelHeight = (Math.cos(helpers.toRadians(this.labelRotation)) * longestLabelWidth) + 1.5 * this.options.labels.fontSize; + minSize.height = Math.min(labelHeight, maxHeight); + } else if (this.options.display) { + minSize.width = Math.min(longestLabelWidth + 6, maxWidth); + } + + this.width = minSize.width; + this.height = minSize.height; + return minSize; + }, + // Actualy draw the scale on the canvas + // @param {rectangle} chartArea : the area of the chart to draw full grid lines on + draw: function(chartArea) { + if (this.options.display) { + + var setContextLineSettings; + + // Make sure we draw text in the correct color + this.ctx.fillStyle = this.options.labels.fontColor; + + if (this.isHorizontal()) { + setContextLineSettings = true; + var yTickStart = this.options.position == "bottom" ? this.top : this.bottom - 10; + var yTickEnd = this.options.position == "bottom" ? this.top + 10 : this.bottom; + var isRotated = this.labelRotation !== 0; + + helpers.each(this.data.labels, function(label, index) { + var xLineValue = this.getPixelForValue(label, index, null, false); // xvalues for grid lines + var xLabelValue = this.getPixelForValue(label, index, null, true); // x values for labels (need to consider offsetLabel option) + + if (this.options.gridLines.show) { + if (index === 0) { + // Draw the first index specially + this.ctx.lineWidth = this.options.gridLines.zeroLineWidth; + this.ctx.strokeStyle = this.options.gridLines.zeroLineColor; + setContextLineSettings = true; // reset next time + } else if (setContextLineSettings) { + this.ctx.lineWidth = this.options.gridLines.lineWidth; + this.ctx.strokeStyle = this.options.gridLines.color; + setContextLineSettings = false; + } + + xLineValue += helpers.aliasPixel(this.ctx.lineWidth); + + // Draw the label area + this.ctx.beginPath(); + + if (this.options.gridLines.drawTicks) { + this.ctx.moveTo(xLineValue, yTickStart); + this.ctx.lineTo(xLineValue, yTickEnd); + } + + // Draw the chart area + if (this.options.gridLines.drawOnChartArea) { + this.ctx.moveTo(xLineValue, chartArea.top); + this.ctx.lineTo(xLineValue, chartArea.bottom); + } + + // Need to stroke in the loop because we are potentially changing line widths & colours + this.ctx.stroke(); + } + + if (this.options.labels.show) { + this.ctx.save(); + this.ctx.translate(xLabelValue, (isRotated) ? this.top + 12 : this.top + 8); + this.ctx.rotate(helpers.toRadians(this.labelRotation) * -1); + this.ctx.font = this.font; + this.ctx.textAlign = (isRotated) ? "right" : "center"; + this.ctx.textBaseline = (isRotated) ? "middle" : "top"; + this.ctx.fillText(label, 0, 0); + this.ctx.restore(); + } + }, this); + } else { + // Vertical + if (this.options.gridLines.show) {} + + if (this.options.labels.show) { + // Draw the labels + } + } + } + } + }); + Chart.scaleService.registerScaleType("category", DatasetScale, defaultConfig); +}).call(this); + +(function() { + "use strict"; + + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; + + var defaultConfig = { + display: true, + position: "left", + id: "y-axis-1", + + // grid line settings + gridLines: { + show: true, + color: "rgba(0, 0, 0, 0.1)", + lineWidth: 1, + drawOnChartArea: true, + drawTicks: true, // draw ticks extending towards the label + zeroLineWidth: 1, + zeroLineColor: "rgba(0,0,0,0.25)", + }, + + // scale numbers + beginAtZero: false, + override: null, + + // label settings + labels: { + show: true, + template: "<%=value.toLocaleString()%>", + fontSize: 12, + fontStyle: "normal", + fontColor: "#666", + fontFamily: "Helvetica Neue", + } + }; + + var LinearScale = Chart.Element.extend({ + isHorizontal: function() { + return this.options.position == "top" || this.options.position == "bottom"; + }, + generateTicks: function(width, height) { + // We need to decide how many ticks we are going to have. Each tick draws a grid line. + // There are two possibilities. The first is that the user has manually overridden the scale + // calculations in which case the job is easy. The other case is that we have to do it ourselves + // + // We assume at this point that the scale object has been updated with the following values + // by the chart. + // min: this is the minimum value of the scale + // max: this is the maximum value of the scale + // options: contains the options for the scale. This is referenced from the user settings + // rather than being cloned. This ensures that updates always propogate to a redraw + + // Reset the ticks array. Later on, we will draw a grid line at these positions + // The array simply contains the numerical value of the spots where ticks will be + this.ticks = []; + + if (this.options.override) { + // The user has specified the manual override. We use <= instead of < so that + // we get the final line + for (var i = 0; i <= this.options.override.steps; ++i) { + var value = this.options.override.start + (i * this.options.override.stepWidth); + ticks.push(value); + } + } else { + // Figure out what the max number of ticks we can support it is based on the size of + // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph + + var maxTicks; + + if (this.isHorizontal()) { + maxTicks = Math.min(11, Math.ceil(width / 50)); + } else { + // The factor of 2 used to scale the font size has been experimentally determined. + maxTicks = Math.min(11, Math.ceil(height / (2 * this.options.labels.fontSize))); + } + + // Make sure we always have at least 2 ticks + maxTicks = Math.max(2, maxTicks); + + // To get a "nice" value for the tick spacing, we will use the appropriately named + // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks + // for details. + + // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, + // do nothing since that would make the chart weird. If the user really wants a weird chart + // axis, they can manually override it + if (this.options.beginAtZero) { + var minSign = helpers.sign(this.min); + var maxSign = helpers.sign(this.max); + + if (minSign < 0 && maxSign < 0) { + // move the top up to 0 + this.max = 0; + } else if (minSign > 0 && maxSign > 0) { + // move the botttom down to 0 + this.min = 0; + } + } + + var niceRange = helpers.niceNum(this.max - this.min, false); + var spacing = helpers.niceNum(niceRange / (maxTicks - 1), true); + var niceMin = Math.floor(this.min / spacing) * spacing; + var niceMax = Math.ceil(this.max / spacing) * spacing; + + // Put the values into the ticks array + for (var j = niceMin; j <= niceMax; j += spacing) { + this.ticks.push(j); + } + } + + if (this.options.position == "left" || this.options.position == "right") { + // We are in a vertical orientation. The top value is the highest. So reverse the array + this.ticks.reverse(); + } + + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + this.max = helpers.max(this.ticks); + this.min = helpers.min(this.ticks); + }, + buildLabels: function() { + // We assume that this has been run after ticks have been generated. We try to figure out + // a label for each tick. + this.labels = []; + + helpers.each(this.ticks, function(tick, index, ticks) { + var label; + + if (this.options.labels.userCallback) { + // If the user provided a callback for label generation, use that as first priority + label = this.options.labels.userCallback(tick, index, ticks); + } else if (this.options.labels.template) { + // else fall back to the template string + label = helpers.template(this.options.labels.template, { + value: tick + }); + } + + this.labels.push(label ? label : ""); // empty string will not render so we're good + }, this); + }, + // Get the correct value. If the value type is object get the x or y based on whether we are horizontal or not + getRightValue: function(rawValue) { + return typeof rawValue === "object" ? (this.isHorizontal() ? rawValue.x : rawValue.y) : rawValue; + }, + getPixelForValue: function(value) { + // This must be called after fit has been run so that + // this.left, this.top, this.right, and this.bottom have been defined + var pixel; + var range = this.max - this.min; + + if (this.isHorizontal()) { + pixel = this.left + (this.width / range * (value - this.min)); + } else { + // Bottom - top since pixels increase downard on a screen + pixel = this.bottom - (this.height / range * (value - this.min)); + } + + return pixel; + }, + + // Functions needed for line charts + calculateRange: function() { + this.min = null; + this.max = null; + + var positiveValues = []; + var negativeValues = []; + + if (this.options.stacked) { + helpers.each(this.data.datasets, function(dataset) { + if (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id) { + helpers.each(dataset.data, function(rawValue, index) { + + var value = this.getRightValue(rawValue); + + positiveValues[index] = positiveValues[index] || 0; + negativeValues[index] = negativeValues[index] || 0; + + if (this.options.relativePoints) { + positiveValues[index] = 100; + } else { + if (value < 0) { + negativeValues[index] += value; + } else { + positiveValues[index] += value; + } + } + }, this); + } + }, this); + + var values = positiveValues.concat(negativeValues); + this.min = helpers.min(values); + this.max = helpers.max(values); + + } else { + helpers.each(this.data.datasets, function(dataset) { + if (this.isHorizontal() ? dataset.xAxisID === this.id : dataset.yAxisID === this.id) { + helpers.each(dataset.data, function(rawValue, index) { + var value = this.getRightValue(rawValue); + + if (this.min === null) { + this.min = value; + } else if (value < this.min) { + this.min = value; + } + + if (this.max === null) { + this.max = value; + } else if (value > this.max) { + this.max = value; + } + }, this); + } + }, this); + } + }, + + getPointPixelForValue: function(rawValue, index, datasetIndex) { + var value = this.getRightValue(rawValue); + + if (this.options.stacked) { + var offsetPos = 0; + var offsetNeg = 0; + + for (var i = 0; i < datasetIndex; ++i) { + if (this.data.datasets[i].data[index] < 0) { + offsetNeg += this.data.datasets[i].data[index]; + } else { + offsetPos += this.data.datasets[i].data[index]; + } + } + + if (value < 0) { + return this.getPixelForValue(offsetNeg + value); + } else { + return this.getPixelForValue(offsetPos + value); + } + } else { + return this.getPixelForValue(value); + } + }, + + // Functions needed for bar charts + calculateBarBase: function(datasetIndex, index) { + var base = 0; + + if (this.options.stacked) { + + var value = this.data.datasets[datasetIndex].data[index]; + + if (value < 0) { + for (var i = 0; i < datasetIndex; i++) { + if (this.data.datasets[i].yAxisID === this.id) { + base += this.data.datasets[i].data[index] < 0 ? this.data.datasets[i].data[index] : 0; + } + } + } else { + for (var j = 0; j < datasetIndex; j++) { + if (this.data.datasets[j].yAxisID === this.id) { + base += this.data.datasets[j].data[index] > 0 ? this.data.datasets[j].data[index] : 0; + } + } + } + + return this.getPixelForValue(base); + } + + base = this.getPixelForValue(this.min); + + if (this.beginAtZero || ((this.min <= 0 && this.max >= 0) || (this.min >= 0 && this.max <= 0))) { + base = this.getPixelForValue(0); + base += this.options.gridLines.lineWidth; + } else if (this.min < 0 && this.max < 0) { + // All values are negative. Use the top as the base + base = this.getPixelForValue(this.max); + } + + return base; + + }, + calculateBarY: function(datasetIndex, index) { + var value = this.data.datasets[datasetIndex].data[index]; + + if (this.options.stacked) { + + var sumPos = 0, + sumNeg = 0; + + for (var i = 0; i < datasetIndex; i++) { + if (this.data.datasets[i].data[index] < 0) { + sumNeg += this.data.datasets[i].data[index] || 0; + } else { + sumPos += this.data.datasets[i].data[index] || 0; + } + } + + if (value < 0) { + return this.getPixelForValue(sumNeg + value); + } else { + return this.getPixelForValue(sumPos + value); + } + + return this.getPixelForValue(value); + } + + var offset = 0; + + for (var j = datasetIndex; j < this.data.datasets.length; j++) { + if (j === datasetIndex && value) { + offset += value; + } else { + offset = offset + value; + } + } + + return this.getPixelForValue(value); + }, + + // Fit this axis to the given size + // @param {number} maxWidth : the max width the axis can be + // @param {number} maxHeight: the max height the axis can be + // @return {object} minSize : the minimum size needed to draw the axis + fit: function(maxWidth, maxHeight) { + this.calculateRange(); + this.generateTicks(maxWidth, maxHeight); + this.buildLabels(); + + var minSize = { + width: 0, + height: 0, + }; + + // In a horizontal axis, we need some room for the scale to be drawn + // + // ----------------------------------------------------- + // | | | | | + // + // In a vertical axis, we need some room for the scale to be drawn. + // The actual grid lines will be drawn on the chart area, however, we need to show + // ticks where the axis actually is. + // We will allocate 25px for this width + // | + // -| + // | + // | + // -| + // | + // | + // -| - // 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 = helpers.min([(this.height / 2 - this.options.pointLabels.fontSize - 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 = helpers.fontString(this.options.pointLabels.fontSize, this.options.pointLabels.fontStyle, this.options.pointLabels.fontFamily); - 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(helpers.template(this.options.labels.template, { - 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; - } - } - } + // Width + if (this.isHorizontal()) { + minSize.width = maxWidth; // fill all the width + } else { + minSize.width = this.options.gridLines.show && this.options.display ? 10 : 0; + } - xProtrusionLeft = furthestLeft; + // height + if (this.isHorizontal()) { + minSize.height = this.options.gridLines.show && this.options.display ? 10 : 0; + } else { + minSize.height = maxHeight; // fill all the height + } - xProtrusionRight = Math.ceil(furthestRight - this.width); - furthestRightAngle = this.getIndexAngle(furthestRightIndex); - furthestLeftAngle = this.getIndexAngle(furthestLeftIndex); + if (this.options.labels.show && this.options.display) { + // Don't bother fitting the labels if we are not showing them + var labelFont = helpers.fontString(this.options.labels.fontSize, + this.options.labels.fontStyle, this.options.labels.fontFamily); - radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2); + if (this.isHorizontal()) { + // A horizontal axis is more constrained by the height. + var maxLabelHeight = maxHeight - minSize.height; + var labelHeight = 1.5 * this.options.labels.fontSize; + minSize.height = Math.min(maxHeight, minSize.height + labelHeight); + } else { + // A vertical axis is more constrained by the width. Labels are the dominant factor + // here, so get that length first + var maxLabelWidth = maxWidth - minSize.width; + var largestTextWidth = helpers.longestText(this.ctx, labelFont, this.labels); - radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2); + if (largestTextWidth < maxLabelWidth) { + // We don't need all the room + minSize.width += largestTextWidth; + } else { + // Expand to max size + minSize.width = maxWidth; + } + } + } - // Ensure we actually need to reduce the size of the chart - radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0; - radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0; + this.width = minSize.width; + this.height = minSize.height; + return minSize; + }, + // Actualy draw the scale on the canvas + // @param {rectangle} chartArea : the area of the chart to draw full grid lines on + draw: function(chartArea) { + if (this.options.display) { - this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2; + var setContextLineSettings; + var hasZero; - //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2]) - this.setCenterPoint(radiusReductionLeft, radiusReductionRight); + // Make sure we draw text in the correct color + this.ctx.fillStyle = this.options.labels.fontColor; - }, - setCenterPoint: function(leftMovement, rightMovement) { + if (this.isHorizontal()) { + if (this.options.gridLines.show) { + // Draw the horizontal line + setContextLineSettings = true; + hasZero = helpers.findNextWhere(this.ticks, function(tick) { + return tick === 0; + }) !== undefined; + var yTickStart = this.options.position == "bottom" ? this.top : this.bottom - 5; + var yTickEnd = this.options.position == "bottom" ? this.top + 5 : this.bottom; - var maxRight = this.width - rightMovement - this.drawingArea, - maxLeft = leftMovement + this.drawingArea; + helpers.each(this.ticks, function(tick, index) { + // Grid lines are vertical + var xValue = this.getPixelForValue(tick); - this.xCenter = (maxLeft + maxRight) / 2; - // Always vertically in the centre as the text height doesn't change - this.yCenter = (this.height / 2); - }, + if (tick === 0 || (!hasZero && index === 0)) { + // Draw the 0 point specially or the left if there is no 0 + this.ctx.lineWidth = this.options.gridLines.zeroLineWidth; + this.ctx.strokeStyle = this.options.gridLines.zeroLineColor; + setContextLineSettings = true; // reset next time + } else if (setContextLineSettings) { + this.ctx.lineWidth = this.options.gridLines.lineWidth; + this.ctx.strokeStyle = this.options.gridLines.color; + setContextLineSettings = false; + } - getIndexAngle: function(index) { - var angleMultiplier = (Math.PI * 2) / this.valuesCount; - // Start from the top instead of right, so remove a quarter of the circle + xValue += helpers.aliasPixel(this.ctx.lineWidth); - 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.options.display) { - var ctx = this.ctx; - helpers.each(this.yLabels, function(label, index) { - // Don't draw a centre value - if (index > 0) { - var yCenterOffset = index * (this.drawingArea / Math.max(this.ticks.length, 1)), - yHeight = this.yCenter - yCenterOffset, - pointPosition; + // Draw the label area + this.ctx.beginPath(); - // Draw circular lines around the scale - if (this.options.gridLines.show) { - ctx.strokeStyle = this.options.gridLines.color; - ctx.lineWidth = this.options.gridLines.lineWidth; + if (this.options.gridLines.drawTicks) { + this.ctx.moveTo(xValue, yTickStart); + this.ctx.lineTo(xValue, yTickEnd); + } - if (this.options.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.ticks[index])); - if (i === 0) { - ctx.moveTo(pointPosition.x, pointPosition.y); - } else { - ctx.lineTo(pointPosition.x, pointPosition.y); - } - } - ctx.closePath(); - ctx.stroke(); - } - } + // Draw the chart area + if (this.options.gridLines.drawOnChartArea) { + this.ctx.moveTo(xValue, chartArea.top); + this.ctx.lineTo(xValue, chartArea.bottom); + } - if (this.options.labels.show) { - ctx.font = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily); + // Need to stroke in the loop because we are potentially changing line widths & colours + this.ctx.stroke(); + }, this); + } - if (this.showLabelBackdrop) { - var labelWidth = ctx.measureText(label).width; - ctx.fillStyle = this.options.labels.backdropColor; - ctx.fillRect( - this.xCenter - labelWidth / 2 - this.options.labels.backdropPaddingX, - yHeight - this.fontSize / 2 - this.options.labels.backdropPaddingY, - labelWidth + this.options.labels.backdropPaddingX * 2, - this.options.labels.fontSize + this.options.lables.backdropPaddingY * 2 - ); - } + if (this.options.labels.show) { + // Draw the labels - ctx.textAlign = 'center'; - ctx.textBaseline = "middle"; - ctx.fillStyle = this.options.labels.fontColor; - ctx.fillText(label, this.xCenter, yHeight); - } - } - }, this); + var labelStartY; - if (!this.options.lineArc) { - ctx.lineWidth = this.options.angleLines.lineWidth; - ctx.strokeStyle = this.options.angleLines.color; + if (this.options.position == "top") { + labelStartY = this.bottom - 10; + this.ctx.textBaseline = "bottom"; + } else { + // bottom side + labelStartY = this.top + 10; + this.ctx.textBaseline = "top"; + } - for (var i = this.valuesCount - 1; i >= 0; i--) { - if (this.options.angleLines.show) { - 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 = helpers.fontString(this.options.pointLabels.fontSize, this.options.pointLabels.fontStyle, this.options.pointLabels.fontFamily); - ctx.fillStyle = this.options.pointLabels.fontColor; + this.ctx.textAlign = "center"; + this.ctx.font = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily); - 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'; - } + helpers.each(this.labels, function(label, index) { + var xValue = this.getPixelForValue(this.ticks[index]); + this.ctx.fillText(label, xValue, labelStartY); + }, this); + } + } else { + // Vertical + if (this.options.gridLines.show) { - // Set the correct text baseline based on outer positioning - if (exactQuarter) { - ctx.textBaseline = 'middle'; - } else if (upperHalf) { - ctx.textBaseline = 'bottom'; - } else { - ctx.textBaseline = 'top'; - } + // Draw the vertical line + setContextLineSettings = true; + hasZero = helpers.findNextWhere(this.ticks, function(tick) { + return tick === 0; + }) !== undefined; + var xTickStart = this.options.position == "right" ? this.left : this.right - 5; + var xTickEnd = this.options.position == "right" ? this.left + 5 : this.right; - ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y); - } - } - } - } - }); - Chart.scales.registerScaleType("radialLinear", LinearRadialScale); + helpers.each(this.ticks, function(tick, index) { + // Grid lines are horizontal + var yValue = this.getPixelForValue(tick); + + if (tick === 0 || (!hasZero && index === 0)) { + // Draw the 0 point specially or the bottom if there is no 0 + this.ctx.lineWidth = this.options.gridLines.zeroLineWidth; + this.ctx.strokeStyle = this.options.gridLines.zeroLineColor; + setContextLineSettings = true; // reset next time + } else if (setContextLineSettings) { + this.ctx.lineWidth = this.options.gridLines.lineWidth; + this.ctx.strokeStyle = this.options.gridLines.color; + setContextLineSettings = false; // use boolean to indicate that we only want to do this once + } + + yValue += helpers.aliasPixel(this.ctx.lineWidth); + + // Draw the label area + this.ctx.beginPath(); + + if (this.options.gridLines.drawTicks) { + this.ctx.moveTo(xTickStart, yValue); + this.ctx.lineTo(xTickEnd, yValue); + } + + // Draw the chart area + if (this.options.gridLines.drawOnChartArea) { + this.ctx.moveTo(chartArea.left, yValue); + this.ctx.lineTo(chartArea.right, yValue); + } + + this.ctx.stroke(); + }, this); + } + + if (this.options.labels.show) { + // Draw the labels + + var labelStartX; + + if (this.options.position == "left") { + labelStartX = this.right - 10; + this.ctx.textAlign = "right"; + } else { + // right side + labelStartX = this.left + 5; + this.ctx.textAlign = "left"; + } + + this.ctx.textBaseline = "middle"; + this.ctx.font = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily); + + helpers.each(this.labels, function(label, index) { + var yValue = this.getPixelForValue(this.ticks[index]); + this.ctx.fillText(label, labelStartX, yValue); + }, this); + } + } + } + } + }); + Chart.scaleService.registerScaleType("linear", LinearScale, defaultConfig); + +}).call(this); + +(function() { + "use strict"; + + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; + + var defaultConfig = { + display: true, + + //Boolean - Whether to animate scaling the chart from the centre + animate: false, + + lineArc: false, + + // grid line settings + gridLines: { + show: true, + color: "rgba(0, 0, 0, 0.1)", + lineWidth: 1, + }, + + angleLines: { + show: true, + color: "rgba(0,0,0, 0.1)", + lineWidth: 1 + }, + + // scale numbers + beginAtZero: true, + + // label settings + labels: { + show: true, + template: "<%=value.toLocaleString()%>", + fontSize: 12, + fontStyle: "normal", + fontColor: "#666", + fontFamily: "Helvetica Neue", + + //Boolean - Show a backdrop to the scale label + showLabelBackdrop: true, + + //String - The colour of the label backdrop + backdropColor: "rgba(255,255,255,0.75)", + + //Number - The backdrop padding above & below the label in pixels + backdropPaddingY: 2, + + //Number - The backdrop padding to the side of the label in pixels + backdropPaddingX: 2, + }, + + pointLabels: { + //String - Point label font declaration + fontFamily: "'Arial'", + + //String - Point label font weight + fontStyle: "normal", + + //Number - Point label font size in pixels + fontSize: 10, + + //String - Point label font colour + fontColor: "#666", + }, + }; + + var LinearRadialScale = Chart.Element.extend({ + initialize: function() { + this.size = helpers.min([this.height, this.width]); + this.drawingArea = (this.options.display) ? (this.size / 2) - (this.options.labels.fontSize / 2 + this.options.labels.backdropPaddingY) : (this.size / 2); + }, + update: function() { + if (!this.options.lineArc) { + this.setScaleSize(); + } else { + this.drawingArea = (this.options.display) ? (this.size / 2) - (this.fontSize / 2 + this.backdropPaddingY) : (this.size / 2); + } + + this.buildYLabels(); + }, + calculateRange: function() { + this.min = null; + this.max = null; + + helpers.each(this.data.datasets, function(dataset) { + helpers.each(dataset.data, function(value, index) { + if (this.min === null) { + this.min = value; + } else if (value < this.min) { + this.min = value; + } + + if (this.max === null) { + this.max = value; + } else if (value > this.max) { + this.max = value; + } + }, this); + }, this); + }, + generateTicks: function() { + // We need to decide how many ticks we are going to have. Each tick draws a grid line. + // There are two possibilities. The first is that the user has manually overridden the scale + // calculations in which case the job is easy. The other case is that we have to do it ourselves + // + // We assume at this point that the scale object has been updated with the following values + // by the chart. + // min: this is the minimum value of the scale + // max: this is the maximum value of the scale + // options: contains the options for the scale. This is referenced from the user settings + // rather than being cloned. This ensures that updates always propogate to a redraw + + // Reset the ticks array. Later on, we will draw a grid line at these positions + // The array simply contains the numerical value of the spots where ticks will be + this.ticks = []; + + if (this.options.override) { + // The user has specified the manual override. We use <= instead of < so that + // we get the final line + for (var i = 0; i <= this.options.override.steps; ++i) { + var value = this.options.override.start + (i * this.options.override.stepWidth); + ticks.push(value); + } + } else { + // Figure out what the max number of ticks we can support it is based on the size of + // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph + + var maxTicks = Math.min(11, Math.ceil(this.drawingArea / (2 * this.options.labels.fontSize))); + + // Make sure we always have at least 2 ticks + maxTicks = Math.max(2, maxTicks); + + // To get a "nice" value for the tick spacing, we will use the appropriately named + // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks + // for details. + + // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, + // do nothing since that would make the chart weird. If the user really wants a weird chart + // axis, they can manually override it + if (this.options.beginAtZero) { + var minSign = helpers.sign(this.min); + var maxSign = helpers.sign(this.max); + + if (minSign < 0 && maxSign < 0) { + // move the top up to 0 + this.max = 0; + } else if (minSign > 0 && maxSign > 0) { + // move the botttom down to 0 + this.min = 0; + } + } + + var niceRange = helpers.niceNum(this.max - this.min, false); + var spacing = helpers.niceNum(niceRange / (maxTicks - 1), true); + var niceMin = Math.floor(this.min / spacing) * spacing; + var niceMax = Math.ceil(this.max / spacing) * spacing; + + // Put the values into the ticks array + for (var j = niceMin; j <= niceMax; j += spacing) { + this.ticks.push(j); + } + } + + if (this.options.position == "left" || this.options.position == "right") { + // We are in a vertical orientation. The top value is the highest. So reverse the array + this.ticks.reverse(); + } + + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + this.max = helpers.max(this.ticks); + this.min = helpers.min(this.ticks); + }, + buildYLabels: function() { + this.yLabels = []; + + helpers.each(this.ticks, function(tick, index, ticks) { + var label; + + if (this.options.labels.userCallback) { + // If the user provided a callback for label generation, use that as first priority + label = this.options.labels.userCallback(tick, index, ticks); + } else if (this.options.labels.template) { + // else fall back to the template string + label = helpers.template(this.options.labels.template, { + value: tick + }); + } + + this.yLabels.push(label ? label : ""); + }, this); + }, + 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 = helpers.min([(this.height / 2 - this.options.pointLabels.fontSize - 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 = helpers.fontString(this.options.pointLabels.fontSize, this.options.pointLabels.fontStyle, this.options.pointLabels.fontFamily); + 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(helpers.template(this.options.labels.template, { + 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 = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0; + radiusReductionLeft = (helpers.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); + }, + getDistanceFromCenterForValue: 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; + }, + getPointPosition: function(index, distanceFromCenter) { + var thisAngle = this.getIndexAngle(index); + return { + x: (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter, + y: (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter + }; + }, + getPointPositionForValue: function(index, value) { + return this.getPointPosition(index, this.getDistanceFromCenterForValue(value)); + }, + draw: function() { + if (this.options.display) { + var ctx = this.ctx; + helpers.each(this.yLabels, function(label, index) { + // Don't draw a centre value + if (index > 0) { + var yCenterOffset = this.getDistanceFromCenterForValue(this.ticks[index]); + var yHeight = this.yCenter - yCenterOffset; + + // Draw circular lines around the scale + if (this.options.gridLines.show) { + ctx.strokeStyle = this.options.gridLines.color; + ctx.lineWidth = this.options.gridLines.lineWidth; + + if (this.options.lineArc) { + // Draw circular arcs between the points + ctx.beginPath(); + ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI * 2); + ctx.closePath(); + ctx.stroke(); + } else { + // Draw straight lines connecting each index + ctx.beginPath(); + for (var i = 0; i < this.valuesCount; i++) { + var pointPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(this.ticks[index])); + if (i === 0) { + ctx.moveTo(pointPosition.x, pointPosition.y); + } else { + ctx.lineTo(pointPosition.x, pointPosition.y); + } + } + ctx.closePath(); + ctx.stroke(); + } + } + + if (this.options.labels.show) { + ctx.font = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily); + + if (this.options.labels.showLabelBackdrop) { + var labelWidth = ctx.measureText(label).width; + ctx.fillStyle = this.options.labels.backdropColor; + ctx.fillRect( + this.xCenter - labelWidth / 2 - this.options.labels.backdropPaddingX, + yHeight - this.fontSize / 2 - this.options.labels.backdropPaddingY, + labelWidth + this.options.labels.backdropPaddingX * 2, + this.options.labels.fontSize + this.options.labels.backdropPaddingY * 2 + ); + } + + ctx.textAlign = 'center'; + ctx.textBaseline = "middle"; + ctx.fillStyle = this.options.labels.fontColor; + ctx.fillText(label, this.xCenter, yHeight); + } + } + }, this); + + if (!this.options.lineArc) { + ctx.lineWidth = this.options.angleLines.lineWidth; + ctx.strokeStyle = this.options.angleLines.color; + + for (var i = this.valuesCount - 1; i >= 0; i--) { + if (this.options.angleLines.show) { + var outerPosition = this.getPointPosition(i, this.getDistanceFromCenterForValue(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.getDistanceFromCenterForValue(this.max) + 5); + ctx.font = helpers.fontString(this.options.pointLabels.fontSize, this.options.pointLabels.fontStyle, this.options.pointLabels.fontFamily); + ctx.fillStyle = this.options.pointLabels.fontColor; + + 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.scaleService.registerScaleType("radialLinear", LinearRadialScale, defaultConfig); }).call(this); @@ -2922,83 +4356,83 @@ (function() { - "use strict"; + "use strict"; - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; - Chart.defaults.global.elements.arc = { - backgroundColor: Chart.defaults.global.defaultColor, - borderColor: "#fff", - borderWidth: 2 - }; + Chart.defaults.global.elements.arc = { + backgroundColor: Chart.defaults.global.defaultColor, + borderColor: "#fff", + borderWidth: 2 + }; - Chart.Arc = Chart.Element.extend({ - inGroupRange: function(mouseX) { - var vm = this._view; + Chart.Arc = Chart.Element.extend({ + inGroupRange: function(mouseX) { + var vm = this._view; - if (vm) { - return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); - } else { - return false; - } - }, - inRange: function(chartX, chartY) { + if (vm) { + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); + } else { + return false; + } + }, + inRange: function(chartX, chartY) { - var vm = this._view; + var vm = this._view; - var pointRelativePosition = helpers.getAngleFromPoint(vm, { - x: chartX, - y: chartY - }); + var pointRelativePosition = helpers.getAngleFromPoint(vm, { + x: chartX, + y: chartY + }); - // Put into the range of (-PI/2, 3PI/2] - var startAngle = vm.startAngle < (-0.5 * Math.PI) ? vm.startAngle + (2.0 * Math.PI) : vm.startAngle > (1.5 * Math.PI) ? vm.startAngle - (2.0 * Math.PI) : vm.startAngle; - var endAngle = vm.endAngle < (-0.5 * Math.PI) ? vm.endAngle + (2.0 * Math.PI) : vm.endAngle > (1.5 * Math.PI) ? vm.endAngle - (2.0 * Math.PI) : vm.endAngle + // Put into the range of (-PI/2, 3PI/2] + var startAngle = vm.startAngle < (-0.5 * Math.PI) ? vm.startAngle + (2.0 * Math.PI) : vm.startAngle > (1.5 * Math.PI) ? vm.startAngle - (2.0 * Math.PI) : vm.startAngle; + var endAngle = vm.endAngle < (-0.5 * Math.PI) ? vm.endAngle + (2.0 * Math.PI) : vm.endAngle > (1.5 * Math.PI) ? vm.endAngle - (2.0 * Math.PI) : vm.endAngle - //Check if within the range of the open/close angle - var betweenAngles = (pointRelativePosition.angle >= startAngle && pointRelativePosition.angle <= endAngle), - withinRadius = (pointRelativePosition.distance >= vm.innerRadius && pointRelativePosition.distance <= vm.outerRadius); + //Check if within the range of the open/close angle + var betweenAngles = (pointRelativePosition.angle >= startAngle && pointRelativePosition.angle <= endAngle), + withinRadius = (pointRelativePosition.distance >= vm.innerRadius && pointRelativePosition.distance <= vm.outerRadius); - return (betweenAngles && withinRadius); - //Ensure within the outside of the arc centre, but inside arc outer - }, - tooltipPosition: function() { - var vm = this._view; + return (betweenAngles && withinRadius); + //Ensure within the outside of the arc centre, but inside arc outer + }, + tooltipPosition: function() { + var vm = this._view; - var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2), - rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; - return { - x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), - y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) - }; - }, - draw: function() { + var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2), + rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; + return { + x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), + y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) + }; + }, + draw: function() { - var ctx = this._chart.ctx; - var vm = this._view; + var ctx = this._chart.ctx; + var vm = this._view; - ctx.beginPath(); + ctx.beginPath(); - ctx.arc(vm.x, vm.y, vm.outerRadius, vm.startAngle, vm.endAngle); + 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.arc(vm.x, vm.y, vm.innerRadius, vm.endAngle, vm.startAngle, true); - ctx.closePath(); - ctx.strokeStyle = vm.borderColor; - ctx.lineWidth = vm.borderWidth; + ctx.closePath(); + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = vm.borderWidth; - ctx.fillStyle = vm.backgroundColor; + ctx.fillStyle = vm.backgroundColor; - ctx.fill(); - ctx.lineJoin = 'bevel'; + ctx.fill(); + ctx.lineJoin = 'bevel'; - if (vm.borderWidth) { - ctx.stroke(); - } - } - }); + if (vm.borderWidth) { + ctx.stroke(); + } + } + }); }).call(this); @@ -3016,181 +4450,169 @@ (function() { - "use strict"; + "use strict"; - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; - Chart.defaults.global.elements.line = { - tension: 0.4, - backgroundColor: Chart.defaults.global.defaultColor, - borderWidth: 3, - borderColor: Chart.defaults.global.defaultColor, - fill: true, // do we fill in the area between the line and its base axis - skipNull: true, - drawNull: false, - }; + Chart.defaults.global.elements.line = { + tension: 0.4, + backgroundColor: Chart.defaults.global.defaultColor, + borderWidth: 3, + borderColor: Chart.defaults.global.defaultColor, + fill: true, // do we fill in the area between the line and its base axis + skipNull: true, + drawNull: false, + }; - Chart.Line = Chart.Element.extend({ - draw: function() { + Chart.elements.Line = Chart.Element.extend({ + draw: function() { - var vm = this._view; - var ctx = this._chart.ctx; - var first = this._children[0]; - var last = this._children[this._children.length - 1]; + var vm = this._view; + var ctx = this._chart.ctx; + var first = this._children[0]; + var last = this._children[this._children.length - 1]; - // Draw the background first (so the border is always on top) - helpers.each(this._children, function(point, index) { - var previous = this.previousPoint(point, this._children, index); - var next = this.nextPoint(point, this._children, index); + // Draw the background first (so the border is always on top) + helpers.each(this._children, function(point, index) { + var previous = helpers.previousItem(this._children, index); + var next = helpers.nextItem(this._children, index); - // First point only - if (index === 0) { - ctx.moveTo(point._view.x, point._view.y); - return; - } + // First point only + if (index === 0) { + ctx.moveTo(point._view.x, point._view.y); + return; + } - // Start Skip and drag along scale baseline - if (point._view.skip && vm.skipNull && !this._loop) { - ctx.lineTo(previous._view.x, point._view.y); - ctx.moveTo(next._view.x, point._view.y); - } - // End Skip Stright line from the base line - else if (previous._view.skip && vm.skipNull && !this._loop) { - ctx.moveTo(point._view.x, previous._view.y); - ctx.lineTo(point._view.x, point._view.y); - } + // Start Skip and drag along scale baseline + if (point._view.skip && vm.skipNull && !this._loop) { + ctx.lineTo(previous._view.x, point._view.y); + ctx.moveTo(next._view.x, point._view.y); + } + // End Skip Stright line from the base line + else if (previous._view.skip && vm.skipNull && !this._loop) { + ctx.moveTo(point._view.x, previous._view.y); + ctx.lineTo(point._view.x, point._view.y); + } - if (previous._view.skip && vm.skipNull) { - ctx.moveTo(point._view.x, point._view.y); - } - // Normal Bezier Curve - else { - if (vm.tension > 0) { - ctx.bezierCurveTo( - previous._view.controlPointNextX, - previous._view.controlPointNextY, - point._view.controlPointPreviousX, - point._view.controlPointPreviousY, - point._view.x, - point._view.y - ); - } else { - ctx.lineTo(point._view.x, point._view.y); - } - } - }, this); + if (previous._view.skip && vm.skipNull) { + ctx.moveTo(point._view.x, point._view.y); + } + // Normal Bezier Curve + else { + if (vm.tension > 0) { + ctx.bezierCurveTo( + previous._view.controlPointNextX, + previous._view.controlPointNextY, + point._view.controlPointPreviousX, + point._view.controlPointPreviousY, + point._view.x, + point._view.y + ); + } else { + ctx.lineTo(point._view.x, point._view.y); + } + } + }, this); - // For radial scales, loop back around to the first point - if (this._loop) { - if (vm.tension > 0 && !first._view.skip) { + // For radial scales, loop back around to the first point + if (this._loop) { + if (vm.tension > 0 && !first._view.skip) { - ctx.bezierCurveTo( - last._view.controlPointNextX, - last._view.controlPointNextY, - first._view.controlPointPreviousX, - first._view.controlPointPreviousY, - first._view.x, - first._view.y - ); - } else { - ctx.lineTo(first._view.x, first._view.y); - } - } + ctx.bezierCurveTo( + last._view.controlPointNextX, + last._view.controlPointNextY, + first._view.controlPointPreviousX, + first._view.controlPointPreviousY, + first._view.x, + first._view.y + ); + } else { + ctx.lineTo(first._view.x, first._view.y); + } + } - // If we had points and want to fill this line, do so. - if (this._children.length > 0 && vm.fill) { - //Round off the line by going to the base of the chart, back to the start, then fill. - ctx.lineTo(this._children[this._children.length - 1]._view.x, vm.scaleZero); - ctx.lineTo(this._children[0]._view.x, vm.scaleZero); - ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; - ctx.closePath(); - ctx.fill(); - } + // If we had points and want to fill this line, do so. + if (this._children.length > 0 && vm.fill) { + //Round off the line by going to the base of the chart, back to the start, then fill. + ctx.lineTo(this._children[this._children.length - 1]._view.x, vm.scaleZero); + ctx.lineTo(this._children[0]._view.x, vm.scaleZero); + ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; + ctx.closePath(); + ctx.fill(); + } - // Now draw the line between all the points with any borders - ctx.lineWidth = vm.borderWidth || Chart.defaults.global.defaultColor; - ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; - ctx.beginPath(); + // Now draw the line between all the points with any borders + ctx.lineWidth = vm.borderWidth || Chart.defaults.global.defaultColor; + ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; + ctx.beginPath(); - helpers.each(this._children, function(point, index) { - var previous = this.previousPoint(point, this._children, index); - var next = this.nextPoint(point, this._children, index); + helpers.each(this._children, function(point, index) { + var previous = helpers.previousItem(this._children, index); + var next = helpers.nextItem(this._children, index); - // First point only - if (index === 0) { - ctx.moveTo(point._view.x, point._view.y); - return; - } + // First point only + if (index === 0) { + ctx.moveTo(point._view.x, point._view.y); + return; + } - // Start Skip and drag along scale baseline - if (point._view.skip && vm.skipNull && !this._loop) { - ctx.moveTo(previous._view.x, point._view.y); - ctx.moveTo(next._view.x, point._view.y); - return; - } - // End Skip Stright line from the base line - if (previous._view.skip && vm.skipNull && !this._loop) { - ctx.moveTo(point._view.x, previous._view.y); - ctx.moveTo(point._view.x, point._view.y); - return; - } + // Start Skip and drag along scale baseline + if (point._view.skip && vm.skipNull && !this._loop) { + ctx.moveTo(previous._view.x, point._view.y); + ctx.moveTo(next._view.x, point._view.y); + return; + } + // End Skip Stright line from the base line + if (previous._view.skip && vm.skipNull && !this._loop) { + ctx.moveTo(point._view.x, previous._view.y); + ctx.moveTo(point._view.x, point._view.y); + return; + } - if (previous._view.skip && vm.skipNull) { - ctx.moveTo(point._view.x, point._view.y); - return; - } - // Normal Bezier Curve - if (vm.tension > 0) { - ctx.bezierCurveTo( - previous._view.controlPointNextX, - previous._view.controlPointNextY, - point._view.controlPointPreviousX, - point._view.controlPointPreviousY, - point._view.x, - point._view.y - ); - } else { - ctx.lineTo(point._view.x, point._view.y); - } - }, this); + if (previous._view.skip && vm.skipNull) { + ctx.moveTo(point._view.x, point._view.y); + return; + } + // Normal Bezier Curve + if (vm.tension > 0) { + ctx.bezierCurveTo( + previous._view.controlPointNextX, + previous._view.controlPointNextY, + point._view.controlPointPreviousX, + point._view.controlPointPreviousY, + point._view.x, + point._view.y + ); + } else { + ctx.lineTo(point._view.x, point._view.y); + } + }, this); - if (this._loop && !first._view.skip) { - if (vm.tension > 0) { + if (this._loop && !first._view.skip) { + if (vm.tension > 0) { - ctx.bezierCurveTo( - last._view.controlPointNextX, - last._view.controlPointNextY, - first._view.controlPointPreviousX, - first._view.controlPointPreviousY, - first._view.x, - first._view.y - ); - } else { - ctx.lineTo(first._view.x, first._view.y); - } - } + ctx.bezierCurveTo( + last._view.controlPointNextX, + last._view.controlPointNextY, + first._view.controlPointPreviousX, + first._view.controlPointPreviousY, + first._view.x, + first._view.y + ); + } else { + ctx.lineTo(first._view.x, first._view.y); + } + } - ctx.stroke(); + ctx.stroke(); - }, - nextPoint: function(point, collection, index) { - if (this.loop) { - return collection[index + 1] || collection[0]; - } - return collection[index + 1] || collection[collection.length - 1]; - }, - previousPoint: function(point, collection, index) { - if (this.loop) { - return collection[index - 1] || collection[collection.length - 1]; - } - return collection[index - 1] || collection[0]; - }, - }); + }, + }); }).call(this); @@ -3207,3251 +4629,237 @@ (function() { - "use strict"; - - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; - - Chart.defaults.global.elements.point = { - radius: 3, - backgroundColor: Chart.defaults.global.defaultColor, - borderWidth: 1, - borderColor: Chart.defaults.global.defaultColor, - // Hover - hitRadius: 1, - hoverRadius: 4, - hoverBorderWidth: 1, - }; - - - Chart.Point = Chart.Element.extend({ - inRange: function(mouseX, mouseY) { - var vm = this._view; - var hoverRange = vm.hitRadius + 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._view; - - if (vm) { - return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)); - } else { - return false; - } - }, - tooltipPosition: function() { - var vm = this._view; - return { - x: vm.x, - y: vm.y, - padding: vm.radius + vm.borderWidth - }; - }, - draw: function() { - - var vm = this._view; - var ctx = this._chart.ctx; - - - if (vm.skip) { - return; - } - - if (vm.radius > 0 || vm.borderWidth > 0) { - - ctx.beginPath(); - - ctx.arc(vm.x, vm.y, vm.radius || Chart.defaults.global.elements.point.radius, 0, Math.PI * 2); - ctx.closePath(); - - ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; - ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.point.borderWidth; - - ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; - - ctx.fill(); - ctx.stroke(); - } - } - }); - - -}).call(this); - -/*! - * Chart.js - * http://chartjs.org/ - * Version: 2.0.0-alpha - * - * Copyright 2015 Nick Downie - * Released under the MIT license - * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md - */ - - -(function() { - - "use strict"; - - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; - - Chart.defaults.global.elements.rectangle = { - backgroundColor: Chart.defaults.global.defaultColor, - borderWidth: 0, - borderColor: Chart.defaults.global.defaultColor, - }; - - Chart.Rectangle = Chart.Element.extend({ - draw: function() { - - var ctx = this._chart.ctx; - var vm = this._view; - - var 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._view; - return vm.base - vm.y; - }, - inRange: function(mouseX, mouseY) { - var vm = this._view; - 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._view; - return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2); - }, - tooltipPosition: function() { - var vm = this._view; - if (vm.y < vm.base) { - return { - x: vm.x, - y: vm.y - }; - } else { - return { - x: vm.x, - y: vm.base - }; - } - }, - }); - -}).call(this); - -(function() { - "use strict"; - - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; - - var defaultConfig = { - - stacked: false, - valueSpacing: 5, - datasetSpacing: 1, - - hover: { - mode: "label" - }, - - scales: { - xAxes: [{ - type: "category", // scatter should not use a dataset axis - display: true, - position: "bottom", - id: "x-axis-1", // need an ID so datasets can reference the scale - - // grid line settings - gridLines: { - show: true, - color: "rgba(0, 0, 0, 0.05)", - lineWidth: 1, - drawOnChartArea: true, - drawTicks: true, - zeroLineWidth: 1, - zeroLineColor: "rgba(0,0,0,0.25)", - offsetGridLines: true, - }, - - // label settings - labels: { - show: true, - template: "<%=value%>", - fontSize: 12, - fontStyle: "normal", - fontColor: "#666", - fontFamily: "Helvetica Neue", - }, - }], - yAxes: [{ - type: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance - display: true, - position: "left", - id: "y-axis-1", - - // grid line settings - gridLines: { - show: true, - color: "rgba(0, 0, 0, 0.05)", - lineWidth: 1, - drawOnChartArea: true, - drawTicks: true, // draw ticks extending towards the label - zeroLineWidth: 1, - zeroLineColor: "rgba(0,0,0,0.25)", - }, - - // scale numbers - beginAtZero: false, - override: null, - - // label settings - labels: { - show: true, - template: "<%=value%>", - fontSize: 12, - fontStyle: "normal", - fontColor: "#666", - fontFamily: "Helvetica Neue", - } - }], - }, - - }; - - - Chart.Type.extend({ - name: "Bar", - defaults: defaultConfig, - initialize: function() { - - var _this = this; - - // Events - helpers.bindEvents(this, this.options.events, this.events); - - //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 Chart.Rectangle({ - _chart: this.chart, - _datasetIndex: datasetIndex, - _index: index, - })); - }, this); - - // The bar chart only supports a single x axis because the x axis is always a dataset axis - dataset.xAxisID = this.options.scales.xAxes[0].id; - - if (!dataset.yAxisID) { - dataset.yAxisID = this.options.scales.yAxes[0].id; - } - }, this); - - // Build and fit the scale. Needs to happen after the axis IDs have been set - this.buildScale(); - - // 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); - - // Need to fit scales before we reset elements. - Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - - // So that we animate from the baseline - this.resetElements(); - - // Update the chart with the latest data. - this.update(); - }, - resetElements: function() { - // Update the points - this.eachElement(function(bar, index, dataset, datasetIndex) { - var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID]; - var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID]; - - var yScalePoint; - - if (yScale.min < 0 && yScale.max < 0) { - // all less than 0. use the top - yScalePoint = yScale.getPixelForValue(yScale.max); - } else if (yScale.min > 0 && yScale.max > 0) { - yScalePoint = yScale.getPixelForValue(yScale.min); - } else { - yScalePoint = yScale.getPixelForValue(0); - } - - helpers.extend(bar, { - // Utility - _chart: this.chart, - _xScale: xScale, - _yScale: yScale, - _datasetIndex: datasetIndex, - _index: index, - - // Desired view properties - _model: { - x: xScale.calculateBarX(this.data.datasets.length, datasetIndex, index), - y: yScalePoint, - - // Appearance - base: yScale.calculateBarBase(datasetIndex, index), - width: xScale.calculateBarWidth(this.data.datasets.length), - backgroundColor: bar.custom && bar.custom.backgroundColor ? bar.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].backgroundColor, index, this.options.elements.rectangle.backgroundColor), - borderColor: bar.custom && bar.custom.borderColor ? bar.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderColor, index, this.options.elements.rectangle.borderColor), - borderWidth: bar.custom && bar.custom.borderWidth ? bar.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderWidth, index, this.options.elements.rectangle.borderWidth), - - // Tooltip - label: this.data.labels[index], - datasetLabel: this.data.datasets[datasetIndex].label, - }, - }); - bar.pivot(); - }, this); - }, - update: function(animationDuration) { - // Update the scale sizes - Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - - // Update the points - this.eachElement(function(bar, index, dataset, datasetIndex) { - var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID]; - var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID]; - - helpers.extend(bar, { - // Utility - _chart: this.chart, - _xScale: xScale, - _yScale: yScale, - _datasetIndex: datasetIndex, - _index: index, - - // Desired view properties - _model: { - x: xScale.calculateBarX(this.data.datasets.length, datasetIndex, index), - y: yScale.calculateBarY(datasetIndex, index), - - // Appearance - base: yScale.calculateBarBase(datasetIndex, index), - width: xScale.calculateBarWidth(this.data.datasets.length), - backgroundColor: bar.custom && bar.custom.backgroundColor ? bar.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].backgroundColor, index, this.options.elements.rectangle.backgroundColor), - borderColor: bar.custom && bar.custom.borderColor ? bar.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderColor, index, this.options.elements.rectangle.borderColor), - borderWidth: bar.custom && bar.custom.borderWidth ? bar.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderWidth, index, this.options.elements.rectangle.borderWidth), - - // Tooltip - label: this.data.labels[index], - datasetLabel: this.data.datasets[datasetIndex].label, - }, - }); - bar.pivot(); - }, this); - - - this.render(animationDuration); - }, - buildScale: function(labels) { - var self = this; - - // Function to determine the range of all the - var calculateYRange = function() { - this.min = null; - this.max = null; - - var positiveValues = []; - var negativeValues = []; - - if (self.options.stacked) { - helpers.each(self.data.datasets, function(dataset) { - if (dataset.yAxisID === this.id) { - helpers.each(dataset.data, function(value, index) { - positiveValues[index] = positiveValues[index] || 0; - negativeValues[index] = negativeValues[index] || 0; - - if (self.options.relativePoints) { - positiveValues[index] = 100; - } else { - if (value < 0) { - negativeValues[index] += value; - } else { - positiveValues[index] += value; - } - } - }, this); - } - }, this); - - var values = positiveValues.concat(negativeValues); - this.min = helpers.min(values); - this.max = helpers.max(values); - - } else { - helpers.each(self.data.datasets, function(dataset) { - if (dataset.yAxisID === this.id) { - helpers.each(dataset.data, function(value, index) { - if (this.min === null) { - this.min = value; - } else if (value < this.min) { - this.min = value; - } - - if (this.max === null) { - this.max = value; - } else if (value > this.max) { - this.max = value; - } - }, this); - } - }, this); - } - }; - - // Map of scale ID to scale object so we can lookup later - this.scales = {}; - - // Build the x axis. The line chart only supports a single x axis - var ScaleClass = Chart.scales.getScaleConstructor(this.options.scales.xAxes[0].type); - var xScale = new ScaleClass({ - ctx: this.chart.ctx, - options: this.options.scales.xAxes[0], - id: this.options.scales.xAxes[0].id, - calculateRange: function() { - this.labels = self.data.labels; - this.min = 0; - this.max = this.labels.length; - }, - calculateBaseWidth: function() { - return (this.getPixelForValue(null, 1, true) - this.getPixelForValue(null, 0, true)) - (2 * self.options.valueSpacing); - }, - 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) * self.options.datasetSpacing); - - if (self.options.stacked) { - return baseWidth; - } - return (baseWidth / datasetCount); - }, - calculateBarX: function(datasetCount, datasetIndex, elementIndex) { - var xWidth = this.calculateBaseWidth(), - xAbsolute = this.getPixelForValue(null, elementIndex, true) - (xWidth / 2), - barWidth = this.calculateBarWidth(datasetCount); - - if (self.options.stacked) { - return xAbsolute + barWidth / 2; - } - - return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * self.options.datasetSpacing) + barWidth / 2; - }, - }); - this.scales[xScale.id] = xScale; - - // Build up all the y scales - helpers.each(this.options.scales.yAxes, function(yAxisOptions) { - var ScaleClass = Chart.scales.getScaleConstructor(yAxisOptions.type); - var scale = new ScaleClass({ - ctx: this.chart.ctx, - options: yAxisOptions, - calculateRange: calculateYRange, - calculateBarBase: function(datasetIndex, index) { - var base = 0; - - if (self.options.stacked) { - - var value = self.data.datasets[datasetIndex].data[index]; - - if (value < 0) { - for (var i = 0; i < datasetIndex; i++) { - if (self.data.datasets[i].yAxisID === this.id) { - base += self.data.datasets[i].data[index] < 0 ? self.data.datasets[i].data[index] : 0; - } - } - } else { - for (var j = 0; j < datasetIndex; j++) { - if (self.data.datasets[j].yAxisID === this.id) { - base += self.data.datasets[j].data[index] > 0 ? self.data.datasets[j].data[index] : 0; - } - } - } - - return this.getPixelForValue(base); - } - - base = this.getPixelForValue(this.min); - - if (this.beginAtZero || ((this.min <= 0 && this.max >= 0) || (this.min >= 0 && this.max <= 0))) { - base = this.getPixelForValue(0); - base += this.options.gridLines.lineWidth; - } else if (this.min < 0 && this.max < 0) { - // All values are negative. Use the top as the base - base = this.getPixelForValue(this.max); - } - - return base; - - }, - calculateBarY: function(datasetIndex, index) { - - var value = self.data.datasets[datasetIndex].data[index]; - - if (self.options.stacked) { - - var sumPos = 0, - sumNeg = 0; - - for (var i = 0; i < datasetIndex; i++) { - if (self.data.datasets[i].data[index] < 0) { - sumNeg += self.data.datasets[i].data[index] || 0; - } else { - sumPos += self.data.datasets[i].data[index] || 0; - } - } - - if (value < 0) { - return this.getPixelForValue(sumNeg + value); - } else { - return this.getPixelForValue(sumPos + value); - } - - return this.getPixelForValue(value); - } - - var offset = 0; - - for (var j = datasetIndex; j < self.data.datasets.length; j++) { - if (j === datasetIndex && value) { - offset += value; - } else { - offset = offset + value; - } - } - - return this.getPixelForValue(value); - }, - id: yAxisOptions.id, - }); - - this.scales[scale.id] = scale; - }, this); - }, - draw: function(ease) { - - var easingDecimal = ease || 1; - this.clear(); - - // Draw all the scales - helpers.each(this.scales, function(scale) { - scale.draw(this.chartArea); - }, this); - - //Draw all the bars for each dataset - this.eachElement(function(bar, index, datasetIndex) { - bar.transition(easingDecimal).draw(); - }, this); - - // Finally draw the tooltip - this.tooltip.transition(easingDecimal).draw(); - }, - events: function(e) { - - - // If exiting chart - if (e.type == 'mouseout') { - return this; - } - - this.lastActive = this.lastActive || []; - - // Find Active Elements - this.active = function() { - switch (this.options.hover.mode) { - 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.hover.onHover) { - this.options.hover.onHover.call(this, this.active); - } - - if (e.type == 'mouseup' || e.type == 'click') { - if (this.options.onClick) { - this.options.onClick.call(this, e, this.active); - } - } - - var dataset; - var index; - // Remove styling for last active (even if it may still be active) - if (this.lastActive.length) { - switch (this.options.hover.mode) { - case 'single': - dataset = this.data.datasets[this.lastActive[0]._datasetIndex]; - index = this.lastActive[0]._index; - - this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.rectangle.backgroundColor); - this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.rectangle.borderColor); - this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.rectangle.borderWidth); - break; - case 'label': - for (var i = 0; i < this.lastActive.length; i++) { - dataset = this.data.datasets[this.lastActive[i]._datasetIndex]; - index = this.lastActive[i]._index; - - this.lastActive[i]._model.backgroundColor = this.lastActive[i].custom && this.lastActive[i].custom.backgroundColor ? this.lastActive[i].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.rectangle.backgroundColor); - this.lastActive[i]._model.borderColor = this.lastActive[i].custom && this.lastActive[i].custom.borderColor ? this.lastActive[i].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.rectangle.borderColor); - this.lastActive[i]._model.borderWidth = this.lastActive[i].custom && this.lastActive[i].custom.borderWidth ? this.lastActive[i].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.rectangle.borderWidth); - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } - - // Built in hover styling - if (this.active.length && this.options.hover.mode) { - switch (this.options.hover.mode) { - case 'single': - dataset = this.data.datasets[this.active[0]._datasetIndex]; - index = this.active[0]._index; - - this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); - this.active[0]._model.borderColor = this.active[0].custom && this.active[0].custom.hoverBorderColor ? this.active[0].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(this.active[0]._model.borderColor).saturate(0.5).darken(0.1).rgbString()); - this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.active[0]._model.borderWidth); - break; - case 'label': - for (var i = 0; i < this.active.length; i++) { - dataset = this.data.datasets[this.active[i]._datasetIndex]; - index = this.active[i]._index; - - this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); - this.active[i]._model.borderColor = this.active[i].custom && this.active[i].custom.hoverBorderColor ? this.active[i].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(this.active[i]._model.borderColor).saturate(0.5).darken(0.1).rgbString()); - this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.active[i]._model.borderWidth); - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } - - - // Built in Tooltips - if (this.options.tooltips.enabled) { - - // The usual updates - this.tooltip.initialize(); - - // Active - if (this.active.length) { - this.tooltip._model.opacity = 1; - - helpers.extend(this.tooltip, { - _active: this.active, - }); - - this.tooltip.update(); - } else { - // Inactive - this.tooltip._model.opacity = 0; - } - } - - - this.tooltip.pivot(); - - // Hover animations - 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; - }, - }); + "use strict"; + + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; + + Chart.defaults.global.elements.point = { + radius: 3, + backgroundColor: Chart.defaults.global.defaultColor, + borderWidth: 1, + borderColor: Chart.defaults.global.defaultColor, + // Hover + hitRadius: 1, + hoverRadius: 4, + hoverBorderWidth: 1, + }; + + + Chart.elements.Point = Chart.Element.extend({ + inRange: function(mouseX, mouseY) { + var vm = this._view; + var hoverRange = vm.hitRadius + 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._view; + + if (vm) { + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)); + } else { + return false; + } + }, + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y, + padding: vm.radius + vm.borderWidth + }; + }, + draw: function() { + + var vm = this._view; + var ctx = this._chart.ctx; + + + if (vm.skip) { + return; + } + + if (vm.radius > 0 || vm.borderWidth > 0) { + + ctx.beginPath(); + + ctx.arc(vm.x, vm.y, vm.radius || Chart.defaults.global.elements.point.radius, 0, Math.PI * 2); + ctx.closePath(); + + ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; + ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.point.borderWidth; + + ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; + + ctx.fill(); + ctx.stroke(); + } + } + }); }).call(this); (function() { - "use strict"; - var root = this, - Chart = root.Chart, - //Cache a local reference to Chart.helpers - helpers = Chart.helpers; + "use strict"; + + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; + + Chart.defaults.global.elements.rectangle = { + backgroundColor: Chart.defaults.global.defaultColor, + borderWidth: 0, + borderColor: Chart.defaults.global.defaultColor, + }; + + Chart.elements.Rectangle = Chart.Element.extend({ + draw: function() { + + var ctx = this._chart.ctx; + var vm = this._view; + + var 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._view; + return vm.base - vm.y; + }, + inRange: function(mouseX, mouseY) { + var vm = this._view; + 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._view; + return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2); + }, + tooltipPosition: function() { + var vm = this._view; + if (vm.y < vm.base) { + return { + x: vm.x, + y: vm.y + }; + } else { + return { + x: vm.x, + y: vm.base + }; + } + }, + }); - var defaultConfig = { - - animation: { - //Boolean - Whether we animate the rotation of the Doughnut - animateRotate: true, - - //Boolean - Whether we animate scaling the Doughnut from the centre - animateScale: false, - }, - - hover: { - mode: 'single' - }, - - //The percentage of the chart that we cut out of the middle. - - cutoutPercentage: 50, - - }; - - 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() { - - //Set up tooltip events on the chart - helpers.bindEvents(this, this.options.events, this.events); - - //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 Chart.Arc({ - _chart: this.chart, - _datasetIndex: datasetIndex, - _index: index, - _model: {} - })); - }, 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); - - this.resetElements(); - - // Update the chart with the latest data. - this.update(); - - }, - - calculateCircumference: function(dataset, value) { - if (dataset.total > 0) { - return (Math.PI * 2) * (value / dataset.total); - } else { - return 0; - } - }, - resetElements: function() { - this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.elements.arc.borderWidth / 2) / 2; - this.innerRadius = this.options.cutoutPercentage ? (this.outerRadius / 100) * (this.options.cutoutPercentage) : 1; - this.radiusLength = (this.outerRadius - this.innerRadius) / this.data.datasets.length; - - // Update the points - helpers.each(this.data.datasets, function(dataset, datasetIndex) { - // So that calculateCircumference works - dataset.total = 0; - helpers.each(dataset.data, function(value) { - dataset.total += Math.abs(value); - }, this); - - dataset.outerRadius = this.outerRadius - (this.radiusLength * datasetIndex); - dataset.innerRadius = dataset.outerRadius - this.radiusLength; - - helpers.each(dataset.metaData, function(slice, index) { - helpers.extend(slice, { - _model: { - x: this.chart.width / 2, - y: this.chart.height / 2, - startAngle: Math.PI * -0.5, // use - PI / 2 instead of 3PI / 2 to make animations better. It means that we never deal with overflow during the transition function - circumference: (this.options.animation.animateRotate) ? 0 : this.calculateCircumference(metaSlice.value), - outerRadius: (this.options.animation.animateScale) ? 0 : dataset.outerRadius, - innerRadius: (this.options.animation.animateScale) ? 0 : dataset.innerRadius, - - backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor), - hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, this.options.elements.arc.hoverBackgroundColor), - borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth), - borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor), - - label: helpers.getValueAtIndexOrDefault(dataset.label, index, this.data.labels[index]) - }, - }); - - slice.pivot(); - }, this); - - }, this); - }, - update: function(animationDuration) { - - this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.elements.arc.borderWidth / 2) / 2; - this.innerRadius = this.options.cutoutPercentage ? (this.outerRadius / 100) * (this.options.cutoutPercentage) : 1; - this.radiusLength = (this.outerRadius - this.innerRadius) / this.data.datasets.length; - - - // Update the points - helpers.each(this.data.datasets, function(dataset, datasetIndex) { - - dataset.total = 0; - helpers.each(dataset.data, function(value) { - dataset.total += Math.abs(value); - }, this); - - - dataset.outerRadius = this.outerRadius - (this.radiusLength * datasetIndex); - - dataset.innerRadius = dataset.outerRadius - this.radiusLength; - - helpers.each(dataset.metaData, function(slice, index) { - - helpers.extend(slice, { - // Utility - _chart: this.chart, - _datasetIndex: datasetIndex, - _index: index, - - // Desired view properties - _model: { - x: this.chart.width / 2, - y: this.chart.height / 2, - circumference: this.calculateCircumference(dataset, dataset.data[index]), - outerRadius: dataset.outerRadius, - innerRadius: dataset.innerRadius, - - backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor), - hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, this.options.elements.arc.hoverBackgroundColor), - borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth), - borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor), - - label: helpers.getValueAtIndexOrDefault(dataset.label, index, this.data.labels[index]) - }, - }); - - if (index === 0) { - slice._model.startAngle = Math.PI * -0.5; // use - PI / 2 instead of 3PI / 2 to make animations better. It means that we never deal with overflow during the transition function - } else { - slice._model.startAngle = dataset.metaData[index - 1]._model.endAngle; - } - - slice._model.endAngle = slice._model.startAngle + slice._model.circumference; - - - //Check to see if it's the last slice, if not get the next and update its start angle - if (index < dataset.data.length - 1) { - dataset.metaData[index + 1]._model.startAngle = slice._model.endAngle; - } - - slice.pivot(); - }, this); - - }, this); - - this.render(animationDuration); - }, - draw: function(easeDecimal) { - easeDecimal = easeDecimal || 1; - this.clear(); - - this.eachElement(function(slice) { - slice.transition(easeDecimal).draw(); - }, this); - - this.tooltip.transition(easeDecimal).draw(); - }, - events: function(e) { - - this.lastActive = this.lastActive || []; - - // Find Active Elements - if (e.type == 'mouseout') { - this.active = []; - } else { - - this.active = function() { - switch (this.options.hover.mode) { - case 'single': - return this.getSliceAtEvent(e); - case 'label': - return this.getSlicesAtEvent(e); - case 'dataset': - return this.getDatasetAtEvent(e); - default: - return e; - } - }.call(this); - } - - // On Hover hook - if (this.options.hover.onHover) { - this.options.hover.onHover.call(this, this.active); - } - - if (e.type == 'mouseup' || e.type == 'click') { - if (this.options.onClick) { - this.options.onClick.call(this, e, this.active); - } - } - - var dataset; - var index; - // Remove styling for last active (even if it may still be active) - if (this.lastActive.length) { - switch (this.options.hover.mode) { - case 'single': - dataset = this.data.datasets[this.lastActive[0]._datasetIndex]; - index = this.lastActive[0]._index; - - this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor); - this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor); - this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth); - break; - case 'label': - for (var i = 0; i < this.lastActive.length; i++) { - dataset = this.data.datasets[this.lastActive[i]._datasetIndex]; - index = this.lastActive[i]._index; - - this.lastActive[i]._model.backgroundColor = this.lastActive[i].custom && this.lastActive[i].custom.backgroundColor ? this.lastActive[i].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor); - this.lastActive[i]._model.borderColor = this.lastActive[i].custom && this.lastActive[i].custom.borderColor ? this.lastActive[i].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor); - this.lastActive[i]._model.borderWidth = this.lastActive[i].custom && this.lastActive[i].custom.borderWidth ? this.lastActive[i].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth); - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } - - // Built in hover styling - if (this.active.length && this.options.hover.mode) { - switch (this.options.hover.mode) { - case 'single': - dataset = this.data.datasets[this.active[0]._datasetIndex]; - index = this.active[0]._index; - - this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); - this.active[0]._model.borderColor = this.active[0].custom && this.active[0].custom.hoverBorderColor ? this.active[0].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, this.active[0]._model.borderColor); - this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, this.active[0]._model.borderWidth); - break; - case 'label': - for (var i = 0; i < this.active.length; i++) { - dataset = this.data.datasets[this.active[i]._datasetIndex]; - index = this.active[i]._index; - - this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); - this.active[i]._model.borderColor = this.active[i].custom && this.active[i].custom.hoverBorderColor ? this.active[i].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, this.active[0]._model.borderColor); - this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, this.active[i]._model.borderWidth); - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } - - - // Built in Tooltips - if (this.options.tooltips.enabled) { - - // The usual updates - this.tooltip.initialize(); - - // Active - if (this.active.length) { - this.tooltip._model.opacity = 1; - - helpers.extend(this.tooltip, { - _active: this.active, - }); - - this.tooltip.update(); - } else { - // Inactive - this.tooltip._model.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.hover.animationDuration); - } - } - - // Remember Last Active - this.lastActive = this.active; - return this; - }, - getSliceAtEvent: function(e) { - var elements = []; - - var location = helpers.getRelativePosition(e); - - this.eachElement(function(slice, index) { - if (slice.inRange(location.x, location.y)) { - elements.push(slice); - } - }, this); - return elements; - }, - /*getSlicesAtEvent: function(e) { - var elements = []; - - var location = helpers.getRelativePosition(e); - - this.eachElement(function(slice, index) { - if (slice.inGroupRange(location.x, location.y)) { - elements.push(slice); - } - }, this); - return elements; - },*/ - }); - - Chart.types.Doughnut.extend({ - name: "Pie", - defaults: helpers.merge(defaultConfig, { - cutoutPercentage: 0 - }) - }); - -}).call(this); - -(function() { - "use strict"; - - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; - - var defaultConfig = { - - stacked: false, - - hover: { - mode: "label" - }, - - scales: { - xAxes: [{ - type: "category", // scatter should not use a dataset axis - display: true, - position: "bottom", - id: "x-axis-1", // need an ID so datasets can reference the scale - - // grid line settings - gridLines: { - show: true, - color: "rgba(0, 0, 0, 0.05)", - lineWidth: 1, - drawOnChartArea: true, - drawTicks: true, - zeroLineWidth: 1, - zeroLineColor: "rgba(0,0,0,0.25)", - offsetGridLines: false, - }, - - // label settings - labels: { - show: true, - template: "<%=value%>", - fontSize: 12, - fontStyle: "normal", - fontColor: "#666", - fontFamily: "Helvetica Neue", - }, - }], - yAxes: [{ - type: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance - display: true, - position: "left", - id: "y-axis-1", - - // grid line settings - gridLines: { - show: true, - color: "rgba(0, 0, 0, 0.05)", - lineWidth: 1, - drawOnChartArea: true, - drawTicks: true, // draw ticks extending towards the label - zeroLineWidth: 1, - zeroLineColor: "rgba(0,0,0,0.25)", - }, - - // scale numbers - beginAtZero: false, - override: null, - - // label settings - labels: { - show: true, - template: "<%=value.toLocaleString()%>", - fontSize: 12, - fontStyle: "normal", - fontColor: "#666", - fontFamily: "Helvetica Neue", - } - }], - }, - }; - - - Chart.Type.extend({ - name: "Line", - defaults: defaultConfig, - initialize: function() { - - var _this = this; - - // Events - helpers.bindEvents(this, this.options.events, this.events); - - // 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({ - _chart: this.chart, - _datasetIndex: datasetIndex, - _points: dataset.metaData, - }); - - dataset.metaData = []; - - helpers.each(dataset.data, function(dataPoint, index) { - dataset.metaData.push(new Chart.Point({ - _datasetIndex: datasetIndex, - _index: index, - _chart: this.chart, - _model: { - x: 0, //xScale.getPixelForValue(null, index, true), - y: 0, //this.chartArea.bottom, - }, - })); - - }, this); - - // The line chart onlty supports a single x axis because the x axis is always a dataset axis - dataset.xAxisID = this.options.scales.xAxes[0].id; - - if (!dataset.yAxisID) { - dataset.yAxisID = this.options.scales.yAxes[0].id; - } - - }, this); - - // Build and fit the scale. Needs to happen after the axis IDs have been set - this.buildScale(); - - // 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); - - // Need to fit scales before we reset elements. - Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - - // Reset so that we animation from the baseline - this.resetElements(); - - // Update that shiz - this.update(); - }, - nextPoint: function(collection, index) { - return collection[index + 1] || collection[index]; - }, - previousPoint: function(collection, index) { - return collection[index - 1] || collection[index]; - }, - resetElements: function() { - // Update the points - this.eachElement(function(point, index, dataset, datasetIndex) { - var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID]; - var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID]; - - var yScalePoint; - - if (yScale.min < 0 && yScale.max < 0) { - // all less than 0. use the top - yScalePoint = yScale.getPixelForValue(yScale.max); - } else if (yScale.min > 0 && yScale.max > 0) { - yScalePoint = yScale.getPixelForValue(yScale.min); - } else { - yScalePoint = yScale.getPixelForValue(0); - } - - helpers.extend(point, { - // Utility - _chart: this.chart, - _xScale: xScale, - _yScale: yScale, - _datasetIndex: datasetIndex, - _index: index, - - // Desired view properties - _model: { - x: xScale.getPixelForValue(null, index, true), // value not used in dataset scale, but we want a consistent API between scales - y: yScalePoint, - - // Appearance - tension: point.custom && point.custom.tension ? point.custom.tension : this.options.elements.line.tension, - radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].radius, index, this.options.elements.point.radius), - backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBackgroundColor, index, this.options.elements.point.backgroundColor), - borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderColor, index, this.options.elements.point.borderColor), - borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderWidth, index, this.options.elements.point.borderWidth), - skip: this.data.datasets[datasetIndex].data[index] === null, - - // Tooltip - hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].hitRadius, index, this.options.elements.point.hitRadius), - }, - }); - }, this); - - // Update control points for the bezier curve - this.eachElement(function(point, index, dataset, datasetIndex) { - var controlPoints = helpers.splineCurve( - this.previousPoint(dataset, index)._model, - point._model, - this.nextPoint(dataset, index)._model, - point._model.tension - ); - - point._model.controlPointPreviousX = controlPoints.previous.x; - point._model.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.chartArea.bottom) { - point._model.controlPointNextY = this.chartArea.bottom; - } else if (controlPoints.next.y < this.chartArea.top) { - point._model.controlPointNextY = this.chartArea.top; - } else { - point._model.controlPointNextY = controlPoints.next.y; - } - - // Cap inner bezier handles to the upper/lower scale bounds - if (controlPoints.previous.y > this.chartArea.bottom) { - point._model.controlPointPreviousY = this.chartArea.bottom; - } else if (controlPoints.previous.y < this.chartArea.top) { - point._model.controlPointPreviousY = this.chartArea.top; - } else { - point._model.controlPointPreviousY = controlPoints.previous.y; - } - - // Now pivot the point for animation - point.pivot(); - }, this); - }, - update: function(animationDuration) { - - Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - - // Update the lines - this.eachDataset(function(dataset, datasetIndex) { - var yScale = this.scales[dataset.yAxisID]; - var scaleBase; - - if (yScale.min < 0 && yScale.max < 0) { - scaleBase = yScale.getPixelForValue(yScale.max); - } else if (yScale.min > 0 && yScale.max > 0) { - scaleBase = yScale.getPixelForValue(yScale.min); - } else { - scaleBase = yScale.getPixelForValue(0); - } - - helpers.extend(dataset.metaDataset, { - // Utility - _scale: yScale, - _datasetIndex: datasetIndex, - // Data - _children: dataset.metaData, - // Model - _model: { - // Appearance - tension: dataset.metaDataset.custom && dataset.metaDataset.custom.tension ? dataset.metaDataset.custom.tension : (dataset.tension || this.options.elements.line.tension), - backgroundColor: dataset.metaDataset.custom && dataset.metaDataset.custom.backgroundColor ? dataset.metaDataset.custom.backgroundColor : (dataset.backgroundColor || this.options.elements.line.backgroundColor), - borderWidth: dataset.metaDataset.custom && dataset.metaDataset.custom.borderWidth ? dataset.metaDataset.custom.borderWidth : (dataset.borderWidth || this.options.elements.line.borderWidth), - borderColor: dataset.metaDataset.custom && dataset.metaDataset.custom.borderColor ? dataset.metaDataset.custom.borderColor : (dataset.borderColor || this.options.elements.line.borderColor), - fill: dataset.metaDataset.custom && dataset.metaDataset.custom.fill ? dataset.metaDataset.custom.fill : (dataset.fill !== undefined ? dataset.fill : this.options.elements.line.fill), - skipNull: dataset.skipNull !== undefined ? dataset.skipNull : this.options.elements.line.skipNull, - drawNull: dataset.drawNull !== undefined ? dataset.drawNull : this.options.elements.line.drawNull, - // Scale - scaleTop: yScale.top, - scaleBottom: yScale.bottom, - scaleZero: scaleBase, - }, - }); - - dataset.metaDataset.pivot(); - }); - - // Update the points - this.eachElement(function(point, index, dataset, datasetIndex) { - var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID]; - var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID]; - - helpers.extend(point, { - // Utility - _chart: this.chart, - _xScale: xScale, - _yScale: yScale, - _datasetIndex: datasetIndex, - _index: index, - - // Desired view properties - _model: { - x: xScale.getPixelForValue(null, index, true), // value not used in dataset scale, but we want a consistent API between scales - y: yScale.getPointPixelForValue(this.data.datasets[datasetIndex].data[index], index, datasetIndex), - - // Appearance - tension: point.custom && point.custom.tension ? point.custom.tension : this.options.elements.line.tension, - radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].radius, index, this.options.elements.point.radius), - backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBackgroundColor, index, this.options.elements.point.backgroundColor), - borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderColor, index, this.options.elements.point.borderColor), - borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderWidth, index, this.options.elements.point.borderWidth), - skip: this.data.datasets[datasetIndex].data[index] === null, - - // Tooltip - hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].hitRadius, index, this.options.elements.point.hitRadius), - }, - }); - }, this); - - - // Update control points for the bezier curve - this.eachElement(function(point, index, dataset, datasetIndex) { - var controlPoints = helpers.splineCurve( - this.previousPoint(dataset, index)._model, - point._model, - this.nextPoint(dataset, index)._model, - point._model.tension - ); - - point._model.controlPointPreviousX = controlPoints.previous.x; - point._model.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.chartArea.bottom) { - point._model.controlPointNextY = this.chartArea.bottom; - } else if (controlPoints.next.y < this.chartArea.top) { - point._model.controlPointNextY = this.chartArea.top; - } else { - point._model.controlPointNextY = controlPoints.next.y; - } - - // Cap inner bezier handles to the upper/lower scale bounds - if (controlPoints.previous.y > this.chartArea.bottom) { - point._model.controlPointPreviousY = this.chartArea.bottom; - } else if (controlPoints.previous.y < this.chartArea.top) { - point._model.controlPointPreviousY = this.chartArea.top; - } else { - point._model.controlPointPreviousY = controlPoints.previous.y; - } - - // Now pivot the point for animation - point.pivot(); - }, this); - - this.render(animationDuration); - }, - buildScale: function() { - var self = this; - - // Function to determine the range of all the - var calculateYRange = function() { - this.min = null; - this.max = null; - - var positiveValues = []; - var negativeValues = []; - - if (self.options.stacked) { - helpers.each(self.data.datasets, function(dataset) { - if (dataset.yAxisID === this.id) { - helpers.each(dataset.data, function(value, index) { - positiveValues[index] = positiveValues[index] || 0; - negativeValues[index] = negativeValues[index] || 0; - - if (self.options.relativePoints) { - positiveValues[index] = 100; - } else { - if (value < 0) { - negativeValues[index] += value; - } else { - positiveValues[index] += value; - } - } - }, this); - } - }, this); - - var values = positiveValues.concat(negativeValues); - this.min = helpers.min(values); - this.max = helpers.max(values); - } else { - helpers.each(self.data.datasets, function(dataset) { - if (dataset.yAxisID === this.id) { - helpers.each(dataset.data, function(value, index) { - if (this.min === null) { - this.min = value; - } else if (value < this.min) { - this.min = value; - } - - if (this.max === null) { - this.max = value; - } else if (value > this.max) { - this.max = value; - } - }, this); - } - }, this); - } - }; - - // Map of scale ID to scale object so we can lookup later - this.scales = {}; - - // Build the x axis. The line chart only supports a single x axis - var ScaleClass = Chart.scales.getScaleConstructor(this.options.scales.xAxes[0].type); - var xScale = new ScaleClass({ - ctx: this.chart.ctx, - options: this.options.scales.xAxes[0], - calculateRange: function() { - this.labels = self.data.labels; - this.min = 0; - this.max = this.labels.length; - }, - id: this.options.scales.xAxes[0].id, - }); - this.scales[xScale.id] = xScale; - - // Build up all the y scales - helpers.each(this.options.scales.yAxes, function(yAxisOptions) { - var ScaleClass = Chart.scales.getScaleConstructor(yAxisOptions.type); - var scale = new ScaleClass({ - ctx: this.chart.ctx, - options: yAxisOptions, - calculateRange: calculateYRange, - getPointPixelForValue: function(value, index, datasetIndex) { - if (self.options.stacked) { - var offsetPos = 0; - var offsetNeg = 0; - - for (var i = 0; i < datasetIndex; ++i) { - if (self.data.datasets[i].data[index] < 0) { - offsetNeg += self.data.datasets[i].data[index]; - } else { - offsetPos += self.data.datasets[i].data[index]; - } - } - - if (value < 0) { - return this.getPixelForValue(offsetNeg + value); - } else { - return this.getPixelForValue(offsetPos + value); - } - } else { - return this.getPixelForValue(value); - } - }, - id: yAxisOptions.id, - }); - - this.scales[scale.id] = scale; - }, this); - }, - draw: function(ease) { - - var easingDecimal = ease || 1; - this.clear(); - - // Draw all the scales - helpers.each(this.scales, function(scale) { - scale.draw(this.chartArea); - }, this); - - // reverse for-loop for proper stacking - for (var i = this.data.datasets.length - 1; i >= 0; i--) { - - var dataset = this.data.datasets[i]; - - // 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(); - }); - } - - // Finally draw the tooltip - this.tooltip.transition(easingDecimal).draw(); - }, - events: function(e) { - - this.lastActive = this.lastActive || []; - - // Find Active Elements - if (e.type == 'mouseout') { - this.active = []; - } else { - this.active = function() { - switch (this.options.hover.mode) { - 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.hover.onHover) { - this.options.hover.onHover.call(this, this.active); - } - - if (e.type == 'mouseup' || e.type == 'click') { - if (this.options.onClick) { - this.options.onClick.call(this, e, this.active); - } - } - - var dataset; - var index; - // Remove styling for last active (even if it may still be active) - if (this.lastActive.length) { - switch (this.options.hover.mode) { - case 'single': - dataset = this.data.datasets[this.lastActive[0]._datasetIndex]; - index = this.lastActive[0]._index; - - this.lastActive[0]._model.radius = this.lastActive[0].custom && this.lastActive[0].custom.radius ? this.lastActive[0].custom.radius : helpers.getValueAtIndexOrDefault(dataset.radius, index, this.options.elements.point.radius); - this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, this.options.elements.point.backgroundColor); - this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, this.options.elements.point.borderColor); - this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.options.elements.point.borderWidth); - break; - case 'label': - for (var i = 0; i < this.lastActive.length; i++) { - dataset = this.data.datasets[this.lastActive[i]._datasetIndex]; - index = this.lastActive[i]._index; - - this.lastActive[i]._model.radius = this.lastActive[i].custom && this.lastActive[i].custom.radius ? this.lastActive[i].custom.radius : helpers.getValueAtIndexOrDefault(dataset.radius, index, this.options.elements.point.radius); - this.lastActive[i]._model.backgroundColor = this.lastActive[i].custom && this.lastActive[i].custom.backgroundColor ? this.lastActive[i].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, this.options.elements.point.backgroundColor); - this.lastActive[i]._model.borderColor = this.lastActive[i].custom && this.lastActive[i].custom.borderColor ? this.lastActive[i].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, this.options.elements.point.borderColor); - this.lastActive[i]._model.borderWidth = this.lastActive[i].custom && this.lastActive[i].custom.borderWidth ? this.lastActive[i].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.options.elements.point.borderWidth); - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } - - // Built in hover styling - if (this.active.length && this.options.hover.mode) { - switch (this.options.hover.mode) { - case 'single': - dataset = this.data.datasets[this.active[0]._datasetIndex]; - index = this.active[0]._index; - - this.active[0]._model.radius = this.active[0].custom && this.active[0].custom.radius ? this.active[0].custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.options.elements.point.hoverRadius); - this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); - this.active[0]._model.borderColor = this.active[0].custom && this.active[0].custom.hoverBorderColor ? this.active[0].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(this.active[0]._model.borderColor).saturate(0.5).darken(0.1).rgbString()); - this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[0]._model.borderWidth); - break; - case 'label': - for (var i = 0; i < this.active.length; i++) { - dataset = this.data.datasets[this.active[i]._datasetIndex]; - index = this.active[i]._index; - - this.active[i]._model.radius = this.active[i].custom && this.active[i].custom.radius ? this.active[i].custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.options.elements.point.hoverRadius); - this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); - this.active[i]._model.borderColor = this.active[i].custom && this.active[i].custom.hoverBorderColor ? this.active[i].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(this.active[i]._model.borderColor).saturate(0.5).darken(0.1).rgbString()); - this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[i]._model.borderWidth); - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } - - - // Built in Tooltips - if (this.options.tooltips.enabled) { - - // The usual updates - this.tooltip.initialize(); - - // Active - if (this.active.length) { - this.tooltip._model.opacity = 1; - - helpers.extend(this.tooltip, { - _active: this.active, - }); - - this.tooltip.update(); - } else { - // Inactive - this.tooltip._model.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.hover.animationDuration); - } - } - - // Remember Last Active - this.lastActive = this.active; - return this; - }, - }); }).call(this); (function() { - "use strict"; - - var root = this, - Chart = root.Chart, - //Cache a local reference to Chart.helpers - helpers = Chart.helpers; - - var defaultConfig = { - - scale: { - type: "radialLinear", - display: true, - - //Boolean - Whether to animate scaling the chart from the centre - animate: false, - - lineArc: true, - - // grid line settings - gridLines: { - show: true, - color: "rgba(0, 0, 0, 0.05)", - lineWidth: 1, - }, - - // scale numbers - beginAtZero: true, - - // label settings - labels: { - show: true, - template: "<%=value.toLocaleString()%>", - fontSize: 12, - fontStyle: "normal", - fontColor: "#666", - fontFamily: "Helvetica Neue", - - //Boolean - Show a backdrop to the scale label - showLabelBackdrop: true, - - //String - The colour of the label backdrop - backdropColor: "rgba(255,255,255,0.75)", - - //Number - The backdrop padding above & below the label in pixels - backdropPaddingY: 2, - - //Number - The backdrop padding to the side of the label in pixels - backdropPaddingX: 2, - } - }, - - //Boolean - Whether to animate the rotation of the chart - animateRotate: true, - }; - - - Chart.Type.extend({ - //Passing in a name registers this chart in the Chart namespace - name: "PolarArea", - //Providing a defaults will also register the deafults in the chart namespace - defaults: defaultConfig, - //Initialize is fired when the chart is initialized - Data is passed in as a parameter - //Config is automatically merged by the core of Chart.js, and is available at this.options - initialize: function() { - - // Scale setup - var self = this; - var ScaleClass = Chart.scales.getScaleConstructor(this.options.scale.type); - this.scale = new ScaleClass({ - options: this.options.scale, - lineArc: true, - width: this.chart.width, - height: this.chart.height, - xCenter: this.chart.width / 2, - yCenter: this.chart.height / 2, - ctx: this.chart.ctx, - valuesCount: this.data.length, - calculateRange: function() { - this.min = null; - this.max = null; - - helpers.each(self.data.datasets[0].data, function(value) { - if (this.min === null) { - this.min = value; - } else if (value < this.min) { - this.min = value; - } - - if (this.max === null) { - this.max = value; - } else if (value > this.max) { - this.max = value; - } - }, this); - } - }); - - helpers.bindEvents(this, this.options.events, this.events); - - //Set up tooltip events on the chart - helpers.bindEvents(this, this.options.events, this.events); - - //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 Chart.Arc({ - _chart: this.chart, - _datasetIndex: datasetIndex, - _index: index, - _model: {} - })); - }, 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); - - // Fit the scale before we animate - this.updateScaleRange(); - this.scale.calculateRange(); - Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - - // so that we animate nicely - this.resetElements(); - - // Update the chart with the latest data. - this.update(); - - }, - updateScaleRange: function() { - helpers.extend(this.scale, { - size: helpers.min([this.chart.width, this.chart.height]), - xCenter: this.chart.width / 2, - yCenter: this.chart.height / 2 - }); - }, - resetElements: function() { - var circumference = 1 / this.data.datasets[0].data.length * 2; - - // Map new data to data points - helpers.each(this.data.datasets[0].metaData, function(slice, index) { - - var value = this.data.datasets[0].data[index]; - - helpers.extend(slice, { - _index: index, - _model: { - x: this.chart.width / 2, - y: this.chart.height / 2, - innerRadius: 0, - outerRadius: 0, - startAngle: Math.PI * -0.5, - endAngle: Math.PI * -0.5, - - backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[0].backgroundColor, index, this.options.elements.arc.backgroundColor), - hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[0].hoverBackgroundColor, index, this.options.elements.arc.hoverBackgroundColor), - borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[0].borderWidth, index, this.options.elements.arc.borderWidth), - borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[0].borderColor, index, this.options.elements.arc.borderColor), - - label: helpers.getValueAtIndexOrDefault(this.data.datasets[0].labels, index, this.data.datasets[0].labels[index]) - }, - }); - - slice.pivot(); - }, this); - }, - update: function(animationDuration) { - - this.updateScaleRange(); - this.scale.calculateRange(); - this.scale.generateTicks(); - this.scale.buildYLabels(); - - Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - - var circumference = 1 / this.data.datasets[0].data.length * 2; - - // Map new data to data points - helpers.each(this.data.datasets[0].metaData, function(slice, index) { - - var value = this.data.datasets[0].data[index]; - - var startAngle = (-0.5 * Math.PI) + (Math.PI * circumference) * index; - var endAngle = startAngle + (circumference * Math.PI); - - helpers.extend(slice, { - _index: index, - _model: { - x: this.chart.width / 2, - y: this.chart.height / 2, - innerRadius: 0, - outerRadius: this.scale.calculateCenterOffset(value), - startAngle: startAngle, - endAngle: endAngle, - - backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[0].backgroundColor, index, this.options.elements.arc.backgroundColor), - hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[0].hoverBackgroundColor, index, this.options.elements.arc.hoverBackgroundColor), - borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[0].borderWidth, index, this.options.elements.arc.borderWidth), - borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[0].borderColor, index, this.options.elements.arc.borderColor), - - label: helpers.getValueAtIndexOrDefault(this.data.datasets[0].labels, index, this.data.datasets[0].labels[index]) - }, - }); - slice.pivot(); - - console.log(slice); - - }, this); - - this.render(animationDuration); - }, - draw: function(ease) { - var easingDecimal = ease || 1; - - this.clear(); - - helpers.each(this.data.datasets[0].metaData, function(slice, index) { - slice.transition(easingDecimal).draw(); - }, this); - - this.scale.draw(); - - this.tooltip.transition(easingDecimal).draw(); - }, - events: function(e) { - - // If exiting chart - if (e.type == 'mouseout') { - return this; - } - - this.lastActive = this.lastActive || []; - - // Find Active Elements - this.active = function() { - switch (this.options.hover.mode) { - case 'single': - return this.getSliceAtEvent(e); - case 'label': - return this.getSlicesAtEvent(e); - case 'dataset': - return this.getDatasetAtEvent(e); - default: - return e; - } - }.call(this); - - // On Hover hook - if (this.options.hover.onHover) { - this.options.hover.onHover.call(this, this.active); - } - - if (e.type == 'mouseup' || e.type == 'click') { - if (this.options.onClick) { - this.options.onClick.call(this, e, this.active); - } - } - - var dataset; - var index; - // Remove styling for last active (even if it may still be active) - if (this.lastActive.length) { - switch (this.options.hover.mode) { - case 'single': - dataset = this.data.datasets[this.lastActive[0]._datasetIndex]; - index = this.lastActive[0]._index; - - this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor); - this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor); - this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth); - break; - case 'label': - for (var i = 0; i < this.lastActive.length; i++) { - dataset = this.data.datasets[this.lastActive[i]._datasetIndex]; - index = this.lastActive[i]._index; - - this.lastActive[i]._model.backgroundColor = this.lastActive[i].custom && this.lastActive[i].custom.backgroundColor ? this.lastActive[i].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor); - this.lastActive[i]._model.borderColor = this.lastActive[i].custom && this.lastActive[i].custom.borderColor ? this.lastActive[i].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor); - this.lastActive[i]._model.borderWidth = this.lastActive[i].custom && this.lastActive[i].custom.borderWidth ? this.lastActive[i].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth); - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } - - // Built in hover styling - if (this.active.length && this.options.hover.mode) { - switch (this.options.hover.mode) { - case 'single': - dataset = this.data.datasets[this.active[0]._datasetIndex]; - index = this.active[0]._index; - - this.active[0]._model.radius = this.active[0].custom && this.active[0].custom.hoverRadius ? this.active[0].custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.active[0]._model.radius + 1); - this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); - this.active[0]._model.borderColor = this.active[0].custom && this.active[0].custom.hoverBorderColor ? this.active[0].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(this.active[0]._model.borderColor).saturate(0.5).darken(0.1).rgbString()); - this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[0]._model.borderWidth); - break; - case 'label': - for (var i = 0; i < this.active.length; i++) { - dataset = this.data.datasets[this.active[i]._datasetIndex]; - index = this.active[i]._index; - - this.active[i]._model.radius = this.active[i].custom && this.active[i].custom.hoverRadius ? this.active[i].custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.active[i]._model.radius + 1); - this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); - this.active[i]._model.borderColor = this.active[i].custom && this.active[i].custom.hoverBorderColor ? this.active[i].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(this.active[i]._model.borderColor).saturate(0.5).darken(0.1).rgbString()); - this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[i]._model.borderWidth); - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } - - - // Built in Tooltips - if (this.options.tooltips.enabled) { - - // The usual updates - this.tooltip.initialize(); - - // Active - if (this.active.length) { - this.tooltip._model.opacity = 1; - - helpers.extend(this.tooltip, { - _active: this.active, - }); - - this.tooltip.update(); - } else { - // Inactive - this.tooltip._model.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.hover.animationDuration); - } - } - - // Remember Last Active - this.lastActive = this.active; - return this; - }, - getSliceAtEvent: function(e) { - var elements = []; - - var location = helpers.getRelativePosition(e); - - this.eachElement(function(slice, index) { - if (slice.inRange(location.x, location.y)) { - elements.push(slice); - } - }, this); - return elements; - }, - /*getSlicesAtEvent: function(e) { - var elements = []; - - var location = helpers.getRelativePosition(e); - - this.eachElement(function(slice, index) { - if (slice.inGroupRange(location.x, location.y)) { - elements.push(slice); - } - }, this); - return elements; - },*/ - }); - -}).call(this); - -(function() { - "use strict"; - - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; - - - - Chart.Type.extend({ - name: "Radar", - defaults: { - - scale: { - type: "radialLinear", - display: true, - - //Boolean - Whether to animate scaling the chart from the centre - animate: false, - - lineArc: false, - - // grid line settings - gridLines: { - show: true, - color: "rgba(0, 0, 0, 0.05)", - lineWidth: 1, - }, - - angleLines: { - show: true, - color: "rgba(0,0,0,.1)", - lineWidth: 1 - }, - - // scale numbers - beginAtZero: true, - - // label settings - labels: { - show: true, - template: "<%=value.toLocaleString()%>", - fontSize: 12, - fontStyle: "normal", - fontColor: "#666", - fontFamily: "Helvetica Neue", - - //Boolean - Show a backdrop to the scale label - showLabelBackdrop: true, - - //String - The colour of the label backdrop - backdropColor: "rgba(255,255,255,0.75)", - - //Number - The backdrop padding above & below the label in pixels - backdropPaddingY: 2, - - //Number - The backdrop padding to the side of the label in pixels - backdropPaddingX: 2, - }, - - pointLabels: { - //String - Point label font declaration - fontFamily: "'Arial'", - - //String - Point label font weight - fontStyle: "normal", - - //Number - Point label font size in pixels - fontSize: 10, - - //String - Point label font colour - fontColor: "#666", - }, - }, - - elements: { - line: { - tension: 0, // no bezier in radar - } - }, - - //String - A legend template - legendTemplate: "" - - }, - - initialize: function() { - - // Events - helpers.bindEvents(this, this.options.events, this.events); - - // 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({ - _chart: this.chart, - _datasetIndex: datasetIndex, - _points: dataset.metaData, - _loop: true - }); - - dataset.metaData = []; - - helpers.each(dataset.data, function(dataPoint, index) { - dataset.metaData.push(new Chart.Point({ - _datasetIndex: datasetIndex, - _index: index, - _chart: this.chart, - _model: { - x: 0, //xScale.getPixelForValue(null, index, true), - y: 0, //this.chartArea.bottom, - }, - })); - - }, this); - }, this); - - // Build the scale. - this.buildScale(); - - // 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); - - // Need to fit scales before we reset elements. - Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - - // Reset so that we animation from the baseline - this.resetElements(); - - // Update that shiz - this.update(); - }, - nextPoint: function(collection, index) { - return collection[index + 1] || collection[0]; - }, - previousPoint: function(collection, index) { - return collection[index - 1] || collection[collection.length - 1]; - }, - resetElements: function() { - - // Update the points - this.eachElement(function(point, index, dataset, datasetIndex) { - helpers.extend(point, { - // Utility - _chart: this.chart, - _datasetIndex: datasetIndex, - _index: index, - _scale: this.scale, - - // Desired view properties - _model: { - x: this.scale.xCenter, - y: this.scale.yCenter, - - // Appearance - tension: point.custom && point.custom.tension ? point.custom.tension : this.options.elements.line.tension, - radius: point.custom && point.custom.radius ? point.custom.pointRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointRadius, index, this.options.elements.point.radius), - backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBackgroundColor, index, this.options.elements.point.backgroundColor), - borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderColor, index, this.options.elements.point.borderColor), - borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderWidth, index, this.options.elements.point.borderWidth), - skip: this.data.datasets[datasetIndex].data[index] === null, - - // Tooltip - hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].hitRadius, index, this.options.elements.point.hitRadius), - }, - }); - }, this); - - // Update control points for the bezier curve - this.eachElement(function(point, index, dataset, datasetIndex) { - var controlPoints = helpers.splineCurve( - this.previousPoint(dataset, index)._model, - point._model, - this.nextPoint(dataset, index)._model, - point._model.tension - ); - - point._model.controlPointPreviousX = this.scale.xCenter; - point._model.controlPointPreviousY = this.scale.yCenter; - point._model.controlPointNextX = this.scale.xCenter; - point._model.controlPointNextY = this.scale.yCenter; - - // Now pivot the point for animation - point.pivot(); - }, this); - }, - update: function(animationDuration) { - Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - - // Update the lines - this.eachDataset(function(dataset, datasetIndex) { - var scaleBase; - - if (this.scale.min < 0 && this.scale.max < 0) { - scaleBase = this.scale.getPointPosition(0, this.scale.max); - } else if (this.scale.min > 0 && this.scale.max > 0) { - scaleBase = this.scale.getPointPosition(0, this.scale.min); - } else { - scaleBase = this.scale.getPointPosition(0, 0); - } - - helpers.extend(dataset.metaDataset, { - // Utility - _datasetIndex: datasetIndex, - - // Data - _children: dataset.metaData, - - // Model - _model: { - // Appearance - tension: dataset.tension || this.options.elements.line.tension, - backgroundColor: dataset.backgroundColor || this.options.elements.line.backgroundColor, - borderWidth: dataset.borderWidth || this.options.elements.line.borderWidth, - borderColor: dataset.borderColor || this.options.elements.line.borderColor, - fill: dataset.fill !== undefined ? dataset.fill : this.options.elements.line.fill, // use the value from the dataset if it was provided. else fall back to the default - skipNull: dataset.skipNull !== undefined ? dataset.skipNull : this.options.elements.line.skipNull, - drawNull: dataset.drawNull !== undefined ? dataset.drawNull : this.options.elements.line.drawNull, - - // Scale - scaleTop: this.scale.top, - scaleBottom: this.scale.bottom, - scaleZero: scaleBase, - }, - }); - - dataset.metaDataset.pivot(); - }); - - // Update the points - this.eachElement(function(point, index, dataset, datasetIndex) { - var pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(this.data.datasets[datasetIndex].data[index])); - - helpers.extend(point, { - // Utility - _chart: this.chart, - _datasetIndex: datasetIndex, - _index: index, - - // Desired view properties - _model: { - x: pointPosition.x, // value not used in dataset scale, but we want a consistent API between scales - y: pointPosition.y, - - // Appearance - tension: point.custom && point.custom.tension ? point.custom.tension : this.options.elements.line.tension, - radius: point.custom && point.custom.radius ? point.custom.pointRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointRadius, index, this.options.elements.point.radius), - backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBackgroundColor, index, this.options.elements.point.backgroundColor), - borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderColor, index, this.options.elements.point.borderColor), - borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderWidth, index, this.options.elements.point.borderWidth), - skip: this.data.datasets[datasetIndex].data[index] === null, - - // Tooltip - hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].hitRadius, index, this.options.elements.point.hitRadius), - }, - }); - }, this); - - - // Update control points for the bezier curve - this.eachElement(function(point, index, dataset, datasetIndex) { - var controlPoints = helpers.splineCurve( - this.previousPoint(dataset, index)._model, - point._model, - this.nextPoint(dataset, index)._model, - point._model.tension - ); - - point._model.controlPointPreviousX = controlPoints.previous.x; - point._model.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.chartArea.bottom) { - point._model.controlPointNextY = this.chartArea.bottom; - } else if (controlPoints.next.y < this.chartArea.top) { - point._model.controlPointNextY = this.chartArea.top; - } else { - point._model.controlPointNextY = controlPoints.next.y; - } - - // Cap inner bezier handles to the upper/lower scale bounds - if (controlPoints.previous.y > this.chartArea.bottom) { - point._model.controlPointPreviousY = this.chartArea.bottom; - } else if (controlPoints.previous.y < this.chartArea.top) { - point._model.controlPointPreviousY = this.chartArea.top; - } else { - point._model.controlPointPreviousY = controlPoints.previous.y; - } - - // Now pivot the point for animation - point.pivot(); - }, this); - - this.render(animationDuration); - }, - buildScale: function() { - var self = this; - - var ScaleConstructor = Chart.scales.getScaleConstructor(this.options.scale.type); - this.scale = new ScaleConstructor({ - options: this.options.scale, - height: this.chart.height, - width: this.chart.width, - xCenter: this.chart.width / 2, - yCenter: this.chart.height / 2, - ctx: this.chart.ctx, - labels: this.data.labels, - valuesCount: this.data.datasets[0].data.length, - calculateRange: function() { - this.min = null; - this.max = null; - - helpers.each(self.data.datasets, function(dataset) { - if (dataset.yAxisID === this.id) { - helpers.each(dataset.data, function(value, index) { - if (this.min === null) { - this.min = value; - } else if (value < this.min) { - this.min = value; - } - - if (this.max === null) { - this.max = value; - } else if (value > this.max) { - this.max = value; - } - }, this); - } - }, this); - } - }); - - this.scale.setScaleSize(); - this.scale.calculateRange(); - this.scale.generateTicks(); - this.scale.buildYLabels(); - }, - draw: function(ease) { - var easingDecimal = ease || 1; - this.clear(); - - // Draw all the scales - this.scale.draw(this.chartArea); - - // reverse for-loop for proper stacking - for (var i = this.data.datasets.length - 1; i >= 0; i--) { - - var dataset = this.data.datasets[i]; - - // 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(); - }); - } - - // Finally draw the tooltip - this.tooltip.transition(easingDecimal).draw(); - }, - events: function(e) { - - this.lastActive = this.lastActive || []; - - // Find Active Elements - // If exiting chart - if (e.type == 'mouseout') { - this.active = []; - } else { - this.active = function() { - switch (this.options.hover.mode) { - 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.hover.onHover) { - this.options.hover.onHover.call(this, this.active); - } - - if (e.type == 'mouseup' || e.type == 'click') { - if (this.options.onClick) { - this.options.onClick.call(this, e, this.active); - } - } - - var dataset; - var index; - // Remove styling for last active (even if it may still be active) - if (this.lastActive.length) { - switch (this.options.hover.mode) { - case 'single': - dataset = this.data.datasets[this.lastActive[0]._datasetIndex]; - index = this.lastActive[0]._index; - - this.lastActive[0]._model.radius = this.lastActive[0].custom && this.lastActive[0].custom.radius ? this.lastActive[0].custom.pointRadius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, this.options.elements.point.radius); - this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, this.options.elements.point.backgroundColor); - this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, this.options.elements.point.borderColor); - this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.options.elements.point.borderWidth); - break; - case 'label': - for (var i = 0; i < this.lastActive.length; i++) { - dataset = this.data.datasets[this.lastActive[i]._datasetIndex]; - index = this.lastActive[i]._index; - - this.lastActive[i]._model.radius = this.lastActive[i].custom && this.lastActive[i].custom.radius ? this.lastActive[i].custom.pointRadius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, this.options.elements.point.radius); - this.lastActive[i]._model.backgroundColor = this.lastActive[i].custom && this.lastActive[i].custom.backgroundColor ? this.lastActive[i].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, this.options.elements.point.backgroundColor); - this.lastActive[i]._model.borderColor = this.lastActive[i].custom && this.lastActive[i].custom.borderColor ? this.lastActive[i].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, this.options.elements.point.borderColor); - this.lastActive[i]._model.borderWidth = this.lastActive[i].custom && this.lastActive[i].custom.borderWidth ? this.lastActive[i].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.options.elements.point.borderWidth); - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } - - // Built in hover styling - if (this.active.length && this.options.hover.mode) { - switch (this.options.hover.mode) { - case 'single': - dataset = this.data.datasets[this.active[0]._datasetIndex]; - index = this.active[0]._index; - - this.active[0]._model.radius = this.active[0].custom && this.active[0].custom.hoverRadius ? this.active[0].custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.active[0]._model.radius + 2); - this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); - this.active[0]._model.borderColor = this.active[0].custom && this.active[0].custom.hoverBorderColor ? this.active[0].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(this.active[0]._model.borderColor).saturate(0.5).darken(0.1).rgbString()); - this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[0]._model.borderWidth + 2); - break; - case 'label': - for (var i = 0; i < this.active.length; i++) { - dataset = this.data.datasets[this.active[i]._datasetIndex]; - index = this.active[i]._index; - - this.active[i]._model.radius = this.active[i].custom && this.active[i].custom.hoverRadius ? this.active[i].custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.active[i]._model.radius + 2); - this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); - this.active[i]._model.borderColor = this.active[i].custom && this.active[i].custom.hoverBorderColor ? this.active[i].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(this.active[i]._model.borderColor).saturate(0.5).darken(0.1).rgbString()); - this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[i]._model.borderWidth + 2); - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } - - // Built in Tooltips - if (this.options.tooltips.enabled) { - - // The usual updates - this.tooltip.initialize(); - - // Active - if (this.active.length) { - this.tooltip._model.opacity = 1; - - helpers.extend(this.tooltip, { - _active: this.active, - }); - - this.tooltip.update(); - } else { - // Inactive - this.tooltip._model.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.hover.animationDuration); - } - } - - // Remember Last Active - this.lastActive = this.active; - return this; - }, - }); -}).call(this); - -(function() { - "use strict"; - - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; - - var defaultConfig = { - hover: { - mode: 'single', - }, - - scales: { - xAxes: [{ - type: "linear", // scatter should not use a dataset axis - display: true, - position: "bottom", - id: "x-axis-1", // need an ID so datasets can reference the scale - - // grid line settings - gridLines: { - show: true, - color: "rgba(0, 0, 0, 0.05)", - lineWidth: 1, - drawOnChartArea: true, - drawTicks: true, - zeroLineWidth: 1, - zeroLineColor: "rgba(0,0,0,0.25)", - }, - - // scale numbers - beginAtZero: false, - integersOnly: false, - override: null, - - // label settings - labels: { - show: true, - template: "<%=value.toLocaleString()%>", - fontSize: 12, - fontStyle: "normal", - fontColor: "#666", - fontFamily: "Helvetica Neue", - }, - }], - yAxes: [{ - type: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance - display: true, - position: "left", - id: "y-axis-1", - - // grid line settings - gridLines: { - show: true, - color: "rgba(0, 0, 0, 0.05)", - lineWidth: 1, - drawOnChartArea: true, - drawTicks: true, // draw ticks extending towards the label - zeroLineWidth: 1, - zeroLineColor: "rgba(0,0,0,0.25)", - }, - - // scale numbers - beginAtZero: false, - integersOnly: false, - override: null, - - // label settings - labels: { - show: true, - template: "<%=value.toLocaleString()%>", - fontSize: 12, - fontStyle: "normal", - fontColor: "#666", - fontFamily: "Helvetica Neue", - } - }], - }, - - //String - A legend template - legendTemplate: "", - - tooltips: { - template: "(<%= value.x %>, <%= value.y %>)", - multiTemplate: "<%if (datasetLabel){%><%=datasetLabel%>: <%}%>(<%= value.x %>, <%= value.y %>)", - }, - - }; - - - Chart.Type.extend({ - name: "Scatter", - defaults: defaultConfig, - initialize: function() { - - // Events - helpers.bindEvents(this, this.options.events, this.events); - - //Custom Point Defaults - helpers.each(this.data.datasets, function(dataset, datasetIndex) { - dataset.metaDataset = new Chart.Line({ - _chart: this.chart, - _datasetIndex: datasetIndex, - _points: dataset.metaData, - }); - - dataset.metaData = []; - - helpers.each(dataset.data, function(dataPoint, index) { - dataset.metaData.push(new Chart.Point({ - _datasetIndex: datasetIndex, - _index: index, - _chart: this.chart, - _model: { - x: 0, //xScale.getPixelForValue(null, index, true), - y: 0, //this.chartArea.bottom, - }, - })); - - }, this); - - // The line chart onlty supports a single x axis because the x axis is always a dataset axis - if (!dataset.xAxisID) { - dataset.xAxisID = this.options.scales.xAxes[0].id; - } - - if (!dataset.yAxisID) { - dataset.yAxisID = this.options.scales.yAxes[0].id; - } - - }, this); - - // Build and fit the scale. Needs to happen after the axis IDs have been set - this.buildScale(); - - // 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); - - // Need to fit scales before we reset elements. - Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - - // Reset so that we animation from the baseline - this.resetElements(); - - // Update that shiz - this.update(); - }, - nextPoint: function(collection, index) { - return collection[index + 1] || collection[index]; - }, - previousPoint: function(collection, index) { - return collection[index - 1] || collection[index]; - }, - resetElements: function() { - // Update the points - this.eachElement(function(point, index, dataset, datasetIndex) { - var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID]; - var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID]; - - var yScalePoint; - - if (yScale.min < 0 && yScale.max < 0) { - // all less than 0. use the top - yScalePoint = yScale.getPixelForValue(yScale.max); - } else if (yScale.min > 0 && yScale.max > 0) { - yScalePoint = yScale.getPixelForValue(yScale.min); - } else { - yScalePoint = yScale.getPixelForValue(0); - } - - helpers.extend(point, { - // Utility - _chart: this.chart, - _xScale: xScale, - _yScale: yScale, - _datasetIndex: datasetIndex, - _index: index, - - // Desired view properties - _model: { - x: xScale.getPixelForValue(this.data.datasets[datasetIndex].data[index].x), // value not used in dataset scale, but we want a consistent API between scales - y: yScalePoint, - - // Appearance - tension: point.custom && point.custom.tension ? point.custom.tension : this.options.elements.line.tension, - radius: point.custom && point.custom.radius ? point.custom.pointRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointRadius, index, this.options.elements.point.radius), - backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBackgroundColor, index, this.options.elements.point.backgroundColor), - borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderColor, index, this.options.elements.point.borderColor), - borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderWidth, index, this.options.elements.point.borderWidth), - skip: (this.data.datasets[datasetIndex].data[index] === null) || (this.data.datasets[datasetIndex].data[index].x === null) || (this.data.datasets[datasetIndex].data[index].y === null), - - // Tooltip - hoverRadius: point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointHitRadius, index, this.options.elements.point.hitRadius), - }, - }); - }, this); - - // Update control points for the bezier curve - this.eachElement(function(point, index, dataset, datasetIndex) { - var controlPoints = helpers.splineCurve( - this.previousPoint(dataset, index)._model, - point._model, - this.nextPoint(dataset, index)._model, - point._model.tension - ); - - point._model.controlPointPreviousX = controlPoints.previous.x; - point._model.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.chartArea.bottom) { - point._model.controlPointNextY = this.chartArea.bottom; - } else if (controlPoints.next.y < this.chartArea.top) { - point._model.controlPointNextY = this.chartArea.top; - } else { - point._model.controlPointNextY = controlPoints.next.y; - } - - // Cap inner bezier handles to the upper/lower scale bounds - if (controlPoints.previous.y > this.chartArea.bottom) { - point._model.controlPointPreviousY = this.chartArea.bottom; - } else if (controlPoints.previous.y < this.chartArea.top) { - point._model.controlPointPreviousY = this.chartArea.top; - } else { - point._model.controlPointPreviousY = controlPoints.previous.y; - } - - // Now pivot the point for animation - point.pivot(); - }, this); - }, - update: function() { - Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - - // Update the lines - this.eachDataset(function(dataset, datasetIndex) { - var yScale = this.scales[dataset.yAxisID]; - var scaleBase; - - if (yScale.min < 0 && yScale.max < 0) { - scaleBase = yScale.getPixelForValue(yScale.max); - } else if (yScale.min > 0 && yScale.max > 0) { - scaleBase = yScale.getPixelForValue(yScale.min); - } else { - scaleBase = yScale.getPixelForValue(0); - } - - helpers.extend(dataset.metaDataset, { - // Utility - _scale: yScale, - _datasetIndex: datasetIndex, - // Data - _children: dataset.metaData, - // Model - _model: { - // Appearance - tension: dataset.tension || this.options.elements.line.tension, - backgroundColor: dataset.backgroundColor || this.options.elements.line.backgroundColor, - borderWidth: dataset.borderWidth || this.options.elements.line.borderWidth, - borderColor: dataset.borderColor || this.options.elements.line.borderColor, - fill: dataset.fill !== undefined ? dataset.fill : this.options.elements.line.fill, // use the value from the dataset if it was provided. else fall back to the default - skipNull: dataset.skipNull !== undefined ? dataset.skipNull : this.options.elements.line.skipNull, - drawNull: dataset.drawNull !== undefined ? dataset.drawNull : this.options.elements.line.drawNull, - // Scale - scaleTop: yScale.top, - scaleBottom: yScale.bottom, - scaleZero: scaleBase, - }, - }); - - dataset.metaDataset.pivot(); - }); - - // Update the points - this.eachElement(function(point, index, dataset, datasetIndex) { - var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID]; - var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID]; - - helpers.extend(point, { - // Utility - _chart: this.chart, - _xScale: xScale, - _yScale: yScale, - _datasetIndex: datasetIndex, - _index: index, - - // Desired view properties - _model: { - x: xScale.getPixelForValue(this.data.datasets[datasetIndex].data[index].x), - y: yScale.getPixelForValue(this.data.datasets[datasetIndex].data[index].y), - - // Appearance - tension: point.custom && point.custom.tension ? point.custom.tension : this.options.elements.line.tension, - radius: point.custom && point.custom.radius ? point.custom.pointRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointRadius, index, this.options.elements.point.radius), - backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBackgroundColor, index, this.options.elements.point.backgroundColor), - borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderColor, index, this.options.elements.point.borderColor), - borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderWidth, index, this.options.elements.point.borderWidth), - skip: (this.data.datasets[datasetIndex].data[index] === null) || (this.data.datasets[datasetIndex].data[index].x === null) || (this.data.datasets[datasetIndex].data[index].y === null), - - // Tooltip - hoverRadius: point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointHitRadius, index, this.options.elements.point.hitRadius), - }, - }); - }, this); - - - // Update control points for the bezier curve - this.eachElement(function(point, index, dataset, datasetIndex) { - var controlPoints = helpers.splineCurve( - this.previousPoint(dataset, index)._model, - point._model, - this.nextPoint(dataset, index)._model, - point._model.tension - ); - - point._model.controlPointPreviousX = controlPoints.previous.x; - point._model.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.chartArea.bottom) { - point._model.controlPointNextY = this.chartArea.bottom; - } else if (controlPoints.next.y < this.chartArea.top) { - point._model.controlPointNextY = this.chartArea.top; - } else { - point._model.controlPointNextY = controlPoints.next.y; - } - - // Cap inner bezier handles to the upper/lower scale bounds - if (controlPoints.previous.y > this.chartArea.bottom) { - point._model.controlPointPreviousY = this.chartArea.bottom; - } else if (controlPoints.previous.y < this.chartArea.top) { - point._model.controlPointPreviousY = this.chartArea.top; - } else { - point._model.controlPointPreviousY = controlPoints.previous.y; - } - - // Now pivot the point for animation - point.pivot(); - }, this); - - this.render(); - }, - buildScale: function() { - var self = this; - - var calculateXRange = function() { - this.min = null; - this.max = null; - - helpers.each(self.data.datasets, function(dataset) { - // Only set the scale range for datasets that actually use this axis - if (dataset.xAxisID === this.id) { - helpers.each(dataset.data, function(value) { - if (this.min === null) { - this.min = value.x; - } else if (value.x < this.min) { - this.min = value.x; - } - - if (this.max === null) { - this.max = value.x; - } else if (value.x > this.max) { - this.max = value.x; - } - }, this); - } - }, this); - }; - - var calculateYRange = function() { - this.min = null; - this.max = null; - - helpers.each(self.data.datasets, function(dataset) { - if (dataset.yAxisID === this.id) { - helpers.each(dataset.data, function(value) { - if (this.min === null) { - this.min = value.y; - } else if (value.y < this.min) { - this.min = value.y; - } - - if (this.max === null) { - this.max = value.y; - } else if (value.y > this.max) { - this.max = value.y; - } - }, this); - } - }, this); - }; - - // Map of scale ID to scale object so we can lookup later - this.scales = {}; - - helpers.each(this.options.scales.xAxes, function(xAxisOptions) { - var ScaleClass = Chart.scales.getScaleConstructor(xAxisOptions.type); - var scale = new ScaleClass({ - ctx: this.chart.ctx, - options: xAxisOptions, - calculateRange: calculateXRange, - id: xAxisOptions.id, - }); - - this.scales[scale.id] = scale; - }, this); - - helpers.each(this.options.scales.yAxes, function(yAxisOptions) { - var ScaleClass = Chart.scales.getScaleConstructor(yAxisOptions.type); - var scale = new ScaleClass({ - ctx: this.chart.ctx, - options: yAxisOptions, - calculateRange: calculateYRange, - id: yAxisOptions.id, - getPointPixelForValue: function(value, index, datasetIndex) { - return this.getPixelForValue(value); - } - }); - - this.scales[scale.id] = scale; - }, this); - }, - draw: function(ease) { - var easingDecimal = ease || 1; - this.clear(); - - // Draw all the scales - helpers.each(this.scales, function(scale) { - scale.draw(this.chartArea); - }, this); - - // reverse for-loop for proper stacking - for (var i = this.data.datasets.length - 1; i >= 0; i--) { - - var dataset = this.data.datasets[i]; - - // 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(); - }); - } - - // Finally draw the tooltip - this.tooltip.transition(easingDecimal).draw(); - }, - events: function(e) { - // If exiting chart - if (e.type == 'mouseout') { - return this; - } - - this.lastActive = this.lastActive || []; - - // Find Active Elements - this.active = function() { - switch (this.options.hover.mode) { - 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.hover.onHover) { - this.options.hover.onHover.call(this, this.active); - } - - var dataset; - var index; - // Remove styling for last active (even if it may still be active) - if (this.lastActive.length) { - switch (this.options.hover.mode) { - case 'single': - dataset = this.data.datasets[this.lastActive[0]._datasetIndex]; - index = this.lastActive[0]._index; - - this.lastActive[0]._model.radius = this.lastActive[0].custom && this.lastActive[0].custom.radius ? this.lastActive[0].custom.pointRadius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, this.options.elements.point.radius); - this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, this.options.elements.point.backgroundColor); - this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, this.options.elements.point.borderColor); - this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.options.elements.point.borderWidth); - break; - case 'label': - for (var i = 0; i < this.lastActive.length; i++) { - dataset = this.data.datasets[this.lastActive[i]._datasetIndex]; - index = this.lastActive[i]._index; - - this.lastActive[i]._model.radius = this.lastActive[i].custom && this.lastActive[i].custom.radius ? this.lastActive[i].custom.pointRadius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, this.options.elements.point.radius); - this.lastActive[i]._model.backgroundColor = this.lastActive[i].custom && this.lastActive[i].custom.backgroundColor ? this.lastActive[i].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, this.options.elements.point.backgroundColor); - this.lastActive[i]._model.borderColor = this.lastActive[i].custom && this.lastActive[i].custom.borderColor ? this.lastActive[i].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, this.options.elements.point.borderColor); - this.lastActive[i]._model.borderWidth = this.lastActive[i].custom && this.lastActive[i].custom.borderWidth ? this.lastActive[i].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.options.elements.point.borderWidth); - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } - - // Built in hover styling - if (this.active.length && this.options.hover.mode) { - switch (this.options.hover.mode) { - case 'single': - dataset = this.data.datasets[this.active[0]._datasetIndex]; - index = this.active[0]._index; - - this.active[0]._model.radius = this.active[0].custom && this.active[0].custom.hoverRadius ? this.active[0].custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.active[0]._model.radius + 1); - this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); - this.active[0]._model.borderColor = this.active[0].custom && this.active[0].custom.hoverBorderColor ? this.active[0].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(this.active[0]._model.borderColor).saturate(0.5).darken(0.1).rgbString()); - this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[0]._model.borderWidth); - break; - case 'label': - for (var i = 0; i < this.active.length; i++) { - dataset = this.data.datasets[this.active[i]._datasetIndex]; - index = this.active[i]._index; - - this.active[i]._model.radius = this.active[i].custom && this.active[i].custom.hoverRadius ? this.active[i].custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.active[i]._model.radius + 1); - this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); - this.active[i]._model.borderColor = this.active[i].custom && this.active[i].custom.hoverBorderColor ? this.active[i].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(this.active[i]._model.borderColor).saturate(0.5).darken(0.1).rgbString()); - this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[i]._model.borderWidth); - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } - - // Built in Tooltips - if (this.options.tooltips.enabled) { - - // The usual updates - this.tooltip.initialize(); - - // Active - if (this.active.length) { - this.tooltip._model.opacity = 1; - - helpers.extend(this.tooltip, { - _active: this.active, - }); - - this.tooltip.update(); - } else { - // Inactive - this.tooltip._model.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; - - }, - }); + "use strict"; + + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; + + + Chart.defaults.bar = { + hover: { + mode: "label" + }, + + scales: { + xAxes: [{ + type: "category", + categorySpacing: 10, + spacing: 1, + + // grid line settings + gridLines: { + offsetGridLines: true, + }, + }], + yAxes: [{ + type: "linear", + }], + }, + }; + + // Chart.Type.extend({ + // name: "Bar", + // defaults: defaultConfig, + // initialize: function() { + // this.elementController = new Chart.RectangularElementController(this); + // this.canvasController = new Chart.RectangularCanvasController(this, this.elementController); + + // //Create a new bar for each piece of data + // helpers.each(this.data.datasets, function(dataset, datasetIndex) { + // helpers.each(dataset.data, function(dataPoint, index) { + // this.elementController.addRectangle(dataset, datasetIndex, index); + // }, this); + + // // The bar chart only supports a single x axis because the x axis is always a category axis + // dataset.xAxisID = this.options.scales.xAxes[0].id; + + // if (!dataset.yAxisID) { + // dataset.yAxisID = this.options.scales.yAxes[0].id; + // } + // }, this); + + // this.canvasController.initialize(); + // }, + // draw: function(ease) { + + // var easingDecimal = ease || 1; + // this.clear(); + + // // Draw all the scales + // helpers.each(this.scales, function(scale) { + // scale.draw(this.chartArea); + // }, this); + + // //Draw all the bars for each dataset + // this.eachElement(function(bar, index, datasetIndex) { + // bar.transition(easingDecimal).draw(); + // }, this); + + // // Finally draw the tooltip + // this.tooltip.transition(easingDecimal).draw(); + // }, + // }); }).call(this); diff --git a/Chart.min.js b/Chart.min.js index ebde0d71a..858da0d56 100644 --- a/Chart.min.js +++ b/Chart.min.js @@ -7,7 +7,8 @@ * 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,o=this.height=e(t.canvas,"Height")||t.canvas.height;return t.canvas.width=i,t.canvas.height=o,i=this.width=t.canvas.width,o=this.height=t.canvas.height,this.aspectRatio=this.width/this.height,s.retinaScale(this),this},o="rgba(0,0,0,0.1)";i.defaults={global:{responsive:!0,maintainAspectRatio:!0,events:["mousemove","mouseout","click","touchstart","touchmove","touchend"],hover:{onHover:null,mode:"single",animationDuration:400},onClick:null,defaultColor:o,elements:{}}},i.types={};{var s=i.helpers={},a=s.each=function(t,e,i){var o=Array.prototype.slice.call(arguments,3);if(t)if(t.length===+t.length){var s;for(s=0;s=0;o--){var s=t[o];if(e(s))return s}},s.inherits=function(t){var e=this,i=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return e.apply(this,arguments)},o=function(){this.constructor=i};return o.prototype=e.prototype,i.prototype=new o,i.extend=h,t&&n(i.prototype,t),i.__super__=e.prototype,i}),l=s.noop=function(){},c=s.uid=function(){var t=0;return function(){return"chart-"+t++}}(),d=s.warn=function(t){window.console&&"function"==typeof window.console.warn&&console.warn(t)},u=s.amd="function"==typeof define&&define.amd,m=s.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},v=s.max=function(t){return Math.max.apply(Math,t)},g=s.min=function(t){return Math.min.apply(Math,t)},p=(s.sign=function(t){return Math.sign?Math.sign(t):(t=+t,0===t||isNaN(t)?t:t>0?1:-1)},s.log10=function(t){return Math.log10?Math.log10(t):Math.log(t)/Math.LN10},s.cap=function(t,e,i){if(m(e)){if(t>e)return e}else if(m(i)&&i>t)return i;return t},s.getDecimalPlaces=function(t){if(t%1!==0&&m(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},s.toRadians=function(t){return t*(Math.PI/180)},s.toDegrees=function(t){return t*(180/Math.PI)},s.getAngleFromPoint=function(t,e){var i=e.x-t.x,o=e.y-t.y,s=Math.sqrt(i*i+o*o),a=Math.atan2(o,i);return a<-.5*Math.PI&&(a+=2*Math.PI),{angle:a,distance:s}},s.aliasPixel=function(t){return t%2===0?0:.5},s.splineCurve=function(t,e,i,o){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)),r=o*s/(s+a),n=o*a/(s+a);return{previous:{x:e.x-r*(i.x-t.x),y:e.y-r*(i.y-t.y)},next:{x:e.x+n*(i.x-t.x),y:e.y+n*(i.y-t.y)}}},s.calculateOrderOfMagnitude=function(t){return Math.floor(Math.log(t)/Math.LN10)}),f=(s.calculateScaleRange=function(t,e,i,o,s){var a=2,r=Math.floor(e/(1.5*i)),n=a>=r,h=v(t),l=g(t);h===l&&(h+=.5,l>=.5&&!o?l-=.5:h+=.5);for(var c=Math.abs(h-l),d=p(c),u=Math.ceil(h/(1*Math.pow(10,d)))*Math.pow(10,d),m=o?0:Math.floor(l/(1*Math.pow(10,d)))*Math.pow(10,d),f=u-m,b=Math.pow(10,d),x=Math.round(f/b);(x>r||r>2*x)&&!n;)if(x>r)b*=2,x=Math.round(f/b),x%1!==0&&(n=!0);else if(s&&d>=0){if(b/2%1!==0)break;b/=2,x=Math.round(f/b)}else b/=2,x=Math.round(f/b);return n&&(x=a,b=f/x),{steps:x,stepValue:b,min:m,max:m+x*b}},s.niceNum=function(t,e){var i,o=Math.floor(s.log10(t)),a=t/Math.pow(10,o);return i=e?1.5>a?1:3>a?2:7>a?5:10:1>=a?1:2>=a?2:5>=a?5:10,i*Math.pow(10,o)},s.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('');"):o[t]=o[t];return e?i(e):i}if(t instanceof Function)return t(e);var o={};return i(t,e)}),b=(s.generateLabels=function(t,e,i,o){var s=new Array(e);return t&&a(s,function(e,a){s[a]=f(t,{value:i+o*(a+1)})}),s},s.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,o=1;return 0===t?0:1==(t/=1)?1:(i||(i=.3),ot?-.5*o*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-e)*Math.PI/i):o*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-b.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*b.easeInBounce(2*t):.5*b.easeOutBounce(2*t-1)+.5}}),x=s.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),A=(s.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),s.animationLoop=function(t,e,i,o,s,a){var r=0,n=b[i]||b.linear,h=function(){r++;var i=r/e,l=n(i);t.call(a,l,i,r),o.call(a,l,i),e>r?a.animationFrame=x(h):s.apply(a)};x(h)},s.getRelativePosition=function(t){var e,i,o=t.originalEvent||t,s=t.currentTarget||t.srcElement,a=s.getBoundingClientRect();return o.touches?(e=o.touches[0].clientX-a.left,i=o.touches[0].clientY-a.top):(e=o.clientX-a.left,i=o.clientY-a.top),{x:e,y:i}},s.addEvent=function(t,e,i){t.addEventListener?t.addEventListener(e,i):t.attachEvent?t.attachEvent("on"+e,i):t["on"+e]=i}),C=s.removeEvent=function(t,e,i){t.removeEventListener?t.removeEventListener(e,i,!1):t.detachEvent?t.detachEvent("on"+e,i):t["on"+e]=l},y=(s.bindEvents=function(t,e,i){t.events||(t.events={}),a(e,function(e){t.events[e]=function(){i.apply(t,arguments)},A(t.chart.canvas,e,t.events[e])})},s.unbindEvents=function(t,e){a(e,function(e,i){C(t.chart.canvas,i,e)})}),w=s.getMaximumWidth=function(t){var e=t.parentNode,i=parseInt(k(e,"padding-left"))+parseInt(k(e,"padding-right"));return e.clientWidth-i},_=s.getMaximumHeight=function(t){var e=t.parentNode,i=parseInt(k(e,"padding-bottom"))+parseInt(k(e,"padding-top"));return e.clientHeight-i},k=s.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)},S=(s.getMaximumSize=s.getMaximumWidth,s.retinaScale=function(t){var e=t.ctx,i=t.canvas.width,o=t.canvas.height;window.devicePixelRatio&&(e.canvas.style.width=i+"px",e.canvas.style.height=o+"px",e.canvas.height=o*window.devicePixelRatio,e.canvas.width=i*window.devicePixelRatio,e.scale(window.devicePixelRatio,window.devicePixelRatio))}),I=s.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)};s.fontString=function(t,e,i){return e+" "+t+"px "+i},s.longestText=function(t,e,i){t.font=e;var o=0;return a(i,function(e){var i=t.measureText(e).width;o=i>o?i:o}),o},s.drawRoundedRectangle=function(t,e,i,o,s,a){t.beginPath(),t.moveTo(e+a,i),t.lineTo(e+o-a,i),t.quadraticCurveTo(e+o,i,e+o,i+a),t.lineTo(e+o,i+s-a),t.quadraticCurveTo(e+o,i+s,e+o-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()},s.color=function(t){return window.Color?window.Color(t):(console.log("Color.js not found!"),t)},s.isArray=function(t){return Array.isArray?Array.isArray(t):"[object Array]"===Object.prototype.toString.call(arg)}}i.instances={},i.Type=function(t,e){this.data=t.data,this.options=t.options,this.chart=e,this.id=c(),i.instances[this.id]=this,this.options.responsive&&this.resize(),this.initialize.call(this)},n(i.Type.prototype,{initialize:function(){return this},clear:function(){return I(this.chart),this},stop:function(){return i.animationService.cancelAnimation(this),this},resize:function(){this.stop();var t=this.chart.canvas,e=w(this.chart.canvas),i=this.options.maintainAspectRatio?e/this.chart.aspectRatio:_(this.chart.canvas);return t.width=this.chart.width=e,t.height=this.chart.height=i,S(this.chart),this},redraw:l,render:function(t){if(0!==this.options.animation.duration||t){var e=new i.Animation;e.numSteps=(t||this.options.animation.duration)/16.66,e.easing=this.options.animation.easing,e.render=function(t,e){var i=s.easingEffects[e.easing],o=e.currentStep/e.numSteps,a=i(o);t.draw(a,o,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){s.each(this.data.datasets,function(e,i){s.each(e.metaData,t,this,e.metaData,i)},this)},eachValue:function(t){s.each(this.data.datasets,function(e,i){s.each(e.data,t,this,i)},this)},eachDataset:function(t){s.each(this.data.datasets,t,this)},getElementsAtEvent:function(t){for(var e,i=[],o=s.getRelativePosition(t),a=function(t){i.push(t.metaData[e])},r=0;r=n[a].length||!n[a][i].type?n[a].push(e.configMerge(t.type?Chart.scaleService.getScaleDefaults(t.type):{},t)):t.type!==n[a][i].type?n[a][i]=e.configMerge(n[a][i],t.type?Chart.scaleService.getScaleDefaults(t.type):{},t):n[a][i]=e.configMerge(n[a][i],t)}):(n[a]=[],e.each(t,function(t){n[a].push(e.configMerge(t.type?Chart.scaleService.getScaleDefaults(t.type):{},t))})):n.hasOwnProperty(a)&&"object"==typeof n[a]&&null!==n[a]&&"object"==typeof t?n[a]=e.configMerge(n[a],t):n[a]=t)}),n},e.getValueAtIndexOrDefault=function(t,i,a){return t?e.isArray(t)&&i=0;a--){var n=t[a];if(e(n))return n}},e.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=s,t&&n(i.prototype,t),i.__super__=e.prototype,i}),o=e.noop=function(){},r=(e.uid=function(){var t=0;return function(){return"chart-"+t++}}(),e.warn=function(t){window.console&&"function"==typeof window.console.warn&&console.warn(t)},e.amd="function"==typeof define&&define.amd,e.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)}),h=e.max=function(t){return Math.max.apply(Math,t)},l=e.min=function(t){return Math.min.apply(Math,t)},c=(e.sign=function(t){return Math.sign?Math.sign(t):(t=+t,0===t||isNaN(t)?t:t>0?1:-1)},e.log10=function(t){return Math.log10?Math.log10(t):Math.log(t)/Math.LN10},e.cap=function(t,e,i){if(r(e)){if(t>e)return e}else if(r(i)&&i>t)return i;return t},e.getDecimalPlaces=function(t){if(t%1!==0&&r(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},e.toRadians=function(t){return t*(Math.PI/180)},e.toDegrees=function(t){return t*(180/Math.PI)},e.getAngleFromPoint=function(t,e){var i=e.x-t.x,a=e.y-t.y,n=Math.sqrt(i*i+a*a),s=Math.atan2(a,i);return s<-.5*Math.PI&&(s+=2*Math.PI),{angle:s,distance:n}},e.aliasPixel=function(t){return t%2===0?0:.5},e.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{previous:{x:e.x-o*(i.x-t.x),y:e.y-o*(i.y-t.y)},next:{x:e.x+r*(i.x-t.x),y:e.y+r*(i.y-t.y)}}},e.nextItem=function(t,e,i){return i?t[e+1]||t[0]:t[e+1]||t[t.length-1]},e.previousItem=function(t,e,i){return i?t[e-1]||t[t.length-1]:t[e-1]||t[0]},e.calculateOrderOfMagnitude=function(t){return Math.floor(Math.log(t)/Math.LN10)}),u=(e.calculateScaleRange=function(t,e,i,a,n){var s=2,o=Math.floor(e/(1.5*i)),r=s>=o,u=h(t),d=l(t);u===d&&(u+=.5,d>=.5&&!a?d-=.5:u+=.5);for(var f=Math.abs(u-d),g=c(f),p=Math.ceil(u/(1*Math.pow(10,g)))*Math.pow(10,g),v=a?0:Math.floor(d/(1*Math.pow(10,g)))*Math.pow(10,g),m=p-v,b=Math.pow(10,g),x=Math.round(m/b);(x>o||o>2*x)&&!r;)if(x>o)b*=2,x=Math.round(m/b),x%1!==0&&(r=!0);else if(n&&g>=0){if(b/2%1!==0)break;b/=2,x=Math.round(m/b)}else b/=2,x=Math.round(m/b);return r&&(x=s,b=m/x),{steps:x,stepValue:b,min:v,max:v+x*b}},e.niceNum=function(t,i){var a,n=Math.floor(e.log10(t)),s=t/Math.pow(10,n);return a=i?1.5>s?1:3>s?2:7>s?5:10:1>=s?1:2>=s?2:5>=s?5:10,a*Math.pow(10,n)},e.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)}),d=(e.generateLabels=function(t,e,a,n){var s=new Array(e);return t&&i(s,function(e,i){s[i]=u(t,{value:a+n*(i+1)})}),s},e.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-d.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*d.easeInBounce(2*t):.5*d.easeOutBounce(2*t-1)+.5}}),f=e.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),g=(e.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),e.animationLoop=function(t,e,i,a,n,s){var o=0,r=d[i]||d.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=f(h):n.apply(s)};f(h)},e.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}},e.addEvent=function(t,e,i){t.addEventListener?t.addEventListener(e,i):t.attachEvent?t.attachEvent("on"+e,i):t["on"+e]=i}),p=e.removeEvent=function(t,e,i){t.removeEventListener?t.removeEventListener(e,i,!1):t.detachEvent?t.detachEvent("on"+e,i):t["on"+e]=o},v=(e.bindEvents=function(t,e,a){t.events||(t.events={}),i(e,function(e){t.events[e]=function(){a.apply(t,arguments)},g(t.chart.canvas,e,t.events[e])})},e.unbindEvents=function(t,e){i(e,function(e,i){p(t.chart.canvas,i,e)})},e.getMaximumWidth=function(t){var e=t.parentNode,i=parseInt(v(e,"padding-left"))+parseInt(v(e,"padding-right"));return e.clientWidth-i},e.getMaximumHeight=function(t){var e=t.parentNode,i=parseInt(v(e,"padding-bottom"))+parseInt(v(e,"padding-top"));return e.clientHeight-i},e.getStyle=function(t,e){return t.currentStyle?t.currentStyle[e]:document.defaultView.getComputedStyle(t,null).getPropertyValue(e)});e.getMaximumSize=e.getMaximumWidth,e.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))},e.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},e.fontString=function(t,e,i){return e+" "+t+"px "+i},e.longestText=function(t,e,a){t.font=e;var n=0;return i(a,function(e){var i=t.measureText(e).width;n=i>n?i:n}),n},e.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()},e.color=function(t){return window.Color?window.Color(t):(console.log("Color.js not found!"),t)},e.isArray=function(t){return Array.isArray?Array.isArray(t):"[object Array]"===Object.prototype.toString.call(arg)}}.call(this),function(){"use strict";var t=this,e=(t.Chart,Chart.helpers);Chart.elements={},Chart.Element=function(t){e.extend(this,t),this.initialize.apply(this,arguments)},e.extend(Chart.Element.prototype,{initialize:function(){},pivot:function(){return this._view||(this._view=e.clone(this._model)),this._start=e.clone(this._view),this},transition:function(t){return this._view||(this._view=e.clone(this._model)),this._start||this.pivot(),e.each(this._model,function(i,a){if("_"!==a[0]&&this._model.hasOwnProperty(a))if(this._view[a])if(this._model[a]===this._view[a]);else if("string"==typeof i)try{var n=e.color(this._start[a]).mix(e.color(this._model[a]),t);this._view[a]=n.rgbString()}catch(s){this._view[a]=i}else if("number"==typeof i){var o=void 0!==this._start[a]?this._start[a]:0;this._view[a]=(this._model[a]-o)*t+o}else this._view[a]=i;else"number"==typeof i?this._view[a]=i*t:this._view[a]=i||null;else;},this),1===t&&delete this._start,this},tooltipPosition:function(){return{x:this._model.x,y:this._model.y}},hasValue:function(){return e.isNumber(this._model.x)&&e.isNumber(this._model.y)}}),Chart.Element.extend=e.inherits}.call(this),function(){"use strict";var t=this,e=t.Chart,i=e.helpers;e.defaults.global.animation={duration:1e3,easing:"easeOutQuart",onProgress:function(){},onComplete:function(){}},e.Animation=e.Element.extend({currentStep:null,numSteps:60,easing:"",render:null,onAnimationProgress:null,onAnimationComplete:null}),e.animationService={frameDuration:17,animations:[],dropFrames:0,addAnimation:function(t,e,a){a||(t.animating=!0);for(var n=0;n1&&(e=Math.floor(this.dropFrames),this.dropFrames-=e);for(var a=0;athis.animations[a].animationObject.numSteps&&(this.animations[a].animationObject.currentStep=this.animations[a].animationObject.numSteps),this.animations[a].animationObject.render(this.animations[a].chartInstance,this.animations[a].animationObject),this.animations[a].animationObject.currentStep==this.animations[a].animationObject.numSteps&&(this.animations[a].chartInstance.animating=!1,this.animations.splice(a,1),a--);var n=Date.now(),s=n-t-this.frameDuration,o=s/this.frameDuration;o>1&&(this.dropFrames+=o),this.animations.length>0&&i.requestAnimFrame.call(window,this.digestWrapper)}}}.call(this),function(){"use strict";var t=this,e=(t.Chart,Chart.helpers);Chart.types={},Chart.instances={},Chart.controllers={},Chart.Controller=function(t){return this.chart=t,this.config=t.config,this.data=this.config.data,this.options=this.config.options=e.configMerge(Chart.defaults.global,Chart.defaults[this.config.type],this.config.options||{}),this.id=e.uid(),Chart.instances[this.id]=this,this.options.responsive&&this.resize(),this.initialize.call(this),this},e.extend(Chart.Controller.prototype,{initialize:function(){return this.bindEvents(),this.buildScales(),this.buildControllers(),this.resetElements(),this.initToolTip(),this.update(),this},clear:function(){return e.clear(this.chart),this},stop:function(){return Chart.animationService.cancelAnimation(this),this},resize:function(){this.stop();var t=this.chart.canvas,i=e.getMaximumWidth(this.chart.canvas),a=this.options.maintainAspectRatio?i/this.chart.aspectRatio:getMaximumHeight(this.chart.canvas);return t.width=this.chart.width=i,t.height=this.chart.height=a,e.retinaScale(this.chart),this},buildScales:function(){this.scales={},e.each(this.options.scales.xAxes,function(t){var e=Chart.scaleService.getScaleConstructor(t.type),i=new e({ctx:this.chart.ctx,options:t,data:this.data,id:t.id});this.scales[i.id]=i},this),e.each(this.options.scales.yAxes,function(t){var e=Chart.scaleService.getScaleConstructor(t.type),i=new e({ctx:this.chart.ctx,options:t,data:this.data,id:t.id});this.scales[i.id]=i},this),Chart.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height)},buildControllers:function(){this.eachDataset(function(t,e){var i=t.type||this.config.type;return t.controller?void t.controller.updateIndex(e):void(t.controller=new Chart.controllers[i](this,e))})},resetElements:function(){this.eachDataset(function(t,e){t.controller.reset()})},update:function(t){Chart.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height),this.eachDataset(function(t,e){t.controller.update()}),this.render(t)},render:function(t){if(0!==this.options.animation.duration||t){var i=new Chart.Animation;i.numSteps=(t||this.options.animation.duration)/16.66,i.easing=this.options.animation.easing,i.render=function(t,i){var a=e.easingEffects[i.easing],n=i.currentStep/i.numSteps,s=a(n);t.draw(s,n,i.currentStep)},i.onAnimationProgress=this.options.onAnimationProgress,i.onAnimationComplete=this.options.onAnimationComplete,Chart.animationService.addAnimation(this,i,t)}else this.draw(),this.options.onAnimationComplete.call(this);return this},draw:function(t){var i=t||1;this.clear(),e.each(this.scales,function(t){t.draw(this.chartArea)},this),e.each(this.data.datasets,function(e,i){e.controller.draw(t)},this),this.tooltip.transition(i).draw()},eachValue:function(t){e.each(this.data.datasets,function(i,a){e.each(i.data,t,this,a)},this)},eachElement:function(t){e.each(this.data.datasets,function(i,a){e.each(i.metaData,t,this,i.metaData,a)},this)},eachDataset:function(t){e.each(this.data.datasets,t,this)},nextElement:function(t,e,i){return this.loop?dataset[e+1]||dataset[0]:t[e+1]||t[e]},previousElement:function(t,e,i){return this.loop?dataset[e-1]||dataset[dataset.length-1]:t[e-1]||t[e]},getElementAtEvent:function(t){for(var i=[],a=e.getRelativePosition(t),n=0;n30?5:2,s=a>30?5:2;if(t){var o=i.where(t.scales,function(t){return"left"==t.options.position}),r=i.where(t.scales,function(t){return"right"==t.options.position}),h=i.where(t.scales,function(t){return"top"==t.options.position}),l=i.where(t.scales,function(t){return"bottom"==t.options.position}),c=e/2,u=a/2;c-=2*n,u-=2*s;var d=(e-c)/(o.length+r.length),f=(a-u)/(h.length+l.length),g=[],p=function(t){var e=t.fit(d,u);g.push({horizontal:!1,minSize:e,scale:t})},v=function(t){var e=t.fit(c,f);g.push({horizontal:!0,minSize:e,scale:t})};i.each(o,p),i.each(r,p),i.each(h,v),i.each(l,v);var m=a-2*s,b=e-2*n;i.each(g,function(t){t.horizontal?m-=t.minSize.height:b-=t.minSize.width});var x=function(t){var e=i.findNextWhere(g,function(e){return e.scale===t});e&&t.fit(e.minSize.width,m)},y=function(t){var e=i.findNextWhere(g,function(e){return e.scale===t}),a={left:w,right:k,top:0,bottom:0};e&&t.fit(b,e.minSize.height,a)},w=n,k=n,_=s,C=s;i.each(o,x),i.each(r,x),i.each(o,function(t){w+=t.width}),i.each(r,function(t){k+=t.width}),i.each(h,y),i.each(l,y),i.each(h,function(t){_+=t.height}),i.each(l,function(t){C+=t.height}),i.each(o,function(t){var e=i.findNextWhere(g,function(e){return e.scale===t}),a={left:0,right:0,top:_,bottom:C};e&&t.fit(e.minSize.width,m,a)}),i.each(r,function(t){var e=i.findNextWhere(g,function(e){return e.scale===t}),a={left:0,right:0,top:_,bottom:C};e&&t.fit(e.minSize.width,m,a)});var S=n,P=s,M=function(t){t.left=S,t.right=S+t.width,t.top=_,t.bottom=_+m,S=t.right},A=function(t){t.left=w,t.right=w+b,t.top=P,t.bottom=P+t.height,P=t.bottom};i.each(o,M),i.each(h,A),S+=b,P+=m,i.each(r,M),i.each(l,A),t.chartArea={left:w,top:_,right:w+b,bottom:_+m}}}}}.call(this),function(){"use strict";var t=this,e=t.Chart,i=e.helpers;e.defaults.global.tooltips={enabled:!0,custom:null,backgroundColor:"rgba(0,0,0,0.8)",fontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",fontSize:10,fontStyle:"normal",fontColor:"#fff",titleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",titleFontSize:12,titleFontStyle:"bold",titleFontColor:"#fff",yPadding:6,xPadding:6,caretSize:8,cornerRadius:6,xOffset:10,template:["<% if(label){ %>","<%=label %>: ","<% } %>","<%=value %>"].join(""),multiTemplate:["<%if (datasetLabel){ %>","<%=datasetLabel %>: ","<% } %>","<%=value %>"].join(""),multiKeyBackground:"#fff"},e.Tooltip=e.Element.extend({initialize:function(){var t=this._options;i.extend(this,{_model:{xPadding:t.tooltips.xPadding,yPadding:t.tooltips.yPadding,xOffset:t.tooltips.xOffset,textColor:t.tooltips.fontColor,_fontFamily:t.tooltips.fontFamily,_fontStyle:t.tooltips.fontStyle,fontSize:t.tooltips.fontSize,titleTextColor:t.tooltips.titleFontColor,_titleFontFamily:t.tooltips.titleFontFamily,_titleFontStyle:t.tooltips.titleFontStyle,titleFontSize:t.tooltips.titleFontSize,caretHeight:t.tooltips.caretSize,cornerRadius:t.tooltips.cornerRadius,backgroundColor:t.tooltips.backgroundColor,opacity:0,legendColorBackground:t.tooltips.multiKeyBackground}})},update:function(){var t=this._chart.ctx;switch(this._options.hover.mode){case"single":i.extend(this._model,{text:i.template(this._options.tooltips.template,{element:this._active[0],value:this._data.datasets[this._active[0]._datasetIndex].data[this._active[0]._index],label:this._data.labels?this._data.labels[this._active[0]._index]:""})});var e=this._active[0].tooltipPosition();i.extend(this._model,{x:Math.round(e.x),y:Math.round(e.y),caretPadding:e.padding});break;case"label":for(var a,n,s=[],o=[],r=this._data.datasets.length-1;r>=0&&(a=this._data.datasets[r].metaData,n=i.indexOf(a,this._active[0]),-1===n);r--);var h=function(t){var e,a,r,h,l,c=[],u=[],d=[];return i.each(this._data.datasets,function(t){e=t.metaData,e[n]&&e[n].hasValue()&&c.push(e[n])},this),i.each(this._options.stacked?c.reverse():c,function(t){u.push(t._view.x),d.push(t._view.y),s.push(i.template(this._options.tooltips.multiTemplate,{element:t,datasetLabel:this._data.datasets[t._datasetIndex].label,value:this._data.datasets[t._datasetIndex].data[t._index]})),o.push({fill:t._view.backgroundColor,stroke:t._view.borderColor})},this),l=i.min(d),r=i.max(d),h=i.min(u),a=i.max(u),{x:h>this._chart.width/2?h:a,y:(l+r)/2}}.call(this,n);i.extend(this._model,{x:h.x,y:h.y,labels:s,title:this._data.labels&&this._data.labels.length?this._data.labels[this._active[0]._index]:"",legendColors:o,legendBackgroundColor:this._options.tooltips.multiKeyBackground}),this._model.height=s.length*this._model.fontSize+(s.length-1)*(this._model.fontSize/2)+2*this._model.yPadding+1.5*this._model.titleFontSize;var l=t.measureText(this.title).width,c=i.longestText(t,this.font,s)+this._model.fontSize+3,u=i.max([c,l]);this._model.width=u+2*this._model.xPadding;var d=this._model.height/2;this._model.y-d<0?this._model.y=d:this._model.y+d>this._chart.height&&(this._model.y=this._chart.height-d),this._model.x>this._chart.width/2?this._model.x-=this._model.xOffset+this._model.width:this._model.x+=this._model.xOffset}return this},draw:function(){var t=this._chart.ctx,e=this._view;switch(this._options.hover.mode){case"single":t.font=i.fontString(e.fontSize,e._fontStyle,e._fontFamily),e.xAlign="center",e.yAlign="above";var a=e.caretPadding||2,n=t.measureText(e.text).width+2*e.xPadding,s=e.fontSize+2*e.yPadding,o=s+e.caretHeight+a;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=i.color(e.backgroundColor).alpha(e.opacity).rgbString(),this._custom)this._custom(this._view);else{switch(e.yAlign){case"above":t.beginPath(),t.moveTo(e.x,e.y-a),t.lineTo(e.x+e.caretHeight,e.y-(a+e.caretHeight)),t.lineTo(e.x-e.caretHeight,e.y-(a+e.caretHeight)),t.closePath(),t.fill();break;case"below":h=e.y+a+e.caretHeight,t.beginPath(),t.moveTo(e.x,e.y+a),t.lineTo(e.x+e.caretHeight,e.y+a+e.caretHeight),t.lineTo(e.x-e.caretHeight,e.y+a+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)}i.drawRoundedRectangle(t,r,h,n,s,e.cornerRadius),t.fill(),t.fillStyle=i.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":i.drawRoundedRectangle(t,e.x,e.y-e.height/2,e.width,e.height,e.cornerRadius),t.fillStyle=i.color(e.backgroundColor).alpha(e.opacity).rgbString(),t.fill(),t.closePath(),t.textAlign="left",t.textBaseline="middle",t.fillStyle=i.color(e.titleTextColor).alpha(e.opacity).rgbString(),t.font=i.fontString(e.fontSize,e._titleFontStyle,e._titleFontFamily),t.fillText(e.title,e.x+e.xPadding,this.getLineHeight(0)),t.font=i.fontString(e.fontSize,e._fontStyle,e._fontFamily),i.each(e.labels,function(a,n){t.fillStyle=i.color(e.textColor).alpha(e.opacity).rgbString(),t.fillText(a,e.x+e.xPadding+e.fontSize+3,this.getLineHeight(n+1)),t.fillStyle=i.color(e.legendColors[n].stroke).alpha(e.opacity).rgbString(),t.fillRect(e.x+e.xPadding-1,this.getLineHeight(n+1)-e.fontSize/2-1,e.fontSize+2,e.fontSize+2),t.fillStyle=i.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._view.y-this._view.height/2+this._view.yPadding,i=t-1;return 0===t?e+this._view.titleFontSize/2:e+(1.5*this._view.fontSize*i+this._view.fontSize/2)+1.5*this._view.titleFontSize}})}.call(this),function(){"use strict";var t=this,e=t.Chart,i=e.helpers;e.controllers.bar=function(t,e){this.initialize.call(this,t,e)},i.extend(e.controllers.bar.prototype,{initialize:function(t,e){this.chart=t,this.index=e,this.linkScales(),this.addElements()},linkScales:function(){this.getDataset().xAxisID||(this.getDataset().xAxisID=this.chart.options.scales.xAxes[0].id),this.getDataset().yAxisID||(this.getDataset().yAxisID=this.chart.options.scales.yAxes[0].id)},getDataset:function(){return this.chart.data.datasets[this.index]},getScaleForId:function(t){return this.chart.scales[t]},addElements:function(){this.getDataset().metaData=this.getDataset().metaData||[],i.each(this.getDataset().data,function(t,i){this.getDataset().metaData[i]=this.getDataset().metaData[i]||new e.elements.Rectangle({_chart:this.chart.chart,_datasetIndex:this.index,_index:i})},this)},reset:function(){this.update(!0)},update:function(t){var e=this.getScaleForId(this.getDataset().xAxisID),a=this.getScaleForId(this.getDataset().yAxisID);i.each(this.getDataset().metaData,function(n,s){var o;o=a.min<0&&a.max<0?a.getPixelForValue(a.max):a.min>0&&a.max>0?a.getPixelForValue(a.min):a.getPixelForValue(0),i.extend(n,{_chart:this.chart.chart,_xScale:e,_yScale:a,_datasetIndex:this.index,_index:s,_model:{x:e.calculateBarX(this.chart.data.datasets.length,this.index,s),y:t?o:a.getPixelForValue(this.getDataset().data[s]),label:this.chart.data.labels[s],datasetLabel:this.getDataset().label,base:a.calculateBarBase(this.index,s),width:e.calculateBarWidth(this.chart.data.datasets.length),backgroundColor:n.custom&&n.custom.backgroundColor?n.custom.backgroundColor:i.getValueAtIndexOrDefault(this.getDataset().backgroundColor,s,this.chart.options.elements.rectangle.backgroundColor),borderColor:n.custom&&n.custom.borderColor?n.custom.borderColor:i.getValueAtIndexOrDefault(this.getDataset().borderColor,s,this.chart.options.elements.rectangle.borderColor),borderWidth:n.custom&&n.custom.borderWidth?n.custom.borderWidth:i.getValueAtIndexOrDefault(this.getDataset().borderWidth,s,this.chart.options.elements.rectangle.borderWidth) +}}),n.pivot()},this)},draw:function(t){var e=t||1;i.each(this.getDataset().metaData,function(t,i){t.transition(e).draw()},this)},getElementsAtEvent:function(t){for(var e,a=[],n=i.getRelativePosition(t),s=function(t){a.push(t.metaData[e])},o=0;o0&&s.max>0?s.getPixelForValue(s.min):s.getPixelForValue(0),i.extend(a,{_scale:s,_datasetIndex:this.index,_children:n,_model:{tension:a.custom&&a.custom.tension?a.custom.tension:this.getDataset().tension||this.chart.options.elements.line.tension,backgroundColor:a.custom&&a.custom.backgroundColor?a.custom.backgroundColor:this.getDataset().backgroundColor||this.chart.options.elements.line.backgroundColor,borderWidth:a.custom&&a.custom.borderWidth?a.custom.borderWidth:this.getDataset().borderWidth||this.chart.options.elements.line.borderWidth,borderColor:a.custom&&a.custom.borderColor?a.custom.borderColor:this.getDataset().borderColor||this.chart.options.elements.line.borderColor,fill:a.custom&&a.custom.fill?a.custom.fill:void 0!==this.getDataset().fill?this.getDataset().fill:this.chart.options.elements.line.fill,skipNull:void 0!==this.getDataset().skipNull?this.getDataset().skipNull:this.chart.options.elements.line.skipNull,drawNull:void 0!==this.getDataset().drawNull?this.getDataset().drawNull:this.chart.options.elements.line.drawNull,scaleTop:s.top,scaleBottom:s.bottom,scaleZero:e}}),a.pivot(),i.each(n,function(t,e){i.extend(t,{_chart:this.chart.chart,_xScale:o,_yScale:s,_datasetIndex:this.index,_index:e,_model:{x:o.getPointPixelForValue(this.getDataset().data[e],e,this.index),y:s.getPointPixelForValue(this.getDataset().data[e],e,this.index),tension:t.custom&&t.custom.tension?t.custom.tension:this.chart.options.elements.line.tension,radius:t.custom&&t.custom.radius?t.custom.radius:i.getValueAtIndexOrDefault(this.getDataset().radius,e,this.chart.options.elements.point.radius),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:i.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor,e,this.chart.options.elements.point.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.getDataset().pointBorderColor,e,this.chart.options.elements.point.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth,e,this.chart.options.elements.point.borderWidth),skip:null===this.getDataset().data[e],hitRadius:t.custom&&t.custom.hitRadius?t.custom.hitRadius:i.getValueAtIndexOrDefault(this.getDataset().hitRadius,e,this.chart.options.elements.point.hitRadius)}})},this),i.each(this.getDataset().metaData,function(t,e){var a=i.splineCurve(i.previousItem(this.getDataset().metaData,e)._model,t._model,i.nextItem(this.getDataset().metaData,e)._model,t._model.tension);t._model.controlPointPreviousX=a.previous.x,t._model.controlPointNextX=a.next.x,a.next.y>this.chart.chartArea.bottom?t._model.controlPointNextY=this.chart.chartArea.bottom:a.next.ythis.chart.chartArea.bottom?t._model.controlPointPreviousY=this.chart.chartArea.bottom:a.previous.y",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue"}},n=e.Element.extend({isHorizontal:function(){return"top"==this.options.position||"bottom"==this.options.position},getPixelForValue:function(t,e,i,a){if(this.isHorizontal()){var n=(this.labelRotation>0,this.width-(this.paddingLeft+this.paddingRight)),s=n/Math.max(this.data.labels.length-(this.options.gridLines.offsetGridLines?0:1),1),o=s*e+this.paddingLeft;return this.options.gridLines.offsetGridLines&&a&&(o+=s/2),this.left+Math.round(o)}return this.top+e*(this.height/this.data.labels.length)},getPointPixelForValue:function(t,e,i){return this.getPixelForValue(t,e,i,!0)},calculateBaseWidth:function(){return this.getPixelForValue(null,1,0,!0)-this.getPixelForValue(null,0,0,!0)-2*this.options.categorySpacing},calculateBarWidth:function(t){var e=this.calculateBaseWidth()-(t-1)*this.options.spacing;return this.options.stacked?e:e/t},calculateBarX:function(t,e,i){var a=this.calculateBaseWidth(),n=this.getPixelForValue(null,i,e,!0)-a/2,s=this.calculateBarWidth(t);return this.options.stacked?n+s/2:n+s*e+e*this.options.spacing+s/2},calculateLabelRotation:function(t,e){var a=i.fontString(this.options.labels.fontSize,this.options.labels.fontStyle,this.options.labels.fontFamily);this.ctx.font=a;var n,s,o=this.ctx.measureText(this.data.labels[0]).width,r=this.ctx.measureText(this.data.labels[this.data.labels.length-1]).width;if(this.paddingRight=r/2+3,this.paddingLeft=o/2+3,this.labelRotation=0,this.options.display){var h,l,c=i.longestText(this.ctx,a,this.data.labels);this.labelWidth=c;for(var u=Math.floor(this.getPixelForValue(0,1)-this.getPixelForValue(0,0))-6;this.labelWidth>u&&0===this.labelRotation||this.labelWidth>u&&this.labelRotation<=90&&this.labelRotation>0;){if(h=Math.cos(i.toRadians(this.labelRotation)),l=Math.sin(i.toRadians(this.labelRotation)),n=h*o,s=h*r,n+this.options.labels.fontSize/2>this.yLabelWidth&&(this.paddingLeft=n+this.options.labels.fontSize/2),this.paddingRight=this.options.labels.fontSize/2,l*c>t){this.labelRotation--;break}this.labelRotation++,this.labelWidth=h*c}}else this.labelWidth=0,this.paddingRight=0,this.paddingLeft=0;e&&(this.paddingLeft-=e.left,this.paddingRight-=e.right,this.paddingLeft=Math.max(this.paddingLeft,0),this.paddingRight=Math.max(this.paddingRight,0))},fit:function(t,e,a){this.calculateLabelRotation(e,a);var n={width:0,height:0},s=i.fontString(this.options.labels.fontSize,this.options.labels.fontStyle,this.options.labels.fontFamily),o=i.longestText(this.ctx,s,this.data.labels);if(this.isHorizontal()?(n.width=t,this.width=t):this.options.display&&(n.width=Math.min(o+6,t)),this.isHorizontal()&&this.options.display){var r=Math.cos(i.toRadians(this.labelRotation))*o+1.5*this.options.labels.fontSize;n.height=Math.min(r,e)}else this.options.display&&(n.width=Math.min(o+6,t));return this.width=n.width,this.height=n.height,n},draw:function(t){if(this.options.display){var e;if(this.ctx.fillStyle=this.options.labels.fontColor,this.isHorizontal()){e=!0;var a="bottom"==this.options.position?this.top:this.bottom-10,n="bottom"==this.options.position?this.top+10:this.bottom,s=0!==this.labelRotation;i.each(this.data.labels,function(o,r){var h=this.getPixelForValue(o,r,null,!1),l=this.getPixelForValue(o,r,null,!0);this.options.gridLines.show&&(0===r?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,e=!0):e&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,e=!1),h+=i.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(h,a),this.ctx.lineTo(h,n)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(h,t.top),this.ctx.lineTo(h,t.bottom)),this.ctx.stroke()),this.options.labels.show&&(this.ctx.save(),this.ctx.translate(l,s?this.top+12:this.top+8),this.ctx.rotate(-1*i.toRadians(this.labelRotation)),this.ctx.font=this.font,this.ctx.textAlign=s?"right":"center",this.ctx.textBaseline=s?"middle":"top",this.ctx.fillText(o,0,0),this.ctx.restore())},this)}else this.options.gridLines.show,this.options.labels.show}}});e.scaleService.registerScaleType("category",n,a)}.call(this),function(){"use strict";var t=this,e=t.Chart,i=e.helpers,a={display:!0,position:"left",id:"y-axis-1",gridLines:{show:!0,color:"rgba(0, 0, 0, 0.1)",lineWidth:1,drawOnChartArea:!0,drawTicks:!0,zeroLineWidth:1,zeroLineColor:"rgba(0,0,0,0.25)"},beginAtZero:!1,override:null,labels:{show:!0,template:"<%=value.toLocaleString()%>",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue"}},n=e.Element.extend({isHorizontal:function(){return"top"==this.options.position||"bottom"==this.options.position},generateTicks:function(t,e){if(this.ticks=[],this.options.override)for(var a=0;a<=this.options.override.steps;++a){var n=this.options.override.start+a*this.options.override.stepWidth;ticks.push(n)}else{var s;if(s=this.isHorizontal()?Math.min(11,Math.ceil(t/50)):Math.min(11,Math.ceil(e/(2*this.options.labels.fontSize))),s=Math.max(2,s),this.options.beginAtZero){var o=i.sign(this.min),r=i.sign(this.max);0>o&&0>r?this.max=0:o>0&&r>0&&(this.min=0)}for(var h=i.niceNum(this.max-this.min,!1),l=i.niceNum(h/(s-1),!0),c=Math.floor(this.min/l)*l,u=Math.ceil(this.max/l)*l,d=c;u>=d;d+=l)this.ticks.push(d)}("left"==this.options.position||"right"==this.options.position)&&this.ticks.reverse(),this.max=i.max(this.ticks),this.min=i.min(this.ticks)},buildLabels:function(){this.labels=[],i.each(this.ticks,function(t,e,a){var n;this.options.labels.userCallback?n=this.options.labels.userCallback(t,e,a):this.options.labels.template&&(n=i.template(this.options.labels.template,{value:t})),this.labels.push(n?n:"")},this)},getRightValue:function(t){return"object"==typeof t?this.isHorizontal()?t.x:t.y:t},getPixelForValue:function(t){var e,i=this.max-this.min;return e=this.isHorizontal()?this.left+this.width/i*(t-this.min):this.bottom-this.height/i*(t-this.min)},calculateRange:function(){this.min=null,this.max=null;var t=[],e=[];if(this.options.stacked){i.each(this.data.datasets,function(a){(this.isHorizontal()?a.xAxisID===this.id:a.yAxisID===this.id)&&i.each(a.data,function(i,a){var n=this.getRightValue(i);t[a]=t[a]||0,e[a]=e[a]||0,this.options.relativePoints?t[a]=100:0>n?e[a]+=n:t[a]+=n},this)},this);var a=t.concat(e);this.min=i.min(a),this.max=i.max(a)}else i.each(this.data.datasets,function(t){(this.isHorizontal()?t.xAxisID===this.id:t.yAxisID===this.id)&&i.each(t.data,function(t,e){var i=this.getRightValue(t);null===this.min?this.min=i:ithis.max&&(this.max=i)},this)},this)},getPointPixelForValue:function(t,e,i){var a=this.getRightValue(t);if(this.options.stacked){for(var n=0,s=0,o=0;i>o;++o)this.data.datasets[o].data[e]<0?s+=this.data.datasets[o].data[e]:n+=this.data.datasets[o].data[e];return 0>a?this.getPixelForValue(s+a):this.getPixelForValue(n+a)}return this.getPixelForValue(a)},calculateBarBase:function(t,e){var i=0;if(this.options.stacked){var a=this.data.datasets[t].data[e];if(0>a)for(var n=0;t>n;n++)this.data.datasets[n].yAxisID===this.id&&(i+=this.data.datasets[n].data[e]<0?this.data.datasets[n].data[e]:0);else for(var s=0;t>s;s++)this.data.datasets[s].yAxisID===this.id&&(i+=this.data.datasets[s].data[e]>0?this.data.datasets[s].data[e]:0);return this.getPixelForValue(i)}return i=this.getPixelForValue(this.min),this.beginAtZero||this.min<=0&&this.max>=0||this.min>=0&&this.max<=0?(i=this.getPixelForValue(0),i+=this.options.gridLines.lineWidth):this.min<0&&this.max<0&&(i=this.getPixelForValue(this.max)),i},calculateBarY:function(t,e){var i=this.data.datasets[t].data[e];if(this.options.stacked){for(var a=0,n=0,s=0;t>s;s++)this.data.datasets[s].data[e]<0?n+=this.data.datasets[s].data[e]||0:a+=this.data.datasets[s].data[e]||0;return 0>i?this.getPixelForValue(n+i):this.getPixelForValue(a+i)}for(var o=0,r=t;rr?a.width+=r:a.width=t}}return this.width=a.width,this.height=a.height,a},draw:function(t){if(this.options.display){var e,a;if(this.ctx.fillStyle=this.options.labels.fontColor,this.isHorizontal()){if(this.options.gridLines.show){e=!0,a=void 0!==i.findNextWhere(this.ticks,function(t){return 0===t});var n="bottom"==this.options.position?this.top:this.bottom-5,s="bottom"==this.options.position?this.top+5:this.bottom;i.each(this.ticks,function(o,r){var h=this.getPixelForValue(o);0===o||!a&&0===r?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,e=!0):e&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,e=!1),h+=i.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(h,n),this.ctx.lineTo(h,s)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(h,t.top),this.ctx.lineTo(h,t.bottom)),this.ctx.stroke()},this)}if(this.options.labels.show){var o;"top"==this.options.position?(o=this.bottom-10,this.ctx.textBaseline="bottom"):(o=this.top+10,this.ctx.textBaseline="top"),this.ctx.textAlign="center",this.ctx.font=i.fontString(this.options.labels.fontSize,this.options.labels.fontStyle,this.options.labels.fontFamily),i.each(this.labels,function(t,e){var i=this.getPixelForValue(this.ticks[e]);this.ctx.fillText(t,i,o)},this)}}else{if(this.options.gridLines.show){e=!0,a=void 0!==i.findNextWhere(this.ticks,function(t){return 0===t});var r="right"==this.options.position?this.left:this.right-5,h="right"==this.options.position?this.left+5:this.right;i.each(this.ticks,function(n,s){var o=this.getPixelForValue(n);0===n||!a&&0===s?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,e=!0):e&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,e=!1),o+=i.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(r,o),this.ctx.lineTo(h,o)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(t.left,o),this.ctx.lineTo(t.right,o)),this.ctx.stroke()},this)}if(this.options.labels.show){var l;"left"==this.options.position?(l=this.right-10,this.ctx.textAlign="right"):(l=this.left+5,this.ctx.textAlign="left"),this.ctx.textBaseline="middle",this.ctx.font=i.fontString(this.options.labels.fontSize,this.options.labels.fontStyle,this.options.labels.fontFamily),i.each(this.labels,function(t,e){var i=this.getPixelForValue(this.ticks[e]);this.ctx.fillText(t,l,i)},this)}}}}});e.scaleService.registerScaleType("linear",n,a)}.call(this),function(){"use strict";var t=this,e=t.Chart,i=e.helpers,a={display:!0,animate:!1,lineArc:!1,gridLines:{show:!0,color:"rgba(0, 0, 0, 0.1)",lineWidth:1},angleLines:{show:!0,color:"rgba(0,0,0, 0.1)",lineWidth:1},beginAtZero:!0,labels:{show:!0,template:"<%=value.toLocaleString()%>",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue",showLabelBackdrop:!0,backdropColor:"rgba(255,255,255,0.75)",backdropPaddingY:2,backdropPaddingX:2},pointLabels:{fontFamily:"'Arial'",fontStyle:"normal",fontSize:10,fontColor:"#666"}},n=e.Element.extend({initialize:function(){this.size=i.min([this.height,this.width]),this.drawingArea=this.options.display?this.size/2-(this.options.labels.fontSize/2+this.options.labels.backdropPaddingY):this.size/2},update:function(){this.options.lineArc?this.drawingArea=this.options.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2:this.setScaleSize(),this.buildYLabels()},calculateRange:function(){this.min=null,this.max=null,i.each(this.data.datasets,function(t){i.each(t.data,function(t,e){null===this.min?this.min=t:tthis.max&&(this.max=t)},this)},this)},generateTicks:function(){if(this.ticks=[],this.options.override)for(var t=0;t<=this.options.override.steps;++t){var e=this.options.override.start+t*this.options.override.stepWidth;ticks.push(e)}else{var a=Math.min(11,Math.ceil(this.drawingArea/(2*this.options.labels.fontSize)));if(a=Math.max(2,a),this.options.beginAtZero){var n=i.sign(this.min),s=i.sign(this.max);0>n&&0>s?this.max=0:n>0&&s>0&&(this.min=0)}for(var o=i.niceNum(this.max-this.min,!1),r=i.niceNum(o/(a-1),!0),h=Math.floor(this.min/r)*r,l=Math.ceil(this.max/r)*r,c=h;l>=c;c+=r)this.ticks.push(c)}("left"==this.options.position||"right"==this.options.position)&&this.ticks.reverse(),this.max=i.max(this.ticks),this.min=i.min(this.ticks)},buildYLabels:function(){this.yLabels=[],i.each(this.ticks,function(t,e,a){var n;this.options.labels.userCallback?n=this.options.labels.userCallback(t,e,a):this.options.labels.template&&(n=i.template(this.options.labels.template,{value:t})),this.yLabels.push(n?n:"")},this)},getCircumference:function(){return 2*Math.PI/this.valuesCount},setScaleSize:function(){var t,e,a,n,s,o,r,h,l,c,u,d,f=i.min([this.height/2-this.options.pointLabels.fontSize-5,this.width/2]),g=this.width,p=0;for(this.ctx.font=i.fontString(this.options.pointLabels.fontSize,this.options.pointLabels.fontStyle,this.options.pointLabels.fontFamily),e=0;eg&&(g=t.x+n,s=e),t.x-ng&&(g=t.x+a,s=e):e>this.valuesCount/2&&t.x-a0){var n=this.getDistanceFromCenterForValue(this.ticks[a]),s=this.yCenter-n;if(this.options.gridLines.show)if(t.strokeStyle=this.options.gridLines.color,t.lineWidth=this.options.gridLines.lineWidth,this.options.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.options.angleLines.show){var a=this.getPointPosition(e,this.getDistanceFromCenterForValue(this.max));t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(a.x,a.y),t.stroke(),t.closePath()}var n=this.getPointPosition(e,this.getDistanceFromCenterForValue(this.max)+5);t.font=i.fontString(this.options.pointLabels.fontSize,this.options.pointLabels.fontStyle,this.options.pointLabels.fontFamily),t.fillStyle=this.options.pointLabels.fontColor;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],n.x,n.y)}}}}});e.scaleService.registerScaleType("radialLinear",n,a)}.call(this),/*! * Chart.js * http://chartjs.org/ * Version: 2.0.0-alpha @@ -16,7 +17,7 @@ * 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=e.helpers;e.defaults.global.animation={duration:1e3,easing:"easeOutQuart",onProgress:function(){},onComplete:function(){}},e.Animation=e.Element.extend({currentStep:null,numSteps:60,easing:"",render:null,onAnimationProgress:null,onAnimationComplete:null}),e.animationService={frameDuration:17,animations:[],dropFrames:0,addAnimation:function(t,e,o){o||(t.animating=!0);for(var s=0;s1&&(e=Math.floor(this.dropFrames),this.dropFrames-=e);for(var o=0;othis.animations[o].animationObject.numSteps&&(this.animations[o].animationObject.currentStep=this.animations[o].animationObject.numSteps),this.animations[o].animationObject.render(this.animations[o].chartInstance,this.animations[o].animationObject),this.animations[o].animationObject.currentStep==this.animations[o].animationObject.numSteps&&(this.animations[o].chartInstance.animating=!1,this.animations.splice(o,1),o--);var s=Date.now(),a=s-t-this.frameDuration,r=a/this.frameDuration;r>1&&(this.dropFrames+=r),this.animations.length>0&&i.requestAnimFrame.call(window,this.digestWrapper)}}}.call(this),function(){"use strict";var t=this,e=t.Chart,i=e.helpers;e.scaleService={fitScalesForChart:function(t,e,o){var s=e>30?5:2,a=o>30?5:2;if(t){var r=i.where(t.scales,function(t){return"left"==t.options.position}),n=i.where(t.scales,function(t){return"right"==t.options.position}),h=i.where(t.scales,function(t){return"top"==t.options.position}),l=i.where(t.scales,function(t){return"bottom"==t.options.position}),c=(i.where(t.scales,function(t){return"left"==t.options.position}),i.where(t.scales,function(t){return"right"==t.options.position}),i.where(t.scales,function(t){return"top"==t.options.position}),i.where(t.scales,function(t){return"bottom"==t.options.position}),e/2),d=o/2;c-=2*s,d-=2*a;var u=(e-c)/(r.length+n.length),m=(o-d)/(h.length+l.length),v=[],g=function(t){var e=t.fit(u,d);v.push({horizontal:!1,minSize:e,scale:t})},p=function(t){var e=t.fit(c,m);v.push({horizontal:!0,minSize:e,scale:t})};i.each(r,g),i.each(n,g),i.each(h,p),i.each(l,p);var f=o-2*a,b=e-2*s;i.each(v,function(t){t.horizontal?f-=t.minSize.height:b-=t.minSize.width});var x=function(t){var e=i.findNextWhere(v,function(e){return e.scale===t});e&&t.fit(e.minSize.width,f)},A=function(t){var e=i.findNextWhere(v,function(e){return e.scale===t}),o={left:C,right:y,top:0,bottom:0};e&&t.fit(b,e.minSize.height,o)},C=s,y=s,w=a,_=a;i.each(r,x),i.each(n,x),i.each(r,function(t){C+=t.width}),i.each(n,function(t){y+=t.width}),i.each(h,A),i.each(l,A),i.each(h,function(t){w+=t.height}),i.each(l,function(t){_+=t.height}),i.each(r,function(t){var e=i.findNextWhere(v,function(e){return e.scale===t}),o={left:0,right:0,top:w,bottom:_};e&&t.fit(e.minSize.width,f,o)}),i.each(n,function(t){var e=i.findNextWhere(v,function(e){return e.scale===t}),o={left:0,right:0,top:w,bottom:_};e&&t.fit(e.minSize.width,f,o)});var k=s,S=a,I=function(t){t.left=k,t.right=k+t.width,t.top=w,t.bottom=w+f,k=t.right},P=function(t){t.left=C,t.right=C+b,t.top=S,t.bottom=S+t.height,S=t.bottom};i.each(r,I),i.each(h,P),k+=b,S+=f,i.each(n,I),i.each(l,P),t.chartArea={left:C,top:w,right:C+b,bottom:w+f}}}},e.scales={constructors:{},registerScaleType:function(t,e){this.constructors[t]=e},getScaleConstructor:function(t){return this.constructors.hasOwnProperty(t)?this.constructors[t]:void 0}}}.call(this),/*! +function(){"use strict";var t=this,e=t.Chart,i=e.helpers;e.defaults.global.elements.arc={backgroundColor:e.defaults.global.defaultColor,borderColor:"#fff",borderWidth:2},e.Arc=e.Element.extend({inGroupRange:function(t){var e=this._view;return e?Math.pow(t-e.x,2)1.5*Math.PI?a.startAngle-2*Math.PI:a.startAngle,o=a.endAngle<-.5*Math.PI?a.endAngle+2*Math.PI:a.endAngle>1.5*Math.PI?a.endAngle-2*Math.PI:a.endAngle,r=n.angle>=s&&n.angle<=o,h=n.distance>=a.innerRadius&&n.distance<=a.outerRadius;return r&&h},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,i=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*i,y:t.y+Math.sin(e)*i}},draw:function(){var t=this._chart.ctx,e=this._view;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()}})}.call(this),/*! * Chart.js * http://chartjs.org/ * Version: 2.0.0-alpha @@ -25,7 +26,7 @@ function(){"use strict";var t=this,e=t.Chart,i=e.helpers;e.defaults.global.anima * 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=e.helpers;e.defaults.global.tooltips={enabled:!0,custom:null,backgroundColor:"rgba(0,0,0,0.8)",fontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",fontSize:10,fontStyle:"normal",fontColor:"#fff",titleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",titleFontSize:12,titleFontStyle:"bold",titleFontColor:"#fff",yPadding:6,xPadding:6,caretSize:8,cornerRadius:6,xOffset:10,template:["<% if(label){ %>","<%=label %>: ","<% } %>","<%=value %>"].join(""),multiTemplate:["<%if (datasetLabel){ %>","<%=datasetLabel %>: ","<% } %>","<%=value %>"].join(""),multiKeyBackground:"#fff"},e.Tooltip=e.Element.extend({initialize:function(){var t=this._options;i.extend(this,{_model:{xPadding:t.tooltips.xPadding,yPadding:t.tooltips.yPadding,xOffset:t.tooltips.xOffset,textColor:t.tooltips.fontColor,_fontFamily:t.tooltips.fontFamily,_fontStyle:t.tooltips.fontStyle,fontSize:t.tooltips.fontSize,titleTextColor:t.tooltips.titleFontColor,_titleFontFamily:t.tooltips.titleFontFamily,_titleFontStyle:t.tooltips.titleFontStyle,titleFontSize:t.tooltips.titleFontSize,caretHeight:t.tooltips.caretSize,cornerRadius:t.tooltips.cornerRadius,backgroundColor:t.tooltips.backgroundColor,opacity:0,legendColorBackground:t.tooltips.multiKeyBackground}})},update:function(){var t=this._chart.ctx;switch(this._options.hover.mode){case"single":i.extend(this._model,{text:i.template(this._options.tooltips.template,{element:this._active[0],value:this._data.datasets[this._active[0]._datasetIndex].data[this._active[0]._index],label:this._data.labels?this._data.labels[this._active[0]._index]:""})});var e=this._active[0].tooltipPosition();i.extend(this._model,{x:Math.round(e.x),y:Math.round(e.y),caretPadding:e.padding});break;case"label":for(var o,s,a=[],r=[],n=this._data.datasets.length-1;n>=0&&(o=this._data.datasets[n].metaData,s=i.indexOf(o,this._active[0]),-1===s);n--);var h=function(){var t,e,o,n,h,l=[],c=[],d=[];return i.each(this._data.datasets,function(e){t=e.metaData,t[s]&&t[s].hasValue()&&l.push(t[s])},this),i.each(this._options.stacked?l.reverse():l,function(t){c.push(t._view.x),d.push(t._view.y),a.push(i.template(this._options.tooltips.multiTemplate,{element:t,datasetLabel:this._data.datasets[t._datasetIndex].label,value:this._data.datasets[t._datasetIndex].data[t._index]})),r.push({fill:t._view.backgroundColor,stroke:t._view.borderColor})},this),h=i.min(d),o=i.max(d),n=i.min(c),e=i.max(c),{x:n>this._chart.width/2?n:e,y:(h+o)/2}}.call(this,s);i.extend(this._model,{x:h.x,y:h.y,labels:a,title:this._data.labels&&this._data.labels.length?this._data.labels[this._active[0]._index]:"",legendColors:r,legendBackgroundColor:this._options.tooltips.multiKeyBackground}),this._model.height=a.length*this._model.fontSize+(a.length-1)*(this._model.fontSize/2)+2*this._model.yPadding+1.5*this._model.titleFontSize;var l=t.measureText(this.title).width,c=i.longestText(t,this.font,a)+this._model.fontSize+3,d=i.max([c,l]);this._model.width=d+2*this._model.xPadding;var u=this._model.height/2;this._model.y-u<0?this._model.y=u:this._model.y+u>this._chart.height&&(this._model.y=this._chart.height-u),this._model.x>this._chart.width/2?this._model.x-=this._model.xOffset+this._model.width:this._model.x+=this._model.xOffset}return this},draw:function(){var t=this._chart.ctx,e=this._view;switch(this._options.hover.mode){case"single":t.font=i.fontString(e.fontSize,e._fontStyle,e._fontFamily),e.xAlign="center",e.yAlign="above";var o=e.caretPadding||2,s=t.measureText(e.text).width+2*e.xPadding,a=e.fontSize+2*e.yPadding,r=a+e.caretHeight+o;e.x+s/2>this._chart.width?e.xAlign="left":e.x-s/2<0&&(e.xAlign="right"),e.y-r<0&&(e.yAlign="below");var n=e.x-s/2,h=e.y-r;if(t.fillStyle=i.color(e.backgroundColor).alpha(e.opacity).rgbString(),this._custom)this._custom(this._view);else{switch(e.yAlign){case"above":t.beginPath(),t.moveTo(e.x,e.y-o),t.lineTo(e.x+e.caretHeight,e.y-(o+e.caretHeight)),t.lineTo(e.x-e.caretHeight,e.y-(o+e.caretHeight)),t.closePath(),t.fill();break;case"below":h=e.y+o+e.caretHeight,t.beginPath(),t.moveTo(e.x,e.y+o),t.lineTo(e.x+e.caretHeight,e.y+o+e.caretHeight),t.lineTo(e.x-e.caretHeight,e.y+o+e.caretHeight),t.closePath(),t.fill()}switch(e.xAlign){case"left":n=e.x-s+(e.cornerRadius+e.caretHeight);break;case"right":n=e.x-(e.cornerRadius+e.caretHeight)}i.drawRoundedRectangle(t,n,h,s,a,e.cornerRadius),t.fill(),t.fillStyle=i.color(e.textColor).alpha(e.opacity).rgbString(),t.textAlign="center",t.textBaseline="middle",t.fillText(e.text,n+s/2,h+a/2)}break;case"label":i.drawRoundedRectangle(t,e.x,e.y-e.height/2,e.width,e.height,e.cornerRadius),t.fillStyle=i.color(e.backgroundColor).alpha(e.opacity).rgbString(),t.fill(),t.closePath(),t.textAlign="left",t.textBaseline="middle",t.fillStyle=i.color(e.titleTextColor).alpha(e.opacity).rgbString(),t.font=i.fontString(e.fontSize,e._titleFontStyle,e._titleFontFamily),t.fillText(e.title,e.x+e.xPadding,this.getLineHeight(0)),t.font=i.fontString(e.fontSize,e._fontStyle,e._fontFamily),i.each(e.labels,function(o,s){t.fillStyle=i.color(e.textColor).alpha(e.opacity).rgbString(),t.fillText(o,e.x+e.xPadding+e.fontSize+3,this.getLineHeight(s+1)),t.fillStyle=i.color(e.legendColors[s].stroke).alpha(e.opacity).rgbString(),t.fillRect(e.x+e.xPadding-1,this.getLineHeight(s+1)-e.fontSize/2-1,e.fontSize+2,e.fontSize+2),t.fillStyle=i.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._view.y-this._view.height/2+this._view.yPadding,i=t-1;return 0===t?e+this._view.titleFontSize/2:e+(1.5*this._view.fontSize*i+this._view.fontSize/2)+1.5*this._view.titleFontSize}})}.call(this),function(){"use strict";var t=this,e=t.Chart,i=e.helpers,o=e.Element.extend({calculateRange:i.noop,isHorizontal:function(){return"top"==this.options.position||"bottom"==this.options.position},getPixelForValue:function(t,e,i){if(this.isHorizontal()){var o=(this.labelRotation>0,this.width-(this.paddingLeft+this.paddingRight)),s=o/Math.max(this.max-(this.options.gridLines.offsetGridLines?0:1),1),a=s*e+this.paddingLeft;return this.options.gridLines.offsetGridLines&&i&&(a+=s/2),this.left+Math.round(a)}return this.top+e*(this.height/this.max)},calculateLabelRotation:function(t,e){var o=i.fontString(this.options.labels.fontSize,this.options.labels.fontStyle,this.options.labels.fontFamily);this.ctx.font=o;var s,a,r=this.ctx.measureText(this.labels[0]).width,n=this.ctx.measureText(this.labels[this.labels.length-1]).width;if(this.paddingRight=n/2+3,this.paddingLeft=r/2+3,this.labelRotation=0,this.options.display){var h,l,c=i.longestText(this.ctx,o,this.labels);this.labelWidth=c;for(var d=Math.floor(this.getPixelForValue(0,1)-this.getPixelForValue(0,0))-6;this.labelWidth>d&&0===this.labelRotation||this.labelWidth>d&&this.labelRotation<=90&&this.labelRotation>0;){if(h=Math.cos(i.toRadians(this.labelRotation)),l=Math.sin(i.toRadians(this.labelRotation)),s=h*r,a=h*n,s+this.options.labels.fontSize/2>this.yLabelWidth&&(this.paddingLeft=s+this.options.labels.fontSize/2),this.paddingRight=this.options.labels.fontSize/2,l*c>t){this.labelRotation--;break}this.labelRotation++,this.labelWidth=h*c}}else this.labelWidth=0,this.paddingRight=0,this.paddingLeft=0;e&&(this.paddingLeft-=e.left,this.paddingRight-=e.right,this.paddingLeft=Math.max(this.paddingLeft,0),this.paddingRight=Math.max(this.paddingRight,0))},fit:function(t,e,o){this.calculateRange(),this.calculateLabelRotation(e,o);var s={width:0,height:0},a=i.fontString(this.options.labels.fontSize,this.options.labels.fontStyle,this.options.labels.fontFamily),r=i.longestText(this.ctx,a,this.labels);if(this.isHorizontal()?(s.width=t,this.width=t):this.options.display&&(s.width=Math.min(r+6,t)),this.isHorizontal()){var n=Math.cos(i.toRadians(this.labelRotation))*r+1.5*this.options.labels.fontSize;s.height=Math.min(n,e)}else this.options.display&&(s.width=Math.min(r+6,t));return this.width=s.width,this.height=s.height,s},draw:function(t){if(this.options.display){var e;if(this.ctx.fillStyle=this.options.labels.fontColor,this.isHorizontal()){e=!0;var o="bottom"==this.options.position?this.top:this.bottom-10,s="bottom"==this.options.position?this.top+10:this.bottom,a=0!==this.labelRotation;i.each(this.labels,function(r,n){var h=this.getPixelForValue(r,n,!1),l=this.getPixelForValue(r,n,!0);this.options.gridLines.show&&(0===n?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,e=!0):e&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,e=!1),h+=i.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(h,o),this.ctx.lineTo(h,s)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(h,t.top),this.ctx.lineTo(h,t.bottom)),this.ctx.stroke()),this.options.labels.show&&(this.ctx.save(),this.ctx.translate(l,a?this.top+12:this.top+8),this.ctx.rotate(-1*i.toRadians(this.labelRotation)),this.ctx.font=this.font,this.ctx.textAlign=a?"right":"center",this.ctx.textBaseline=a?"middle":"top",this.ctx.fillText(r,0,0),this.ctx.restore())},this)}else this.options.gridLines.show,this.options.labels.show}}});e.scales.registerScaleType("category",o)}.call(this),function(){"use strict";var t=this,e=t.Chart,i=e.helpers,o=e.Element.extend({calculateRange:i.noop,isHorizontal:function(){return"top"==this.options.position||"bottom"==this.options.position},generateTicks:function(t,e){if(this.ticks=[],this.options.override)for(var o=0;o<=this.options.override.steps;++o){var s=this.options.override.start+o*this.options.override.stepWidth;ticks.push(s)}else{var a;if(a=this.isHorizontal()?Math.min(11,Math.ceil(t/50)):Math.min(11,Math.ceil(e/(2*this.options.labels.fontSize))),a=Math.max(2,a),this.options.beginAtZero){var r=i.sign(this.min),n=i.sign(this.max);0>r&&0>n?this.max=0:r>0&&n>0&&(this.min=0)}for(var h=i.niceNum(this.max-this.min,!1),l=i.niceNum(h/(a-1),!0),c=Math.floor(this.min/l)*l,d=Math.ceil(this.max/l)*l,u=c;d>=u;u+=l)this.ticks.push(u)}("left"==this.options.position||"right"==this.options.position)&&this.ticks.reverse(),this.max=i.max(this.ticks),this.min=i.min(this.ticks)},buildLabels:function(){this.labels=[],i.each(this.ticks,function(t,e,o){var s;this.options.labels.userCallback?s=this.options.lables.userCallback(t,e,o):this.options.labels.template&&(s=i.template(this.options.labels.template,{value:t})),this.labels.push(s?s:"")},this)},getPixelForValue:function(t){var e,i=this.max-this.min;return e=this.isHorizontal()?this.left+this.width/i*(t-this.min):this.bottom-this.height/i*(t-this.min)},fit:function(t,e){this.calculateRange(),this.generateTicks(t,e),this.buildLabels();var o={width:0,height:0};if(o.width=this.isHorizontal()?t:this.options.gridLines.show&&this.options.display?10:0,o.height=this.isHorizontal()?this.options.gridLines.show&&this.options.display?10:0:e,this.options.labels.show&&this.options.display){var s=i.fontString(this.options.labels.fontSize,this.options.labels.fontStyle,this.options.labels.fontFamily);if(this.isHorizontal()){var a=(e-o.height,1.5*this.options.labels.fontSize);o.height=Math.min(e,o.height+a)}else{var r=t-o.width,n=i.longestText(this.ctx,s,this.labels);r>n?o.width+=n:o.width=t}}return this.width=o.width,this.height=o.height,o},draw:function(t){if(this.options.display){var e,o;if(this.ctx.fillStyle=this.options.labels.fontColor,this.isHorizontal()){if(this.options.gridLines.show){e=!0,o=void 0!==i.findNextWhere(this.ticks,function(t){return 0===t});var s="bottom"==this.options.position?this.top:this.bottom-5,a="bottom"==this.options.position?this.top+5:this.bottom;i.each(this.ticks,function(r,n){var h=this.getPixelForValue(r);0===r||!o&&0===n?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,e=!0):e&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,e=!1),h+=i.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(h,s),this.ctx.lineTo(h,a)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(h,t.top),this.ctx.lineTo(h,t.bottom)),this.ctx.stroke()},this)}if(this.options.labels.show){var r;"top"==this.options.position?(r=this.bottom-10,this.ctx.textBaseline="bottom"):(r=this.top+10,this.ctx.textBaseline="top"),this.ctx.textAlign="center",this.ctx.font=i.fontString(this.options.labels.fontSize,this.options.labels.fontStyle,this.options.labels.fontFamily),i.each(this.labels,function(t,e){var i=this.getPixelForValue(this.ticks[e]);this.ctx.fillText(t,i,r)},this)}}else{if(this.options.gridLines.show){e=!0,o=void 0!==i.findNextWhere(this.ticks,function(t){return 0===t});var n="right"==this.options.position?this.left:this.right-5,h="right"==this.options.position?this.left+5:this.right;i.each(this.ticks,function(s,a){var r=this.getPixelForValue(s);0===s||!o&&0===a?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,e=!0):e&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,e=!1),r+=i.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(n,r),this.ctx.lineTo(h,r)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(t.left,r),this.ctx.lineTo(t.right,r)),this.ctx.stroke()},this)}if(this.options.labels.show){var l;"left"==this.options.position?(l=this.right-10,this.ctx.textAlign="right"):(l=this.left+5,this.ctx.textAlign="left"),this.ctx.textBaseline="middle",this.ctx.font=i.fontString(this.options.labels.fontSize,this.options.labels.fontStyle,this.options.labels.fontFamily),i.each(this.labels,function(t,e){var i=this.getPixelForValue(this.ticks[e]);this.ctx.fillText(t,l,i)},this)}}}}});e.scales.registerScaleType("linear",o)}.call(this),function(){"use strict";var t=this,e=t.Chart,i=e.helpers,o=e.Element.extend({initialize:function(){this.size=i.min([this.height,this.width]),this.drawingArea=this.options.display?this.size/2-(this.options.labels.fontSize/2+this.options.labels.backdropPaddingY):this.size/2},calculateCenterOffset:function(t){var e=this.drawingArea/(this.max-this.min);return(t-this.min)*e},update:function(){this.options.lineArc?this.drawingArea=this.options.display?this.size/2-(this.fontSize/2+this.backdropPaddingY):this.size/2:this.setScaleSize(),this.buildYLabels()},calculateRange:i.noop,generateTicks:function(){if(this.ticks=[],this.options.override)for(var t=0;t<=this.options.override.steps;++t){var e=this.options.override.start+t*this.options.override.stepWidth;ticks.push(e)}else{var o=Math.min(11,Math.ceil(this.drawingArea/(2*this.options.labels.fontSize)));if(o=Math.max(2,o),this.options.beginAtZero){var s=i.sign(this.min),a=i.sign(this.max);0>s&&0>a?this.max=0:s>0&&a>0&&(this.min=0)}for(var r=i.niceNum(this.max-this.min,!1),n=i.niceNum(r/(o-1),!0),h=Math.floor(this.min/n)*n,l=Math.ceil(this.max/n)*n,c=h;l>=c;c+=n)this.ticks.push(c)}("left"==this.options.position||"right"==this.options.position)&&this.ticks.reverse(),this.max=i.max(this.ticks),this.min=i.min(this.ticks)},buildYLabels:function(){this.yLabels=[],i.each(this.ticks,function(t,e,o){var s;this.options.labels.userCallback?s=this.options.labels.userCallback(t,e,o):this.options.labels.template&&(s=i.template(this.options.labels.template,{value:t})),this.yLabels.push(s?s:"")},this)},getCircumference:function(){return 2*Math.PI/this.valuesCount},setScaleSize:function(){var t,e,o,s,a,r,n,h,l,c,d,u,m=i.min([this.height/2-this.options.pointLabels.fontSize-5,this.width/2]),v=this.width,g=0;for(this.ctx.font=i.fontString(this.options.pointLabels.fontSize,this.options.pointLabels.fontStyle,this.options.pointLabels.fontFamily),e=0;ev&&(v=t.x+s,a=e),t.x-sv&&(v=t.x+o,a=e):e>this.valuesCount/2&&t.x-o0){var s,a=o*(this.drawingArea/Math.max(this.ticks.length,1)),r=this.yCenter-a;if(this.options.gridLines.show)if(t.strokeStyle=this.options.gridLines.color,t.lineWidth=this.options.gridLines.lineWidth,this.options.lineArc)t.beginPath(),t.arc(this.xCenter,this.yCenter,a,0,2*Math.PI),t.closePath(),t.stroke();else{t.beginPath();for(var n=0;n=0;e--){if(this.options.angleLines.show){var o=this.getPointPosition(e,this.calculateCenterOffset(this.max));t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(o.x,o.y),t.stroke(),t.closePath()}var s=this.getPointPosition(e,this.calculateCenterOffset(this.max)+5);t.font=i.fontString(this.options.pointLabels.fontSize,this.options.pointLabels.fontStyle,this.options.pointLabels.fontFamily),t.fillStyle=this.options.pointLabels.fontColor;var a=this.labels.length,r=this.labels.length/2,n=r/2,h=n>e||e>a-n,l=e===n||e===a-n;t.textAlign=0===e?"center":e===r?"center":r>e?"left":"right",t.textBaseline=l?"middle":h?"bottom":"top",t.fillText(this.labels[e],s.x,s.y)}}}}});e.scales.registerScaleType("radialLinear",o)}.call(this),/*! +function(){"use strict";var t=this,e=t.Chart,i=e.helpers;e.defaults.global.elements.line={tension:.4,backgroundColor:e.defaults.global.defaultColor,borderWidth:3,borderColor:e.defaults.global.defaultColor,fill:!0,skipNull:!0,drawNull:!1},e.elements.Line=e.Element.extend({draw:function(){var t=this._view,a=this._chart.ctx,n=this._children[0],s=this._children[this._children.length-1];i.each(this._children,function(e,n){var s=i.previousItem(this._children,n),o=i.nextItem(this._children,n);return 0===n?void a.moveTo(e._view.x,e._view.y):(e._view.skip&&t.skipNull&&!this._loop?(a.lineTo(s._view.x,e._view.y),a.moveTo(o._view.x,e._view.y)):s._view.skip&&t.skipNull&&!this._loop&&(a.moveTo(e._view.x,s._view.y),a.lineTo(e._view.x,e._view.y)),void(s._view.skip&&t.skipNull?a.moveTo(e._view.x,e._view.y):t.tension>0?a.bezierCurveTo(s._view.controlPointNextX,s._view.controlPointNextY,e._view.controlPointPreviousX,e._view.controlPointPreviousY,e._view.x,e._view.y):a.lineTo(e._view.x,e._view.y)))},this),this._loop&&(t.tension>0&&!n._view.skip?a.bezierCurveTo(s._view.controlPointNextX,s._view.controlPointNextY,n._view.controlPointPreviousX,n._view.controlPointPreviousY,n._view.x,n._view.y):a.lineTo(n._view.x,n._view.y)),this._children.length>0&&t.fill&&(a.lineTo(this._children[this._children.length-1]._view.x,t.scaleZero),a.lineTo(this._children[0]._view.x,t.scaleZero),a.fillStyle=t.backgroundColor||e.defaults.global.defaultColor,a.closePath(),a.fill()),a.lineWidth=t.borderWidth||e.defaults.global.defaultColor,a.strokeStyle=t.borderColor||e.defaults.global.defaultColor,a.beginPath(),i.each(this._children,function(e,n){var s=i.previousItem(this._children,n),o=i.nextItem(this._children,n);return 0===n?void a.moveTo(e._view.x,e._view.y):e._view.skip&&t.skipNull&&!this._loop?(a.moveTo(s._view.x,e._view.y),void a.moveTo(o._view.x,e._view.y)):s._view.skip&&t.skipNull&&!this._loop?(a.moveTo(e._view.x,s._view.y),void a.moveTo(e._view.x,e._view.y)):s._view.skip&&t.skipNull?void a.moveTo(e._view.x,e._view.y):void(t.tension>0?a.bezierCurveTo(s._view.controlPointNextX,s._view.controlPointNextY,e._view.controlPointPreviousX,e._view.controlPointPreviousY,e._view.x,e._view.y):a.lineTo(e._view.x,e._view.y))},this),this._loop&&!n._view.skip&&(t.tension>0?a.bezierCurveTo(s._view.controlPointNextX,s._view.controlPointNextY,n._view.controlPointPreviousX,n._view.controlPointPreviousY,n._view.x,n._view.y):a.lineTo(n._view.x,n._view.y)),a.stroke()}})}.call(this),/*! * Chart.js * http://chartjs.org/ * Version: 2.0.0-alpha @@ -34,33 +35,4 @@ function(){"use strict";var t=this,e=t.Chart,i=e.helpers;e.defaults.global.toolt * 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=e.helpers;e.defaults.global.elements.arc={backgroundColor:e.defaults.global.defaultColor,borderColor:"#fff",borderWidth:2},e.Arc=e.Element.extend({inGroupRange:function(t){var e=this._view;return e?Math.pow(t-e.x,2)1.5*Math.PI?o.startAngle-2*Math.PI:o.startAngle,r=o.endAngle<-.5*Math.PI?o.endAngle+2*Math.PI:o.endAngle>1.5*Math.PI?o.endAngle-2*Math.PI:o.endAngle,n=s.angle>=a&&s.angle<=r,h=s.distance>=o.innerRadius&&s.distance<=o.outerRadius;return n&&h},tooltipPosition:function(){var t=this._view,e=t.startAngle+(t.endAngle-t.startAngle)/2,i=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(e)*i,y:t.y+Math.sin(e)*i}},draw:function(){var t=this._chart.ctx,e=this._view;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()}})}.call(this),/*! - * Chart.js - * http://chartjs.org/ - * Version: 2.0.0-alpha - * - * Copyright 2015 Nick Downie - * 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=e.helpers;e.defaults.global.elements.line={tension:.4,backgroundColor:e.defaults.global.defaultColor,borderWidth:3,borderColor:e.defaults.global.defaultColor,fill:!0,skipNull:!0,drawNull:!1},e.Line=e.Element.extend({draw:function(){var t=this._view,o=this._chart.ctx,s=this._children[0],a=this._children[this._children.length-1];i.each(this._children,function(e,i){var s=this.previousPoint(e,this._children,i),a=this.nextPoint(e,this._children,i);return 0===i?void o.moveTo(e._view.x,e._view.y):(e._view.skip&&t.skipNull&&!this._loop?(o.lineTo(s._view.x,e._view.y),o.moveTo(a._view.x,e._view.y)):s._view.skip&&t.skipNull&&!this._loop&&(o.moveTo(e._view.x,s._view.y),o.lineTo(e._view.x,e._view.y)),void(s._view.skip&&t.skipNull?o.moveTo(e._view.x,e._view.y):t.tension>0?o.bezierCurveTo(s._view.controlPointNextX,s._view.controlPointNextY,e._view.controlPointPreviousX,e._view.controlPointPreviousY,e._view.x,e._view.y):o.lineTo(e._view.x,e._view.y)))},this),this._loop&&(t.tension>0&&!s._view.skip?o.bezierCurveTo(a._view.controlPointNextX,a._view.controlPointNextY,s._view.controlPointPreviousX,s._view.controlPointPreviousY,s._view.x,s._view.y):o.lineTo(s._view.x,s._view.y)),this._children.length>0&&t.fill&&(o.lineTo(this._children[this._children.length-1]._view.x,t.scaleZero),o.lineTo(this._children[0]._view.x,t.scaleZero),o.fillStyle=t.backgroundColor||e.defaults.global.defaultColor,o.closePath(),o.fill()),o.lineWidth=t.borderWidth||e.defaults.global.defaultColor,o.strokeStyle=t.borderColor||e.defaults.global.defaultColor,o.beginPath(),i.each(this._children,function(e,i){var s=this.previousPoint(e,this._children,i),a=this.nextPoint(e,this._children,i);return 0===i?void o.moveTo(e._view.x,e._view.y):e._view.skip&&t.skipNull&&!this._loop?(o.moveTo(s._view.x,e._view.y),void o.moveTo(a._view.x,e._view.y)):s._view.skip&&t.skipNull&&!this._loop?(o.moveTo(e._view.x,s._view.y),void o.moveTo(e._view.x,e._view.y)):s._view.skip&&t.skipNull?void o.moveTo(e._view.x,e._view.y):void(t.tension>0?o.bezierCurveTo(s._view.controlPointNextX,s._view.controlPointNextY,e._view.controlPointPreviousX,e._view.controlPointPreviousY,e._view.x,e._view.y):o.lineTo(e._view.x,e._view.y))},this),this._loop&&!s._view.skip&&(t.tension>0?o.bezierCurveTo(a._view.controlPointNextX,a._view.controlPointNextY,s._view.controlPointPreviousX,s._view.controlPointPreviousY,s._view.x,s._view.y):o.lineTo(s._view.x,s._view.y)),o.stroke()},nextPoint:function(t,e,i){return this.loop?e[i+1]||e[0]:e[i+1]||e[e.length-1]},previousPoint:function(t,e,i){return this.loop?e[i-1]||e[e.length-1]:e[i-1]||e[0]}})}.call(this),/*! - * Chart.js - * http://chartjs.org/ - * Version: 2.0.0-alpha - * - * Copyright 2015 Nick Downie - * Released under the MIT license - * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md - */ -function(){"use strict";{var t=this,e=t.Chart;e.helpers}e.defaults.global.elements.point={radius:3,backgroundColor:e.defaults.global.defaultColor,borderWidth:1,borderColor:e.defaults.global.defaultColor,hitRadius:1,hoverRadius:4,hoverBorderWidth:1},e.Point=e.Element.extend({inRange:function(t,e){var i=this._view,o=i.hitRadius+i.radius;return Math.pow(t-i.x,2)+Math.pow(e-i.y,2)0||t.borderWidth>0)&&(i.beginPath(),i.arc(t.x,t.y,t.radius||e.defaults.global.elements.point.radius,0,2*Math.PI),i.closePath(),i.strokeStyle=t.borderColor||e.defaults.global.defaultColor,i.lineWidth=t.borderWidth||e.defaults.global.elements.point.borderWidth,i.fillStyle=t.backgroundColor||e.defaults.global.defaultColor,i.fill(),i.stroke())}})}.call(this),/*! - * Chart.js - * http://chartjs.org/ - * Version: 2.0.0-alpha - * - * Copyright 2015 Nick Downie - * Released under the MIT license - * https://github.com/nnnick/Chart.js/blob/master/LICENSE.md - */ -function(){"use strict";{var t=this,e=t.Chart;e.helpers}e.defaults.global.elements.rectangle={backgroundColor:e.defaults.global.defaultColor,borderWidth:0,borderColor:e.defaults.global.defaultColor},e.Rectangle=e.Element.extend({draw:function(){var t=this._chart.ctx,e=this._view,i=e.width/2,o=e.x-i,s=e.x+i,a=e.base-(e.base-e.y),r=e.borderWidth/2;e.borderWidth&&(o+=r,s-=r,a+=r),t.beginPath(),t.fillStyle=e.backgroundColor,t.strokeStyle=e.borderColor,t.lineWidth=e.borderWidth,t.moveTo(o,e.base),t.lineTo(o,a),t.lineTo(s,a),t.lineTo(s,e.base),t.fill(),e.borderWidth&&t.stroke()},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){var i=this._view;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._view;return t>=e.x-e.width/2&&t<=e.x+e.width/2},tooltipPosition:function(){var t=this._view;return t.y",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue"}}],yAxes:[{type:"linear",display:!0,position:"left",id:"y-axis-1",gridLines:{show:!0,color:"rgba(0, 0, 0, 0.05)",lineWidth:1,drawOnChartArea:!0,drawTicks:!0,zeroLineWidth:1,zeroLineColor:"rgba(0,0,0,0.25)"},beginAtZero:!1,override:null,labels:{show:!0,template:"<%=value%>",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue"}}]}};e.Type.extend({name:"Bar",defaults:o,initialize:function(){i.bindEvents(this,this.options.events,this.events),i.each(this.data.datasets,function(t,o){t.metaData=[],i.each(t.data,function(i,s){t.metaData.push(new e.Rectangle({_chart:this.chart,_datasetIndex:o,_index:s}))},this),t.xAxisID=this.options.scales.xAxes[0].id,t.yAxisID||(t.yAxisID=this.options.scales.yAxes[0].id)},this),this.buildScale(),this.tooltip=new e.Tooltip({_chart:this.chart,_data:this.data,_options:this.options},this),e.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height),this.resetElements(),this.update()},resetElements:function(){this.eachElement(function(t,e,o,s){var a,r=this.scales[this.data.datasets[s].xAxisID],n=this.scales[this.data.datasets[s].yAxisID];a=n.getPixelForValue(n.min<0&&n.max<0?n.max:n.min>0&&n.max>0?n.min:0),i.extend(t,{_chart:this.chart,_xScale:r,_yScale:n,_datasetIndex:s,_index:e,_model:{x:r.calculateBarX(this.data.datasets.length,s,e),y:a,base:n.calculateBarBase(s,e),width:r.calculateBarWidth(this.data.datasets.length),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[s].backgroundColor,e,this.options.elements.rectangle.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[s].borderColor,e,this.options.elements.rectangle.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[s].borderWidth,e,this.options.elements.rectangle.borderWidth),label:this.data.labels[e],datasetLabel:this.data.datasets[s].label}}),t.pivot()},this)},update:function(t){e.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height),this.eachElement(function(t,e,o,s){var a=this.scales[this.data.datasets[s].xAxisID],r=this.scales[this.data.datasets[s].yAxisID];i.extend(t,{_chart:this.chart,_xScale:a,_yScale:r,_datasetIndex:s,_index:e,_model:{x:a.calculateBarX(this.data.datasets.length,s,e),y:r.calculateBarY(s,e),base:r.calculateBarBase(s,e),width:a.calculateBarWidth(this.data.datasets.length),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[s].backgroundColor,e,this.options.elements.rectangle.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[s].borderColor,e,this.options.elements.rectangle.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[s].borderWidth,e,this.options.elements.rectangle.borderWidth),label:this.data.labels[e],datasetLabel:this.data.datasets[s].label}}),t.pivot()},this),this.render(t)},buildScale:function(){var t=this,o=function(){this.min=null,this.max=null;var e=[],o=[];if(t.options.stacked){i.each(t.data.datasets,function(s){s.yAxisID===this.id&&i.each(s.data,function(i,s){e[s]=e[s]||0,o[s]=o[s]||0,t.options.relativePoints?e[s]=100:0>i?o[s]+=i:e[s]+=i},this)},this);var s=e.concat(o);this.min=i.min(s),this.max=i.max(s)}else i.each(t.data.datasets,function(t){t.yAxisID===this.id&&i.each(t.data,function(t){null===this.min?this.min=t:tthis.max&&(this.max=t)},this)},this)};this.scales={};var s=e.scales.getScaleConstructor(this.options.scales.xAxes[0].type),a=new s({ctx:this.chart.ctx,options:this.options.scales.xAxes[0],id:this.options.scales.xAxes[0].id,calculateRange:function(){this.labels=t.data.labels,this.min=0,this.max=this.labels.length},calculateBaseWidth:function(){return this.getPixelForValue(null,1,!0)-this.getPixelForValue(null,0,!0)-2*t.options.valueSpacing},calculateBarWidth:function(e){var i=this.calculateBaseWidth()-(e-1)*t.options.datasetSpacing;return t.options.stacked?i:i/e},calculateBarX:function(e,i,o){var s=this.calculateBaseWidth(),a=this.getPixelForValue(null,o,!0)-s/2,r=this.calculateBarWidth(e);return t.options.stacked?a+r/2:a+r*i+i*t.options.datasetSpacing+r/2}});this.scales[a.id]=a,i.each(this.options.scales.yAxes,function(i){var s=e.scales.getScaleConstructor(i.type),a=new s({ctx:this.chart.ctx,options:i,calculateRange:o,calculateBarBase:function(e,i){var o=0;if(t.options.stacked){var s=t.data.datasets[e].data[i];if(0>s)for(var a=0;e>a;a++)t.data.datasets[a].yAxisID===this.id&&(o+=t.data.datasets[a].data[i]<0?t.data.datasets[a].data[i]:0);else for(var r=0;e>r;r++)t.data.datasets[r].yAxisID===this.id&&(o+=t.data.datasets[r].data[i]>0?t.data.datasets[r].data[i]:0);return this.getPixelForValue(o)}return o=this.getPixelForValue(this.min),this.beginAtZero||this.min<=0&&this.max>=0||this.min>=0&&this.max<=0?(o=this.getPixelForValue(0),o+=this.options.gridLines.lineWidth):this.min<0&&this.max<0&&(o=this.getPixelForValue(this.max)),o},calculateBarY:function(e,i){var o=t.data.datasets[e].data[i];if(t.options.stacked){for(var s=0,a=0,r=0;e>r;r++)t.data.datasets[r].data[i]<0?a+=t.data.datasets[r].data[i]||0:s+=t.data.datasets[r].data[i]||0;return this.getPixelForValue(0>o?a+o:s+o)}for(var n=0,h=e;h0?2*Math.PI*(e/t.total):0},resetElements:function(){this.outerRadius=(i.min([this.chart.width,this.chart.height])-this.options.elements.arc.borderWidth/2)/2,this.innerRadius=this.options.cutoutPercentage?this.outerRadius/100*this.options.cutoutPercentage:1,this.radiusLength=(this.outerRadius-this.innerRadius)/this.data.datasets.length,i.each(this.data.datasets,function(t,e){t.total=0,i.each(t.data,function(e){t.total+=Math.abs(e)},this),t.outerRadius=this.outerRadius-this.radiusLength*e,t.innerRadius=t.outerRadius-this.radiusLength,i.each(t.metaData,function(e,o){i.extend(e,{_model:{x:this.chart.width/2,y:this.chart.height/2,startAngle:Math.PI*-.5,circumference:this.options.animation.animateRotate?0:this.calculateCircumference(metaSlice.value),outerRadius:this.options.animation.animateScale?0:t.outerRadius,innerRadius:this.options.animation.animateScale?0:t.innerRadius,backgroundColor:e.custom&&e.custom.backgroundColor?e.custom.backgroundColor:i.getValueAtIndexOrDefault(t.backgroundColor,o,this.options.elements.arc.backgroundColor),hoverBackgroundColor:e.custom&&e.custom.hoverBackgroundColor?e.custom.hoverBackgroundColor:i.getValueAtIndexOrDefault(t.hoverBackgroundColor,o,this.options.elements.arc.hoverBackgroundColor),borderWidth:e.custom&&e.custom.borderWidth?e.custom.borderWidth:i.getValueAtIndexOrDefault(t.borderWidth,o,this.options.elements.arc.borderWidth),borderColor:e.custom&&e.custom.borderColor?e.custom.borderColor:i.getValueAtIndexOrDefault(t.borderColor,o,this.options.elements.arc.borderColor),label:i.getValueAtIndexOrDefault(t.label,o,this.data.labels[o])}}),e.pivot()},this)},this)},update:function(t){this.outerRadius=(i.min([this.chart.width,this.chart.height])-this.options.elements.arc.borderWidth/2)/2,this.innerRadius=this.options.cutoutPercentage?this.outerRadius/100*this.options.cutoutPercentage:1,this.radiusLength=(this.outerRadius-this.innerRadius)/this.data.datasets.length,i.each(this.data.datasets,function(t,e){t.total=0,i.each(t.data,function(e){t.total+=Math.abs(e)},this),t.outerRadius=this.outerRadius-this.radiusLength*e,t.innerRadius=t.outerRadius-this.radiusLength,i.each(t.metaData,function(o,s){i.extend(o,{_chart:this.chart,_datasetIndex:e,_index:s,_model:{x:this.chart.width/2,y:this.chart.height/2,circumference:this.calculateCircumference(t,t.data[s]),outerRadius:t.outerRadius,innerRadius:t.innerRadius,backgroundColor:o.custom&&o.custom.backgroundColor?o.custom.backgroundColor:i.getValueAtIndexOrDefault(t.backgroundColor,s,this.options.elements.arc.backgroundColor),hoverBackgroundColor:o.custom&&o.custom.hoverBackgroundColor?o.custom.hoverBackgroundColor:i.getValueAtIndexOrDefault(t.hoverBackgroundColor,s,this.options.elements.arc.hoverBackgroundColor),borderWidth:o.custom&&o.custom.borderWidth?o.custom.borderWidth:i.getValueAtIndexOrDefault(t.borderWidth,s,this.options.elements.arc.borderWidth),borderColor:o.custom&&o.custom.borderColor?o.custom.borderColor:i.getValueAtIndexOrDefault(t.borderColor,s,this.options.elements.arc.borderColor),label:i.getValueAtIndexOrDefault(t.label,s,this.data.labels[s])}}),o._model.startAngle=0===s?Math.PI*-.5:t.metaData[s-1]._model.endAngle,o._model.endAngle=o._model.startAngle+o._model.circumference,s",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue"}}],yAxes:[{type:"linear",display:!0,position:"left",id:"y-axis-1",gridLines:{show:!0,color:"rgba(0, 0, 0, 0.05)",lineWidth:1,drawOnChartArea:!0,drawTicks:!0,zeroLineWidth:1,zeroLineColor:"rgba(0,0,0,0.25)"},beginAtZero:!1,override:null,labels:{show:!0,template:"<%=value.toLocaleString()%>",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue"}}]}};e.Type.extend({name:"Line",defaults:o,initialize:function(){i.bindEvents(this,this.options.events,this.events),i.each(this.data.datasets,function(t,o){t.metaDataset=new e.Line({_chart:this.chart,_datasetIndex:o,_points:t.metaData}),t.metaData=[],i.each(t.data,function(i,s){t.metaData.push(new e.Point({_datasetIndex:o,_index:s,_chart:this.chart,_model:{x:0,y:0}}))},this),t.xAxisID=this.options.scales.xAxes[0].id,t.yAxisID||(t.yAxisID=this.options.scales.yAxes[0].id)},this),this.buildScale(),this.tooltip=new e.Tooltip({_chart:this.chart,_data:this.data,_options:this.options},this),e.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height),this.resetElements(),this.update()},nextPoint:function(t,e){return t[e+1]||t[e]},previousPoint:function(t,e){return t[e-1]||t[e]},resetElements:function(){this.eachElement(function(t,e,o,s){var a,r=this.scales[this.data.datasets[s].xAxisID],n=this.scales[this.data.datasets[s].yAxisID];a=n.getPixelForValue(n.min<0&&n.max<0?n.max:n.min>0&&n.max>0?n.min:0),i.extend(t,{_chart:this.chart,_xScale:r,_yScale:n,_datasetIndex:s,_index:e,_model:{x:r.getPixelForValue(null,e,!0),y:a,tension:t.custom&&t.custom.tension?t.custom.tension:this.options.elements.line.tension,radius:t.custom&&t.custom.radius?t.custom.radius:i.getValueAtIndexOrDefault(this.data.datasets[s].radius,e,this.options.elements.point.radius),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBackgroundColor,e,this.options.elements.point.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBorderColor,e,this.options.elements.point.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBorderWidth,e,this.options.elements.point.borderWidth),skip:null===this.data.datasets[s].data[e],hitRadius:t.custom&&t.custom.hitRadius?t.custom.hitRadius:i.getValueAtIndexOrDefault(this.data.datasets[s].hitRadius,e,this.options.elements.point.hitRadius)}})},this),this.eachElement(function(t,e,o){var s=i.splineCurve(this.previousPoint(o,e)._model,t._model,this.nextPoint(o,e)._model,t._model.tension);t._model.controlPointPreviousX=s.previous.x,t._model.controlPointNextX=s.next.x,t._model.controlPointNextY=s.next.y>this.chartArea.bottom?this.chartArea.bottom:s.next.ythis.chartArea.bottom?this.chartArea.bottom:s.previous.y0&&s.max>0?s.min:0),i.extend(t.metaDataset,{_scale:s,_datasetIndex:e,_children:t.metaData,_model:{tension:t.metaDataset.custom&&t.metaDataset.custom.tension?t.metaDataset.custom.tension:t.tension||this.options.elements.line.tension,backgroundColor:t.metaDataset.custom&&t.metaDataset.custom.backgroundColor?t.metaDataset.custom.backgroundColor:t.backgroundColor||this.options.elements.line.backgroundColor,borderWidth:t.metaDataset.custom&&t.metaDataset.custom.borderWidth?t.metaDataset.custom.borderWidth:t.borderWidth||this.options.elements.line.borderWidth,borderColor:t.metaDataset.custom&&t.metaDataset.custom.borderColor?t.metaDataset.custom.borderColor:t.borderColor||this.options.elements.line.borderColor,fill:t.metaDataset.custom&&t.metaDataset.custom.fill?t.metaDataset.custom.fill:void 0!==t.fill?t.fill:this.options.elements.line.fill,skipNull:void 0!==t.skipNull?t.skipNull:this.options.elements.line.skipNull,drawNull:void 0!==t.drawNull?t.drawNull:this.options.elements.line.drawNull,scaleTop:s.top,scaleBottom:s.bottom,scaleZero:o}}),t.metaDataset.pivot()}),this.eachElement(function(t,e,o,s){var a=this.scales[this.data.datasets[s].xAxisID],r=this.scales[this.data.datasets[s].yAxisID];i.extend(t,{_chart:this.chart,_xScale:a,_yScale:r,_datasetIndex:s,_index:e,_model:{x:a.getPixelForValue(null,e,!0),y:r.getPointPixelForValue(this.data.datasets[s].data[e],e,s),tension:t.custom&&t.custom.tension?t.custom.tension:this.options.elements.line.tension,radius:t.custom&&t.custom.radius?t.custom.radius:i.getValueAtIndexOrDefault(this.data.datasets[s].radius,e,this.options.elements.point.radius),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBackgroundColor,e,this.options.elements.point.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBorderColor,e,this.options.elements.point.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBorderWidth,e,this.options.elements.point.borderWidth),skip:null===this.data.datasets[s].data[e],hitRadius:t.custom&&t.custom.hitRadius?t.custom.hitRadius:i.getValueAtIndexOrDefault(this.data.datasets[s].hitRadius,e,this.options.elements.point.hitRadius)}})},this),this.eachElement(function(t,e,o){var s=i.splineCurve(this.previousPoint(o,e)._model,t._model,this.nextPoint(o,e)._model,t._model.tension);t._model.controlPointPreviousX=s.previous.x,t._model.controlPointNextX=s.next.x,t._model.controlPointNextY=s.next.y>this.chartArea.bottom?this.chartArea.bottom:s.next.ythis.chartArea.bottom?this.chartArea.bottom:s.previous.yi?o[s]+=i:e[s]+=i},this)},this);var s=e.concat(o);this.min=i.min(s),this.max=i.max(s)}else i.each(t.data.datasets,function(t){t.yAxisID===this.id&&i.each(t.data,function(t){null===this.min?this.min=t:tthis.max&&(this.max=t)},this)},this)};this.scales={};var s=e.scales.getScaleConstructor(this.options.scales.xAxes[0].type),a=new s({ctx:this.chart.ctx,options:this.options.scales.xAxes[0],calculateRange:function(){this.labels=t.data.labels,this.min=0,this.max=this.labels.length},id:this.options.scales.xAxes[0].id});this.scales[a.id]=a,i.each(this.options.scales.yAxes,function(i){var s=e.scales.getScaleConstructor(i.type),a=new s({ctx:this.chart.ctx,options:i,calculateRange:o,getPointPixelForValue:function(e,i,o){if(t.options.stacked){for(var s=0,a=0,r=0;o>r;++r)t.data.datasets[r].data[i]<0?a+=t.data.datasets[r].data[i]:s+=t.data.datasets[r].data[i];return this.getPixelForValue(0>e?a+e:s+e)}return this.getPixelForValue(e)},id:i.id});this.scales[a.id]=a},this)},draw:function(t){var e=t||1;this.clear(),i.each(this.scales,function(t){t.draw(this.chartArea)},this);for(var o=this.data.datasets.length-1;o>=0;o--){var s=this.data.datasets[o];i.each(s.metaData,function(t){t.transition(e)},this),s.metaDataset.transition(e).draw(),i.each(s.metaData,function(t){t.draw()})}this.tooltip.transition(e).draw()},events:function(t){this.lastActive=this.lastActive||[],this.active="mouseout"==t.type?[]:function(){switch(this.options.hover.mode){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.hover.onHover&&this.options.hover.onHover.call(this,this.active),("mouseup"==t.type||"click"==t.type)&&this.options.onClick&&this.options.onClick.call(this,t,this.active);var e,o;if(this.lastActive.length)switch(this.options.hover.mode){case"single":e=this.data.datasets[this.lastActive[0]._datasetIndex],o=this.lastActive[0]._index,this.lastActive[0]._model.radius=this.lastActive[0].custom&&this.lastActive[0].custom.radius?this.lastActive[0].custom.radius:i.getValueAtIndexOrDefault(e.radius,o,this.options.elements.point.radius),this.lastActive[0]._model.backgroundColor=this.lastActive[0].custom&&this.lastActive[0].custom.backgroundColor?this.lastActive[0].custom.backgroundColor:i.getValueAtIndexOrDefault(e.pointBackgroundColor,o,this.options.elements.point.backgroundColor),this.lastActive[0]._model.borderColor=this.lastActive[0].custom&&this.lastActive[0].custom.borderColor?this.lastActive[0].custom.borderColor:i.getValueAtIndexOrDefault(e.pointBorderColor,o,this.options.elements.point.borderColor),this.lastActive[0]._model.borderWidth=this.lastActive[0].custom&&this.lastActive[0].custom.borderWidth?this.lastActive[0].custom.borderWidth:i.getValueAtIndexOrDefault(e.pointBorderWidth,o,this.options.elements.point.borderWidth);break;case"label":for(var s=0;s",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue",showLabelBackdrop:!0,backdropColor:"rgba(255,255,255,0.75)",backdropPaddingY:2,backdropPaddingX:2}},animateRotate:!0};e.Type.extend({name:"PolarArea",defaults:o,initialize:function(){var t=this,o=e.scales.getScaleConstructor(this.options.scale.type);this.scale=new o({options:this.options.scale,lineArc:!0,width:this.chart.width,height:this.chart.height,xCenter:this.chart.width/2,yCenter:this.chart.height/2,ctx:this.chart.ctx,valuesCount:this.data.length,calculateRange:function(){this.min=null,this.max=null,i.each(t.data.datasets[0].data,function(t){null===this.min?this.min=t:tthis.max&&(this.max=t)},this)}}),i.bindEvents(this,this.options.events,this.events),i.bindEvents(this,this.options.events,this.events),i.each(this.data.datasets,function(t,o){t.metaData=[],i.each(t.data,function(i,s){t.metaData.push(new e.Arc({_chart:this.chart,_datasetIndex:o,_index:s,_model:{}}))},this)},this),this.tooltip=new e.Tooltip({_chart:this.chart,_data:this.data,_options:this.options},this),this.updateScaleRange(),this.scale.calculateRange(),e.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height),this.resetElements(),this.update()},updateScaleRange:function(){i.extend(this.scale,{size:i.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2})},resetElements:function(){1/this.data.datasets[0].data.length*2;i.each(this.data.datasets[0].metaData,function(t,e){this.data.datasets[0].data[e];i.extend(t,{_index:e,_model:{x:this.chart.width/2,y:this.chart.height/2,innerRadius:0,outerRadius:0,startAngle:Math.PI*-.5,endAngle:Math.PI*-.5,backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[0].backgroundColor,e,this.options.elements.arc.backgroundColor),hoverBackgroundColor:t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[0].hoverBackgroundColor,e,this.options.elements.arc.hoverBackgroundColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[0].borderWidth,e,this.options.elements.arc.borderWidth),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[0].borderColor,e,this.options.elements.arc.borderColor),label:i.getValueAtIndexOrDefault(this.data.datasets[0].labels,e,this.data.datasets[0].labels[e])}}),t.pivot()},this)},update:function(t){this.updateScaleRange(),this.scale.calculateRange(),this.scale.generateTicks(),this.scale.buildYLabels(),e.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height);var o=1/this.data.datasets[0].data.length*2;i.each(this.data.datasets[0].metaData,function(t,e){var s=this.data.datasets[0].data[e],a=-.5*Math.PI+Math.PI*o*e,r=a+o*Math.PI;i.extend(t,{_index:e,_model:{x:this.chart.width/2,y:this.chart.height/2,innerRadius:0,outerRadius:this.scale.calculateCenterOffset(s),startAngle:a,endAngle:r,backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[0].backgroundColor,e,this.options.elements.arc.backgroundColor),hoverBackgroundColor:t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[0].hoverBackgroundColor,e,this.options.elements.arc.hoverBackgroundColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[0].borderWidth,e,this.options.elements.arc.borderWidth),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[0].borderColor,e,this.options.elements.arc.borderColor),label:i.getValueAtIndexOrDefault(this.data.datasets[0].labels,e,this.data.datasets[0].labels[e])}}),t.pivot(),console.log(t)},this),this.render(t)},draw:function(t){var e=t||1;this.clear(),i.each(this.data.datasets[0].metaData,function(t){t.transition(e).draw()},this),this.scale.draw(),this.tooltip.transition(e).draw()},events:function(t){if("mouseout"==t.type)return this;this.lastActive=this.lastActive||[],this.active=function(){switch(this.options.hover.mode){case"single":return this.getSliceAtEvent(t);case"label":return this.getSlicesAtEvent(t);case"dataset":return this.getDatasetAtEvent(t);default:return t}}.call(this),this.options.hover.onHover&&this.options.hover.onHover.call(this,this.active),("mouseup"==t.type||"click"==t.type)&&this.options.onClick&&this.options.onClick.call(this,t,this.active);var e,o;if(this.lastActive.length)switch(this.options.hover.mode){case"single":e=this.data.datasets[this.lastActive[0]._datasetIndex],o=this.lastActive[0]._index,this.lastActive[0]._model.backgroundColor=this.lastActive[0].custom&&this.lastActive[0].custom.backgroundColor?this.lastActive[0].custom.backgroundColor:i.getValueAtIndexOrDefault(e.backgroundColor,o,this.options.elements.arc.backgroundColor),this.lastActive[0]._model.borderColor=this.lastActive[0].custom&&this.lastActive[0].custom.borderColor?this.lastActive[0].custom.borderColor:i.getValueAtIndexOrDefault(e.borderColor,o,this.options.elements.arc.borderColor),this.lastActive[0]._model.borderWidth=this.lastActive[0].custom&&this.lastActive[0].custom.borderWidth?this.lastActive[0].custom.borderWidth:i.getValueAtIndexOrDefault(e.borderWidth,o,this.options.elements.arc.borderWidth);break;case"label":for(var s=0;s",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue",showLabelBackdrop:!0,backdropColor:"rgba(255,255,255,0.75)",backdropPaddingY:2,backdropPaddingX:2},pointLabels:{fontFamily:"'Arial'",fontStyle:"normal",fontSize:10,fontColor:"#666"}},elements:{line:{tension:0}},legendTemplate:'
    <% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
'},initialize:function(){i.bindEvents(this,this.options.events,this.events),i.each(this.data.datasets,function(t,o){t.metaDataset=new e.Line({_chart:this.chart,_datasetIndex:o,_points:t.metaData,_loop:!0}),t.metaData=[],i.each(t.data,function(i,s){t.metaData.push(new e.Point({_datasetIndex:o,_index:s,_chart:this.chart,_model:{x:0,y:0}}))},this)},this),this.buildScale(),this.tooltip=new e.Tooltip({_chart:this.chart,_data:this.data,_options:this.options},this),e.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height),this.resetElements(),this.update()},nextPoint:function(t,e){return t[e+1]||t[0]},previousPoint:function(t,e){return t[e-1]||t[t.length-1]},resetElements:function(){this.eachElement(function(t,e,o,s){i.extend(t,{_chart:this.chart,_datasetIndex:s,_index:e,_scale:this.scale,_model:{x:this.scale.xCenter,y:this.scale.yCenter,tension:t.custom&&t.custom.tension?t.custom.tension:this.options.elements.line.tension,radius:t.custom&&t.custom.radius?t.custom.pointRadius:i.getValueAtIndexOrDefault(this.data.datasets[s].pointRadius,e,this.options.elements.point.radius),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBackgroundColor,e,this.options.elements.point.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBorderColor,e,this.options.elements.point.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBorderWidth,e,this.options.elements.point.borderWidth),skip:null===this.data.datasets[s].data[e],hitRadius:t.custom&&t.custom.hitRadius?t.custom.hitRadius:i.getValueAtIndexOrDefault(this.data.datasets[s].hitRadius,e,this.options.elements.point.hitRadius)}})},this),this.eachElement(function(t,e,o){i.splineCurve(this.previousPoint(o,e)._model,t._model,this.nextPoint(o,e)._model,t._model.tension);t._model.controlPointPreviousX=this.scale.xCenter,t._model.controlPointPreviousY=this.scale.yCenter,t._model.controlPointNextX=this.scale.xCenter,t._model.controlPointNextY=this.scale.yCenter,t.pivot()},this)},update:function(t){e.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height),this.eachDataset(function(t,e){var o;o=this.scale.min<0&&this.scale.max<0?this.scale.getPointPosition(0,this.scale.max):this.scale.min>0&&this.scale.max>0?this.scale.getPointPosition(0,this.scale.min):this.scale.getPointPosition(0,0),i.extend(t.metaDataset,{_datasetIndex:e,_children:t.metaData,_model:{tension:t.tension||this.options.elements.line.tension,backgroundColor:t.backgroundColor||this.options.elements.line.backgroundColor,borderWidth:t.borderWidth||this.options.elements.line.borderWidth,borderColor:t.borderColor||this.options.elements.line.borderColor,fill:void 0!==t.fill?t.fill:this.options.elements.line.fill,skipNull:void 0!==t.skipNull?t.skipNull:this.options.elements.line.skipNull,drawNull:void 0!==t.drawNull?t.drawNull:this.options.elements.line.drawNull,scaleTop:this.scale.top,scaleBottom:this.scale.bottom,scaleZero:o}}),t.metaDataset.pivot()}),this.eachElement(function(t,e,o,s){var a=this.scale.getPointPosition(e,this.scale.calculateCenterOffset(this.data.datasets[s].data[e]));i.extend(t,{_chart:this.chart,_datasetIndex:s,_index:e,_model:{x:a.x,y:a.y,tension:t.custom&&t.custom.tension?t.custom.tension:this.options.elements.line.tension,radius:t.custom&&t.custom.radius?t.custom.pointRadius:i.getValueAtIndexOrDefault(this.data.datasets[s].pointRadius,e,this.options.elements.point.radius),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBackgroundColor,e,this.options.elements.point.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBorderColor,e,this.options.elements.point.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBorderWidth,e,this.options.elements.point.borderWidth),skip:null===this.data.datasets[s].data[e],hitRadius:t.custom&&t.custom.hitRadius?t.custom.hitRadius:i.getValueAtIndexOrDefault(this.data.datasets[s].hitRadius,e,this.options.elements.point.hitRadius)}})},this),this.eachElement(function(t,e,o){var s=i.splineCurve(this.previousPoint(o,e)._model,t._model,this.nextPoint(o,e)._model,t._model.tension);t._model.controlPointPreviousX=s.previous.x,t._model.controlPointNextX=s.next.x,t._model.controlPointNextY=s.next.y>this.chartArea.bottom?this.chartArea.bottom:s.next.ythis.chartArea.bottom?this.chartArea.bottom:s.previous.ythis.max&&(this.max=t)},this)},this)}}),this.scale.setScaleSize(),this.scale.calculateRange(),this.scale.generateTicks(),this.scale.buildYLabels()},draw:function(t){var e=t||1;this.clear(),this.scale.draw(this.chartArea);for(var o=this.data.datasets.length-1;o>=0;o--){var s=this.data.datasets[o];i.each(s.metaData,function(t){t.transition(e)},this),s.metaDataset.transition(e).draw(),i.each(s.metaData,function(t){t.draw()})}this.tooltip.transition(e).draw()},events:function(t){this.lastActive=this.lastActive||[],this.active="mouseout"==t.type?[]:function(){switch(this.options.hover.mode){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.hover.onHover&&this.options.hover.onHover.call(this,this.active),("mouseup"==t.type||"click"==t.type)&&this.options.onClick&&this.options.onClick.call(this,t,this.active);var e,o;if(this.lastActive.length)switch(this.options.hover.mode){case"single":e=this.data.datasets[this.lastActive[0]._datasetIndex],o=this.lastActive[0]._index,this.lastActive[0]._model.radius=this.lastActive[0].custom&&this.lastActive[0].custom.radius?this.lastActive[0].custom.pointRadius:i.getValueAtIndexOrDefault(e.pointRadius,o,this.options.elements.point.radius),this.lastActive[0]._model.backgroundColor=this.lastActive[0].custom&&this.lastActive[0].custom.backgroundColor?this.lastActive[0].custom.backgroundColor:i.getValueAtIndexOrDefault(e.pointBackgroundColor,o,this.options.elements.point.backgroundColor),this.lastActive[0]._model.borderColor=this.lastActive[0].custom&&this.lastActive[0].custom.borderColor?this.lastActive[0].custom.borderColor:i.getValueAtIndexOrDefault(e.pointBorderColor,o,this.options.elements.point.borderColor),this.lastActive[0]._model.borderWidth=this.lastActive[0].custom&&this.lastActive[0].custom.borderWidth?this.lastActive[0].custom.borderWidth:i.getValueAtIndexOrDefault(e.pointBorderWidth,o,this.options.elements.point.borderWidth);break;case"label":for(var s=0;s",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue"}}],yAxes:[{type:"linear",display:!0,position:"left",id:"y-axis-1",gridLines:{show:!0,color:"rgba(0, 0, 0, 0.05)",lineWidth:1,drawOnChartArea:!0,drawTicks:!0,zeroLineWidth:1,zeroLineColor:"rgba(0,0,0,0.25)"},beginAtZero:!1,integersOnly:!1,override:null,labels:{show:!0,template:"<%=value.toLocaleString()%>",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue"}}]},legendTemplate:'
    <% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
',tooltips:{template:"(<%= value.x %>, <%= value.y %>)",multiTemplate:"<%if (datasetLabel){%><%=datasetLabel%>: <%}%>(<%= value.x %>, <%= value.y %>)"}};e.Type.extend({name:"Scatter",defaults:o,initialize:function(){i.bindEvents(this,this.options.events,this.events),i.each(this.data.datasets,function(t,o){t.metaDataset=new e.Line({_chart:this.chart,_datasetIndex:o,_points:t.metaData}),t.metaData=[],i.each(t.data,function(i,s){t.metaData.push(new e.Point({_datasetIndex:o,_index:s,_chart:this.chart,_model:{x:0,y:0}}))},this),t.xAxisID||(t.xAxisID=this.options.scales.xAxes[0].id),t.yAxisID||(t.yAxisID=this.options.scales.yAxes[0].id)},this),this.buildScale(),this.tooltip=new e.Tooltip({_chart:this.chart,_data:this.data,_options:this.options},this),e.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height),this.resetElements(),this.update()},nextPoint:function(t,e){return t[e+1]||t[e]},previousPoint:function(t,e){return t[e-1]||t[e]},resetElements:function(){this.eachElement(function(t,e,o,s){var a,r=this.scales[this.data.datasets[s].xAxisID],n=this.scales[this.data.datasets[s].yAxisID];a=n.getPixelForValue(n.min<0&&n.max<0?n.max:n.min>0&&n.max>0?n.min:0),i.extend(t,{_chart:this.chart,_xScale:r,_yScale:n,_datasetIndex:s,_index:e,_model:{x:r.getPixelForValue(this.data.datasets[s].data[e].x),y:a,tension:t.custom&&t.custom.tension?t.custom.tension:this.options.elements.line.tension,radius:t.custom&&t.custom.radius?t.custom.pointRadius:i.getValueAtIndexOrDefault(this.data.datasets[s].pointRadius,e,this.options.elements.point.radius),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBackgroundColor,e,this.options.elements.point.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBorderColor,e,this.options.elements.point.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBorderWidth,e,this.options.elements.point.borderWidth),skip:null===this.data.datasets[s].data[e]||null===this.data.datasets[s].data[e].x||null===this.data.datasets[s].data[e].y,hoverRadius:t.custom&&t.custom.hoverRadius?t.custom.hoverRadius:i.getValueAtIndexOrDefault(this.data.datasets[s].pointHitRadius,e,this.options.elements.point.hitRadius)}})},this),this.eachElement(function(t,e,o){var s=i.splineCurve(this.previousPoint(o,e)._model,t._model,this.nextPoint(o,e)._model,t._model.tension);t._model.controlPointPreviousX=s.previous.x,t._model.controlPointNextX=s.next.x,t._model.controlPointNextY=s.next.y>this.chartArea.bottom?this.chartArea.bottom:s.next.ythis.chartArea.bottom?this.chartArea.bottom:s.previous.y0&&s.max>0?s.min:0),i.extend(t.metaDataset,{_scale:s,_datasetIndex:e,_children:t.metaData,_model:{tension:t.tension||this.options.elements.line.tension,backgroundColor:t.backgroundColor||this.options.elements.line.backgroundColor,borderWidth:t.borderWidth||this.options.elements.line.borderWidth,borderColor:t.borderColor||this.options.elements.line.borderColor,fill:void 0!==t.fill?t.fill:this.options.elements.line.fill,skipNull:void 0!==t.skipNull?t.skipNull:this.options.elements.line.skipNull,drawNull:void 0!==t.drawNull?t.drawNull:this.options.elements.line.drawNull,scaleTop:s.top,scaleBottom:s.bottom,scaleZero:o}}),t.metaDataset.pivot()}),this.eachElement(function(t,e,o,s){var a=this.scales[this.data.datasets[s].xAxisID],r=this.scales[this.data.datasets[s].yAxisID];i.extend(t,{_chart:this.chart,_xScale:a,_yScale:r,_datasetIndex:s,_index:e,_model:{x:a.getPixelForValue(this.data.datasets[s].data[e].x),y:r.getPixelForValue(this.data.datasets[s].data[e].y),tension:t.custom&&t.custom.tension?t.custom.tension:this.options.elements.line.tension,radius:t.custom&&t.custom.radius?t.custom.pointRadius:i.getValueAtIndexOrDefault(this.data.datasets[s].pointRadius,e,this.options.elements.point.radius),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBackgroundColor,e,this.options.elements.point.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBorderColor,e,this.options.elements.point.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBorderWidth,e,this.options.elements.point.borderWidth),skip:null===this.data.datasets[s].data[e]||null===this.data.datasets[s].data[e].x||null===this.data.datasets[s].data[e].y,hoverRadius:t.custom&&t.custom.hoverRadius?t.custom.hoverRadius:i.getValueAtIndexOrDefault(this.data.datasets[s].pointHitRadius,e,this.options.elements.point.hitRadius)}})},this),this.eachElement(function(t,e,o){var s=i.splineCurve(this.previousPoint(o,e)._model,t._model,this.nextPoint(o,e)._model,t._model.tension);t._model.controlPointPreviousX=s.previous.x,t._model.controlPointNextX=s.next.x,t._model.controlPointNextY=s.next.y>this.chartArea.bottom?this.chartArea.bottom:s.next.ythis.chartArea.bottom?this.chartArea.bottom:s.previous.ythis.max&&(this.max=t.x)},this)},this)},s=function(){this.min=null,this.max=null,i.each(t.data.datasets,function(t){t.yAxisID===this.id&&i.each(t.data,function(t){null===this.min?this.min=t.y:t.ythis.max&&(this.max=t.y)},this)},this)};this.scales={},i.each(this.options.scales.xAxes,function(t){var i=e.scales.getScaleConstructor(t.type),s=new i({ctx:this.chart.ctx,options:t,calculateRange:o,id:t.id});this.scales[s.id]=s},this),i.each(this.options.scales.yAxes,function(t){var i=e.scales.getScaleConstructor(t.type),o=new i({ctx:this.chart.ctx,options:t,calculateRange:s,id:t.id,getPointPixelForValue:function(t){return this.getPixelForValue(t)}});this.scales[o.id]=o},this)},draw:function(t){var e=t||1;this.clear(),i.each(this.scales,function(t){t.draw(this.chartArea)},this);for(var o=this.data.datasets.length-1;o>=0;o--){var s=this.data.datasets[o];i.each(s.metaData,function(t){t.transition(e)},this),s.metaDataset.transition(e).draw(),i.each(s.metaData,function(t){t.draw()})}this.tooltip.transition(e).draw()},events:function(t){if("mouseout"==t.type)return this;this.lastActive=this.lastActive||[],this.active=function(){switch(this.options.hover.mode){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.hover.onHover&&this.options.hover.onHover.call(this,this.active);var e,o;if(this.lastActive.length)switch(this.options.hover.mode){case"single":e=this.data.datasets[this.lastActive[0]._datasetIndex],o=this.lastActive[0]._index,this.lastActive[0]._model.radius=this.lastActive[0].custom&&this.lastActive[0].custom.radius?this.lastActive[0].custom.pointRadius:i.getValueAtIndexOrDefault(e.pointRadius,o,this.options.elements.point.radius),this.lastActive[0]._model.backgroundColor=this.lastActive[0].custom&&this.lastActive[0].custom.backgroundColor?this.lastActive[0].custom.backgroundColor:i.getValueAtIndexOrDefault(e.pointBackgroundColor,o,this.options.elements.point.backgroundColor),this.lastActive[0]._model.borderColor=this.lastActive[0].custom&&this.lastActive[0].custom.borderColor?this.lastActive[0].custom.borderColor:i.getValueAtIndexOrDefault(e.pointBorderColor,o,this.options.elements.point.borderColor),this.lastActive[0]._model.borderWidth=this.lastActive[0].custom&&this.lastActive[0].custom.borderWidth?this.lastActive[0].custom.borderWidth:i.getValueAtIndexOrDefault(e.pointBorderWidth,o,this.options.elements.point.borderWidth);break;case"label":for(var s=0;s=o?o/12.92:Math.pow((o+.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,o=this.alpha()-t.alpha(),s=((i*o==-1?i:(i+o)/(1+i*o))+1)/2,a=1-s,r=this.rgbArray(),n=t.rgbArray(),h=0;he&&(e+=360),o=(n+h)/2,i=h==n?0:.5>=o?l/(h+n):l/(2-h-n),[e,100*i,100*o]}function o(t){var e,i,o,s=t[0],a=t[1],r=t[2],n=Math.min(s,a,r),h=Math.max(s,a,r),l=h-n;return i=0==h?0:l/h*1e3/10,h==n?e=0:s==h?e=(a-r)/l:a==h?e=2+(r-s)/l:r==h&&(e=4+(s-a)/l),e=Math.min(60*e,360),0>e&&(e+=360),o=h/255*1e3/10,[e,i,o]}function s(t){var e=t[0],o=t[1],s=t[2],a=i(t)[0],r=1/255*Math.min(e,Math.min(o,s)),s=1-1/255*Math.max(e,Math.max(o,s));return[a,100*r,100*s]}function a(t){var e,i,o,s,a=t[0]/255,r=t[1]/255,n=t[2]/255;return s=Math.min(1-a,1-r,1-n),e=(1-a-s)/(1-s)||0,i=(1-r-s)/(1-s)||0,o=(1-n-s)/(1-s)||0,[100*e,100*i,100*o,100*s]}function n(t){return U[JSON.stringify(t)]}function h(t){var e=t[0]/255,i=t[1]/255,o=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,o=o>.04045?Math.pow((o+.055)/1.055,2.4):o/12.92;var s=.4124*e+.3576*i+.1805*o,a=.2126*e+.7152*i+.0722*o,r=.0193*e+.1192*i+.9505*o;return[100*s,100*a,100*r]}function l(t){var e,i,o,s=h(t),a=s[0],r=s[1],n=s[2];return a/=95.047,r/=100,n/=108.883,a=a>.008856?Math.pow(a,1/3):7.787*a+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,n=n>.008856?Math.pow(n,1/3):7.787*n+16/116,e=116*r-16,i=500*(a-r),o=200*(r-n),[e,i,o]}function c(t){return L(l(t))}function d(t){var e,i,o,s,a,r=t[0]/360,n=t[1]/100,h=t[2]/100;if(0==n)return a=255*h,[a,a,a];i=.5>h?h*(1+n):h+n-h*n,e=2*h-i,s=[0,0,0];for(var l=0;3>l;l++)o=r+1/3*-(l-1),0>o&&o++,o>1&&o--,a=1>6*o?e+6*(i-e)*o:1>2*o?i:2>3*o?e+(i-e)*(2/3-o)*6:e,s[l]=255*a;return s}function u(t){var e,i,o=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),[o,100*e,100*i]}function m(t){return s(d(t))}function v(t){return a(d(t))}function p(t){return n(d(t))}function f(t){var e=t[0]/60,i=t[1]/100,o=t[2]/100,s=Math.floor(e)%6,a=e-Math.floor(e),r=255*o*(1-i),n=255*o*(1-i*a),h=255*o*(1-i*(1-a)),o=255*o;switch(s){case 0:return[o,h,r];case 1:return[n,o,r];case 2:return[r,o,h];case 3:return[r,n,o];case 4:return[h,r,o];case 5:return[o,r,n]}}function x(t){var e,i,o=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,[o,100*e,100*i]}function A(t){return s(f(t))}function C(t){return a(f(t))}function y(t){return n(f(t))}function w(t){var e,i,o,s,a=t[0]/360,n=t[1]/100,h=t[2]/100,l=n+h;switch(l>1&&(n/=l,h/=l),e=Math.floor(6*a),i=1-h,o=6*a-e,0!=(1&e)&&(o=1-o),s=n+o*(i-n),e){default:case 6:case 0:r=i,g=s,b=n;break;case 1:r=s,g=i,b=n;break;case 2:r=n,g=i,b=s;break;case 3:r=n,g=s,b=i;break;case 4:r=s,g=n,b=i;break;case 5:r=i,g=n,b=s}return[255*r,255*g,255*b]}function _(t){return i(w(t))}function k(t){return o(w(t))}function S(t){return a(w(t))}function I(t){return n(w(t))}function P(t){var e,i,o,s=t[0]/100,a=t[1]/100,r=t[2]/100,n=t[3]/100;return e=1-Math.min(1,s*(1-n)+n),i=1-Math.min(1,a*(1-n)+n),o=1-Math.min(1,r*(1-n)+n),[255*e,255*i,255*o]}function W(t){return i(P(t))}function D(t){return o(P(t))}function O(t){return s(P(t))}function M(t){return n(P(t))}function V(t){var e,i,o,s=t[0]/100,a=t[1]/100,r=t[2]/100;return e=3.2406*s+-1.5372*a+r*-.4986,i=s*-.9689+1.8758*a+.0415*r,o=.0557*s+a*-.204+1.057*r,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,o=o>.0031308?1.055*Math.pow(o,1/2.4)-.055:o=12.92*o,e=Math.min(Math.max(0,e),1),i=Math.min(Math.max(0,i),1),o=Math.min(Math.max(0,o),1),[255*e,255*i,255*o]}function R(t){var e,i,o,s=t[0],a=t[1],r=t[2];return s/=95.047,a/=100,r/=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,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,e=116*a-16,i=500*(s-a),o=200*(a-r),[e,i,o]}function B(t){return L(R(t))}function z(t){var e,i,o,s,a=t[0],r=t[1],n=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*(r/500+s-16/116)/7.787:95.047*Math.pow(r/500+s,3),o=.008859>=o/108.883?o=108.883*(s-n/200-16/116)/7.787:108.883*Math.pow(s-n/200,3),[e,i,o]}function L(t){var e,i,o,s=t[0],a=t[1],r=t[2];return e=Math.atan2(r,a),i=360*e/2/Math.PI,0>i&&(i+=360),o=Math.sqrt(a*a+r*r),[s,o,i]}function T(t){return V(z(t))}function F(t){var e,i,o,s=t[0],a=t[1],r=t[2];return o=r/360*2*Math.PI,e=a*Math.cos(o),i=a*Math.sin(o),[s,e,i]}function E(t){return z(F(t))}function N(t){return T(F(t))}function H(t){return G[t]}function q(t){return i(H(t))}function Y(t){return o(H(t))}function j(t){return s(H(t))}function X(t){return a(H(t))}function Z(t){return l(H(t))}function Q(t){return h(H(t))}e.exports={rgb2hsl:i,rgb2hsv:o,rgb2hwb:s,rgb2cmyk:a,rgb2keyword:n,rgb2xyz:h,rgb2lab:l,rgb2lch:c,hsl2rgb:d,hsl2hsv:u,hsl2hwb:m,hsl2cmyk:v,hsl2keyword:p,hsv2rgb:f,hsv2hsl:x,hsv2hwb:A,hsv2cmyk:C,hsv2keyword:y,hwb2rgb:w,hwb2hsl:_,hwb2hsv:k,hwb2cmyk:S,hwb2keyword:I,cmyk2rgb:P,cmyk2hsl:W,cmyk2hsv:D,cmyk2hwb:O,cmyk2keyword:M,keyword2rgb:H,keyword2hsl:q,keyword2hsv:Y,keyword2hwb:j,keyword2cmyk:X,keyword2lab:Z,keyword2xyz:Q,xyz2rgb:V,xyz2lab:R,xyz2lch:B,lab2xyz:z,lab2rgb:T,lab2lch:L,lch2lab:F,lch2xyz:E,lch2rgb:N};var G={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]},U={};for(var $ in G)U[JSON.stringify(G[$])]=$},{}],3:[function(t,e){var i=t("./conversions"),o=function(){return new h};for(var s in i){o[s+"Raw"]=function(t){return function(e){return"number"==typeof e&&(e=Array.prototype.slice.call(arguments)),i[t](e)}}(s);var a=/(\w+)2(\w+)/.exec(s),r=a[1],n=a[2];o[r]=o[r]||{},o[r][n]=o[s]=function(t){return function(e){"number"==typeof e&&(e=Array.prototype.slice.call(arguments));var o=i[t](e);if("string"==typeof o||void 0===o)return o;for(var s=0;se||t[3]&&t[3]<1?c(t,e):"rgb("+t[0]+", "+t[1]+", "+t[2]+")"}function c(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 u(t,e);var i=Math.round(t[0]/255*100),o=Math.round(t[1]/255*100),s=Math.round(t[2]/255*100);return"rgb("+i+"%, "+o+"%, "+s+"%)"}function u(t,e){var i=Math.round(t[0]/255*100),o=Math.round(t[1]/255*100),s=Math.round(t[2]/255*100);return"rgba("+i+"%, "+o+"%, "+s+"%, "+(e||t[3]||1)+")"}function m(t,e){return 1>e||t[3]&&t[3]<1?v(t,e):"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"}function v(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hsla("+t[0]+", "+t[1]+"%, "+t[2]+"%, "+e+")"}function g(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 p(t){return A[t.slice(0,3)]}function f(t,e,i){return Math.min(Math.max(e,t),i)}function b(t){var e=t.toString(16).toUpperCase();return e.length<2?"0"+e:e}var x=t("color-name");e.exports={getRgba:i,getHsla:o,getRgb:a,getHsl:r,getHwb:s,getAlpha:n,hexString:h,rgbString:l,rgbaString:c,percentString:d,percentaString:u,hslString:m,hslaString:v,hwbString:g,keyword:p};var A={};for(var C in x)A[x[C]]=C},{"color-name":5}],5:[function(t,e){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;e.helpers;e.defaults.global.elements.point={radius:3,backgroundColor:e.defaults.global.defaultColor,borderWidth:1,borderColor:e.defaults.global.defaultColor,hitRadius:1,hoverRadius:4,hoverBorderWidth:1},e.elements.Point=e.Element.extend({inRange:function(t,e){var i=this._view,a=i.hitRadius+i.radius;return Math.pow(t-i.x,2)+Math.pow(e-i.y,2)0||t.borderWidth>0)&&(i.beginPath(),i.arc(t.x,t.y,t.radius||e.defaults.global.elements.point.radius,0,2*Math.PI),i.closePath(),i.strokeStyle=t.borderColor||e.defaults.global.defaultColor,i.lineWidth=t.borderWidth||e.defaults.global.elements.point.borderWidth,i.fillStyle=t.backgroundColor||e.defaults.global.defaultColor,i.fill(),i.stroke())}})}.call(this),function(){"use strict";var t=this,e=t.Chart;e.helpers;e.defaults.global.elements.rectangle={backgroundColor:e.defaults.global.defaultColor,borderWidth:0,borderColor:e.defaults.global.defaultColor},e.elements.Rectangle=e.Element.extend({draw:function(){var t=this._chart.ctx,e=this._view,i=e.width/2,a=e.x-i,n=e.x+i,s=e.base-(e.base-e.y),o=e.borderWidth/2;e.borderWidth&&(a+=o,n-=o,s+=o),t.beginPath(),t.fillStyle=e.backgroundColor,t.strokeStyle=e.borderColor,t.lineWidth=e.borderWidth,t.moveTo(a,e.base),t.lineTo(a,s),t.lineTo(n,s),t.lineTo(n,e.base),t.fill(),e.borderWidth&&t.stroke()},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){var i=this._view;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._view;return t>=e.x-e.width/2&&t<=e.x+e.width/2},tooltipPosition:function(){var t=this._view;return t.y=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 O(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 f(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 p(t){return s(d(t))}function v(t){return o(d(t))}function m(t){return h(d(t))}function x(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 y(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 w(t){return s(x(t))}function k(t){return o(x(t))}function _(t){return h(x(t))}function C(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 S(t){return a(C(t))}function P(t){return n(C(t))}function M(t){return o(C(t))}function A(t){return h(C(t))}function D(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(D(t))}function I(t){return n(D(t))}function z(t){return s(D(t))}function R(t){return h(D(t))}function T(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 L(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 V(t){return O(L(t))}function W(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 O(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 E(t){return T(W(t))}function N(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 W(N(t))}function B(t){return E(N(t))}function q(t){return U[t]}function j(t){return a(q(t))}function Y(t){return n(q(t))}function X(t){return s(q(t))}function G(t){return o(q(t))}function Q(t){return c(q(t))}function Z(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:f,hsl2hwb:p,hsl2cmyk:v,hsl2keyword:m,hsv2rgb:x,hsv2hsl:y,hsv2hwb:w,hsv2cmyk:k,hsv2keyword:_,hwb2rgb:C,hwb2hsl:S,hwb2hsv:P,hwb2cmyk:M,hwb2keyword:A,cmyk2rgb:D,cmyk2hsl:F,cmyk2hsv:I,cmyk2hwb:z,cmyk2keyword:R,keyword2rgb:q,keyword2hsl:j,keyword2hsv:Y,keyword2hwb:X,keyword2cmyk:G,keyword2lab:Q,keyword2xyz:Z,xyz2rgb:T,xyz2lab:L,xyz2lch:V,lab2xyz:W,lab2rgb:E,lab2lch:O,lch2lab:N,lch2xyz:H,lch2rgb:B};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 f(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 f(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?p(t,e):"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"}function p(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 w[t.slice(0,3)]}function b(t,e,i){return Math.min(Math.max(e,t),i)}function x(t){var e=t.toString(16).toUpperCase();return e.length<2?"0"+e:e}var y=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:f,hslString:g,hslaString:p,hwbString:v,keyword:m};var w={};for(var k in y)w[y[k]]=k},{"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/combo-bar-line.html b/samples/combo-bar-line.html new file mode 100644 index 000000000..a4cd5fbfd --- /dev/null +++ b/samples/combo-bar-line.html @@ -0,0 +1,63 @@ + + + + + Bar Chart + + + + + +
+ +
+ + + + + diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index ef235c18d..1e74c8a3e 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -93,7 +93,6 @@ backgroundColor: rectangle.custom && rectangle.custom.backgroundColor ? rectangle.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().backgroundColor, index, this.chart.options.elements.rectangle.backgroundColor), borderColor: rectangle.custom && rectangle.custom.borderColor ? rectangle.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().borderColor, index, this.chart.options.elements.rectangle.borderColor), borderWidth: rectangle.custom && rectangle.custom.borderWidth ? rectangle.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().borderWidth, index, this.chart.options.elements.rectangle.borderWidth), - }, }); rectangle.pivot(); @@ -122,16 +121,6 @@ // }, this); // }, - // eachRectangle: function(callback) { - // helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) { - // helpers.each(dataset.metaData, function(element, index) { - // if (element instanceof Chart.Rectangle) { - // callback(element, index, dataset, datasetIndex); - // } - // }, this); - // }, this); - // }, - // addLine: function addLine(dataset, datasetIndex) { // if (dataset) { // dataset.metaDataset = new Chart.Line({ @@ -322,7 +311,6 @@ // }, - // setElementHoverStyle: function setElementHoverStyle(element) { // if (element instanceof Chart.Point) { // this.setPointHoverStyle(element); @@ -341,7 +329,7 @@ // point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, point._model.borderWidth); // }, - // setRectangleHoverStyle: function(rectangle) { + // setHoverStyle: function(rectangle) { // var dataset = this.chart.data.datasets[rectangle._datasetIndex]; // var index = rectangle._index; @@ -388,42 +376,42 @@ // }, this); // }, - // getElementsAtEvent: function(e) { - // var elementsArray = [], - // eventPosition = helpers.getRelativePosition(e), - // datasetIterator = function(dataset) { - // elementsArray.push(dataset.metaData[elementIndex]); - // }, - // elementIndex; + getElementsAtEvent: function(e) { + var elementsArray = [], + eventPosition = helpers.getRelativePosition(e), + datasetIterator = function(dataset) { + elementsArray.push(dataset.metaData[elementIndex]); + }, + elementIndex; - // for (var datasetIndex = 0; datasetIndex < this.chart.data.datasets.length; datasetIndex++) { - // for (elementIndex = 0; elementIndex < this.chart.data.datasets[datasetIndex].metaData.length; elementIndex++) { - // if (this.chart.data.datasets[datasetIndex].metaData[elementIndex].inGroupRange(eventPosition.x, eventPosition.y)) { - // helpers.each(this.chart.data.datasets, datasetIterator); - // } - // } - // } + for (var datasetIndex = 0; datasetIndex < this.chart.data.datasets.length; datasetIndex++) { + for (elementIndex = 0; elementIndex < this.chart.data.datasets[datasetIndex].metaData.length; elementIndex++) { + if (this.chart.data.datasets[datasetIndex].metaData[elementIndex].inGroupRange(eventPosition.x, eventPosition.y)) { + helpers.each(this.chart.data.datasets, datasetIterator); + } + } + } - // return elementsArray.length ? elementsArray : []; - // }, + 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); + getElementAtEvent: function(e) { + var element = []; + var eventPosition = helpers.getRelativePosition(e); - // for (var datasetIndex = 0; datasetIndex < this.chart.data.datasets.length; ++datasetIndex) { - // for (var elementIndex = 0; elementIndex < this.chart.data.datasets[datasetIndex].metaData.length; ++elementIndex) { - // if (this.chart.data.datasets[datasetIndex].metaData[elementIndex].inRange(eventPosition.x, eventPosition.y)) { - // element.push(this.chart.data.datasets[datasetIndex].metaData[elementIndex]); - // return element; - // } - // } - // } + for (var datasetIndex = 0; datasetIndex < this.chart.data.datasets.length; ++datasetIndex) { + for (var elementIndex = 0; elementIndex < this.chart.data.datasets[datasetIndex].metaData.length; ++elementIndex) { + if (this.chart.data.datasets[datasetIndex].metaData[elementIndex].inRange(eventPosition.x, eventPosition.y)) { + element.push(this.chart.data.datasets[datasetIndex].metaData[elementIndex]); + return element; + } + } + } - // return []; - // }, + return []; + }, }); diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js new file mode 100644 index 000000000..fd460eb92 --- /dev/null +++ b/src/controllers/controller.line.js @@ -0,0 +1,371 @@ +(function() { + + "use strict"; + + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; + + Chart.controllers.line = function(chart, datasetIndex) { + this.initialize.call(this, chart, datasetIndex); + }; + + helpers.extend(Chart.controllers.line.prototype, { + + initialize: function(chart, datasetIndex) { + this.chart = chart; + this.index = datasetIndex; + this.linkScales(); + this.addElements(); + }, + + linkScales: function() { + if (!this.getDataset().xAxisID) { + this.getDataset().xAxisID = this.chart.options.scales.xAxes[0].id; + } + + if (!this.getDataset().yAxisID) { + this.getDataset().yAxisID = this.chart.options.scales.yAxes[0].id; + } + }, + + getDataset: function() { + return this.chart.data.datasets[this.index]; + }, + + getScaleForId: function(scaleID) { + return this.chart.scales[scaleID]; + }, + + addElements: function() { + + this.getDataset().metaData = this.getDataset().metaData || []; + + this.getDataset().metaDataset = this.getDataset().metaDataset || new Chart.elements.Line({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _points: this.getDataset().metaData, + }); + + helpers.each(this.getDataset().data, function(value, index) { + this.getDataset().metaData[index] = this.getDataset().metaData[index] || new Chart.elements.Point({ + _chart: this.chart.chart, + _datasetIndex: this.index, + _index: index, + }); + }, this); + }, + + reset: function() { + this.update(true); + }, + + update: function(reset) { + + var line = this.getDataset().metaDataset; + var points = this.getDataset().metaData; + + var yScale = this.getScaleForId(this.getDataset().yAxisID); + var xScale = this.getScaleForId(this.getDataset().xAxisID); + var scaleBase; + + if (yScale.min < 0 && yScale.max < 0) { + scaleBase = yScale.getPixelForValue(yScale.max); + } else if (yScale.min > 0 && yScale.max > 0) { + scaleBase = yScale.getPixelForValue(yScale.min); + } else { + scaleBase = yScale.getPixelForValue(0); + } + + + // Update Line + helpers.extend(line, { + // Utility + _scale: yScale, + _datasetIndex: this.index, + // Data + _children: points, + // Model + _model: { + // Appearance + tension: line.custom && line.custom.tension ? line.custom.tension : (this.getDataset().tension || this.chart.options.elements.line.tension), + backgroundColor: line.custom && line.custom.backgroundColor ? line.custom.backgroundColor : (this.getDataset().backgroundColor || this.chart.options.elements.line.backgroundColor), + borderWidth: line.custom && line.custom.borderWidth ? line.custom.borderWidth : (this.getDataset().borderWidth || this.chart.options.elements.line.borderWidth), + borderColor: line.custom && line.custom.borderColor ? line.custom.borderColor : (this.getDataset().borderColor || this.chart.options.elements.line.borderColor), + fill: line.custom && line.custom.fill ? line.custom.fill : (this.getDataset().fill !== undefined ? this.getDataset().fill : this.chart.options.elements.line.fill), + skipNull: this.getDataset().skipNull !== undefined ? this.getDataset().skipNull : this.chart.options.elements.line.skipNull, + drawNull: this.getDataset().drawNull !== undefined ? this.getDataset().drawNull : this.chart.options.elements.line.drawNull, + // Scale + scaleTop: yScale.top, + scaleBottom: yScale.bottom, + scaleZero: scaleBase, + }, + }); + line.pivot(); + + // Update Points + helpers.each(points, function(point, index) { + helpers.extend(point, { + // Utility + _chart: this.chart.chart, + _xScale: xScale, + _yScale: yScale, + _datasetIndex: this.index, + _index: index, + + // Desired view properties + _model: { + x: xScale.getPointPixelForValue(this.getDataset().data[index], index, this.index), + y: yScale.getPointPixelForValue(this.getDataset().data[index], index, this.index), + // Appearance + tension: point.custom && point.custom.tension ? point.custom.tension : this.chart.options.elements.line.tension, + radius: point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(this.getDataset().radius, index, this.chart.options.elements.point.radius), + backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBackgroundColor, index, this.chart.options.elements.point.backgroundColor), + borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderColor, index, this.chart.options.elements.point.borderColor), + borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.getDataset().pointBorderWidth, index, this.chart.options.elements.point.borderWidth), + skip: this.getDataset().data[index] === null, + + // Tooltip + hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.getDataset().hitRadius, index, this.chart.options.elements.point.hitRadius), + }, + + }); + }, this); + + // Update bezier control points + helpers.each(this.getDataset().metaData, function(point, index) { + var controlPoints = helpers.splineCurve( + helpers.previousItem(this.getDataset().metaData, index)._model, + point._model, + helpers.nextItem(this.getDataset().metaData, index)._model, + point._model.tension + ); + + point._model.controlPointPreviousX = controlPoints.previous.x; + point._model.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.chart.chartArea.bottom) { + point._model.controlPointNextY = this.chart.chartArea.bottom; + } else if (controlPoints.next.y < this.chart.chartArea.top) { + point._model.controlPointNextY = this.chart.chartArea.top; + } else { + point._model.controlPointNextY = controlPoints.next.y; + } + + // Cap inner bezier handles to the upper/lower scale bounds + if (controlPoints.previous.y > this.chart.chartArea.bottom) { + point._model.controlPointPreviousY = this.chart.chartArea.bottom; + } else if (controlPoints.previous.y < this.chart.chartArea.top) { + point._model.controlPointPreviousY = this.chart.chartArea.top; + } else { + point._model.controlPointPreviousY = controlPoints.previous.y; + } + + // Now pivot the point for animation + point.pivot(); + }, this); + + }, + + draw: function(ease) { + var easingDecimal = ease || 1; + + // Transition Point Locations + helpers.each(this.getDataset().metaData, function(point, index) { + point.transition(easingDecimal); + }, this); + + // Transition and Draw the line + this.getDataset().metaDataset.transition(easingDecimal).draw(); + + // Draw the points + helpers.each(this.getDataset().metaData, function(point) { + point.draw(); + }); + }, + + + + + + + + + // eachLine: function eachLine(callback) { + // helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) { + // if (dataset.metaDataset && dataset.metaDataset instanceof Chart.Line) { + // callback.call(this, dataset, datasetIndex); + // } + // }, this); + // }, + + // addPoint: function addPoint(dataset, datasetIndex, index) { + // if (dataset) { + // dataset.metaData = dataset.metaData || new Array(this.chart.data.datasets[datasetIndex].data.length); + + // if (index < dataset.metaData.length) { + // dataset.metaData[index] = new Chart.Point({ + // _datasetIndex: datasetIndex, + // _index: index, + // _chart: this.chart.chart, + // _model: { + // x: 0, + // y: 0, + // }, + // }); + // } + // } + // }, + + + + // resetElements: function resetElements() { + // helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) { + // // All elements must be the same type for the given dataset so we are fine to check just the first one + // if (dataset.metaData[0] instanceof Chart.Point) { + // // Have points. Update all of them + // this.resetDatasetPoints(dataset, datasetIndex); + // } else if (dataset.metaData[0] instanceof Chart.Rectangle) { + // // Have rectangles (lines) + // this.resetDatasetRectangles(dataset, datasetIndex); + // } + // }, this); + // }, + + // resetDatasetPoints: function resetDatasetPoints(dataset, datasetIndex) { + // helpers.each(dataset.metaData, function(point, index) { + // var xScale = this.getScaleForId(this.chart.data.datasets[datasetIndex].xAxisID); + // var yScale = this.getScaleForId(this.chart.data.datasets[datasetIndex].yAxisID); + + // var yScalePoint; + + // if (yScale.min < 0 && yScale.max < 0) { + // // all less than 0. use the top + // yScalePoint = yScale.getPixelForValue(yScale.max); + // } else if (yScale.min > 0 && yScale.max > 0) { + // yScalePoint = yScale.getPixelForValue(yScale.min); + // } else { + // yScalePoint = yScale.getPixelForValue(0); + // } + + // helpers.extend(point, { + // // Utility + // _chart: this.chart.chart, //WTF + // _xScale: xScale, + // _yScale: yScale, + // _datasetIndex: datasetIndex, + // _index: index, + + // // Desired view properties + // _model: { + // x: xScale.getPointPixelForValue(this.chart.data.datasets[datasetIndex].data[index], index, datasetIndex), + // y: yScalePoint, + // }, + // }); + + // this.updatePointElementAppearance(point, datasetIndex, index); + // }, this); + + // this.updateBezierControlPoints(dataset); + // }, + + // resetDatasetRectangles: function resetDatasetRectangles(dataset, datasetIndex) { + + // }, + + // resetElementAppearance: function(element, datasetIndex, index) { + // if (element instanceof Chart.Point) { + // this.updatePointElementAppearance(element, datasetIndex, index); + // } else if (element instanceof Chart.Rectangle) { + // this.updateRectangleElementAppearance(element, datasetIndex, index); + // } + // }, + + // updateElements: function updateElements() { + // // Update the lines + // this.updateLines(); + + // helpers.each(this.chart.data.datasets, function(dataset, datasetIndex) { + // // All elements must be the same type for the given dataset so we are fine to check just the first one + // if (dataset.metaData[0] instanceof Chart.Point) { + // // Have points. Update all of them + // this.updatePoints(dataset, datasetIndex); + // } else if (dataset.metaData[0] instanceof Chart.Rectangle) { + // // Have rectangles (lines) + // this.updateRectangles(dataset, datasetIndex); + // } + // }, this); + // }, + + + // setElementHoverStyle: function setElementHoverStyle(element) { + // if (element instanceof Chart.Point) { + // this.setPointHoverStyle(element); + // } else if (element instanceof Chart.Rectangle) { + // this.setRectangleHoverStyle(element); + // } + // }, + + // setPointHoverStyle: function setPointHoverStyle(point) { + // var dataset = this.chart.data.datasets[point._datasetIndex]; + // var index = point._index; + + // point._model.radius = point.custom && point.custom.radius ? point.custom.radius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.chart.options.elements.point.hoverRadius); + // point._model.backgroundColor = point.custom && point.custom.hoverBackgroundColor ? point.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(point._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); + // point._model.borderColor = point.custom && point.custom.hoverBorderColor ? point.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBorderColor, index, helpers.color(point._model.borderColor).saturate(0.5).darken(0.1).rgbString()); + // point._model.borderWidth = point.custom && point.custom.hoverBorderWidth ? point.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, point._model.borderWidth); + // }, + + // setHoverStyle: function(rectangle) { + // var dataset = this.chart.data.datasets[rectangle._datasetIndex]; + // var index = rectangle._index; + + // rectangle._model.backgroundColor = rectangle.custom && rectangle.custom.hoverBackgroundColor ? rectangle.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(rectangle._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); + // rectangle._model.borderColor = rectangle.custom && rectangle.custom.hoverBorderColor ? rectangle.custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(rectangle._model.borderColor).saturate(0.5).darken(0.1).rgbString()); + // rectangle._model.borderWidth = rectangle.custom && rectangle.custom.hoverBorderWidth ? rectangle.custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, rectangle._model.borderWidth); + // }, + + getElementsAtEvent: function(e) { + var elementsArray = [], + eventPosition = helpers.getRelativePosition(e), + datasetIterator = function(dataset) { + elementsArray.push(dataset.metaData[elementIndex]); + }, + elementIndex; + + for (var datasetIndex = 0; datasetIndex < this.chart.data.datasets.length; datasetIndex++) { + for (elementIndex = 0; elementIndex < this.chart.data.datasets[datasetIndex].metaData.length; elementIndex++) { + if (this.chart.data.datasets[datasetIndex].metaData[elementIndex].inGroupRange(eventPosition.x, eventPosition.y)) { + helpers.each(this.chart.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.chart.data.datasets.length; ++datasetIndex) { + for (var elementIndex = 0; elementIndex < this.chart.data.datasets[datasetIndex].metaData.length; ++elementIndex) { + if (this.chart.data.datasets[datasetIndex].metaData[elementIndex].inRange(eventPosition.x, eventPosition.y)) { + element.push(this.chart.data.datasets[datasetIndex].metaData[elementIndex]); + return element; + } + } + } + + return []; + }, + }); + + + +}).call(this); diff --git a/src/core/core.controller.js b/src/core/core.controller.js index 4c2535ddd..cacdf29b4 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -191,6 +191,12 @@ this.tooltip.transition(easingDecimal).draw(); }, + + + + + + eachValue: function eachValue(callback) { helpers.each(this.data.datasets, function(dataset, datasetIndex) { helpers.each(dataset.data, callback, this, datasetIndex); diff --git a/src/core/core.helpers.js b/src/core/core.helpers.js index c3e649437..29f925bd5 100644 --- a/src/core/core.helpers.js +++ b/src/core/core.helpers.js @@ -327,6 +327,18 @@ } }; }, + nextItem = helpers.nextItem = function(collection, index, loop) { + if (loop) { + return collection[index + 1] || collection[0]; + } + return collection[index + 1] || collection[collection.length - 1]; + }, + previousItem = helpers.previousItem = function(collection, index, loop) { + if (loop) { + return collection[index - 1] || collection[collection.length - 1]; + } + return collection[index - 1] || collection[0]; + }, calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val) { return Math.floor(Math.log(val) / Math.LN10); }, diff --git a/src/elements/element.line.js b/src/elements/element.line.js index edefce6d0..bd5ba8326 100644 --- a/src/elements/element.line.js +++ b/src/elements/element.line.js @@ -28,7 +28,7 @@ }; - Chart.Line = Chart.Element.extend({ + Chart.elements.Line = Chart.Element.extend({ draw: function() { var vm = this._view; @@ -38,8 +38,8 @@ // Draw the background first (so the border is always on top) helpers.each(this._children, function(point, index) { - var previous = this.previousPoint(point, this._children, index); - var next = this.nextPoint(point, this._children, index); + var previous = helpers.previousItem(this._children, index); + var next = helpers.nextItem(this._children, index); // First point only if (index === 0) { @@ -112,8 +112,8 @@ ctx.beginPath(); helpers.each(this._children, function(point, index) { - var previous = this.previousPoint(point, this._children, index); - var next = this.nextPoint(point, this._children, index); + var previous = helpers.previousItem(this._children, index); + var next = helpers.nextItem(this._children, index); // First point only if (index === 0) { @@ -173,18 +173,6 @@ ctx.stroke(); }, - nextPoint: function(point, collection, index) { - if (this.loop) { - return collection[index + 1] || collection[0]; - } - return collection[index + 1] || collection[collection.length - 1]; - }, - previousPoint: function(point, collection, index) { - if (this.loop) { - return collection[index - 1] || collection[collection.length - 1]; - } - return collection[index - 1] || collection[0]; - }, }); }).call(this); diff --git a/src/elements/element.point.js b/src/elements/element.point.js index 13e272de6..ff9517c37 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.js @@ -29,7 +29,7 @@ }; - Chart.Point = Chart.Element.extend({ + Chart.elements.Point = Chart.Element.extend({ inRange: function(mouseX, mouseY) { var vm = this._view; var hoverRange = vm.hitRadius + vm.radius;