From: Tanner Linsley Date: Fri, 12 Jun 2015 20:00:48 +0000 (-0600) Subject: More maintainable file structure X-Git-Tag: 2.0.0-alpha2~22 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=5732e0d72a5aa0f84132c5b45ecef2b489df1a95;p=thirdparty%2FChart.js.git More maintainable file structure --- diff --git a/Chart.js b/Chart.js index 55be0663a..3c61a8d0d 100644 --- a/Chart.js +++ b/Chart.js @@ -54,12 +54,6 @@ //Globally expose the defaults to allow for user updating/changing Chart.defaults = { global: { - animation: { - duration: 1000, - easing: "easeOutQuart", - onProgress: function() {}, - onComplete: function() {}, - }, responsive: true, maintainAspectRatio: true, events: ["mousemove", "mouseout", "click", "touchstart", "touchmove", "touchend"], @@ -69,74 +63,10 @@ animationDuration: 400, }, onClick: null, - 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', - - }, defaultColor: defaultColor, - // Element defaults - elements: { - line: { - tension: 0.4, - backgroundColor: defaultColor, - borderWidth: 3, - borderColor: defaultColor, - fill: true, // do we fill in the area between the line and the x axis - skipNull: true, - drawNull: false, - }, - point: { - radius: 3, - backgroundColor: defaultColor, - borderWidth: 1, - borderColor: defaultColor, - // Hover - hitRadius: 1, - hoverRadius: 4, - hoverBorderWidth: 1, - }, - bar: { - backgroundColor: defaultColor, - borderWidth: 0, - borderColor: defaultColor, - valueSpacing: 5, - datasetSpacing: 1, - }, - slice: { - backgroundColor: defaultColor, - borderColor: "#fff", - borderWidth: 2, - }, - } + // Element defaults defined in element extensions + elements: {} }, }; @@ -1219,58 +1149,168 @@ Chart.Element.extend = inherits; - 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)); - }, + // 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.elements.arc = { + backgroundColor: Chart.defaults.global.defaultColor, + borderColor: "#fff", + borderWidth: 2 + }; + + 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.hitRadius, 2)); + 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 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 + + //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; + + var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2), + rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; return { - x: vm.x, - y: vm.y, - padding: vm.radius + vm.borderWidth + x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), + y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) }; }, draw: function() { - var vm = this._view; var ctx = this._chart.ctx; + var vm = this._view; + ctx.beginPath(); - if (vm.skip) { - return; - } - - if (vm.radius > 0 || vm.borderWidth > 0) { + ctx.arc(vm.x, vm.y, vm.outerRadius, vm.startAngle, vm.endAngle); - ctx.beginPath(); + ctx.arc(vm.x, vm.y, vm.innerRadius, vm.endAngle, vm.startAngle, true); - ctx.arc(vm.x, vm.y, vm.radius || Chart.defaults.global.elements.point.radius, 0, Math.PI * 2); - ctx.closePath(); + ctx.closePath(); + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = vm.borderWidth; - ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; - ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.point.borderWidth; + ctx.fillStyle = vm.backgroundColor; - ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; + ctx.fill(); + ctx.lineJoin = 'bevel'; - ctx.fill(); + if (vm.borderWidth) { 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.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() { @@ -1430,89 +1470,135 @@ }, }); - Chart.Arc = Chart.Element.extend({ +}).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.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.hoverRadius, 2)); + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)); } else { return false; } }, - inRange: function(chartX, chartY) { - - var vm = this._view; - - 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 - - //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; - - 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) + x: vm.x, + y: vm.y, + padding: vm.radius + vm.borderWidth }; }, draw: function() { - var ctx = this._chart.ctx; var vm = this._view; + var ctx = this._chart.ctx; - ctx.beginPath(); - ctx.arc(vm.x, vm.y, vm.outerRadius, vm.startAngle, vm.endAngle); + if (vm.skip) { + return; + } - ctx.arc(vm.x, vm.y, vm.innerRadius, vm.endAngle, vm.startAngle, true); + if (vm.radius > 0 || vm.borderWidth > 0) { - ctx.closePath(); - ctx.strokeStyle = vm.borderColor; - ctx.lineWidth = vm.borderWidth; + ctx.beginPath(); - ctx.fillStyle = vm.backgroundColor; + ctx.arc(vm.x, vm.y, vm.radius || Chart.defaults.global.elements.point.radius, 0, Math.PI * 2); + ctx.closePath(); - ctx.fill(); - ctx.lineJoin = 'bevel'; + ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; + ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.point.borderWidth; - if (vm.borderWidth) { + ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; + + ctx.fill(); ctx.stroke(); } } }); - 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; +}).call(this); - // 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; +/*! + * 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; } @@ -1565,994 +1651,877 @@ }, }); - 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 +}).call(this); - onAnimationProgress: null, // user specified callback to fire on each step of the animation - onAnimationComplete: null, // user specified callback to fire when the animation finishes - }); +(function() { + "use strict"; - Chart.Tooltip = Chart.Element.extend({ - initialize: function() { - var options = this._options; - extend(this, { - _model: { - // Positioning - xPadding: options.tooltips.xPadding, - yPadding: options.tooltips.yPadding, - xOffset: options.tooltips.xOffset, + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; - // Labels - textColor: options.tooltips.fontColor, - _fontFamily: options.tooltips.fontFamily, - _fontStyle: options.tooltips.fontStyle, - fontSize: options.tooltips.fontSize, + // 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 = 5; + var yPadding = 5; - // Title - titleTextColor: options.tooltips.titleFontColor, - _titleFontFamily: options.tooltips.titleFontFamily, - _titleFontStyle: options.tooltips.titleFontStyle, - titleFontSize: options.tooltips.titleFontSize, + 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"; + }); - // Appearance - caretHeight: options.tooltips.caretSize, - cornerRadius: options.tooltips.cornerRadius, - backgroundColor: options.tooltips.backgroundColor, - opacity: 0, - legendColorBackground: options.tooltips.multiKeyBackground, - }, - }); - }, - update: function() { + // Adjust the padding to take into account displaying labels + if (topScales.length === 0 || bottomScales.length === 0) { + var maxFontHeight = 0; - var ctx = this._chart.ctx; + 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); + } + }; - switch (this._options.hover.mode) { - case 'single': - helpers.extend(this._model, { - text: 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] : '', - }), - }); + helpers.each(leftScales, maxFontHeightFunction); + helpers.each(rightScales, maxFontHeightFunction); - var tooltipPosition = this._active[0].tooltipPosition(); - helpers.extend(this._model, { - x: Math.round(tooltipPosition.x), - y: Math.round(tooltipPosition.y), - caretPadding: tooltipPosition.padding - }); + 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 + } - break; + if (bottomScales.length === 0) { + // Add padding so that we can handle drawing the bottom nicely + yPadding += 1.5 * maxFontHeight; + } + } - case 'label': + // 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 | | + // | | | | + // |------------------------------------------------------| - // Tooltip Content + // 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 - var dataArray, - dataIndex; + // Step 1 + var chartWidth = width / 2; // min 50% + var chartHeight = height / 2; // min 50% + var aspectRatio = chartHeight / chartWidth; + var screenAspectRatio; - var labels = [], - colors = []; + if (chartInstance.options.maintainAspectRatio) { + screenAspectRatio = height / width; - for (var i = this._data.datasets.length - 1; i >= 0; i--) { - dataArray = this._data.datasets[i].metaData; - dataIndex = indexOf(dataArray, this._active[0]); - if (dataIndex !== -1) { - break; - } + if (aspectRatio != screenAspectRatio) { + chartHeight = chartWidth * screenAspectRatio; + aspectRatio = screenAspectRatio; } + } - 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); + chartWidth -= (2 * xPadding); + chartHeight -= (2 * yPadding); - // Reverse labels if stacked - helpers.each(this._options.stacked ? elements.reverse() : elements, function(element) { - xPositions.push(element._view.x); - yPositions.push(element._view.y); + // Step 2 + var verticalScaleWidth = (width - chartWidth) / (leftScales.length + rightScales.length); - //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 - }); + // Step 3 + var horizontalScaleHeight = (height - chartHeight) / (topScales.length + bottomScales.length); - }, this); + // Step 4; + var minimumScaleSizes = []; - yMin = min(yPositions); - yMax = max(yPositions); + var verticalScaleMinSizeFunction = function(scaleInstance) { + var minSize = scaleInstance.fit(verticalScaleWidth, chartHeight); + minimumScaleSizes.push({ + horizontal: false, + minSize: minSize, + scale: scaleInstance, + }); + }; - xMin = min(xPositions); - xMax = max(xPositions); + var horizontalScaleMinSizeFunction = function(scaleInstance) { + var minSize = scaleInstance.fit(chartWidth, horizontalScaleHeight); + minimumScaleSizes.push({ + horizontal: true, + minSize: minSize, + scale: scaleInstance, + }); + }; - return { - x: (xMin > this._chart.width / 2) ? xMin : xMax, - y: (yMin + yMax) / 2, - }; - }).call(this, dataIndex); + // vertical scales + helpers.each(leftScales, verticalScaleMinSizeFunction); + helpers.each(rightScales, verticalScaleMinSizeFunction); - // 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, - }); + // horizontal scales + helpers.each(topScales, horizontalScaleMinSizeFunction); + helpers.each(bottomScales, horizontalScaleMinSizeFunction); + // Step 5 + var maxChartHeight = height - (2 * yPadding); + var maxChartWidth = width - (2 * xPadding); - // Calculate Appearance Tweaks + helpers.each(minimumScaleSizes, function(wrapper) { + if (wrapper.horizontal) { + maxChartHeight -= wrapper.minSize.height; + } else { + maxChartWidth -= wrapper.minSize.width; + } + }); - this._model.height = (labels.length * this._model.fontSize) + ((labels.length - 1) * (this._model.fontSize / 2)) + (this._model.yPadding * 2) + this._model.titleFontSize * 1.5; + // At this point, maxChartHeight and maxChartWidth are the size the chart area could + // be if the axes are drawn at their minimum sizes. - var titleWidth = ctx.measureText(this.title).width, - //Label has a legend square as well so account for this. - labelWidth = longestText(ctx, this.font, labels) + this._model.fontSize + 3, - longestTextWidth = max([labelWidth, titleWidth]); + // Step 6 + var verticalScaleFitFunction = function(scaleInstance) { + var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { + return wrapper.scale === scaleInstance; + }); - this._model.width = longestTextWidth + (this._model.xPadding * 2); + if (wrapper) { + scaleInstance.fit(wrapper.minSize.width, maxChartHeight); + } + }; + var horizontalScaleFitFunction = function(scaleInstance) { + var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { + return wrapper.scale === scaleInstance; + }); - var halfHeight = this._model.height / 2; + var scaleMargin = { + left: totalLeftWidth, + right: totalRightWidth, + top: 0, + bottom: 0, + }; - //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; + if (wrapper) { + scaleInstance.fit(maxChartWidth, wrapper.minSize.height, scaleMargin); } + }; - //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; - } + var totalLeftWidth = xPadding; + var totalRightWidth = xPadding; + var totalTopHeight = yPadding; + var totalBottomHeight = yPadding; - return this; - }, - draw: function() { + helpers.each(leftScales, verticalScaleFitFunction); + helpers.each(rightScales, verticalScaleFitFunction); - var ctx = this._chart.ctx; - var vm = this._view; + // Figure out how much margin is on the left and right of the horizontal axes + helpers.each(leftScales, function(scaleInstance) { + totalLeftWidth += scaleInstance.width; + }); - switch (this._options.hover.mode) { - case 'single': + helpers.each(rightScales, function(scaleInstance) { + totalRightWidth += scaleInstance.width; + }); - ctx.font = fontString(vm.fontSize, vm._fontStyle, vm._fontFamily); + helpers.each(topScales, horizontalScaleFitFunction); + helpers.each(bottomScales, horizontalScaleFitFunction); - vm.xAlign = "center"; - vm.yAlign = "above"; + helpers.each(topScales, function(scaleInstance) { + totalTopHeight += scaleInstance.height; + }); + helpers.each(bottomScales, function(scaleInstance) { + totalBottomHeight += scaleInstance.height; + }); - //Distance between the actual element.y position and the start of the tooltip caret - var caretPadding = vm.caretPadding || 2; + // 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 tooltipWidth = ctx.measureText(vm.text).width + 2 * vm.xPadding, - tooltipRectHeight = vm.fontSize + 2 * vm.yPadding, - tooltipHeight = tooltipRectHeight + vm.caretHeight + caretPadding; + var scaleMargin = { + left: 0, + right: 0, + top: totalTopHeight, + bottom: totalBottomHeight + }; - if (vm.x + tooltipWidth / 2 > this._chart.width) { - vm.xAlign = "left"; - } else if (vm.x - tooltipWidth / 2 < 0) { - vm.xAlign = "right"; + if (wrapper) { + scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin); } + }); - if (vm.y - tooltipHeight < 0) { - vm.yAlign = "below"; - } + helpers.each(rightScales, function(scaleInstance) { + var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { + return wrapper.scale === scaleInstance; + }); - var tooltipX = vm.x - tooltipWidth / 2, - tooltipY = vm.y - tooltipHeight; + var scaleMargin = { + left: 0, + right: 0, + top: totalTopHeight, + bottom: totalBottomHeight + }; - ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString(); + if (wrapper) { + scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin); + } + }); - // 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; - } + // Step 7 + // Position the scales + var left = xPadding; + var top = yPadding; + var right = 0; + var bottom = 0; - switch (vm.xAlign) { - case "left": - tooltipX = vm.x - tooltipWidth + (vm.cornerRadius + vm.caretHeight); - break; - case "right": - tooltipX = vm.x - (vm.cornerRadius + vm.caretHeight); - break; - } + var verticalScalePlacer = function(scaleInstance) { + scaleInstance.left = left; + scaleInstance.right = left + scaleInstance.width; + scaleInstance.top = totalTopHeight; + scaleInstance.bottom = totalTopHeight + maxChartHeight; - drawRoundedRectangle(ctx, tooltipX, tooltipY, tooltipWidth, tooltipRectHeight, vm.cornerRadius); + // Move to next point + left = scaleInstance.right; + }; - ctx.fill(); + var horizontalScalePlacer = function(scaleInstance) { + scaleInstance.left = totalLeftWidth; + scaleInstance.right = totalLeftWidth + maxChartWidth; + scaleInstance.top = top; + scaleInstance.bottom = top + scaleInstance.height; - 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); + // Move to next point + top = scaleInstance.bottom; + }; - } - break; - case 'label': + helpers.each(leftScales, verticalScalePlacer); + helpers.each(topScales, horizontalScalePlacer); - 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(); + // Account for chart width and height + left += maxChartWidth; + top += maxChartHeight; - ctx.textAlign = "left"; - ctx.textBaseline = "middle"; - ctx.fillStyle = helpers.color(vm.titleTextColor).alpha(vm.opacity).rgbString(); - ctx.font = fontString(vm.fontSize, vm._titleFontStyle, vm._titleFontFamily); - ctx.fillText(vm.title, vm.x + vm.xPadding, this.getLineHeight(0)); + helpers.each(rightScales, verticalScalePlacer); + helpers.each(bottomScales, horizontalScalePlacer); - ctx.font = fontString(vm.fontSize, vm._fontStyle, vm._fontFamily); - helpers.each(vm.labels, function(label, index) { - ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString(); - ctx.fillText(label, vm.x + vm.xPadding + vm.fontSize + 3, this.getLineHeight(index + 1)); + // Step 8 + chartInstance.chartArea = { + left: totalLeftWidth, + top: totalTopHeight, + right: totalLeftWidth + maxChartWidth, + bottom: totalTopHeight + maxChartHeight, + }; + } + } + }; - //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. + // 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(scaleType, scaleConstructor) { + this.constructors[scaleType] = scaleConstructor; + }, + getScaleConstructor: function(scaleType) { + return this.constructors.hasOwnProperty(scaleType) ? this.constructors[scaleType] : undefined; + } + }; - 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); +}).call(this); - 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); +(function() { + "use strict"; + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; - }, this); - break; - } + 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"; }, - getLineHeight: function(index) { - var baseLineHeight = this._view.y - (this._view.height / 2) + this._view.yPadding, - afterTitleIndex = index - 1; + 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; - //If the index is zero, we're getting the title - if (index === 0) { - return baseLineHeight + this._view.titleFontSize / 2; + if (this.options.gridLines.offsetGridLines && includeOffset) { + valueOffset += (valueWidth / 2); + } + + return this.left + Math.round(valueOffset); } else { - return baseLineHeight + ((this._view.fontSize * 1.5 * afterTitleIndex) + this._view.fontSize / 2) + this._view.titleFontSize * 1.5; + 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.animationService = { - frameDuration: 17, - animations: [], - dropFrames: 0, - addAnimation: function(chartInstance, animationObject, duration) { + 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; - if (!duration) { - chartInstance.animating = true; - } + this.paddingRight = lastWidth / 2 + 3; + this.paddingLeft = firstWidth / 2 + 3; - 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.labelRotation = 0; - this.animations.push({ - chartInstance: chartInstance, - animationObject: animationObject - }); + if (this.options.display) { + var originalLabelWidth = helpers.longestText(this.ctx, labelFont, this.labels); + var cosRotation; + var sinRotation; + var firstRotatedWidth; - // 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.labelWidth = originalLabelWidth; - 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() { + //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; - var startTime = Date.now(); - var framesToDrop = 0; + //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 (this.dropFrames > 1) { - framesToDrop = Math.floor(this.dropFrames); - this.dropFrames -= framesToDrop; - } + firstRotated = cosRotation * firstWidth; + lastRotated = cosRotation * lastWidth; - for (var i = 0; i < this.animations.length; i++) { + // We're right aligning the text now. + if (firstRotated + this.options.labels.fontSize / 2 > this.yLabelWidth) { + this.paddingLeft = firstRotated + this.options.labels.fontSize / 2; + } - if (this.animations[i].animationObject.currentStep === null) { - this.animations[i].animationObject.currentStep = 0; - } + this.paddingRight = this.options.labels.fontSize / 2; - 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; - } + if (sinRotation * originalLabelWidth > maxHeight) { + // go back one step + this.labelRotation--; + break; + } - this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject); + this.labelRotation++; + this.labelWidth = cosRotation * originalLabelWidth; - 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--; } + } else { + this.labelWidth = 0; + this.paddingRight = 0; + this.paddingLeft = 0; } - var endTime = Date.now(); - var delay = endTime - startTime - this.frameDuration; - var frameDelay = delay / this.frameDuration; + if (margins) { + this.paddingLeft -= margins.left; + this.paddingRight -= margins.right; - if (frameDelay > 1) { - this.dropFrames += frameDelay; + this.paddingLeft = Math.max(this.paddingLeft, 0); + this.paddingRight = Math.max(this.paddingRight, 0); } - // Do we have more stuff to animate? - if (this.animations.length > 0) { - helpers.requestAnimFrame.call(window, this.digestWrapper); - } - } - }; + }, + // 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); - // 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); - }; - })()); + 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.labels); - if (amd) { - define(function() { - return Chart; - }); - } else if (typeof module === 'object' && module.exports) { - module.exports = Chart; - } + if (this.isHorizontal()) { + minSize.width = maxWidth; + this.width = maxWidth; - root.Chart = Chart; + var labelHeight = (Math.cos(helpers.toRadians(this.labelRotation)) * longestLabelWidth) + 1.5 * this.options.labels.fontSize; + minSize.height = Math.min(labelHeight, maxHeight); + } else { + minSize.height = maxHeight; + this.height = maxHeight; - Chart.noConflict = function() { - root.Chart = previous; - return Chart; - }; + minSize.width = Math.min(longestLabelWidth + 6, maxWidth); + } -}).call(this); + 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) { -(function() { - "use strict"; + var setContextLineSettings; - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; + // Make sure we draw text in the correct color + this.ctx.fillStyle = this.options.labels.fontColor; - var defaultConfig = { + 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; - stacked: false, + 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) - hover: { - mode: "label" - }, + 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; + } - scales: { - xAxes: [{ - scaleType: "dataset", // scatter should not use a dataset axis - display: true, - position: "bottom", - id: "x-axis-1", // need an ID so datasets can reference the scale + xLineValue += helpers.aliasPixel(this.ctx.lineWidth); - // 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, - }, + // Draw the label area + this.ctx.beginPath(); - // label settings - labels: { - show: true, - template: "<%=value%>", - fontSize: 12, - fontStyle: "normal", - fontColor: "#666", - fontFamily: "Helvetica Neue", - }, - }], - yAxes: [{ - scaleType: "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", + if (this.options.gridLines.drawTicks) { + this.ctx.moveTo(xLineValue, yTickStart); + this.ctx.lineTo(xLineValue, yTickEnd); + } - // 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)", - }, + // Draw the chart area + if (this.options.gridLines.drawOnChartArea) { + this.ctx.moveTo(xLineValue, chartArea.top); + this.ctx.lineTo(xLineValue, chartArea.bottom); + } - // scale numbers - beginAtZero: false, - override: null, + // Need to stroke in the loop because we are potentially changing line widths & colours + this.ctx.stroke(); + } - // label settings - labels: { - show: true, - template: "<%=value%>", - fontSize: 12, - fontStyle: "normal", - fontColor: "#666", - fontFamily: "Helvetica Neue", + 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.scales.registerScaleType("dataset", DatasetScale); - }; - Chart.Type.extend({ - name: "Bar", - defaults: defaultConfig, - initialize: function() { +}).call(this); - var _this = this; +(function() { + "use strict"; - // Events - helpers.bindEvents(this, this.options.events, this.events); + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; - //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); + 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 - // 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; + // 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 (!dataset.yAxisID) { - dataset.yAxisID = this.options.scales.yAxes[0].id; + 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); } - }, this); + } 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 - // Build and fit the scale. Needs to happen after the axis IDs have been set - this.buildScale(); + var maxTicks; - // 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); + 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))); + } - // Need to fit scales before we reset elements. - Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); + // Make sure we always have at least 2 ticks + maxTicks = Math.max(2, maxTicks); - // So that we animate from the baseline - this.resetElements(); + // 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. - // 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]; + // 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); - var yScalePoint; + 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 (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); + 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); } + } - helpers.extend(bar, { - // Utility - _chart: this.chart, - _xScale: xScale, - _yScale: yScale, - _datasetIndex: datasetIndex, - _index: index, + 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(); + } - // Desired view properties - _model: { - x: xScale.calculateBarX(this.data.datasets.length, datasetIndex, index), - y: yScalePoint, + // 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 = []; - // 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.bar.backgroundColor), - borderColor: bar.custom && bar.custom.borderColor ? bar.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderColor, index, this.options.elements.bar.borderColor), - borderWidth: bar.custom && bar.custom.borderWidth ? bar.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderWidth, index, this.options.elements.bar.borderWidth), + helpers.each(this.ticks, function(tick, index, ticks) { + var label; - // Tooltip - label: this.data.labels[index], - datasetLabel: this.data.datasets[datasetIndex].label, - }, - }); - bar.pivot(); + 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 + }); + } + + this.labels.push(label ? label : ""); // empty string will not render so we're good }, 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.bar.backgroundColor), - borderColor: bar.custom && bar.custom.borderColor ? bar.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderColor, index, this.options.elements.bar.borderColor), - borderWidth: bar.custom && bar.custom.borderWidth ? bar.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderWidth, index, this.options.elements.bar.borderWidth), - - // Tooltip - label: this.data.labels[index], - datasetLabel: this.data.datasets[datasetIndex].label, - }, - }); - bar.pivot(); - }, 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; + 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)); + } - this.render(animationDuration); + return pixel; }, - buildScale: function(labels) { - var self = this; + // 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(); - // Function to determine the range of all the - var calculateYRange = function() { - this.min = null; - this.max = null; + var minSize = { + width: 0, + height: 0, + }; - var positiveValues = []; - var negativeValues = []; + if (this.isHorizontal()) { + minSize.width = maxWidth; // fill all the width - 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; + // In a horizontal axis, we need some room for the scale to be drawn + // + // ----------------------------------------------------- + // | | | | | + // + minSize.height = this.options.gridLines.show ? 10 : 0; + } else { + minSize.height = maxHeight; // fill all the height - if (self.options.relativePoints) { - positiveValues[index] = 100; - } else { - if (value < 0) { - negativeValues[index] += value; - } else { - positiveValues[index] += value; - } - } - }, this); - } - }, this); + // 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 + // | + // -| + // | + // | + // -| + // | + // | + // -| + minSize.width = this.options.gridLines.show ? 10 : 0; + } - var values = positiveValues.concat(negativeValues); - this.min = helpers.min(values); - this.max = helpers.max(values); + if (this.options.labels.show) { + // 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 { - 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; - } + // 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 (this.max === null) { - this.max = value; - } else if (value > this.max) { - this.max = value; - } - }, this); - } - }, this); + if (largestTextWidth < maxLabelWidth) { + // We don't need all the room + minSize.width += largestTextWidth; + } else { + // Expand to max size + minSize.width = maxWidth; + } } - }; + } - // Map of scale ID to scale object so we can lookup later - this.scales = {}; + 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) { - // Build the x axis. The line chart only supports a single x axis - var ScaleClass = Chart.scales.getScaleConstructor(this.options.scales.xAxes[0].scaleType); - 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.elements.bar.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.elements.bar.datasetSpacing); + var setContextLineSettings; + var hasZero; - 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); + // Make sure we draw text in the correct color + this.ctx.fillStyle = this.options.labels.fontColor; - if (self.options.stacked) { - return xAbsolute + barWidth / 2; - } + 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; - return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * self.options.elements.bar.datasetSpacing) + barWidth / 2; - }, - }); - this.scales[xScale.id] = xScale; + helpers.each(this.ticks, function(tick, index) { + // Grid lines are vertical + var xValue = this.getPixelForValue(tick); - // Build up all the y scales - helpers.each(this.options.scales.yAxes, function(yAxisOptions) { - var ScaleClass = Chart.scales.getScaleConstructor(yAxisOptions.scaleType); - var scale = new ScaleClass({ - ctx: this.chart.ctx, - options: yAxisOptions, - calculateRange: calculateYRange, - calculateBarBase: function(datasetIndex, index) { - var base = 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; + } - if (self.options.stacked) { + xValue += helpers.aliasPixel(this.ctx.lineWidth); - var value = self.data.datasets[datasetIndex].data[index]; + // Draw the label area + this.ctx.beginPath(); - 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; - } - } + if (this.options.gridLines.drawTicks) { + this.ctx.moveTo(xValue, yTickStart); + this.ctx.lineTo(xValue, yTickEnd); } - return this.getPixelForValue(base); - } - - base = this.getPixelForValue(this.min); + // Draw the chart area + if (this.options.gridLines.drawOnChartArea) { + this.ctx.moveTo(xValue, chartArea.top); + this.ctx.lineTo(xValue, chartArea.bottom); + } - 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); - } + // Need to stroke in the loop because we are potentially changing line widths & colours + this.ctx.stroke(); + }, this); + } - return base; + if (this.options.labels.show) { + // Draw the labels - }, - calculateBarY: function(datasetIndex, index) { + var labelStartY; - 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; - } + if (this.options.position == "top") { + labelStartY = this.bottom - 10; + this.ctx.textBaseline = "bottom"; + } else { + // bottom side + labelStartY = this.top + 10; + this.ctx.textBaseline = "top"; } - 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) { + this.ctx.textAlign = "center"; + 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 xValue = this.getPixelForValue(this.ticks[index]); + this.ctx.fillText(label, xValue, labelStartY); + }, this); + } + } else { + // Vertical + if (this.options.gridLines.show) { - // If exiting chart - if (e.type == 'mouseout') { - return this; - } + // 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; - this.lastActive = this.lastActive || []; + helpers.each(this.ticks, function(tick, index) { + // Grid lines are horizontal + var yValue = this.getPixelForValue(tick); - // 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); + 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 + } - // On Hover hook - if (this.options.hover.onHover) { - this.options.hover.onHover.call(this, this.active); - } + yValue += helpers.aliasPixel(this.ctx.lineWidth); - if (e.type == 'mouseup' || e.type == 'click') { - if (this.options.onClick) { - this.options.onClick.call(this, e, this.active); - } - } + // Draw the label area + this.ctx.beginPath(); - 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; + if (this.options.gridLines.drawTicks) { + this.ctx.moveTo(xTickStart, yValue); + this.ctx.lineTo(xTickEnd, yValue); + } - 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.bar.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.bar.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.bar.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; + // Draw the chart area + if (this.options.gridLines.drawOnChartArea) { + this.ctx.moveTo(chartArea.left, yValue); + this.ctx.lineTo(chartArea.right, yValue); + } - 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.bar.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.bar.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.bar.borderWidth); - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } + this.ctx.stroke(); + }, this); + } - // 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; + if (this.options.labels.show) { + // Draw the labels - 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; + var labelStartX; - 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); + if (this.options.position == "left") { + labelStartX = this.right - 10; + this.ctx.textAlign = "right"; + } else { + // right side + labelStartX = this.left + 5; + this.ctx.textAlign = "left"; } - 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.ctx.textBaseline = "middle"; + this.ctx.font = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily); - this.tooltip.pivot(); - - // Hover animations - if (!this.animating) { - var changed; - - helpers.each(this.active, function(element, index) { - if (element !== this.lastActive[index]) { - changed = true; + helpers.each(this.labels, function(label, index) { + var yValue = this.getPixelForValue(this.ticks[index]); + this.ctx.fillText(label, labelStartX, yValue); + }, this); } - }, 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; - }, + } }); + Chart.scales.registerScaleType("linear", LinearScale); }).call(this); @@ -2562,994 +2531,834 @@ var root = this, Chart = root.Chart, - //Cache a local reference to Chart.helpers helpers = Chart.helpers; - 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 + var LinearRadialScale = Chart.Element.extend({ 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(); - + 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); }, - - calculateCircumference: function(dataset, value) { - if (dataset.total > 0) { - return (Math.PI * 2) * (value / dataset.total); + 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 { - return 0; + this.drawingArea = (this.options.display) ? (this.size / 2) - (this.fontSize / 2 + this.backdropPaddingY) : (this.size / 2); } - }, - resetElements: function() { - this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.elements.slice.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.slice.backgroundColor), - hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, this.options.elements.slice.hoverBackgroundColor), - borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.slice.borderWidth), - borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.slice.borderColor), - - label: helpers.getValueAtIndexOrDefault(dataset.label, index, this.data.labels[index]) - }, - }); - slice.pivot(); - }, this); - - }, this); + this.buildYLabels(); }, - update: function(animationDuration) { - - this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.elements.slice.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); + 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 - dataset.innerRadius = dataset.outerRadius - this.radiusLength; + // 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 = []; - helpers.each(dataset.metaData, function(slice, index) { + 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(slice, { - // Utility - _chart: this.chart, - _datasetIndex: datasetIndex, - _index: index, + var maxTicks = Math.min(11, Math.ceil(this.drawingArea / (2 * this.options.labels.fontSize))); - // 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, + // Make sure we always have at least 2 ticks + maxTicks = Math.max(2, maxTicks); - backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.slice.backgroundColor), - hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, this.options.elements.slice.hoverBackgroundColor), - borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.slice.borderWidth), - borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.slice.borderColor), + // 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. - label: helpers.getValueAtIndexOrDefault(dataset.label, index, this.data.labels[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); - 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; + 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; } + } - slice._model.endAngle = slice._model.startAngle + slice._model.circumference; + 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); + } + } - //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; - } + 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(); + } - slice.pivot(); - }, 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); + }, + buildYLabels: function() { + this.yLabels = []; - }, this); + helpers.each(this.ticks, function(tick, index, ticks) { + var label; - this.render(animationDuration); - }, - draw: function(easeDecimal) { - easeDecimal = easeDecimal || 1; - this.clear(); + 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.eachElement(function(slice) { - slice.transition(easeDecimal).draw(); + this.yLabels.push(label ? label : ""); }, 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); - } + 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 + */ - // 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); + // 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; + } } } - 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; + xProtrusionLeft = furthestLeft; - 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.slice.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.slice.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.slice.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; + xProtrusionRight = Math.ceil(furthestRight - this.width); - 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.slice.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.slice.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.slice.borderWidth); - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } + furthestRightAngle = this.getIndexAngle(furthestRightIndex); - // 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; + furthestLeftAngle = this.getIndexAngle(furthestLeftIndex); - 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; + radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2); - 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 - } - } + 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; - // Built in Tooltips - if (this.options.tooltips.enabled) { + this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2; - // The usual updates - this.tooltip.initialize(); + //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2]) + this.setCenterPoint(radiusReductionLeft, radiusReductionRight); - // Active - if (this.active.length) { - this.tooltip._model.opacity = 1; + }, + setCenterPoint: function(leftMovement, rightMovement) { - helpers.extend(this.tooltip, { - _active: this.active, - }); + var maxRight = this.width - rightMovement - this.drawingArea, + maxLeft = leftMovement + this.drawingArea; - this.tooltip.update(); - } else { - // Inactive - this.tooltip._model.opacity = 0; - } - } + 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 - // Hover animations - this.tooltip.pivot(); + 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; - if (!this.animating) { - var changed; + // Draw circular lines around the scale + if (this.options.gridLines.show) { + ctx.strokeStyle = this.options.gridLines.color; + ctx.lineWidth = this.options.gridLines.lineWidth; - helpers.each(this.active, function(element, index) { - if (element !== this.lastActive[index]) { - changed = true; + 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(); + } + } + + if (this.options.labels.show) { + ctx.font = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily); + + 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 + ); + } + + ctx.textAlign = 'center'; + ctx.textBaseline = "middle"; + ctx.fillStyle = this.options.labels.fontColor; + ctx.fillText(label, this.xCenter, yHeight); + } } }, 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 = []; + if (!this.options.lineArc) { + ctx.lineWidth = this.options.angleLines.lineWidth; + ctx.strokeStyle = this.options.angleLines.color; - var location = helpers.getRelativePosition(e); + 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.eachElement(function(slice, index) { - if (slice.inRange(location.x, location.y)) { - elements.push(slice); - } - }, this); - return elements; - }, - /*getSlicesAtEvent: function(e) { - var elements = []; + 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'; + } - var location = helpers.getRelativePosition(e); + // Set the correct text baseline based on outer positioning + if (exactQuarter) { + ctx.textBaseline = 'middle'; + } else if (upperHalf) { + ctx.textBaseline = 'bottom'; + } else { + ctx.textBaseline = 'top'; + } - this.eachElement(function(slice, index) { - if (slice.inGroupRange(location.x, location.y)) { - elements.push(slice); + ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y); + } } - }, this); - return elements; - },*/ + } + } }); + Chart.scales.registerScaleType("radialLinear", LinearRadialScale); - Chart.types.Doughnut.extend({ - name: "Pie", - defaults: helpers.merge(defaultConfig, { - cutoutPercentage: 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 root = this, Chart = root.Chart, helpers = Chart.helpers; - var defaultConfig = { + Chart.defaults.global.animation = { + duration: 1000, + easing: "easeOutQuart", + onProgress: function() {}, + onComplete: function() {}, + }; - stacked: false, + 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 - hover: { - mode: "label" - }, + onAnimationProgress: null, // user specified callback to fire on each step of the animation + onAnimationComplete: null, // user specified callback to fire when the animation finishes + }); - scales: { - xAxes: [{ - scaleType: "dataset", // scatter should not use a dataset axis - display: true, - position: "bottom", - id: "x-axis-1", // need an ID so datasets can reference the scale + Chart.animationService = { + frameDuration: 17, + animations: [], + dropFrames: 0, + addAnimation: function(chartInstance, animationObject, duration) { - // 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, - }, + if (!duration) { + chartInstance.animating = true; + } - // label settings - labels: { - show: true, - template: "<%=value%>", - fontSize: 12, - fontStyle: "normal", - fontColor: "#666", - fontFamily: "Helvetica Neue", - }, - }], - yAxes: [{ - scaleType: "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", + 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; + } + } - // 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)", - }, + this.animations.push({ + chartInstance: chartInstance, + animationObject: animationObject + }); - // scale numbers - beginAtZero: false, - override: null, + // 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; + }); - // label settings - labels: { - show: true, - template: "<%=value.toLocaleString()%>", - fontSize: 12, - fontStyle: "normal", - fontColor: "#666", - fontFamily: "Helvetica Neue", - } - }], + 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; - Chart.Type.extend({ - name: "Line", - defaults: defaultConfig, - initialize: function() { + if (this.dropFrames > 1) { + framesToDrop = Math.floor(this.dropFrames); + this.dropFrames -= framesToDrop; + } - var _this = this; + for (var i = 0; i < this.animations.length; i++) { - // Events - helpers.bindEvents(this, this.options.events, this.events); + if (this.animations[i].animationObject.currentStep === null) { + this.animations[i].animationObject.currentStep = 0; + } - // Create a new line and its points for each dataset and piece of data - helpers.each(this.data.datasets, function(dataset, datasetIndex) { + 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; + } - dataset.metaDataset = new Chart.Line({ - _chart: this.chart, - _datasetIndex: datasetIndex, - _points: dataset.metaData, - }); + this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject); - dataset.metaData = []; + 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--; + } + } - 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, - }, - })); + var endTime = Date.now(); + var delay = endTime - startTime - this.frameDuration; + var frameDelay = delay / this.frameDuration; - }, this); + if (frameDelay > 1) { + this.dropFrames += frameDelay; + } - // 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; + // Do we have more stuff to animate? + if (this.animations.length > 0) { + helpers.requestAnimFrame.call(window, this.digestWrapper); + } + } + }; - if (!dataset.yAxisID) { - dataset.yAxisID = this.options.scales.yAxes[0].id; - } +}).call(this); - }, 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 + */ - // 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); +(function() { - // Need to fit scales before we reset elements. - Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); + "use strict"; - // Reset so that we animation from the baseline - this.resetElements(); + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; - // 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]; + 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', + }; - var yScalePoint; + 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, - 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); - } + // Labels + textColor: options.tooltips.fontColor, + _fontFamily: options.tooltips.fontFamily, + _fontStyle: options.tooltips.fontStyle, + fontSize: options.tooltips.fontSize, - helpers.extend(point, { - // Utility - _chart: this.chart, - _xScale: xScale, - _yScale: yScale, - _datasetIndex: datasetIndex, - _index: index, + // Title + titleTextColor: options.tooltips.titleFontColor, + _titleFontFamily: options.tooltips.titleFontFamily, + _titleFontStyle: options.tooltips.titleFontStyle, + titleFontSize: options.tooltips.titleFontSize, - // 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 + caretHeight: options.tooltips.caretSize, + cornerRadius: options.tooltips.cornerRadius, + backgroundColor: options.tooltips.backgroundColor, + opacity: 0, + legendColorBackground: options.tooltips.multiKeyBackground, + }, + }); + }, + update: function() { - // 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, + var ctx = this._chart.ctx; - // Tooltip - hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].hitRadius, index, this.options.elements.point.hitRadius), - }, - }); - }, this); + switch (this._options.hover.mode) { + case 'single': + helpers.extend(this._model, { + text: 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] : '', + }), + }); - // 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 - ); + var tooltipPosition = this._active[0].tooltipPosition(); + helpers.extend(this._model, { + x: Math.round(tooltipPosition.x), + y: Math.round(tooltipPosition.y), + caretPadding: tooltipPosition.padding + }); - point._model.controlPointPreviousX = controlPoints.previous.x; - point._model.controlPointNextX = controlPoints.next.x; + break; - // Prevent the bezier going outside of the bounds of the graph + case 'label': - // 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; - } + // Tooltip Content - // 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; - } + var dataArray, + dataIndex; - // Now pivot the point for animation - point.pivot(); - }, this); - }, - update: function(animationDuration) { + var labels = [], + colors = []; - Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); + for (var i = this._data.datasets.length - 1; i >= 0; i--) { + dataArray = this._data.datasets[i].metaData; + dataIndex = indexOf(dataArray, this._active[0]); + if (dataIndex !== -1) { + break; + } + } - // Update the lines - this.eachDataset(function(dataset, datasetIndex) { - var yScale = this.scales[dataset.yAxisID]; - var scaleBase; + 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); - 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); - } + // Reverse labels if stacked + helpers.each(this._options.stacked ? elements.reverse() : elements, function(element) { + xPositions.push(element._view.x); + yPositions.push(element._view.y); - 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, // 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, - }, - }); + //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 + }); - dataset.metaDataset.pivot(); - }); + }, this); - // 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]; + yMin = min(yPositions); + yMax = max(yPositions); - helpers.extend(point, { - // Utility - _chart: this.chart, - _xScale: xScale, - _yScale: yScale, - _datasetIndex: datasetIndex, - _index: index, + xMin = min(xPositions); + xMax = max(xPositions); - // 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), + return { + x: (xMin > this._chart.width / 2) ? xMin : xMax, + y: (yMin + yMax) / 2, + }; + }).call(this, dataIndex); - // 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, + // 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, + }); - // Tooltip - hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].hitRadius, index, this.options.elements.point.hitRadius), - }, - }); - }, this); + // Calculate Appearance Tweaks - // 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 - ); + this._model.height = (labels.length * this._model.fontSize) + ((labels.length - 1) * (this._model.fontSize / 2)) + (this._model.yPadding * 2) + this._model.titleFontSize * 1.5; - point._model.controlPointPreviousX = controlPoints.previous.x; - point._model.controlPointNextX = controlPoints.next.x; + var titleWidth = ctx.measureText(this.title).width, + //Label has a legend square as well so account for this. + labelWidth = longestText(ctx, this.font, labels) + this._model.fontSize + 3, + longestTextWidth = max([labelWidth, titleWidth]); - // Prevent the bezier going outside of the bounds of the graph + this._model.width = longestTextWidth + (this._model.xPadding * 2); - // 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; - } + var halfHeight = this._model.height / 2; - // Now pivot the point for animation - point.pivot(); - }, this); + //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; + } - this.render(animationDuration); + //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; }, - buildScale: function() { - var self = this; + draw: function() { - // Function to determine the range of all the - var calculateYRange = function() { - this.min = null; - this.max = null; + var ctx = this._chart.ctx; + var vm = this._view; - var positiveValues = []; - var negativeValues = []; + switch (this._options.hover.mode) { + case 'single': - 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; + ctx.font = helpers.fontString(vm.fontSize, vm._fontStyle, vm._fontFamily); - if (self.options.relativePoints) { - positiveValues[index] = 100; - } else { - if (value < 0) { - negativeValues[index] += value; - } else { - positiveValues[index] += value; - } - } - }, this); - } - }, this); + vm.xAlign = "center"; + vm.yAlign = "above"; - 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; - } + //Distance between the actual element.y position and the start of the tooltip caret + var caretPadding = vm.caretPadding || 2; - if (this.max === null) { - this.max = value; - } else if (value > this.max) { - this.max = value; - } - }, this); - } - }, this); - } - }; + var tooltipWidth = ctx.measureText(vm.text).width + 2 * vm.xPadding, + tooltipRectHeight = vm.fontSize + 2 * vm.yPadding, + tooltipHeight = tooltipRectHeight + vm.caretHeight + caretPadding; - // Map of scale ID to scale object so we can lookup later - this.scales = {}; + if (vm.x + tooltipWidth / 2 > this._chart.width) { + vm.xAlign = "left"; + } else if (vm.x - tooltipWidth / 2 < 0) { + vm.xAlign = "right"; + } - // Build the x axis. The line chart only supports a single x axis - var ScaleClass = Chart.scales.getScaleConstructor(this.options.scales.xAxes[0].scaleType); - 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; + if (vm.y - tooltipHeight < 0) { + vm.yAlign = "below"; + } - // Build up all the y scales - helpers.each(this.options.scales.yAxes, function(yAxisOptions) { - var ScaleClass = Chart.scales.getScaleConstructor(yAxisOptions.scaleType); - 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; + var tooltipX = vm.x - tooltipWidth / 2, + tooltipY = vm.y - tooltipHeight; - 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]; - } - } + ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString(); - if (value < 0) { - return this.getPixelForValue(offsetNeg + value); - } else { - return this.getPixelForValue(offsetPos + value); - } - } else { - return this.getPixelForValue(value); + // 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; } - }, - id: yAxisOptions.id, - }); - - this.scales[scale.id] = scale; - }, this); - }, - draw: function(ease) { - var easingDecimal = ease || 1; - this.clear(); + switch (vm.xAlign) { + case "left": + tooltipX = vm.x - tooltipWidth + (vm.cornerRadius + vm.caretHeight); + break; + case "right": + tooltipX = vm.x - (vm.cornerRadius + vm.caretHeight); + break; + } - // Draw all the scales - helpers.each(this.scales, function(scale) { - scale.draw(this.chartArea); - }, this); + helpers.drawRoundedRectangle(ctx, tooltipX, tooltipY, tooltipWidth, tooltipRectHeight, vm.cornerRadius); - // reverse for-loop for proper stacking - for (var i = this.data.datasets.length - 1; i >= 0; i--) { + ctx.fill(); - var dataset = this.data.datasets[i]; + 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); - // Transition Point Locations - helpers.each(dataset.metaData, function(point, index) { - point.transition(easingDecimal); - }, this); + } + break; + case 'label': - // 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; + 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(); - 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 - } - } + 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)); - // Built in Tooltips - if (this.options.tooltips.enabled) { + //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. - // The usual updates - this.tooltip.initialize(); + 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); - // Active - if (this.active.length) { - this.tooltip._model.opacity = 1; + 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); - helpers.extend(this.tooltip, { - _active: this.active, - }); - this.tooltip.update(); - } else { - // Inactive - this.tooltip._model.opacity = 0; - } + }, this); + break; } + }, + getLineHeight: function(index) { + var baseLineHeight = this._view.y - (this._view.height / 2) + this._view.yPadding, + afterTitleIndex = index - 1; - - // 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); - } + //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; } - // Remember Last Active - this.lastActive = this.active; - return this; }, }); - }).call(this); (function() { @@ -3557,117 +3366,115 @@ var root = this, Chart = root.Chart, - //Cache a local reference to Chart.helpers helpers = Chart.helpers; var defaultConfig = { - scale: { - scaleType: "radialLinear", - display: true, - - //Boolean - Whether to animate scaling the chart from the centre - animate: false, - - lineArc: true, + stacked: false, + valueSpacing: 5, + datasetSpacing: 1, - // grid line settings - gridLines: { - show: true, - color: "rgba(0, 0, 0, 0.05)", - lineWidth: 1, - }, + hover: { + mode: "label" + }, - // scale numbers - beginAtZero: true, + scales: { + xAxes: [{ + scaleType: "dataset", // scatter should not use a dataset axis + display: true, + position: "bottom", + id: "x-axis-1", // need an ID so datasets can reference the scale - // label settings - labels: { - show: true, - template: "<%=value.toLocaleString()%>", - fontSize: 12, - fontStyle: "normal", - fontColor: "#666", - fontFamily: "Helvetica Neue", + // 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, + }, - //Boolean - Show a backdrop to the scale label - showLabelBackdrop: true, + // label settings + labels: { + show: true, + template: "<%=value%>", + fontSize: 12, + fontStyle: "normal", + fontColor: "#666", + fontFamily: "Helvetica Neue", + }, + }], + yAxes: [{ + scaleType: "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", - //String - The colour of the label backdrop - backdropColor: "rgba(255,255,255,0.75)", + // 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)", + }, - //Number - The backdrop padding above & below the label in pixels - backdropPaddingY: 2, + // scale numbers + beginAtZero: false, + override: null, - //Number - The backdrop padding to the side of the label in pixels - backdropPaddingX: 2, - } + // label settings + labels: { + show: true, + template: "<%=value%>", + fontSize: 12, + fontStyle: "normal", + fontColor: "#666", + fontFamily: "Helvetica Neue", + } + }], }, - //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 + name: "Bar", 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.scaleType); - 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); + var _this = this; - //Set up tooltip events on the chart + // 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.Arc({ + dataset.metaData.push(new Chart.Rectangle({ _chart: this.chart, _datasetIndex: datasetIndex, _index: index, - _model: {} })); }, 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, @@ -3675,135 +3482,325 @@ _options: this.options, }, this); - // Fit the scale before we animate - this.updateScaleRange(); - this.scale.calculateRange(); + // Need to fit scales before we reset elements. Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - // so that we animate nicely + // So that we animate from the baseline 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; + // 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]; - // Map new data to data points - helpers.each(this.data.datasets[0].metaData, function(slice, index) { + var yScalePoint; - var value = this.data.datasets[0].data[index]; + 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(slice, { + helpers.extend(bar, { + // Utility + _chart: this.chart, + _xScale: xScale, + _yScale: yScale, + _datasetIndex: datasetIndex, _index: index, + + // Desired view properties _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, + x: xScale.calculateBarX(this.data.datasets.length, datasetIndex, index), + y: yScalePoint, - backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[0].backgroundColor, index, this.options.elements.slice.backgroundColor), - hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[0].hoverBackgroundColor, index, this.options.elements.slice.hoverBackgroundColor), - borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[0].borderWidth, index, this.options.elements.slice.borderWidth), - borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[0].borderColor, index, this.options.elements.slice.borderColor), + // 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.bar.backgroundColor), + borderColor: bar.custom && bar.custom.borderColor ? bar.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderColor, index, this.options.elements.bar.borderColor), + borderWidth: bar.custom && bar.custom.borderWidth ? bar.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderWidth, index, this.options.elements.bar.borderWidth), - label: helpers.getValueAtIndexOrDefault(this.data.datasets[0].labels, index, this.data.datasets[0].labels[index]) + // Tooltip + label: this.data.labels[index], + datasetLabel: this.data.datasets[datasetIndex].label, }, }); - - slice.pivot(); + bar.pivot(); }, this); }, update: function(animationDuration) { - - this.updateScaleRange(); - this.scale.calculateRange(); - this.scale.generateTicks(); - this.scale.buildYLabels(); - + // Update the scale sizes 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); + // 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(slice, { + helpers.extend(bar, { + // Utility + _chart: this.chart, + _xScale: xScale, + _yScale: yScale, + _datasetIndex: datasetIndex, _index: index, + + // Desired view properties _model: { - x: this.chart.width / 2, - y: this.chart.height / 2, - innerRadius: 0, - outerRadius: this.scale.calculateCenterOffset(value), - startAngle: startAngle, - endAngle: endAngle, + x: xScale.calculateBarX(this.data.datasets.length, datasetIndex, index), + y: yScale.calculateBarY(datasetIndex, index), - backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[0].backgroundColor, index, this.options.elements.slice.backgroundColor), - hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[0].hoverBackgroundColor, index, this.options.elements.slice.hoverBackgroundColor), - borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[0].borderWidth, index, this.options.elements.slice.borderWidth), - borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[0].borderColor, index, this.options.elements.slice.borderColor), + // 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.bar.backgroundColor), + borderColor: bar.custom && bar.custom.borderColor ? bar.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderColor, index, this.options.elements.bar.borderColor), + borderWidth: bar.custom && bar.custom.borderWidth ? bar.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderWidth, index, this.options.elements.bar.borderWidth), - label: helpers.getValueAtIndexOrDefault(this.data.datasets[0].labels, index, this.data.datasets[0].labels[index]) + // Tooltip + label: this.data.labels[index], + datasetLabel: this.data.datasets[datasetIndex].label, }, }); - slice.pivot(); - - console.log(slice); - + bar.pivot(); }, this); + this.render(animationDuration); }, - draw: function(ease) { - var easingDecimal = ease || 1; - - this.clear(); + buildScale: function(labels) { + var self = this; - helpers.each(this.data.datasets[0].metaData, function(slice, index) { - slice.transition(easingDecimal).draw(); - }, this); + // Function to determine the range of all the + var calculateYRange = function() { + this.min = null; + this.max = null; - this.scale.draw(); + var positiveValues = []; + var negativeValues = []; - this.tooltip.transition(easingDecimal).draw(); - }, - events: function(e) { + 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 exiting chart - if (e.type == 'mouseout') { - return this; - } + if (self.options.relativePoints) { + positiveValues[index] = 100; + } else { + if (value < 0) { + negativeValues[index] += value; + } else { + positiveValues[index] += value; + } + } + }, this); + } + }, this); - this.lastActive = this.lastActive || []; + var values = positiveValues.concat(negativeValues); + this.min = helpers.min(values); + this.max = helpers.max(values); - // 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); + } 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].scaleType); + 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.elements.bar.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.elements.bar.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.elements.bar.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.scaleType); + 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) { @@ -3825,18 +3822,18 @@ 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.slice.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.slice.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.slice.borderWidth); + 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.bar.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.bar.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.bar.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.slice.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.slice.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.slice.borderWidth); + 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.bar.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.bar.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.bar.borderWidth); } break; case 'dataset': @@ -3853,20 +3850,18 @@ 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); + 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.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); + 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': @@ -3899,9 +3894,9 @@ } - // Hover animations this.tooltip.pivot(); + // Hover animations if (!this.animating) { var changed; @@ -3917,7 +3912,7 @@ (this.lastActive.length && this.active.length && changed)) { this.stop(); - this.render(this.options.hover.animationDuration); + this.render(this.options.hoverAnimationDuration); } } @@ -3925,32 +3920,9 @@ 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() { @@ -3958,120 +3930,54 @@ var root = this, Chart = root.Chart, + //Cache a local reference to Chart.helpers helpers = Chart.helpers; + var defaultConfig = { + animation: { + //Boolean - Whether we animate the rotation of the Doughnut + animateRotate: true, - Chart.Type.extend({ - name: "Radar", - defaults: { + //Boolean - Whether we animate scaling the Doughnut from the centre + animateScale: false, + }, - scale: { - scaleType: "radialLinear", - display: true, + hover: { + mode: 'single' + }, - //Boolean - Whether to animate scaling the chart from the centre - animate: false, + //The percentage of the chart that we cut out of the middle. - 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: "" + 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() { - // Events + + //Set up tooltip events on the chart helpers.bindEvents(this, this.options.events, this.events); - // Create a new line and its points for each dataset and piece of data + //Create a new bar for each 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({ + dataset.metaData.push(new Chart.Arc({ + _chart: this.chart, _datasetIndex: datasetIndex, _index: index, - _chart: this.chart, - _model: { - x: 0, //xScale.getPixelForValue(null, index, true), - y: 0, //this.chartArea.bottom, - }, + _model: {} })); - }, 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, @@ -4079,273 +3985,158 @@ _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 + // Update the chart with the latest data. this.update(); + }, - nextPoint: function(collection, index) { - return collection[index + 1] || collection[0]; - }, - previousPoint: function(collection, index) { - return collection[index - 1] || collection[collection.length - 1]; + + 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.slice.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 - this.eachElement(function(point, index, dataset, datasetIndex) { - helpers.extend(point, { - // Utility - _chart: this.chart, - _datasetIndex: datasetIndex, - _index: index, - _scale: this.scale, + 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); - // Desired view properties - _model: { - x: this.scale.xCenter, - y: this.scale.yCenter, + dataset.outerRadius = this.outerRadius - (this.radiusLength * datasetIndex); + dataset.innerRadius = dataset.outerRadius - this.radiusLength; - // 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, + 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, - // Tooltip - hoverRadius: point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointHitRadius, index, this.options.elements.point.hitRadius), - }, - }); - }, this); + backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.slice.backgroundColor), + hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, this.options.elements.slice.hoverBackgroundColor), + borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.slice.borderWidth), + borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.slice.borderColor), - // 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 - ); + label: helpers.getValueAtIndexOrDefault(dataset.label, index, this.data.labels[index]) + }, + }); - point._model.controlPointPreviousX = this.scale.xCenter; - point._model.controlPointPreviousY = this.scale.yCenter; - point._model.controlPointNextX = this.scale.xCenter; - point._model.controlPointNextY = this.scale.yCenter; + slice.pivot(); + }, this); - // 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; + this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.elements.slice.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; - 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, + // Update the points + helpers.each(this.data.datasets, function(dataset, datasetIndex) { - // Data - _children: dataset.metaData, + dataset.total = 0; + helpers.each(dataset.data, function(value) { + dataset.total += Math.abs(value); + }, this); - // 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.outerRadius = this.outerRadius - (this.radiusLength * datasetIndex); - dataset.metaDataset.pivot(); - }); + dataset.innerRadius = dataset.outerRadius - this.radiusLength; - // 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.each(dataset.metaData, function(slice, index) { - helpers.extend(point, { - // Utility - _chart: this.chart, - _datasetIndex: datasetIndex, - _index: index, + helpers.extend(slice, { + // 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, + // 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, - // 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 - hoverRadius: point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointHitRadius, index, this.options.elements.point.hitRadius), - }, - }); - }, this); + backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.slice.backgroundColor), + hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, this.options.elements.slice.hoverBackgroundColor), + borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.slice.borderWidth), + borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.slice.borderColor), + label: helpers.getValueAtIndexOrDefault(dataset.label, index, this.data.labels[index]) + }, + }); - // 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 - ); + 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; + } - point._model.controlPointPreviousX = controlPoints.previous.x; - point._model.controlPointNextX = controlPoints.next.x; + slice._model.endAngle = slice._model.startAngle + slice._model.circumference; - // 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; - } + //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; + } - // 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; - } + slice.pivot(); + }, this); - // 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.scaleType); - 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; + draw: function(easeDecimal) { + easeDecimal = easeDecimal || 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(); - }); - } + this.eachElement(function(slice) { + slice.transition(easeDecimal).draw(); + }, this); - // Finally draw the tooltip - this.tooltip.transition(easingDecimal).draw(); + this.tooltip.transition(easeDecimal).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); + 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) { @@ -4367,20 +4158,18 @@ 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); + 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.slice.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.slice.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.slice.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); + 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.slice.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.slice.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.slice.borderWidth); } break; case 'dataset': @@ -4397,20 +4186,18 @@ 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); + 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.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); + 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': @@ -4420,6 +4207,7 @@ } } + // Built in Tooltips if (this.options.tooltips.enabled) { @@ -4441,6 +4229,7 @@ } } + // Hover animations this.tooltip.pivot(); @@ -4467,7 +4256,39 @@ 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() { @@ -4477,1200 +4298,1544 @@ 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 = 5; - var yPadding = 5; - - 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 defaultConfig = { - // Adjust the padding to take into account displaying labels - if (topScales.length === 0 || bottomScales.length === 0) { - var maxFontHeight = 0; + stacked: false, - 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 - } + hover: { + mode: "label" + }, - if (bottomScales.length === 0) { - // Add padding so that we can handle drawing the bottom nicely - yPadding += 1.5 * maxFontHeight; - } - } + scales: { + xAxes: [{ + scaleType: "dataset", // scatter should not use a dataset axis + display: true, + position: "bottom", + id: "x-axis-1", // need an ID so datasets can reference the scale - // 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 | | - // | | | | - // |------------------------------------------------------| + // 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, + }, - // 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 + // label settings + labels: { + show: true, + template: "<%=value%>", + fontSize: 12, + fontStyle: "normal", + fontColor: "#666", + fontFamily: "Helvetica Neue", + }, + }], + yAxes: [{ + scaleType: "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", - // Step 1 - var chartWidth = width / 2; // min 50% - var chartHeight = height / 2; // min 50% - var aspectRatio = chartHeight / chartWidth; - var screenAspectRatio; + // 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)", + }, - if (chartInstance.options.maintainAspectRatio) { - screenAspectRatio = height / width; + // scale numbers + beginAtZero: false, + override: null, - if (aspectRatio != screenAspectRatio) { - chartHeight = chartWidth * screenAspectRatio; - aspectRatio = screenAspectRatio; - } + // label settings + labels: { + show: true, + template: "<%=value.toLocaleString()%>", + fontSize: 12, + fontStyle: "normal", + fontColor: "#666", + fontFamily: "Helvetica Neue", } + }], + }, + }; - chartWidth -= (2 * xPadding); - chartHeight -= (2 * yPadding); - // Step 2 - var verticalScaleWidth = (width - chartWidth) / (leftScales.length + rightScales.length); + Chart.Type.extend({ + name: "Line", + defaults: defaultConfig, + initialize: function() { - // Step 3 - var horizontalScaleHeight = (height - chartHeight) / (topScales.length + bottomScales.length); + var _this = this; - // Step 4; - var minimumScaleSizes = []; + // Events + helpers.bindEvents(this, this.options.events, this.events); - var verticalScaleMinSizeFunction = function(scaleInstance) { - var minSize = scaleInstance.fit(verticalScaleWidth, chartHeight); - minimumScaleSizes.push({ - horizontal: false, - minSize: minSize, - scale: scaleInstance, - }); - }; + // Create a new line and its points for each dataset and piece of data + helpers.each(this.data.datasets, function(dataset, datasetIndex) { - var horizontalScaleMinSizeFunction = function(scaleInstance) { - var minSize = scaleInstance.fit(chartWidth, horizontalScaleHeight); - minimumScaleSizes.push({ - horizontal: true, - minSize: minSize, - scale: scaleInstance, - }); - }; + dataset.metaDataset = new Chart.Line({ + _chart: this.chart, + _datasetIndex: datasetIndex, + _points: dataset.metaData, + }); - // vertical scales - helpers.each(leftScales, verticalScaleMinSizeFunction); - helpers.each(rightScales, verticalScaleMinSizeFunction); + dataset.metaData = []; - // horizontal scales - helpers.each(topScales, horizontalScaleMinSizeFunction); - helpers.each(bottomScales, horizontalScaleMinSizeFunction); + 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, + }, + })); - // Step 5 - var maxChartHeight = height - (2 * yPadding); - var maxChartWidth = width - (2 * xPadding); + }, this); - helpers.each(minimumScaleSizes, function(wrapper) { - if (wrapper.horizontal) { - maxChartHeight -= wrapper.minSize.height; - } else { - maxChartWidth -= wrapper.minSize.width; - } - }); + // 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; - // At this point, maxChartHeight and maxChartWidth are the size the chart area could - // be if the axes are drawn at their minimum sizes. + if (!dataset.yAxisID) { + dataset.yAxisID = this.options.scales.yAxes[0].id; + } - // Step 6 - var verticalScaleFitFunction = function(scaleInstance) { - var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { - return wrapper.scale === scaleInstance; - }); + }, this); - if (wrapper) { - scaleInstance.fit(wrapper.minSize.width, maxChartHeight); - } - }; + // Build and fit the scale. Needs to happen after the axis IDs have been set + this.buildScale(); - var horizontalScaleFitFunction = function(scaleInstance) { - var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { - return wrapper.scale === scaleInstance; - }); + // 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); - var scaleMargin = { - left: totalLeftWidth, - right: totalRightWidth, - top: 0, - bottom: 0, - }; + // Need to fit scales before we reset elements. + Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - if (wrapper) { - scaleInstance.fit(maxChartWidth, wrapper.minSize.height, scaleMargin); - } - }; + // Reset so that we animation from the baseline + this.resetElements(); - var totalLeftWidth = xPadding; - var totalRightWidth = xPadding; - var totalTopHeight = yPadding; - var totalBottomHeight = yPadding; + // 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]; - helpers.each(leftScales, verticalScaleFitFunction); - helpers.each(rightScales, verticalScaleFitFunction); + var yScalePoint; - // Figure out how much margin is on the left and right of the horizontal axes - helpers.each(leftScales, function(scaleInstance) { - totalLeftWidth += scaleInstance.width; - }); + 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.each(rightScales, function(scaleInstance) { - totalRightWidth += scaleInstance.width; - }); + helpers.extend(point, { + // Utility + _chart: this.chart, + _xScale: xScale, + _yScale: yScale, + _datasetIndex: datasetIndex, + _index: index, - helpers.each(topScales, horizontalScaleFitFunction); - helpers.each(bottomScales, horizontalScaleFitFunction); + // 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, - helpers.each(topScales, function(scaleInstance) { - totalTopHeight += scaleInstance.height; - }); - helpers.each(bottomScales, function(scaleInstance) { - totalBottomHeight += scaleInstance.height; + // 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); - // Let the left scale know the final margin - helpers.each(leftScales, function(scaleInstance) { - var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { - return wrapper.scale === scaleInstance; - }); + // 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 + ); - var scaleMargin = { - left: 0, - right: 0, - top: totalTopHeight, - bottom: totalBottomHeight - }; + point._model.controlPointPreviousX = controlPoints.previous.x; + point._model.controlPointNextX = controlPoints.next.x; - if (wrapper) { - scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin); - } - }); + // Prevent the bezier going outside of the bounds of the graph - helpers.each(rightScales, function(scaleInstance) { - var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { - return wrapper.scale === scaleInstance; - }); + // 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; + } - var scaleMargin = { - left: 0, - right: 0, - top: totalTopHeight, - bottom: totalBottomHeight - }; + // 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; + } - if (wrapper) { - scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin); - } - }); + // Now pivot the point for animation + point.pivot(); + }, this); + }, + update: function(animationDuration) { - // Step 7 - // Position the scales - var left = xPadding; - var top = yPadding; - var right = 0; - var bottom = 0; + Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - var verticalScalePlacer = function(scaleInstance) { - scaleInstance.left = left; - scaleInstance.right = left + scaleInstance.width; - scaleInstance.top = totalTopHeight; - scaleInstance.bottom = totalTopHeight + maxChartHeight; + // Update the lines + this.eachDataset(function(dataset, datasetIndex) { + var yScale = this.scales[dataset.yAxisID]; + var scaleBase; - // Move to next point - left = scaleInstance.right; - }; + 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); + } - var horizontalScalePlacer = function(scaleInstance) { - scaleInstance.left = totalLeftWidth; - scaleInstance.right = totalLeftWidth + maxChartWidth; - scaleInstance.top = top; - scaleInstance.bottom = top + scaleInstance.height; + 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, + }, + }); - // Move to next point - top = scaleInstance.bottom; - }; + dataset.metaDataset.pivot(); + }); - helpers.each(leftScales, verticalScalePlacer); - helpers.each(topScales, horizontalScalePlacer); + // 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]; - // Account for chart width and height - left += maxChartWidth; - top += maxChartHeight; + helpers.extend(point, { + // Utility + _chart: this.chart, + _xScale: xScale, + _yScale: yScale, + _datasetIndex: datasetIndex, + _index: index, - helpers.each(rightScales, verticalScalePlacer); - helpers.each(bottomScales, horizontalScalePlacer); + // 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), - // Step 8 - chartInstance.chartArea = { - left: totalLeftWidth, - top: totalTopHeight, - right: totalLeftWidth + maxChartWidth, - bottom: totalTopHeight + maxChartHeight, - }; - } - } - }; + // 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, - // 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(scaleType, scaleConstructor) { - this.constructors[scaleType] = scaleConstructor; - }, - getScaleConstructor: function(scaleType) { - return this.constructors.hasOwnProperty(scaleType) ? this.constructors[scaleType] : undefined; - } - }; + // Tooltip + hitRadius: point.custom && point.custom.hitRadius ? point.custom.hitRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].hitRadius, index, this.options.elements.point.hitRadius), + }, + }); + }, this); - 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 - // 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 = []; + // 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 + ); - 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 + point._model.controlPointPreviousX = controlPoints.previous.x; + point._model.controlPointNextX = controlPoints.next.x; - var maxTicks; + // Prevent the bezier going outside of the bounds of the graph - if (this.isHorizontal()) { - maxTicks = Math.min(11, Math.ceil(width / 50)); + // 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 { - // 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))); + point._model.controlPointNextY = controlPoints.next.y; } - // 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. + // 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; + } - // 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); + // Now pivot the point for animation + point.pivot(); + }, this); - 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; - } - } + this.render(animationDuration); + }, + buildScale: function() { + var self = this; - 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; + // Function to determine the range of all the + var calculateYRange = function() { + this.min = null; + this.max = null; - // Put the values into the ticks array - for (var j = niceMin; j <= niceMax; j += spacing) { - this.ticks.push(j); - } - } + var positiveValues = []; + var negativeValues = []; - 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(); - } + 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; - // 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 = []; + if (self.options.relativePoints) { + positiveValues[index] = 100; + } else { + if (value < 0) { + negativeValues[index] += value; + } else { + positiveValues[index] += value; + } + } + }, this); + } + }, this); - helpers.each(this.ticks, function(tick, index, ticks) { - var label; + 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.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 - }); + if (this.max === null) { + this.max = value; + } else if (value > this.max) { + this.max = value; + } + }, this); + } + }, this); } + }; - 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; + // Map of scale ID to scale object so we can lookup later + this.scales = {}; - 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)); - } + // Build the x axis. The line chart only supports a single x axis + var ScaleClass = Chart.scales.getScaleConstructor(this.options.scales.xAxes[0].scaleType); + 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; - return pixel; + // Build up all the y scales + helpers.each(this.options.scales.yAxes, function(yAxisOptions) { + var ScaleClass = Chart.scales.getScaleConstructor(yAxisOptions.scaleType); + 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); }, - // 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(); + draw: function(ease) { - var minSize = { - width: 0, - height: 0, - }; + var easingDecimal = ease || 1; + this.clear(); - if (this.isHorizontal()) { - minSize.width = maxWidth; // fill all the width + // Draw all the scales + helpers.each(this.scales, function(scale) { + scale.draw(this.chartArea); + }, this); - // In a horizontal axis, we need some room for the scale to be drawn - // - // ----------------------------------------------------- - // | | | | | - // - minSize.height = this.options.gridLines.show ? 10 : 0; - } else { - minSize.height = maxHeight; // fill all the height + // reverse for-loop for proper stacking + for (var i = this.data.datasets.length - 1; i >= 0; i--) { - // 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 - // | - // -| - // | - // | - // -| - // | - // | - // -| - minSize.width = this.options.gridLines.show ? 10 : 0; - } + var dataset = this.data.datasets[i]; - if (this.options.labels.show) { - // 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); + // Transition Point Locations + helpers.each(dataset.metaData, function(point, index) { + point.transition(easingDecimal); + }, this); - 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); + // Transition and Draw the line + dataset.metaDataset.transition(easingDecimal).draw(); - if (largestTextWidth < maxLabelWidth) { - // We don't need all the room - minSize.width += largestTextWidth; - } else { - // Expand to max size - minSize.width = maxWidth; - } - } + // Draw the points + helpers.each(dataset.metaData, function(point) { + point.draw(); + }); } - this.width = minSize.width; - this.height = minSize.height; - return minSize; + // Finally draw the tooltip + this.tooltip.transition(easingDecimal).draw(); }, - // 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; + events: function(e) { - // Make sure we draw text in the correct color - this.ctx.fillStyle = this.options.labels.fontColor; + this.lastActive = this.lastActive || []; - 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; - - helpers.each(this.ticks, function(tick, index) { - // Grid lines are vertical - var xValue = this.getPixelForValue(tick); - - 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; - } + // 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); + } - xValue += helpers.aliasPixel(this.ctx.lineWidth); + // On Hover hook + if (this.options.hover.onHover) { + this.options.hover.onHover.call(this, this.active); + } - // Draw the label area - this.ctx.beginPath(); + if (e.type == 'mouseup' || e.type == 'click') { + if (this.options.onClick) { + this.options.onClick.call(this, e, this.active); + } + } - if (this.options.gridLines.drawTicks) { - this.ctx.moveTo(xValue, yTickStart); - this.ctx.lineTo(xValue, yTickEnd); - } + 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; - // Draw the chart area - if (this.options.gridLines.drawOnChartArea) { - this.ctx.moveTo(xValue, chartArea.top); - this.ctx.lineTo(xValue, chartArea.bottom); - } + 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; - // Need to stroke in the loop because we are potentially changing line widths & colours - this.ctx.stroke(); - }, this); - } + 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 + } + } - if (this.options.labels.show) { - // Draw the labels + // 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; - var labelStartY; + 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; - 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.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 + } + } - this.ctx.textAlign = "center"; - 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 xValue = this.getPixelForValue(this.ticks[index]); - this.ctx.fillText(label, xValue, labelStartY); - }, this); - } - } else { - // Vertical - if (this.options.gridLines.show) { + // Built in Tooltips + if (this.options.tooltips.enabled) { - // 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; + // The usual updates + this.tooltip.initialize(); - helpers.each(this.ticks, function(tick, index) { - // Grid lines are horizontal - var yValue = this.getPixelForValue(tick); + // Active + if (this.active.length) { + this.tooltip._model.opacity = 1; - 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 - } + helpers.extend(this.tooltip, { + _active: this.active, + }); - yValue += helpers.aliasPixel(this.ctx.lineWidth); + this.tooltip.update(); + } else { + // Inactive + this.tooltip._model.opacity = 0; + } + } - // Draw the label area - this.ctx.beginPath(); - if (this.options.gridLines.drawTicks) { - this.ctx.moveTo(xTickStart, yValue); - this.ctx.lineTo(xTickEnd, yValue); - } + // Hover animations + this.tooltip.pivot(); - // Draw the chart area - if (this.options.gridLines.drawOnChartArea) { - this.ctx.moveTo(chartArea.left, yValue); - this.ctx.lineTo(chartArea.right, yValue); - } + if (!this.animating) { + var changed; - this.ctx.stroke(); - }, this); + helpers.each(this.active, function(element, index) { + if (element !== this.lastActive[index]) { + changed = true; } + }, 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); + // 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)) { - helpers.each(this.labels, function(label, index) { - var yValue = this.getPixelForValue(this.ticks[index]); - this.ctx.fillText(label, labelStartX, yValue); - }, this); - } + this.stop(); + this.render(this.options.hover.animationDuration); } } - } - }); - Chart.scales.registerScaleType("linear", LinearScale); - 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"; + // Remember Last Active + this.lastActive = this.active; + return this; }, - 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; + }); - if (this.options.gridLines.offsetGridLines && includeOffset) { - valueOffset += (valueWidth / 2); - } - 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; +}).call(this); - 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; +(function() { + "use strict"; - this.paddingRight = lastWidth / 2 + 3; - this.paddingLeft = firstWidth / 2 + 3; + var root = this, + Chart = root.Chart, + //Cache a local reference to Chart.helpers + helpers = Chart.helpers; - this.labelRotation = 0; + var defaultConfig = { - if (this.options.display) { - var originalLabelWidth = helpers.longestText(this.ctx, labelFont, this.labels); - var cosRotation; - var sinRotation; - var firstRotatedWidth; + scale: { + scaleType: "radialLinear", + display: true, - this.labelWidth = originalLabelWidth; + //Boolean - Whether to animate scaling the chart from the centre + animate: false, - //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; + lineArc: true, - //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)); + // grid line settings + gridLines: { + show: true, + color: "rgba(0, 0, 0, 0.05)", + lineWidth: 1, + }, - firstRotated = cosRotation * firstWidth; - lastRotated = cosRotation * lastWidth; + // scale numbers + beginAtZero: true, - // We're right aligning the text now. - if (firstRotated + this.options.labels.fontSize / 2 > this.yLabelWidth) { - this.paddingLeft = firstRotated + this.options.labels.fontSize / 2; - } + // label settings + labels: { + show: true, + template: "<%=value.toLocaleString()%>", + fontSize: 12, + fontStyle: "normal", + fontColor: "#666", + fontFamily: "Helvetica Neue", - this.paddingRight = this.options.labels.fontSize / 2; + //Boolean - Show a backdrop to the scale label + showLabelBackdrop: true, - if (sinRotation * originalLabelWidth > maxHeight) { - // go back one step - this.labelRotation--; - break; - } + //String - The colour of the label backdrop + backdropColor: "rgba(255,255,255,0.75)", - this.labelRotation++; - this.labelWidth = cosRotation * originalLabelWidth; + //Number - The backdrop padding above & below the label in pixels + backdropPaddingY: 2, - } - } else { - this.labelWidth = 0; - this.paddingRight = 0; - this.paddingLeft = 0; + //Number - The backdrop padding to the side of the label in pixels + backdropPaddingX: 2, } + }, - if (margins) { - this.paddingLeft -= margins.left; - this.paddingRight -= margins.right; + //Boolean - Whether to animate the rotation of the chart + animateRotate: true, + }; - 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.calculateRange(); - this.calculateLabelRotation(maxHeight, margins); + 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() { - var minSize = { - width: 0, - height: 0, - }; + // Scale setup + var self = this; + var ScaleClass = Chart.scales.getScaleConstructor(this.options.scale.scaleType); + 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; - 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); + 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.isHorizontal()) { - minSize.width = maxWidth; - this.width = maxWidth; + if (this.max === null) { + this.max = value; + } else if (value > this.max) { + this.max = value; + } + }, this); + } + }); - var labelHeight = (Math.cos(helpers.toRadians(this.labelRotation)) * longestLabelWidth) + 1.5 * this.options.labels.fontSize; - minSize.height = Math.min(labelHeight, maxHeight); - } else { - minSize.height = maxHeight; - this.height = maxHeight; + helpers.bindEvents(this, this.options.events, this.events); - minSize.width = Math.min(longestLabelWidth + 6, maxWidth); - } + //Set up tooltip events on the chart + helpers.bindEvents(this, this.options.events, this.events); - 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) { + //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); - var setContextLineSettings; + // 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); - // Make sure we draw text in the correct color - this.ctx.fillStyle = this.options.labels.fontColor; + // Fit the scale before we animate + this.updateScaleRange(); + this.scale.calculateRange(); + Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - 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; + // so that we animate nicely + this.resetElements(); - 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) + // Update the chart with the latest data. + this.update(); - 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; - } + }, + 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; - xLineValue += helpers.aliasPixel(this.ctx.lineWidth); + // Map new data to data points + helpers.each(this.data.datasets[0].metaData, function(slice, index) { - // Draw the label area - this.ctx.beginPath(); + var value = this.data.datasets[0].data[index]; - if (this.options.gridLines.drawTicks) { - this.ctx.moveTo(xLineValue, yTickStart); - this.ctx.lineTo(xLineValue, yTickEnd); - } + 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, - // Draw the chart area - if (this.options.gridLines.drawOnChartArea) { - this.ctx.moveTo(xLineValue, chartArea.top); - this.ctx.lineTo(xLineValue, chartArea.bottom); - } + backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[0].backgroundColor, index, this.options.elements.slice.backgroundColor), + hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[0].hoverBackgroundColor, index, this.options.elements.slice.hoverBackgroundColor), + borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[0].borderWidth, index, this.options.elements.slice.borderWidth), + borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[0].borderColor, index, this.options.elements.slice.borderColor), - // Need to stroke in the loop because we are potentially changing line widths & colours - this.ctx.stroke(); - } + label: helpers.getValueAtIndexOrDefault(this.data.datasets[0].labels, index, this.data.datasets[0].labels[index]) + }, + }); - 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) {} + slice.pivot(); + }, this); + }, + update: function(animationDuration) { - if (this.options.labels.show) { - // Draw the labels - } - } - } - } - }); - Chart.scales.registerScaleType("dataset", DatasetScale); + this.updateScaleRange(); + this.scale.calculateRange(); + this.scale.generateTicks(); + this.scale.buildYLabels(); - 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; + 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.slice.backgroundColor), + hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[0].hoverBackgroundColor, index, this.options.elements.slice.hoverBackgroundColor), + borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[0].borderWidth, index, this.options.elements.slice.borderWidth), + borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[0].borderColor, index, this.options.elements.slice.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); }, - 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); - } + draw: function(ease) { + var easingDecimal = ease || 1; - this.buildYLabels(); + 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(); }, - 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 + events: function(e) { - // 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 exiting chart + if (e.type == 'mouseout') { + return this; + } - 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); + 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; } - } 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 + }.call(this); - var maxTicks = Math.min(11, Math.ceil(this.drawingArea / (2 * this.options.labels.fontSize))); + // On Hover hook + if (this.options.hover.onHover) { + this.options.hover.onHover.call(this, this.active); + } - // Make sure we always have at least 2 ticks - maxTicks = Math.max(2, maxTicks); + if (e.type == 'mouseup' || e.type == 'click') { + if (this.options.onClick) { + this.options.onClick.call(this, e, this.active); + } + } - // 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. + 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; - // 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); + 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.slice.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.slice.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.slice.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; - 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; - } + 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.slice.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.slice.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.slice.borderWidth); + } + break; + case 'dataset': + break; + default: + // Don't change anything } + } - 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; + // 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; - // Put the values into the ticks array - for (var j = niceMin; j <= niceMax; j += spacing) { - this.ticks.push(j); + 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 } } - 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 = []; + // Built in Tooltips + if (this.options.tooltips.enabled) { - helpers.each(this.ticks, function(tick, index, ticks) { - var label; + // The usual updates + this.tooltip.initialize(); - 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 + // 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.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 - */ + // Hover animations + this.tooltip.pivot(); - // 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; + 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); } } - xProtrusionLeft = furthestLeft; + // 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: { + scaleType: "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 + 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 = 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 + 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(animationDuration); + }, + buildScale: function() { + var self = this; + + var ScaleConstructor = Chart.scales.getScaleConstructor(this.options.scale.scaleType); + 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]; - xProtrusionRight = Math.ceil(furthestRight - this.width); + // Transition Point Locations + helpers.each(dataset.metaData, function(point, index) { + point.transition(easingDecimal); + }, this); - furthestRightAngle = this.getIndexAngle(furthestRightIndex); + // Transition and Draw the line + dataset.metaDataset.transition(easingDecimal).draw(); - furthestLeftAngle = this.getIndexAngle(furthestLeftIndex); + // Draw the points + helpers.each(dataset.metaData, function(point) { + point.draw(); + }); + } - radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2); + // Finally draw the tooltip + this.tooltip.transition(easingDecimal).draw(); + }, + events: function(e) { - radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2); + // If exiting chart + if (e.type == 'mouseout') { + return this; + } - // Ensure we actually need to reduce the size of the chart - radiusReductionRight = (helpers.isNumber(radiusReductionRight)) ? radiusReductionRight : 0; - radiusReductionLeft = (helpers.isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0; + this.lastActive = this.lastActive || []; - this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2; + // 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); - //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2]) - this.setCenterPoint(radiusReductionLeft, radiusReductionRight); + // On Hover hook + if (this.options.hover.onHover) { + this.options.hover.onHover.call(this, this.active); + } - }, - setCenterPoint: function(leftMovement, rightMovement) { + if (e.type == 'mouseup' || e.type == 'click') { + if (this.options.onClick) { + this.options.onClick.call(this, e, this.active); + } + } - var maxRight = this.width - rightMovement - this.drawingArea, - maxLeft = leftMovement + this.drawingArea; + 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.xCenter = (maxLeft + maxRight) / 2; - // Always vertically in the centre as the text height doesn't change - this.yCenter = (this.height / 2); - }, + 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; - getIndexAngle: function(index) { - var angleMultiplier = (Math.PI * 2) / this.valuesCount; - // Start from the top instead of right, so remove a quarter of the circle + 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 + } + } - 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; + // 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; - // Draw circular lines around the scale - if (this.options.gridLines.show) { - ctx.strokeStyle = this.options.gridLines.color; - ctx.lineWidth = this.options.gridLines.lineWidth; + 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; - 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(); - } + 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 + } + } - if (this.options.labels.show) { - ctx.font = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily); + // Built in Tooltips + if (this.options.tooltips.enabled) { - 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 - ); - } + // The usual updates + this.tooltip.initialize(); - ctx.textAlign = 'center'; - ctx.textBaseline = "middle"; - ctx.fillStyle = this.options.labels.fontColor; - ctx.fillText(label, this.xCenter, yHeight); - } - } - }, this); + // Active + if (this.active.length) { + this.tooltip._model.opacity = 1; - if (!this.options.lineArc) { - ctx.lineWidth = this.options.angleLines.lineWidth; - ctx.strokeStyle = this.options.angleLines.color; + helpers.extend(this.tooltip, { + _active: this.active, + }); - 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.tooltip.update(); + } else { + // Inactive + this.tooltip._model.opacity = 0; + } + } - 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'; - } + // Hover animations + this.tooltip.pivot(); - // Set the correct text baseline based on outer positioning - if (exactQuarter) { - ctx.textBaseline = 'middle'; - } else if (upperHalf) { - ctx.textBaseline = 'bottom'; - } else { - ctx.textBaseline = 'top'; - } + if (!this.animating) { + var changed; - ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y); + 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; + }, }); - Chart.scales.registerScaleType("radialLinear", LinearRadialScale); }).call(this); (function() { diff --git a/Chart.min.js b/Chart.min.js index 8bea7c5a1..6139127e6 100644 --- a/Chart.min.js +++ b/Chart.min.js @@ -7,8 +7,61 @@ * 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:{animation:{duration:1e3,easing:"easeOutQuart",onProgress:function(){},onComplete:function(){}},responsive:!0,maintainAspectRatio:!0,events:["mousemove","mouseout","click","touchstart","touchmove","touchend"],hover:{onHover:null,mode:"single",animationDuration:400},onClick:null,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"},defaultColor:o,elements:{line:{tension:.4,backgroundColor:o,borderWidth:3,borderColor:o,fill:!0,skipNull:!0,drawNull:!1},point:{radius:3,backgroundColor:o,borderWidth:1,borderColor:o,hitRadius:1,hoverRadius:4,hoverBorderWidth:1},bar:{backgroundColor:o,borderWidth:0,borderColor:o,valueSpacing:5,datasetSpacing:1},slice:{backgroundColor:o,borderColor:"#fff",borderWidth:2}}}},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&&r(i.prototype,t),i.__super__=e.prototype,i}),c=s.noop=function(){},d=s.uid=function(){var t=0;return function(){return"chart-"+t++}}(),u=s.warn=function(t){window.console&&"function"==typeof window.console.warn&&console.warn(t)},m=s.amd="function"==typeof define&&define.amd,v=s.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},g=s.max=function(t){return Math.max.apply(Math,t)},p=s.min=function(t){return Math.min.apply(Math,t)},f=(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(v(e)){if(t>e)return e}else if(v(i)&&i>t)return i;return t},s.getDecimalPlaces=function(t){if(t%1!==0&&v(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)),n=o*s/(s+a),r=o*a/(s+a);return{previous:{x:e.x-n*(i.x-t.x),y:e.y-n*(i.y-t.y)},next:{x:e.x+r*(i.x-t.x),y:e.y+r*(i.y-t.y)}}},s.calculateOrderOfMagnitude=function(t){return Math.floor(Math.log(t)/Math.LN10)}),b=(s.calculateScaleRange=function(t,e,i,o,s){var a=2,n=Math.floor(e/(1.5*i)),r=a>=n,l=g(t),h=p(t);l===h&&(l+=.5,h>=.5&&!o?h-=.5:l+=.5);for(var c=Math.abs(l-h),d=f(c),u=Math.ceil(l/(1*Math.pow(10,d)))*Math.pow(10,d),m=o?0:Math.floor(h/(1*Math.pow(10,d)))*Math.pow(10,d),v=u-m,b=Math.pow(10,d),x=Math.round(v/b);(x>n||n>2*x)&&!r;)if(x>n)b*=2,x=Math.round(v/b),x%1!==0&&(r=!0);else if(s&&d>=0){if(b/2%1!==0)break;b/=2,x=Math.round(v/b)}else b/=2,x=Math.round(v/b);return r&&(x=a,b=v/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)}),x=(s.generateLabels=function(t,e,i,o){var s=new Array(e);return t&&a(s,function(e,a){s[a]=b(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-x.easeOutBounce(1-t)},easeOutBounce:function(t){return(t/=1)<1/2.75?7.5625*t*t:2/2.75>t?1*(7.5625*(t-=1.5/2.75)*t+.75):2.5/2.75>t?1*(7.5625*(t-=2.25/2.75)*t+.9375):1*(7.5625*(t-=2.625/2.75)*t+.984375)},easeInOutBounce:function(t){return.5>t?.5*x.easeInBounce(2*t):.5*x.easeOutBounce(2*t-1)+.5}}),A=s.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),C=(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 n=0,r=x[i]||x.linear,l=function(){n++;var i=n/e,h=r(i);t.call(a,h,i,n),o.call(a,h,i),e>n?a.animationFrame=A(l):s.apply(a)};A(l)},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}),y=s.removeEvent=function(t,e,i){t.removeEventListener?t.removeEventListener(e,i,!1):t.detachEvent?t.detachEvent("on"+e,i):t["on"+e]=c},_=(s.bindEvents=function(t,e,i){t.events||(t.events={}),a(e,function(e){t.events[e]=function(){i.apply(t,arguments)},C(t.chart.canvas,e,t.events[e])})},s.unbindEvents=function(t,e){a(e,function(e,i){y(t.chart.canvas,i,e)})}),w=s.getMaximumWidth=function(t){var e=t.parentNode,i=parseInt(P(e,"padding-left"))+parseInt(P(e,"padding-right"));return e.clientWidth-i},k=s.getMaximumHeight=function(t){var e=t.parentNode,i=parseInt(P(e,"padding-bottom"))+parseInt(P(e,"padding-top"));return e.clientHeight-i},P=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)},W=s.fontString=function(t,e,i){return e+" "+t+"px "+i},D=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},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=d(),i.instances[this.id]=this,this.options.responsive&&this.resize(),this.initialize.call(this)},r(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:k(this.chart.canvas);return t.width=this.chart.width=e,t.height=this.chart.height=i,S(this.chart),this},redraw:c,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])},n=0;n0||t.borderWidth>0)&&(e.beginPath(),e.arc(t.x,t.y,t.radius||i.defaults.global.elements.point.radius,0,2*Math.PI),e.closePath(),e.strokeStyle=t.borderColor||i.defaults.global.defaultColor,e.lineWidth=t.borderWidth||i.defaults.global.elements.point.borderWidth,e.fillStyle=t.backgroundColor||i.defaults.global.defaultColor,e.fill(),e.stroke())}}),i.Line=i.Element.extend({draw:function(){var t=this._view,e=this._chart.ctx,o=this._children[0],a=this._children[this._children.length-1];s.each(this._children,function(i,o){var s=this.previousPoint(i,this._children,o),a=this.nextPoint(i,this._children,o);return 0===o?void e.moveTo(i._view.x,i._view.y):(i._view.skip&&t.skipNull&&!this._loop?(e.lineTo(s._view.x,i._view.y),e.moveTo(a._view.x,i._view.y)):s._view.skip&&t.skipNull&&!this._loop&&(e.moveTo(i._view.x,s._view.y),e.lineTo(i._view.x,i._view.y)),void(s._view.skip&&t.skipNull?e.moveTo(i._view.x,i._view.y):t.tension>0?e.bezierCurveTo(s._view.controlPointNextX,s._view.controlPointNextY,i._view.controlPointPreviousX,i._view.controlPointPreviousY,i._view.x,i._view.y):e.lineTo(i._view.x,i._view.y)))},this),this._loop&&(t.tension>0&&!o._view.skip?e.bezierCurveTo(a._view.controlPointNextX,a._view.controlPointNextY,o._view.controlPointPreviousX,o._view.controlPointPreviousY,o._view.x,o._view.y):e.lineTo(o._view.x,o._view.y)),this._children.length>0&&t.fill&&(e.lineTo(this._children[this._children.length-1]._view.x,t.scaleZero),e.lineTo(this._children[0]._view.x,t.scaleZero),e.fillStyle=t.backgroundColor||i.defaults.global.defaultColor,e.closePath(),e.fill()),e.lineWidth=t.borderWidth||i.defaults.global.defaultColor,e.strokeStyle=t.borderColor||i.defaults.global.defaultColor,e.beginPath(),s.each(this._children,function(i,o){var s=this.previousPoint(i,this._children,o),a=this.nextPoint(i,this._children,o);return 0===o?void e.moveTo(i._view.x,i._view.y):i._view.skip&&t.skipNull&&!this._loop?(e.moveTo(s._view.x,i._view.y),void e.moveTo(a._view.x,i._view.y)):s._view.skip&&t.skipNull&&!this._loop?(e.moveTo(i._view.x,s._view.y),void e.moveTo(i._view.x,i._view.y)):s._view.skip&&t.skipNull?void e.moveTo(i._view.x,i._view.y):void(t.tension>0?e.bezierCurveTo(s._view.controlPointNextX,s._view.controlPointNextY,i._view.controlPointPreviousX,i._view.controlPointPreviousY,i._view.x,i._view.y):e.lineTo(i._view.x,i._view.y))},this),this._loop&&!o._view.skip&&(t.tension>0?e.bezierCurveTo(a._view.controlPointNextX,a._view.controlPointNextY,o._view.controlPointPreviousX,o._view.controlPointPreviousY,o._view.x,o._view.y):e.lineTo(o._view.x,o._view.y)),e.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]}}),i.Arc=i.Element.extend({inGroupRange:function(t){var e=this._view;return e?Math.pow(t-e.x,2)1.5*Math.PI?i.startAngle-2*Math.PI:i.startAngle,n=i.endAngle<-.5*Math.PI?i.endAngle+2*Math.PI:i.endAngle>1.5*Math.PI?i.endAngle-2*Math.PI:i.endAngle,r=o.angle>=a&&o.angle<=n,l=o.distance>=i.innerRadius&&o.distance<=i.outerRadius;return r&&l},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()}}),i.Rectangle=i.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),n=e.borderWidth/2;e.borderWidth&&(o+=n,s-=n,a+=n),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=0&&(i=this._data.datasets[r].metaData,o=l(i,this._active[0]),-1===o);r--);var h=function(t){var e,i,r,l,h,c=[],d=[],u=[];return s.each(this._data.datasets,function(t){e=t.metaData,e[o]&&e[o].hasValue()&&c.push(e[o])},this),s.each(this._options.stacked?c.reverse():c,function(t){d.push(t._view.x),u.push(t._view.y),a.push(s.template(this._options.tooltips.multiTemplate,{element:t,datasetLabel:this._data.datasets[t._datasetIndex].label,value:this._data.datasets[t._datasetIndex].data[t._index]})),n.push({fill:t._view.backgroundColor,stroke:t._view.borderColor})},this),h=p(u),r=g(u),l=p(d),i=g(d),{x:l>this._chart.width/2?l:i,y:(h+r)/2}}.call(this,o);s.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:n,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 c=t.measureText(this.title).width,d=D(t,this.font,a)+this._model.fontSize+3,u=g([d,c]);this._model.width=u+2*this._model.xPadding;var m=this._model.height/2;this._model.y-m<0?this._model.y=m:this._model.y+m>this._chart.height&&(this._model.y=this._chart.height-m),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=W(e.fontSize,e._fontStyle,e._fontFamily),e.xAlign="center",e.yAlign="above";var i=e.caretPadding||2,o=t.measureText(e.text).width+2*e.xPadding,a=e.fontSize+2*e.yPadding,n=a+e.caretHeight+i;e.x+o/2>this._chart.width?e.xAlign="left":e.x-o/2<0&&(e.xAlign="right"),e.y-n<0&&(e.yAlign="below");var r=e.x-o/2,l=e.y-n;if(t.fillStyle=s.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-i),t.lineTo(e.x+e.caretHeight,e.y-(i+e.caretHeight)),t.lineTo(e.x-e.caretHeight,e.y-(i+e.caretHeight)),t.closePath(),t.fill();break;case"below":l=e.y+i+e.caretHeight,t.beginPath(),t.moveTo(e.x,e.y+i),t.lineTo(e.x+e.caretHeight,e.y+i+e.caretHeight),t.lineTo(e.x-e.caretHeight,e.y+i+e.caretHeight),t.closePath(),t.fill()}switch(e.xAlign){case"left":r=e.x-o+(e.cornerRadius+e.caretHeight);break;case"right":r=e.x-(e.cornerRadius+e.caretHeight)}O(t,r,l,o,a,e.cornerRadius),t.fill(),t.fillStyle=s.color(e.textColor).alpha(e.opacity).rgbString(),t.textAlign="center",t.textBaseline="middle",t.fillText(e.text,r+o/2,l+a/2)}break;case"label":O(t,e.x,e.y-e.height/2,e.width,e.height,e.cornerRadius),t.fillStyle=s.color(e.backgroundColor).alpha(e.opacity).rgbString(),t.fill(),t.closePath(),t.textAlign="left",t.textBaseline="middle",t.fillStyle=s.color(e.titleTextColor).alpha(e.opacity).rgbString(),t.font=W(e.fontSize,e._titleFontStyle,e._titleFontFamily),t.fillText(e.title,e.x+e.xPadding,this.getLineHeight(0)),t.font=W(e.fontSize,e._fontStyle,e._fontFamily),s.each(e.labels,function(i,o){t.fillStyle=s.color(e.textColor).alpha(e.opacity).rgbString(),t.fillText(i,e.x+e.xPadding+e.fontSize+3,this.getLineHeight(o+1)),t.fillStyle=s.color(e.legendColors[o].stroke).alpha(e.opacity).rgbString(),t.fillRect(e.x+e.xPadding-1,this.getLineHeight(o+1)-e.fontSize/2-1,e.fontSize+2,e.fontSize+2),t.fillStyle=s.color(e.legendColors[o].fill).alpha(e.opacity).rgbString(),t.fillRect(e.x+e.xPadding,this.getLineHeight(o+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}}),i.animationService={frameDuration:17,animations:[],dropFrames:0,addAnimation:function(t,e,i){i||(t.animating=!0);for(var o=0;o1&&(e=Math.floor(this.dropFrames),this.dropFrames-=e);for(var i=0;ithis.animations[i].animationObject.numSteps&&(this.animations[i].animationObject.currentStep=this.animations[i].animationObject.numSteps),this.animations[i].animationObject.render(this.animations[i].chartInstance,this.animations[i].animationObject),this.animations[i].animationObject.currentStep==this.animations[i].animationObject.numSteps&&(this.animations[i].chartInstance.animating=!1,this.animations.splice(i,1),i--);var o=Date.now(),a=o-t-this.frameDuration,n=a/this.frameDuration;n>1&&(this.dropFrames+=n),this.animations.length>0&&s.requestAnimFrame.call(window,this.digestWrapper)}},s.addEvent(window,"resize",function(){var t;return function(){clearTimeout(t),t=setTimeout(function(){a(i.instances,function(t){t.options.responsive&&(t.resize(),t.update(),t.render())})},50)}}()),m?define(function(){return i}):"object"==typeof module&&module.exports&&(module.exports=i),t.Chart=i,i.noConflict=function(){return t.Chart=e,i}}).call(this),function(){"use strict";var t=this,e=t.Chart,i=e.helpers,o={stacked:!1,hover:{mode:"label"},scales:{xAxes:[{scaleType:"dataset",display:!0,position:"bottom",id:"x-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)",offsetGridLines:!0},labels:{show:!0,template:"<%=value%>",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue"}}],yAxes:[{scaleType:"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,n=this.scales[this.data.datasets[s].xAxisID],r=this.scales[this.data.datasets[s].yAxisID];a=r.getPixelForValue(r.min<0&&r.max<0?r.max:r.min>0&&r.max>0?r.min:0),i.extend(t,{_chart:this.chart,_xScale:n,_yScale:r,_datasetIndex:s,_index:e,_model:{x:n.calculateBarX(this.data.datasets.length,s,e),y:a,base:r.calculateBarBase(s,e),width:n.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.bar.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[s].borderColor,e,this.options.elements.bar.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[s].borderWidth,e,this.options.elements.bar.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],n=this.scales[this.data.datasets[s].yAxisID];i.extend(t,{_chart:this.chart,_xScale:a,_yScale:n,_datasetIndex:s,_index:e,_model:{x:a.calculateBarX(this.data.datasets.length,s,e),y:n.calculateBarY(s,e),base:n.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.bar.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[s].borderColor,e,this.options.elements.bar.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[s].borderWidth,e,this.options.elements.bar.borderWidth),label:this.data.labels[e],datasetLabel:this.data.datasets[s].label}}),t.pivot()},this),this.render(t)},buildScale:function(t){var o=this,s=function(){this.min=null,this.max=null;var t=[],e=[];if(o.options.stacked){i.each(o.data.datasets,function(s){s.yAxisID===this.id&&i.each(s.data,function(i,s){t[s]=t[s]||0,e[s]=e[s]||0,o.options.relativePoints?t[s]=100:0>i?e[s]+=i:t[s]+=i},this)},this);var s=t.concat(e);this.min=i.min(s),this.max=i.max(s)}else i.each(o.data.datasets,function(t){t.yAxisID===this.id&&i.each(t.data,function(t,e){null===this.min?this.min=t:tthis.max&&(this.max=t)},this)},this)};this.scales={};var a=e.scales.getScaleConstructor(this.options.scales.xAxes[0].scaleType),n=new a({ctx:this.chart.ctx,options:this.options.scales.xAxes[0],id:this.options.scales.xAxes[0].id, -calculateRange:function(){this.labels=o.data.labels,this.min=0,this.max=this.labels.length},calculateBaseWidth:function(){return this.getPixelForValue(null,1,!0)-this.getPixelForValue(null,0,!0)-2*o.options.elements.bar.valueSpacing},calculateBarWidth:function(t){var e=this.calculateBaseWidth()-(t-1)*o.options.elements.bar.datasetSpacing;return o.options.stacked?e:e/t},calculateBarX:function(t,e,i){var s=this.calculateBaseWidth(),a=this.getPixelForValue(null,i,!0)-s/2,n=this.calculateBarWidth(t);return o.options.stacked?a+n/2:a+n*e+e*o.options.elements.bar.datasetSpacing+n/2}});this.scales[n.id]=n,i.each(this.options.scales.yAxes,function(t){var i=e.scales.getScaleConstructor(t.scaleType),a=new i({ctx:this.chart.ctx,options:t,calculateRange:s,calculateBarBase:function(t,e){var i=0;if(o.options.stacked){var s=o.data.datasets[t].data[e];if(0>s)for(var a=0;t>a;a++)o.data.datasets[a].yAxisID===this.id&&(i+=o.data.datasets[a].data[e]<0?o.data.datasets[a].data[e]:0);else for(var n=0;t>n;n++)o.data.datasets[n].yAxisID===this.id&&(i+=o.data.datasets[n].data[e]>0?o.data.datasets[n].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=o.data.datasets[t].data[e];if(o.options.stacked){for(var s=0,a=0,n=0;t>n;n++)o.data.datasets[n].data[e]<0?a+=o.data.datasets[n].data[e]||0:s+=o.data.datasets[n].data[e]||0;return this.getPixelForValue(0>i?a+i:s+i)}for(var r=0,l=t;l0?2*Math.PI*(e/t.total):0},resetElements:function(){this.outerRadius=(i.min([this.chart.width,this.chart.height])-this.options.elements.slice.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.slice.backgroundColor),hoverBackgroundColor:e.custom&&e.custom.hoverBackgroundColor?e.custom.hoverBackgroundColor:i.getValueAtIndexOrDefault(t.hoverBackgroundColor,o,this.options.elements.slice.hoverBackgroundColor),borderWidth:e.custom&&e.custom.borderWidth?e.custom.borderWidth:i.getValueAtIndexOrDefault(t.borderWidth,o,this.options.elements.slice.borderWidth),borderColor:e.custom&&e.custom.borderColor?e.custom.borderColor:i.getValueAtIndexOrDefault(t.borderColor,o,this.options.elements.slice.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.slice.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.slice.backgroundColor),hoverBackgroundColor:o.custom&&o.custom.hoverBackgroundColor?o.custom.hoverBackgroundColor:i.getValueAtIndexOrDefault(t.hoverBackgroundColor,s,this.options.elements.slice.hoverBackgroundColor),borderWidth:o.custom&&o.custom.borderWidth?o.custom.borderWidth:i.getValueAtIndexOrDefault(t.borderWidth,s,this.options.elements.slice.borderWidth),borderColor:o.custom&&o.custom.borderColor?o.custom.borderColor:i.getValueAtIndexOrDefault(t.borderColor,s,this.options.elements.slice.borderColor),label:i.getValueAtIndexOrDefault(t.label,s,this.data.labels[s])}}),0===s?o._model.startAngle=Math.PI*-.5:o._model.startAngle=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:[{scaleType:"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,n=this.scales[this.data.datasets[s].xAxisID],r=this.scales[this.data.datasets[s].yAxisID];a=r.getPixelForValue(r.min<0&&r.max<0?r.max:r.min>0&&r.max>0?r.min:0),i.extend(t,{_chart:this.chart,_xScale:n,_yScale:r,_datasetIndex:s,_index:e,_model:{x:n.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,s){var a=i.splineCurve(this.previousPoint(o,e)._model,t._model,this.nextPoint(o,e)._model,t._model.tension);t._model.controlPointPreviousX=a.previous.x,t._model.controlPointNextX=a.next.x,a.next.y>this.chartArea.bottom?t._model.controlPointNextY=this.chartArea.bottom:a.next.ythis.chartArea.bottom?t._model.controlPointPreviousY=this.chartArea.bottom:a.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],n=this.scales[this.data.datasets[s].yAxisID];i.extend(t,{_chart:this.chart,_xScale:a,_yScale:n,_datasetIndex:s,_index:e,_model:{x:a.getPixelForValue(null,e,!0),y:n.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,s){var a=i.splineCurve(this.previousPoint(o,e)._model,t._model,this.nextPoint(o,e)._model,t._model.tension);t._model.controlPointPreviousX=a.previous.x,t._model.controlPointNextX=a.next.x,a.next.y>this.chartArea.bottom?t._model.controlPointNextY=this.chartArea.bottom:a.next.ythis.chartArea.bottom?t._model.controlPointPreviousY=this.chartArea.bottom:a.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,e){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].scaleType),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.scaleType),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,n=0;o>n;++n)t.data.datasets[n].data[i]<0?a+=t.data.datasets[n].data[i]:s+=t.data.datasets[n].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,i){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||[],"mouseout"==t.type?this.active=[]: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),("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.scaleType);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.slice.backgroundColor),hoverBackgroundColor:t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[0].hoverBackgroundColor,e,this.options.elements.slice.hoverBackgroundColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[0].borderWidth,e,this.options.elements.slice.borderWidth),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[0].borderColor,e,this.options.elements.slice.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,n=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:n,backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[0].backgroundColor,e,this.options.elements.slice.backgroundColor),hoverBackgroundColor:t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[0].hoverBackgroundColor,e,this.options.elements.slice.hoverBackgroundColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[0].borderWidth,e,this.options.elements.slice.borderWidth),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[0].borderColor,e,this.options.elements.slice.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,i){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.slice.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.slice.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.slice.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],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,s){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],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,s){var a=i.splineCurve(this.previousPoint(o,e)._model,t._model,this.nextPoint(o,e)._model,t._model.tension);t._model.controlPointPreviousX=a.previous.x,t._model.controlPointNextX=a.next.x,a.next.y>this.chartArea.bottom?t._model.controlPointNextY=this.chartArea.bottom:a.next.ythis.chartArea.bottom?t._model.controlPointPreviousY=this.chartArea.bottom:a.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,i){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),("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;sn&&0>r?this.max=0:n>0&&r>0&&(this.min=0)}for(var l=i.niceNum(this.max-this.min,!1),h=i.niceNum(l/(a-1),!0),c=Math.floor(this.min/h)*h,d=Math.ceil(this.max/h)*h,u=c;d>=u;u+=h)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(this.isHorizontal()?(o.width=t,o.height=this.options.gridLines.show?10:0):(o.height=e,o.width=this.options.gridLines.show?10:0),this.options.labels.show){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 n=t-o.width,r=i.longestText(this.ctx,s,this.labels);n>r?o.width+=r: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(n,r){var l=this.getPixelForValue(n);0===n||!o&&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),l+=i.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(l,s),this.ctx.lineTo(l,a)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(l,t.top),this.ctx.lineTo(l,t.bottom)),this.ctx.stroke()},this)}if(this.options.labels.show){var n;"top"==this.options.position?(n=this.bottom-10,this.ctx.textBaseline="bottom"):(n=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,n)},this)}}else{if(this.options.gridLines.show){e=!0,o=void 0!==i.findNextWhere(this.ticks,function(t){return 0===t});var r="right"==this.options.position?this.left:this.right-5,l="right"==this.options.position?this.left+5:this.right;i.each(this.ticks,function(s,a){var n=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),n+=i.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(r,n),this.ctx.lineTo(l,n)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(t.left,n),this.ctx.lineTo(t.right,n)),this.ctx.stroke()},this)}if(this.options.labels.show){var h;"left"==this.options.position?(h=this.right-10,this.ctx.textAlign="right"):(h=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,h,i)},this)}}}}});e.scales.registerScaleType("linear",o);var s=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,n=this.ctx.measureText(this.labels[0]).width,r=this.ctx.measureText(this.labels[this.labels.length-1]).width;if(this.paddingRight=r/2+3,this.paddingLeft=n/2+3,this.labelRotation=0,this.options.display){var l,h,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(l=Math.cos(i.toRadians(this.labelRotation)),h=Math.sin(i.toRadians(this.labelRotation)),s=l*n,a=l*r,s+this.options.labels.fontSize/2>this.yLabelWidth&&(this.paddingLeft=s+this.options.labels.fontSize/2),this.paddingRight=this.options.labels.fontSize/2,h*c>t){this.labelRotation--;break}this.labelRotation++,this.labelWidth=l*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),n=i.longestText(this.ctx,a,this.labels);if(this.isHorizontal()){s.width=t,this.width=t;var r=Math.cos(i.toRadians(this.labelRotation))*n+1.5*this.options.labels.fontSize;s.height=Math.min(r,e)}else s.height=e,this.height=e,s.width=Math.min(n+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(n,r){var l=this.getPixelForValue(n,r,!1),h=this.getPixelForValue(n,r,!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),l+=i.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(l,o),this.ctx.lineTo(l,s)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(l,t.top),this.ctx.lineTo(l,t.bottom)),this.ctx.stroke()),this.options.labels.show&&(this.ctx.save(),this.ctx.translate(h,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(n,0,0),this.ctx.restore())},this)}else this.options.gridLines.show,this.options.labels.show}}});e.scales.registerScaleType("dataset",s);var a=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 n=i.niceNum(this.max-this.min,!1),r=i.niceNum(n/(o-1),!0),l=Math.floor(this.min/r)*r,h=Math.ceil(this.max/r)*r,c=l;h>=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,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,n,r,l,h,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)),n=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 r=0;r=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,n=this.labels.length/2,r=n/2,l=r>e||e>a-r,h=e===r||e===a-r;0===e?t.textAlign="center":e===n?t.textAlign="center":n>e?t.textAlign="left":t.textAlign="right",h?t.textBaseline="middle":l?t.textBaseline="bottom":t.textBaseline="top",t.fillText(this.labels[e],s.x,s.y)}}}}});e.scales.registerScaleType("radialLinear",a)}.call(this),function(){"use strict";var t=this,e=t.Chart,i=e.helpers,o={hover:{mode:"single"},scales:{xAxes:[{scaleType:"linear",display:!0,position:"bottom",id:"x-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"}}],yAxes:[{scaleType:"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,n=this.scales[this.data.datasets[s].xAxisID],r=this.scales[this.data.datasets[s].yAxisID];a=r.getPixelForValue(r.min<0&&r.max<0?r.max:r.min>0&&r.max>0?r.min:0),i.extend(t,{_chart:this.chart,_xScale:n,_yScale:r,_datasetIndex:s,_index:e,_model:{x:n.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,s){var a=i.splineCurve(this.previousPoint(o,e)._model,t._model,this.nextPoint(o,e)._model,t._model.tension);t._model.controlPointPreviousX=a.previous.x,t._model.controlPointNextX=a.next.x,a.next.y>this.chartArea.bottom?t._model.controlPointNextY=this.chartArea.bottom:a.next.ythis.chartArea.bottom?t._model.controlPointPreviousY=this.chartArea.bottom:a.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],n=this.scales[this.data.datasets[s].yAxisID];i.extend(t,{_chart:this.chart,_xScale:a,_yScale:n,_datasetIndex:s,_index:e,_model:{x:a.getPixelForValue(this.data.datasets[s].data[e].x),y:n.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,s){var a=i.splineCurve(this.previousPoint(o,e)._model,t._model,this.nextPoint(o,e)._model,t._model.tension);t._model.controlPointPreviousX=a.previous.x,t._model.controlPointNextX=a.next.x,a.next.y>this.chartArea.bottom?t._model.controlPointNextY=this.chartArea.bottom:a.next.ythis.chartArea.bottom?t._model.controlPointPreviousY=this.chartArea.bottom:a.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.scaleType),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.scaleType),o=new i({ctx:this.chart.ctx,options:t,calculateRange:s,id:t.id,getPointPixelForValue:function(t,e,i){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,i){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,n=this.rgbArray(),r=t.rgbArray(),l=0;le&&(e+=360),o=(r+l)/2,i=l==r?0:.5>=o?h/(l+r):h/(2-l-r),[e,100*i,100*o]}function s(t){var e,i,o,s=t[0],a=t[1],n=t[2],r=Math.min(s,a,n),l=Math.max(s,a,n),h=l-r;return i=0==l?0:h/l*1e3/10,l==r?e=0:s==l?e=(a-n)/h:a==l?e=2+(n-s)/h:n==l&&(e=4+(s-a)/h),e=Math.min(60*e,360),0>e&&(e+=360),o=l/255*1e3/10,[e,i,o]}function a(t){var e=t[0],i=t[1],s=t[2],a=o(t)[0],n=1/255*Math.min(e,Math.min(i,s)),s=1-1/255*Math.max(e,Math.max(i,s));return[a,100*n,100*s]}function n(t){var e,i,o,s,a=t[0]/255,n=t[1]/255,r=t[2]/255;return s=Math.min(1-a,1-n,1-r),e=(1-a-s)/(1-s)||0,i=(1-n-s)/(1-s)||0,o=(1-r-s)/(1-s)||0,[100*e,100*i,100*o,100*s]}function l(t){return $[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,n=.0193*e+.1192*i+.9505*o;return[100*s,100*a,100*n]}function c(t){var e,i,o,s=h(t),a=s[0],n=s[1],r=s[2];return a/=95.047,n/=100,r/=108.883,a=a>.008856?Math.pow(a,1/3):7.787*a+16/116,n=n>.008856?Math.pow(n,1/3):7.787*n+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,e=116*n-16,i=500*(a-n),o=200*(n-r),[e,i,o]}function d(t){return L(c(t))}function u(t){var e,i,o,s,a,n=t[0]/360,r=t[1]/100,l=t[2]/100;if(0==r)return a=255*l,[a,a,a];i=.5>l?l*(1+r):l+r-l*r,e=2*l-i,s=[0,0,0];for(var h=0;3>h;h++)o=n+1/3*-(h-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[h]=255*a;return s}function m(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 v(t){return a(u(t))}function p(t){return n(u(t))}function f(t){return l(u(t))}function x(t){var e=t[0]/60,i=t[1]/100,o=t[2]/100,s=Math.floor(e)%6,a=e-Math.floor(e),n=255*o*(1-i),r=255*o*(1-i*a),l=255*o*(1-i*(1-a)),o=255*o;switch(s){case 0:return[o,l,n];case 1:return[r,o,n];case 2:return[n,o,l];case 3:return[n,r,o];case 4:return[l,n,o];case 5:return[o,n,r]}}function A(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 C(t){return a(x(t))}function y(t){return n(x(t))}function _(t){return l(x(t))}function w(t){var e,i,o,s,a=t[0]/360,n=t[1]/100,l=t[2]/100,h=n+l;switch(h>1&&(n/=h,l/=h),e=Math.floor(6*a),i=1-l,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 k(t){return o(w(t))}function P(t){return s(w(t))}function S(t){return n(w(t))}function I(t){return l(w(t))}function W(t){var e,i,o,s=t[0]/100,a=t[1]/100,n=t[2]/100,r=t[3]/100;return e=1-Math.min(1,s*(1-r)+r),i=1-Math.min(1,a*(1-r)+r),o=1-Math.min(1,n*(1-r)+r),[255*e,255*i,255*o]}function D(t){return o(W(t))}function O(t){return s(W(t))}function M(t){return a(W(t))}function V(t){return l(W(t))}function B(t){var e,i,o,s=t[0]/100,a=t[1]/100,n=t[2]/100;return e=3.2406*s+-1.5372*a+n*-.4986,i=s*-.9689+1.8758*a+.0415*n,o=.0557*s+a*-.204+1.057*n,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],n=t[2];return s/=95.047,a/=100,n/=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,n=n>.008856?Math.pow(n,1/3):7.787*n+16/116,e=116*a-16,i=500*(s-a),o=200*(a-n),[e,i,o]}function T(t){return L(R(t))}function z(t){var e,i,o,s,a=t[0],n=t[1],r=t[2];return 8>=a?(i=100*a/903.3,s=7.787*(i/100)+16/116):(i=100*Math.pow((a+16)/116,3),s=Math.pow(i/100,1/3)),e=.008856>=e/95.047?e=95.047*(n/500+s-16/116)/7.787:95.047*Math.pow(n/500+s,3),o=.008859>=o/108.883?o=108.883*(s-r/200-16/116)/7.787:108.883*Math.pow(s-r/200,3),[e,i,o]}function L(t){var e,i,o,s=t[0],a=t[1],n=t[2];return e=Math.atan2(n,a),i=360*e/2/Math.PI,0>i&&(i+=360),o=Math.sqrt(a*a+n*n),[s,o,i]}function F(t){return B(z(t))}function E(t){var e,i,o,s=t[0],a=t[1],n=t[2];return o=n/360*2*Math.PI,e=a*Math.cos(o),i=a*Math.sin(o),[s,e,i]}function N(t){return z(E(t))}function H(t){return F(E(t))}function Y(t){return U[t]}function q(t){return o(Y(t))}function j(t){return s(Y(t))}function X(t){return a(Y(t))}function Z(t){return n(Y(t))}function Q(t){return c(Y(t))}function G(t){return h(Y(t))}e.exports={rgb2hsl:o,rgb2hsv:s,rgb2hwb:a,rgb2cmyk:n,rgb2keyword:l,rgb2xyz:h,rgb2lab:c,rgb2lch:d,hsl2rgb:u,hsl2hsv:m,hsl2hwb:v,hsl2cmyk:p,hsl2keyword:f,hsv2rgb:x,hsv2hsl:A,hsv2hwb:C,hsv2cmyk:y,hsv2keyword:_,hwb2rgb:w,hwb2hsl:k,hwb2hsv:P,hwb2cmyk:S,hwb2keyword:I,cmyk2rgb:W,cmyk2hsl:D,cmyk2hsv:O,cmyk2hwb:M,cmyk2keyword:V,keyword2rgb:Y,keyword2hsl:q,keyword2hsv:j,keyword2hwb:X,keyword2cmyk:Z,keyword2lab:Q,keyword2xyz:G,xyz2rgb:B,xyz2lab:R,xyz2lch:T,lab2xyz:z,lab2rgb:F,lab2lch:L,lch2lab:E,lch2xyz:N,lch2rgb:H};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 o=t("./conversions"),s=function(){return new h};for(var a in o){s[a+"Raw"]=function(t){return function(e){return"number"==typeof e&&(e=Array.prototype.slice.call(arguments)),o[t](e)}}(a);var n=/(\w+)2(\w+)/.exec(a),r=n[1],l=n[2];s[r]=s[r]||{},s[r][l]=s[a]=function(t){return function(e){"number"==typeof e&&(e=Array.prototype.slice.call(arguments));var i=o[t](e);if("string"==typeof i||void 0===i)return i;for(var s=0;se||t[3]&&t[3]<1?d(t,e):"rgb("+t[0]+", "+t[1]+", "+t[2]+")"}function d(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"rgba("+t[0]+", "+t[1]+", "+t[2]+", "+e+")"}function u(t,e){if(1>e||t[3]&&t[3]<1)return m(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 m(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 v(t,e){return 1>e||t[3]&&t[3]<1?g(t,e):"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"}function g(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hsla("+t[0]+", "+t[1]+"%, "+t[2]+"%, "+e+")"}function p(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 f(t){return C[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 A=t("color-name");e.exports={getRgba:o,getHsla:s,getRgb:n,getHsl:r,getHwb:a,getAlpha:l,hexString:h,rgbString:c,rgbaString:d,percentString:u,percentaString:m,hslString:v,hslaString:g,hwbString:p,keyword:f};var C={};for(var y in A)C[A[y]]=y},{"color-name":5}],5:[function(t,e,i){e.exports={aliceblue:[240,248,255],antiquewhite:[250,235,215],aqua:[0,255,255],aquamarine:[127,255,212],azure:[240,255,255],beige:[245,245,220],bisque:[255,228,196],black:[0,0,0],blanchedalmond:[255,235,205],blue:[0,0,255],blueviolet:[138,43,226],brown:[165,42,42],burlywood:[222,184,135],cadetblue:[95,158,160],chartreuse:[127,255,0],chocolate:[210,105,30],coral:[255,127,80],cornflowerblue:[100,149,237],cornsilk:[255,248,220],crimson:[220,20,60],cyan:[0,255,255],darkblue:[0,0,139],darkcyan:[0,139,139],darkgoldenrod:[184,134,11],darkgray:[169,169,169],darkgreen:[0,100,0],darkgrey:[169,169,169],darkkhaki:[189,183,107],darkmagenta:[139,0,139],darkolivegreen:[85,107,47],darkorange:[255,140,0],darkorchid:[153,50,204],darkred:[139,0,0],darksalmon:[233,150,122],darkseagreen:[143,188,143],darkslateblue:[72,61,139],darkslategray:[47,79,79],darkslategrey:[47,79,79],darkturquoise:[0,206,209],darkviolet:[148,0,211],deeppink:[255,20,147],deepskyblue:[0,191,255],dimgray:[105,105,105],dimgrey:[105,105,105],dodgerblue:[30,144,255],firebrick:[178,34,34],floralwhite:[255,250,240],forestgreen:[34,139,34],fuchsia:[255,0,255],gainsboro:[220,220,220],ghostwhite:[248,248,255],gold:[255,215,0],goldenrod:[218,165,32],gray:[128,128,128],green:[0,128,0],greenyellow:[173,255,47],grey:[128,128,128],honeydew:[240,255,240],hotpink:[255,105,180],indianred:[205,92,92],indigo:[75,0,130],ivory:[255,255,240],khaki:[240,230,140],lavender:[230,230,250],lavenderblush:[255,240,245],lawngreen:[124,252,0],lemonchiffon:[255,250,205],lightblue:[173,216,230],lightcoral:[240,128,128],lightcyan:[224,255,255],lightgoldenrodyellow:[250,250,210],lightgray:[211,211,211],lightgreen:[144,238,144],lightgrey:[211,211,211],lightpink:[255,182,193],lightsalmon:[255,160,122],lightseagreen:[32,178,170],lightskyblue:[135,206,250],lightslategray:[119,136,153],lightslategrey:[119,136,153],lightsteelblue:[176,196,222],lightyellow:[255,255,224],lime:[0,255,0],limegreen:[50,205,50],linen:[250,240,230],magenta:[255,0,255],maroon:[128,0,0],mediumaquamarine:[102,205,170],mediumblue:[0,0,205],mediumorchid:[186,85,211],mediumpurple:[147,112,219],mediumseagreen:[60,179,113],mediumslateblue:[123,104,238],mediumspringgreen:[0,250,154],mediumturquoise:[72,209,204],mediumvioletred:[199,21,133],midnightblue:[25,25,112],mintcream:[245,255,250],mistyrose:[255,228,225],moccasin:[255,228,181],navajowhite:[255,222,173],navy:[0,0,128],oldlace:[253,245,230],olive:[128,128,0],olivedrab:[107,142,35],orange:[255,165,0],orangered:[255,69,0],orchid:[218,112,214],palegoldenrod:[238,232,170],palegreen:[152,251,152],paleturquoise:[175,238,238],palevioletred:[219,112,147],papayawhip:[255,239,213],peachpuff:[255,218,185],peru:[205,133,63],pink:[255,192,203],plum:[221,160,221],powderblue:[176,224,230],purple:[128,0,128],rebeccapurple:[102,51,153],red:[255,0,0],rosybrown:[188,143,143],royalblue:[65,105,225],saddlebrown:[139,69,19],salmon:[250,128,114],sandybrown:[244,164,96],seagreen:[46,139,87],seashell:[255,245,238],sienna:[160,82,45],silver:[192,192,192],skyblue:[135,206,235],slateblue:[106,90,205],slategray:[112,128,144],slategrey:[112,128,144],snow:[255,250,250],springgreen:[0,255,127],steelblue:[70,130,180],tan:[210,180,140],teal:[0,128,128],thistle:[216,191,216],tomato:[255,99,71],turquoise:[64,224,208],violet:[238,130,238],wheat:[245,222,179],white:[255,255,255],whitesmoke:[245,245,245],yellow:[255,255,0],yellowgreen:[154,205,50]}},{}]},{},[1]); \ No newline at end of file +(function(){"use strict";var t=this,e=t.Chart,i=function(t){this.canvas=t.canvas,this.ctx=t;var e=function(t,e){return t["offset"+e]?t["offset"+e]:document.defaultView.getComputedStyle(t).getPropertyValue(e)},i=this.width=e(t.canvas,"Width")||t.canvas.width,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=l,t&&r(i.prototype,t),i.__super__=e.prototype,i}),h=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)),n=o*s/(s+a),r=o*a/(s+a);return{previous:{x:e.x-n*(i.x-t.x),y:e.y-n*(i.y-t.y)},next:{x:e.x+r*(i.x-t.x),y:e.y+r*(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,n=Math.floor(e/(1.5*i)),r=a>=n,l=v(t),h=g(t);l===h&&(l+=.5,h>=.5&&!o?h-=.5:l+=.5);for(var c=Math.abs(l-h),d=p(c),u=Math.ceil(l/(1*Math.pow(10,d)))*Math.pow(10,d),m=o?0:Math.floor(h/(1*Math.pow(10,d)))*Math.pow(10,d),f=u-m,b=Math.pow(10,d),x=Math.round(f/b);(x>n||n>2*x)&&!r;)if(x>n)b*=2,x=Math.round(f/b),x%1!==0&&(r=!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 r&&(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 n=0,r=b[i]||b.linear,l=function(){n++;var i=n/e,h=r(i);t.call(a,h,i,n),o.call(a,h,i),e>n?a.animationFrame=x(l):s.apply(a)};x(l)},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]=h},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)})}),_=s.getMaximumWidth=function(t){var e=t.parentNode,i=parseInt(k(e,"padding-left"))+parseInt(k(e,"padding-right"));return e.clientWidth-i},w=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)},P=(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))}),S=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)},r(i.Type.prototype,{initialize:function(){return this},clear:function(){return S(this.chart),this},stop:function(){return i.animationService.cancelAnimation(this),this},resize:function(){this.stop();var t=this.chart.canvas,e=_(this.chart.canvas),i=this.options.maintainAspectRatio?e/this.chart.aspectRatio:w(this.chart.canvas);return t.width=this.chart.width=e,t.height=this.chart.height=i,P(this.chart),this},redraw:h,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])},n=0;n1.5*Math.PI?o.startAngle-2*Math.PI:o.startAngle,n=o.endAngle<-.5*Math.PI?o.endAngle+2*Math.PI:o.endAngle>1.5*Math.PI?o.endAngle-2*Math.PI:o.endAngle,r=s.angle>=a&&s.angle<=n,l=s.distance>=o.innerRadius&&s.distance<=o.outerRadius;return r&&l},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),n=e.borderWidth/2;e.borderWidth&&(o+=n,s-=n,a+=n),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.y0,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,n=this.ctx.measureText(this.labels[0]).width,r=this.ctx.measureText(this.labels[this.labels.length-1]).width;if(this.paddingRight=r/2+3,this.paddingLeft=n/2+3,this.labelRotation=0,this.options.display){var l,h,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(l=Math.cos(i.toRadians(this.labelRotation)),h=Math.sin(i.toRadians(this.labelRotation)),s=l*n,a=l*r,s+this.options.labels.fontSize/2>this.yLabelWidth&&(this.paddingLeft=s+this.options.labels.fontSize/2),this.paddingRight=this.options.labels.fontSize/2,h*c>t){this.labelRotation--;break}this.labelRotation++,this.labelWidth=l*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),n=i.longestText(this.ctx,a,this.labels);if(this.isHorizontal()){s.width=t,this.width=t;var r=Math.cos(i.toRadians(this.labelRotation))*n+1.5*this.options.labels.fontSize;s.height=Math.min(r,e)}else s.height=e,this.height=e,s.width=Math.min(n+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(n,r){var l=this.getPixelForValue(n,r,!1),h=this.getPixelForValue(n,r,!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),l+=i.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(l,o),this.ctx.lineTo(l,s)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(l,t.top),this.ctx.lineTo(l,t.bottom)),this.ctx.stroke()),this.options.labels.show&&(this.ctx.save(),this.ctx.translate(h,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(n,0,0),this.ctx.restore())},this)}else this.options.gridLines.show,this.options.labels.show}}});e.scales.registerScaleType("dataset",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 n=i.sign(this.min),r=i.sign(this.max);0>n&&0>r?this.max=0:n>0&&r>0&&(this.min=0)}for(var l=i.niceNum(this.max-this.min,!1),h=i.niceNum(l/(a-1),!0),c=Math.floor(this.min/h)*h,d=Math.ceil(this.max/h)*h,u=c;d>=u;u+=h)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(this.isHorizontal()?(o.width=t,o.height=this.options.gridLines.show?10:0):(o.height=e,o.width=this.options.gridLines.show?10:0),this.options.labels.show){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 n=t-o.width,r=i.longestText(this.ctx,s,this.labels);n>r?o.width+=r: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(n,r){var l=this.getPixelForValue(n);0===n||!o&&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),l+=i.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(l,s),this.ctx.lineTo(l,a)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(l,t.top),this.ctx.lineTo(l,t.bottom)),this.ctx.stroke()},this)}if(this.options.labels.show){var n;"top"==this.options.position?(n=this.bottom-10,this.ctx.textBaseline="bottom"):(n=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,n)},this)}}else{if(this.options.gridLines.show){e=!0,o=void 0!==i.findNextWhere(this.ticks,function(t){return 0===t});var r="right"==this.options.position?this.left:this.right-5,l="right"==this.options.position?this.left+5:this.right;i.each(this.ticks,function(s,a){var n=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),n+=i.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(r,n),this.ctx.lineTo(l,n)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(t.left,n),this.ctx.lineTo(t.right,n)),this.ctx.stroke()},this)}if(this.options.labels.show){var h;"left"==this.options.position?(h=this.right-10,this.ctx.textAlign="right"):(h=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,h,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 n=i.niceNum(this.max-this.min,!1),r=i.niceNum(n/(o-1),!0),l=Math.floor(this.min/r)*r,h=Math.ceil(this.max/r)*r,c=l;h>=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,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,n,r,l,h,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)),n=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 r=0;r=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,n=this.labels.length/2,r=n/2,l=r>e||e>a-r,h=e===r||e===a-r;0===e?t.textAlign="center":e===n?t.textAlign="center":n>e?t.textAlign="left":t.textAlign="right",h?t.textBaseline="middle":l?t.textBaseline="bottom":t.textBaseline="top",t.fillText(this.labels[e],s.x,s.y)}}}}});e.scales.registerScaleType("radialLinear",o)}.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.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,n=a/this.frameDuration;n>1&&(this.dropFrames+=n),this.animations.length>0&&i.requestAnimFrame.call(window,this.digestWrapper)}}}.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.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: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=[],n=[],r=this._data.datasets.length-1;r>=0&&(o=this._data.datasets[r].metaData,s=indexOf(o,this._active[0]),-1===s);r--);var l=function(t){var e,o,r,l,h,c=[],d=[],u=[];return i.each(this._data.datasets,function(t){e=t.metaData,e[s]&&e[s].hasValue()&&c.push(e[s])},this),i.each(this._options.stacked?c.reverse():c,function(t){d.push(t._view.x),u.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]})),n.push({fill:t._view.backgroundColor,stroke:t._view.borderColor})},this),h=min(u),r=max(u),l=min(d),o=max(d),{x:l>this._chart.width/2?l:o,y:(h+r)/2}}.call(this,s);i.extend(this._model,{x:l.x,y:l.y,labels:a,title:this._data.labels&&this._data.labels.length?this._data.labels[this._active[0]._index]:"",legendColors:n,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 h=t.measureText(this.title).width,c=longestText(t,this.font,a)+this._model.fontSize+3,d=max([c,h]);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,n=a+e.caretHeight+o;e.x+s/2>this._chart.width?e.xAlign="left":e.x-s/2<0&&(e.xAlign="right"),e.y-n<0&&(e.yAlign="below");var r=e.x-s/2,l=e.y-n;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":l=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":r=e.x-s+(e.cornerRadius+e.caretHeight);break;case"right":r=e.x-(e.cornerRadius+e.caretHeight)}i.drawRoundedRectangle(t,r,l,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,r+s/2,l+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={stacked:!1,valueSpacing:5,datasetSpacing:1,hover:{mode:"label"},scales:{xAxes:[{scaleType:"dataset",display:!0,position:"bottom",id:"x-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)",offsetGridLines:!0},labels:{show:!0,template:"<%=value%>",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue"}}],yAxes:[{scaleType:"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,n=this.scales[this.data.datasets[s].xAxisID],r=this.scales[this.data.datasets[s].yAxisID];a=r.getPixelForValue(r.min<0&&r.max<0?r.max:r.min>0&&r.max>0?r.min:0),i.extend(t,{_chart:this.chart,_xScale:n,_yScale:r,_datasetIndex:s,_index:e,_model:{x:n.calculateBarX(this.data.datasets.length,s,e),y:a,base:r.calculateBarBase(s,e),width:n.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.bar.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[s].borderColor,e,this.options.elements.bar.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[s].borderWidth,e,this.options.elements.bar.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],n=this.scales[this.data.datasets[s].yAxisID];i.extend(t,{_chart:this.chart,_xScale:a,_yScale:n,_datasetIndex:s,_index:e,_model:{x:a.calculateBarX(this.data.datasets.length,s,e),y:n.calculateBarY(s,e),base:n.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.bar.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[s].borderColor,e,this.options.elements.bar.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[s].borderWidth,e,this.options.elements.bar.borderWidth),label:this.data.labels[e],datasetLabel:this.data.datasets[s].label}}),t.pivot()},this),this.render(t)},buildScale:function(t){var o=this,s=function(){this.min=null,this.max=null;var t=[],e=[];if(o.options.stacked){i.each(o.data.datasets,function(s){s.yAxisID===this.id&&i.each(s.data,function(i,s){t[s]=t[s]||0,e[s]=e[s]||0,o.options.relativePoints?t[s]=100:0>i?e[s]+=i:t[s]+=i},this)},this);var s=t.concat(e);this.min=i.min(s),this.max=i.max(s)}else i.each(o.data.datasets,function(t){t.yAxisID===this.id&&i.each(t.data,function(t,e){null===this.min?this.min=t:tthis.max&&(this.max=t)},this)},this)};this.scales={};var a=e.scales.getScaleConstructor(this.options.scales.xAxes[0].scaleType),n=new a({ctx:this.chart.ctx,options:this.options.scales.xAxes[0],id:this.options.scales.xAxes[0].id,calculateRange:function(){this.labels=o.data.labels,this.min=0,this.max=this.labels.length},calculateBaseWidth:function(){return this.getPixelForValue(null,1,!0)-this.getPixelForValue(null,0,!0)-2*o.options.elements.bar.valueSpacing},calculateBarWidth:function(t){var e=this.calculateBaseWidth()-(t-1)*o.options.elements.bar.datasetSpacing;return o.options.stacked?e:e/t},calculateBarX:function(t,e,i){var s=this.calculateBaseWidth(),a=this.getPixelForValue(null,i,!0)-s/2,n=this.calculateBarWidth(t);return o.options.stacked?a+n/2:a+n*e+e*o.options.elements.bar.datasetSpacing+n/2}});this.scales[n.id]=n,i.each(this.options.scales.yAxes,function(t){var i=e.scales.getScaleConstructor(t.scaleType),a=new i({ctx:this.chart.ctx,options:t,calculateRange:s,calculateBarBase:function(t,e){var i=0;if(o.options.stacked){var s=o.data.datasets[t].data[e];if(0>s)for(var a=0;t>a;a++)o.data.datasets[a].yAxisID===this.id&&(i+=o.data.datasets[a].data[e]<0?o.data.datasets[a].data[e]:0);else for(var n=0;t>n;n++)o.data.datasets[n].yAxisID===this.id&&(i+=o.data.datasets[n].data[e]>0?o.data.datasets[n].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=o.data.datasets[t].data[e];if(o.options.stacked){for(var s=0,a=0,n=0;t>n;n++)o.data.datasets[n].data[e]<0?a+=o.data.datasets[n].data[e]||0:s+=o.data.datasets[n].data[e]||0;return this.getPixelForValue(0>i?a+i:s+i)}for(var r=0,l=t;l0?2*Math.PI*(e/t.total):0},resetElements:function(){this.outerRadius=(i.min([this.chart.width,this.chart.height])-this.options.elements.slice.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.slice.backgroundColor),hoverBackgroundColor:e.custom&&e.custom.hoverBackgroundColor?e.custom.hoverBackgroundColor:i.getValueAtIndexOrDefault(t.hoverBackgroundColor,o,this.options.elements.slice.hoverBackgroundColor),borderWidth:e.custom&&e.custom.borderWidth?e.custom.borderWidth:i.getValueAtIndexOrDefault(t.borderWidth,o,this.options.elements.slice.borderWidth),borderColor:e.custom&&e.custom.borderColor?e.custom.borderColor:i.getValueAtIndexOrDefault(t.borderColor,o,this.options.elements.slice.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.slice.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.slice.backgroundColor),hoverBackgroundColor:o.custom&&o.custom.hoverBackgroundColor?o.custom.hoverBackgroundColor:i.getValueAtIndexOrDefault(t.hoverBackgroundColor,s,this.options.elements.slice.hoverBackgroundColor),borderWidth:o.custom&&o.custom.borderWidth?o.custom.borderWidth:i.getValueAtIndexOrDefault(t.borderWidth,s,this.options.elements.slice.borderWidth),borderColor:o.custom&&o.custom.borderColor?o.custom.borderColor:i.getValueAtIndexOrDefault(t.borderColor,s,this.options.elements.slice.borderColor),label:i.getValueAtIndexOrDefault(t.label,s,this.data.labels[s])}}),0===s?o._model.startAngle=Math.PI*-.5:o._model.startAngle=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:[{scaleType:"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,n=this.scales[this.data.datasets[s].xAxisID],r=this.scales[this.data.datasets[s].yAxisID];a=r.getPixelForValue(r.min<0&&r.max<0?r.max:r.min>0&&r.max>0?r.min:0),i.extend(t,{_chart:this.chart,_xScale:n,_yScale:r,_datasetIndex:s,_index:e,_model:{x:n.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,s){var a=i.splineCurve(this.previousPoint(o,e)._model,t._model,this.nextPoint(o,e)._model,t._model.tension);t._model.controlPointPreviousX=a.previous.x,t._model.controlPointNextX=a.next.x,a.next.y>this.chartArea.bottom?t._model.controlPointNextY=this.chartArea.bottom:a.next.ythis.chartArea.bottom?t._model.controlPointPreviousY=this.chartArea.bottom:a.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],n=this.scales[this.data.datasets[s].yAxisID];i.extend(t,{_chart:this.chart,_xScale:a,_yScale:n,_datasetIndex:s,_index:e,_model:{x:a.getPixelForValue(null,e,!0),y:n.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,s){var a=i.splineCurve(this.previousPoint(o,e)._model,t._model,this.nextPoint(o,e)._model,t._model.tension);t._model.controlPointPreviousX=a.previous.x,t._model.controlPointNextX=a.next.x,a.next.y>this.chartArea.bottom?t._model.controlPointNextY=this.chartArea.bottom:a.next.ythis.chartArea.bottom?t._model.controlPointPreviousY=this.chartArea.bottom:a.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,e){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].scaleType),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.scaleType),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,n=0;o>n;++n)t.data.datasets[n].data[i]<0?a+=t.data.datasets[n].data[i]:s+=t.data.datasets[n].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,i){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||[],"mouseout"==t.type?this.active=[]: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),("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.scaleType);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.slice.backgroundColor),hoverBackgroundColor:t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[0].hoverBackgroundColor,e,this.options.elements.slice.hoverBackgroundColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[0].borderWidth,e,this.options.elements.slice.borderWidth),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[0].borderColor,e,this.options.elements.slice.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,n=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:n,backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[0].backgroundColor,e,this.options.elements.slice.backgroundColor),hoverBackgroundColor:t.custom&&t.custom.hoverBackgroundColor?t.custom.hoverBackgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[0].hoverBackgroundColor,e,this.options.elements.slice.hoverBackgroundColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[0].borderWidth,e,this.options.elements.slice.borderWidth),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[0].borderColor,e,this.options.elements.slice.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,i){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.slice.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.slice.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.slice.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],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,s){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],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,s){var a=i.splineCurve(this.previousPoint(o,e)._model,t._model,this.nextPoint(o,e)._model,t._model.tension);t._model.controlPointPreviousX=a.previous.x,t._model.controlPointNextX=a.next.x,a.next.y>this.chartArea.bottom?t._model.controlPointNextY=this.chartArea.bottom:a.next.ythis.chartArea.bottom?t._model.controlPointPreviousY=this.chartArea.bottom:a.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,i){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),("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:[{scaleType:"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,n=this.scales[this.data.datasets[s].xAxisID],r=this.scales[this.data.datasets[s].yAxisID];a=r.getPixelForValue(r.min<0&&r.max<0?r.max:r.min>0&&r.max>0?r.min:0),i.extend(t,{_chart:this.chart,_xScale:n,_yScale:r,_datasetIndex:s,_index:e,_model:{x:n.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,s){var a=i.splineCurve(this.previousPoint(o,e)._model,t._model,this.nextPoint(o,e)._model,t._model.tension);t._model.controlPointPreviousX=a.previous.x,t._model.controlPointNextX=a.next.x,a.next.y>this.chartArea.bottom?t._model.controlPointNextY=this.chartArea.bottom:a.next.ythis.chartArea.bottom?t._model.controlPointPreviousY=this.chartArea.bottom:a.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],n=this.scales[this.data.datasets[s].yAxisID];i.extend(t,{_chart:this.chart,_xScale:a,_yScale:n,_datasetIndex:s,_index:e,_model:{x:a.getPixelForValue(this.data.datasets[s].data[e].x),y:n.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,s){var a=i.splineCurve(this.previousPoint(o,e)._model,t._model,this.nextPoint(o,e)._model,t._model.tension);t._model.controlPointPreviousX=a.previous.x,t._model.controlPointNextX=a.next.x,a.next.y>this.chartArea.bottom?t._model.controlPointNextY=this.chartArea.bottom:a.next.ythis.chartArea.bottom?t._model.controlPointPreviousY=this.chartArea.bottom:a.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.scaleType),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.scaleType),o=new i({ctx:this.chart.ctx,options:t,calculateRange:s,id:t.id,getPointPixelForValue:function(t,e,i){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,i){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,n=this.rgbArray(),r=t.rgbArray(),l=0;le&&(e+=360),o=(r+l)/2,i=l==r?0:.5>=o?h/(l+r):h/(2-l-r),[e,100*i,100*o]}function s(t){var e,i,o,s=t[0],a=t[1],n=t[2],r=Math.min(s,a,n),l=Math.max(s,a,n),h=l-r;return i=0==l?0:h/l*1e3/10,l==r?e=0:s==l?e=(a-n)/h:a==l?e=2+(n-s)/h:n==l&&(e=4+(s-a)/h),e=Math.min(60*e,360),0>e&&(e+=360),o=l/255*1e3/10,[e,i,o]}function a(t){var e=t[0],i=t[1],s=t[2],a=o(t)[0],n=1/255*Math.min(e,Math.min(i,s)),s=1-1/255*Math.max(e,Math.max(i,s));return[a,100*n,100*s]}function n(t){var e,i,o,s,a=t[0]/255,n=t[1]/255,r=t[2]/255;return s=Math.min(1-a,1-n,1-r),e=(1-a-s)/(1-s)||0,i=(1-n-s)/(1-s)||0,o=(1-r-s)/(1-s)||0,[100*e,100*i,100*o,100*s]}function l(t){return $[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,n=.0193*e+.1192*i+.9505*o;return[100*s,100*a,100*n]}function c(t){var e,i,o,s=h(t),a=s[0],n=s[1],r=s[2];return a/=95.047,n/=100,r/=108.883,a=a>.008856?Math.pow(a,1/3):7.787*a+16/116,n=n>.008856?Math.pow(n,1/3):7.787*n+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,e=116*n-16,i=500*(a-n),o=200*(n-r),[e,i,o]}function d(t){return L(c(t))}function u(t){var e,i,o,s,a,n=t[0]/360,r=t[1]/100,l=t[2]/100;if(0==r)return a=255*l,[a,a,a];i=.5>l?l*(1+r):l+r-l*r,e=2*l-i,s=[0,0,0];for(var h=0;3>h;h++)o=n+1/3*-(h-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[h]=255*a;return s}function m(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 v(t){return a(u(t))}function p(t){return n(u(t))}function f(t){return l(u(t))}function x(t){var e=t[0]/60,i=t[1]/100,o=t[2]/100,s=Math.floor(e)%6,a=e-Math.floor(e),n=255*o*(1-i),r=255*o*(1-i*a),l=255*o*(1-i*(1-a)),o=255*o;switch(s){case 0:return[o,l,n];case 1:return[r,o,n];case 2:return[n,o,l];case 3:return[n,r,o];case 4:return[l,n,o];case 5:return[o,n,r]}}function A(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 C(t){return a(x(t))}function y(t){return n(x(t))}function _(t){return l(x(t))}function w(t){var e,i,o,s,a=t[0]/360,n=t[1]/100,l=t[2]/100,h=n+l;switch(h>1&&(n/=h,l/=h),e=Math.floor(6*a),i=1-l,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 k(t){return o(w(t))}function P(t){return s(w(t))}function S(t){return n(w(t))}function I(t){return l(w(t))}function W(t){var e,i,o,s=t[0]/100,a=t[1]/100,n=t[2]/100,r=t[3]/100;return e=1-Math.min(1,s*(1-r)+r),i=1-Math.min(1,a*(1-r)+r),o=1-Math.min(1,n*(1-r)+r),[255*e,255*i,255*o]}function D(t){return o(W(t))}function O(t){return s(W(t))}function M(t){return a(W(t))}function V(t){return l(W(t))}function B(t){var e,i,o,s=t[0]/100,a=t[1]/100,n=t[2]/100;return e=3.2406*s+-1.5372*a+n*-.4986,i=s*-.9689+1.8758*a+.0415*n,o=.0557*s+a*-.204+1.057*n,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],n=t[2];return s/=95.047,a/=100,n/=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,n=n>.008856?Math.pow(n,1/3):7.787*n+16/116,e=116*a-16,i=500*(s-a),o=200*(a-n),[e,i,o]}function T(t){return L(R(t))}function z(t){var e,i,o,s,a=t[0],n=t[1],r=t[2];return 8>=a?(i=100*a/903.3,s=7.787*(i/100)+16/116):(i=100*Math.pow((a+16)/116,3),s=Math.pow(i/100,1/3)),e=.008856>=e/95.047?e=95.047*(n/500+s-16/116)/7.787:95.047*Math.pow(n/500+s,3),o=.008859>=o/108.883?o=108.883*(s-r/200-16/116)/7.787:108.883*Math.pow(s-r/200,3),[e,i,o]}function L(t){var e,i,o,s=t[0],a=t[1],n=t[2];return e=Math.atan2(n,a),i=360*e/2/Math.PI,0>i&&(i+=360),o=Math.sqrt(a*a+n*n),[s,o,i]}function F(t){return B(z(t))}function E(t){var e,i,o,s=t[0],a=t[1],n=t[2];return o=n/360*2*Math.PI,e=a*Math.cos(o),i=a*Math.sin(o),[s,e,i]}function N(t){return z(E(t))}function H(t){return F(E(t))}function Y(t){return U[t]}function q(t){return o(Y(t))}function j(t){return s(Y(t))}function X(t){return a(Y(t))}function Z(t){return n(Y(t))}function Q(t){return c(Y(t))}function G(t){return h(Y(t))}e.exports={rgb2hsl:o,rgb2hsv:s,rgb2hwb:a,rgb2cmyk:n,rgb2keyword:l,rgb2xyz:h,rgb2lab:c,rgb2lch:d,hsl2rgb:u,hsl2hsv:m,hsl2hwb:v,hsl2cmyk:p,hsl2keyword:f,hsv2rgb:x,hsv2hsl:A,hsv2hwb:C,hsv2cmyk:y,hsv2keyword:_,hwb2rgb:w,hwb2hsl:k,hwb2hsv:P,hwb2cmyk:S,hwb2keyword:I,cmyk2rgb:W,cmyk2hsl:D,cmyk2hsv:O,cmyk2hwb:M,cmyk2keyword:V,keyword2rgb:Y,keyword2hsl:q,keyword2hsv:j,keyword2hwb:X,keyword2cmyk:Z,keyword2lab:Q,keyword2xyz:G,xyz2rgb:B,xyz2lab:R,xyz2lch:T,lab2xyz:z,lab2rgb:F,lab2lch:L,lch2lab:E,lch2xyz:N,lch2rgb:H};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 o=t("./conversions"),s=function(){return new h};for(var a in o){s[a+"Raw"]=function(t){return function(e){return"number"==typeof e&&(e=Array.prototype.slice.call(arguments)),o[t](e)}}(a);var n=/(\w+)2(\w+)/.exec(a),r=n[1],l=n[2];s[r]=s[r]||{},s[r][l]=s[a]=function(t){return function(e){"number"==typeof e&&(e=Array.prototype.slice.call(arguments));var i=o[t](e);if("string"==typeof i||void 0===i)return i;for(var s=0;se||t[3]&&t[3]<1?d(t,e):"rgb("+t[0]+", "+t[1]+", "+t[2]+")"}function d(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"rgba("+t[0]+", "+t[1]+", "+t[2]+", "+e+")"}function u(t,e){if(1>e||t[3]&&t[3]<1)return m(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 m(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 v(t,e){return 1>e||t[3]&&t[3]<1?g(t,e):"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"}function g(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hsla("+t[0]+", "+t[1]+"%, "+t[2]+"%, "+e+")"}function p(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 f(t){return C[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 A=t("color-name");e.exports={getRgba:o,getHsla:s,getRgb:n,getHsl:r,getHwb:a,getAlpha:l,hexString:h,rgbString:c,rgbaString:d,percentString:u,percentaString:m,hslString:v,hslaString:g,hwbString:p,keyword:f};var C={};for(var y in A)C[A[y]]=y},{"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/gulpfile.js b/gulpfile.js index f1e71472c..e442665ca 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -1,18 +1,18 @@ var gulp = require('gulp'), - concat = require('gulp-concat'), - uglify = require('gulp-uglify'), - util = require('gulp-util'), - jshint = require('gulp-jshint'), - size = require('gulp-size'), - connect = require('gulp-connect'), - replace = require('gulp-replace'), - htmlv = require('gulp-html-validator'), - inquirer = require('inquirer'), - semver = require('semver'), - exec = require('child_process').exec, - fs = require('fs'), - package = require('./package.json'), - bower = require('./bower.json'); + concat = require('gulp-concat'), + uglify = require('gulp-uglify'), + util = require('gulp-util'), + jshint = require('gulp-jshint'), + size = require('gulp-size'), + connect = require('gulp-connect'), + replace = require('gulp-replace'), + htmlv = require('gulp-html-validator'), + inquirer = require('inquirer'), + semver = require('semver'), + exec = require('child_process').exec, + fs = require('fs'), + package = require('./package.json'), + bower = require('./bower.json'); var srcDir = './src/'; /* @@ -21,33 +21,46 @@ var srcDir = './src/'; * - A minified version of this code, in Chart.min.js */ -gulp.task('build', function(){ - - // Default to all of the chart types, with Chart.Core first - var srcFiles = [FileName('Core')], - isCustom = !!(util.env.types), - outputDir = (isCustom) ? 'custom' : '.'; - if (isCustom){ - util.env.types.split(',').forEach(function(type){ return srcFiles.push(FileName(type));}); - } - else{ - // Seems gulp-concat remove duplicates - nice! - // So we can use this to sort out dependency order - aka include Core first! - srcFiles.push(srcDir+'*'); - } - srcFiles.push('./node_modules/color/dist/color.min.js'); - - return gulp.src(srcFiles) - .pipe(concat('Chart.js')) - .pipe(replace('{{ version }}', package.version)) - .pipe(gulp.dest(outputDir)) - .pipe(uglify({preserveComments:'some'})) - .pipe(concat('Chart.min.js')) - .pipe(gulp.dest(outputDir)); - - function FileName(moduleName){ - return srcDir+'Chart.'+moduleName+'.js'; - } +gulp.task('build', function() { + + // Default to all of the chart types, with Chart.Core first + var srcFiles = [ + FileName('Core'), + FileName('Core.**'), + FileName('Scale'), + FileName('Scale.**'), + FileName('Animation'), + FileName('Tooltip'), + ], + isCustom = !!(util.env.types), + outputDir = (isCustom) ? 'custom' : '.'; + if (isCustom) { + util.env.types.split(',').forEach(function(type) { + return srcFiles.push(FileName(type)); + }); + } else { + // Seems gulp-concat remove duplicates - nice! + // So we can use this to sort out dependency order - aka include Core first! + srcFiles.push(srcDir + '*'); + srcFiles.push(srcDir + '*'); + srcFiles.push(srcDir + '*'); + srcFiles.push(srcDir + '*'); + } + srcFiles.push('./node_modules/color/dist/color.min.js'); + + return gulp.src(srcFiles) + .pipe(concat('Chart.js')) + .pipe(replace('{{ version }}', package.version)) + .pipe(gulp.dest(outputDir)) + .pipe(uglify({ + preserveComments: 'some' + })) + .pipe(concat('Chart.min.js')) + .pipe(gulp.dest(outputDir)); + + function FileName(moduleName) { + return srcDir + 'Chart.' + moduleName + '.js'; + } }); /* @@ -56,65 +69,67 @@ gulp.task('build', function(){ * Output: - New version number written into package.json & bower.json */ -gulp.task('bump', function(complete){ - util.log('Current version:', util.colors.cyan(package.version)); - var choices = ['major', 'premajor', 'minor', 'preminor', 'patch', 'prepatch', 'prerelease'].map(function(versionType){ - return versionType + ' (v' + semver.inc(package.version, versionType) + ')'; - }); - inquirer.prompt({ - type: 'list', - name: 'version', - message: 'What version update would you like?', - choices: choices - }, function(res){ - var increment = res.version.split(' ')[0], - newVersion = semver.inc(package.version, increment); - - // Set the new versions into the bower/package object - package.version = newVersion; - bower.version = newVersion; - - // Write these to their own files, then build the output - fs.writeFileSync('package.json', JSON.stringify(package, null, 2)); - fs.writeFileSync('bower.json', JSON.stringify(bower, null, 2)); - - complete(); - }); +gulp.task('bump', function(complete) { + util.log('Current version:', util.colors.cyan(package.version)); + var choices = ['major', 'premajor', 'minor', 'preminor', 'patch', 'prepatch', 'prerelease'].map(function(versionType) { + return versionType + ' (v' + semver.inc(package.version, versionType) + ')'; + }); + inquirer.prompt({ + type: 'list', + name: 'version', + message: 'What version update would you like?', + choices: choices + }, function(res) { + var increment = res.version.split(' ')[0], + newVersion = semver.inc(package.version, increment); + + // Set the new versions into the bower/package object + package.version = newVersion; + bower.version = newVersion; + + // Write these to their own files, then build the output + fs.writeFileSync('package.json', JSON.stringify(package, null, 2)); + fs.writeFileSync('bower.json', JSON.stringify(bower, null, 2)); + + complete(); + }); }); -gulp.task('release', ['build'], function(){ - exec('git tag -a v' + package.version); +gulp.task('release', ['build'], function() { + exec('git tag -a v' + package.version); }); -gulp.task('jshint', function(){ - return gulp.src(srcDir + '*.js') - .pipe(jshint()) - .pipe(jshint.reporter('default')); +gulp.task('jshint', function() { + return gulp.src(srcDir + '*.js') + .pipe(jshint()) + .pipe(jshint.reporter('default')); }); -gulp.task('valid', function(){ - return gulp.src('samples/*.html') - .pipe(htmlv()); +gulp.task('valid', function() { + return gulp.src('samples/*.html') + .pipe(htmlv()); }); -gulp.task('library-size', function(){ - return gulp.src('Chart.min.js') - .pipe(size({ - gzip: true - })); +gulp.task('library-size', function() { + return gulp.src('Chart.min.js') + .pipe(size({ + gzip: true + })); }); -gulp.task('module-sizes', function(){ - return gulp.src(srcDir + '*.js') - .pipe(uglify({preserveComments:'some'})) - .pipe(size({ - showFiles: true, - gzip: true - })); +gulp.task('module-sizes', function() { + return gulp.src(srcDir + '*.js') + .pipe(uglify({ + preserveComments: 'some' + })) + .pipe(size({ + showFiles: true, + gzip: true + })); }); -gulp.task('watch', function(){ - gulp.watch('./src/*', ['build']); +gulp.task('watch', function() { + gulp.watch('./src/*', ['build']); }); gulp.task('test', ['jshint', 'valid']); @@ -123,16 +138,16 @@ gulp.task('size', ['library-size', 'module-sizes']); gulp.task('default', ['build', 'watch']); -gulp.task('server', function(){ - connect.server({ - port: 8000 - }); +gulp.task('server', function() { + connect.server({ + port: 8000 + }); }); // Convenience task for opening the project straight from the command line -gulp.task('_open', function(){ - exec('open http://localhost:8000'); - exec('subl .'); +gulp.task('_open', function() { + exec('open http://localhost:8000'); + exec('subl .'); }); gulp.task('dev', ['server', 'default']); diff --git a/samples/line-multi-axis.html b/samples/line-multi-axis.html index bb4b58a77..227a04c6b 100644 --- a/samples/line-multi-axis.html +++ b/samples/line-multi-axis.html @@ -8,10 +8,8 @@ -
-
- -
+
+