From: Tanner Linsley Date: Thu, 4 Jun 2015 01:45:14 +0000 (-0600) Subject: Gulp Build X-Git-Tag: v2.0-alpha~1 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=d46a1740801a68664da0885c1a60ccb1dcc77853;p=thirdparty%2FChart.js.git Gulp Build --- diff --git a/Chart.js b/Chart.js index f16219b50..2d1fde13a 100644 --- a/Chart.js +++ b/Chart.js @@ -1,7 +1,7 @@ /*! * Chart.js * http://chartjs.org/ - * Version: 1.0.2 + * Version: 2.0.0-alpha * * Copyright 2015 Nick Downie * Released under the MIT license @@ -48,150 +48,96 @@ return this; }; + + var defaultColor = 'rgba(0,0,0,0.1)'; + //Globally expose the defaults to allow for user updating/changing Chart.defaults = { global: { - // Boolean - Whether to animate the chart - animation: true, - - // Number - Number of animation steps - animationDuration: 1000, - - // String - Animation easing effect - animationEasing: "easeOutQuart", - - // Boolean - If we should show the scale at all - showScale: true, - - // Boolean - If we want to override with a hard coded scale - scaleOverride: false, - - // ** Required if scaleOverride is true ** - // Number - The number of steps in a hard coded scale - scaleSteps: null, - // Number - The value jump in the hard coded scale - scaleStepWidth: null, - // Number - The scale starting value - scaleStartValue: null, - - // String - Colour of the scale line - scaleLineColor: "rgba(0,0,0,.1)", - - // Number - Pixel width of the scale line - scaleLineWidth: 1, - - // Boolean - Whether to show labels on the scale - scaleShowLabels: true, - - // Interpolated JS string - can access value - scaleLabel: "<%=value%>", - - // Boolean - Whether the scale should stick to integers, and not show any floats even if drawing space is there - scaleIntegersOnly: true, - - // Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value - scaleBeginAtZero: false, - - // String - Scale label font declaration for the scale label - scaleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", - - // Number - Scale label font size in pixels - scaleFontSize: 12, - - // String - Scale label font weight style - scaleFontStyle: "normal", - - // String - Scale label font colour - scaleFontColor: "#666", - - // Boolean - whether or not the chart should be responsive and resize when the browser does. - responsive: false, - - // Boolean - whether to maintain the starting aspect ratio or not when responsive, if set to false, will take up entire container + animation: { + duration: 1000, + easing: "easeOutQuart", + onProgress: function() {}, + onComplete: function() {}, + }, + responsive: true, maintainAspectRatio: true, - - //String / Boolean - Hover mode for events. - hoverMode: 'single', // 'label', 'dataset', 'false' - - //Function(event) - Custom hover handler - onHover: null, - - //Function(event, clickedElements) - Custom click handler - onClick: null, - - //Function - Custom hover handler - hoverAnimationDuration: 400, - - // Boolean - Determines whether to draw tooltips on the canvas or not - attaches events to touchmove & mousemove - showTooltips: true, - - // Boolean - Determines whether to draw built-in tooltip or call custom tooltip function - customTooltips: false, - - // Array - Array of string names to attach interaction events events: ["mousemove", "mouseout", "click", "touchstart", "touchmove", "touchend"], + hover: { + onHover: null, + mode: 'single', + 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', - // String - Tooltip background colour - tooltipBackgroundColor: "rgba(0,0,0,0.8)", - - // String - Tooltip label font declaration for the scale label - tooltipFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", - - // Number - Tooltip label font size in pixels - tooltipFontSize: 14, - - // String - Tooltip font weight style - tooltipFontStyle: "normal", - - // String - Tooltip label font colour - tooltipFontColor: "#fff", - - // String - Tooltip title font declaration for the scale label - tooltipTitleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", - - // Number - Tooltip title font size in pixels - tooltipTitleFontSize: 14, - - // String - Tooltip title font weight style - tooltipTitleFontStyle: "bold", - - // String - Tooltip title font colour - tooltipTitleFontColor: "#fff", - - // Number - pixel width of padding around tooltip text - tooltipYPadding: 6, - - // Number - pixel width of padding around tooltip text - tooltipXPadding: 6, - - // Number - Size of the caret on the tooltip - tooltipCaretSize: 8, - - // Number - Pixel radius of the tooltip border - tooltipCornerRadius: 6, - - // Number - Pixel offset from point x to tooltip edge - tooltipXOffset: 10, - - // String - Template string for single tooltips - tooltipTemplate: "<%if (label){%><%=label%>: <%}%><%= value %>", - - // String - Template string for single tooltips - multiTooltipTemplate: "<%if (datasetLabel){%><%=datasetLabel%>: <%}%><%= value %>", - - // String - Colour behind the legend colour block - multiTooltipKeyBackground: '#fff', - - // Function - Will fire on animation progression. - onAnimationProgress: function() {}, - - // Function - Will fire on animation completion. - onAnimationComplete: function() {}, - - // Color String - Used for undefined Colros - defaultColor: 'rgba(0,0,0,0.1)', - - } + }, + 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: 6, + hoverRadius: 4, + hoverBorderWidth: 2, + }, + bar: { + backgroundColor: defaultColor, + borderWidth: 0, + borderColor: defaultColor, + valueSpacing: 5, + datasetSpacing: 1, + }, + slice: { + backgroundColor: defaultColor, + borderColor: "#fff", + borderWidth: 2, + }, + } + }, }; //Create a dictionary of chart types, to allow for extension of existing types @@ -242,6 +188,47 @@ args.unshift({}); return extend.apply(null, args); }, + // Need a special merge function to chart configs since they are now grouped + configMerge = helpers.configMerge = function(base) { + helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) { + helpers.each(extension, function(value, key) { + if (extension.hasOwnProperty(key)) { + if (base.hasOwnProperty(key) && helpers.isArray(base[key]) && helpers.isArray(value)) { + // In this case we have an array of objects replacing another array. Rather than doing a strict replace, + // merge. This allows easy scale option merging + var baseArray = base[key]; + + helpers.each(value, function(valueObj, index) { + if (index < baseArray.length) { + baseArray[index] = helpers.configMerge(baseArray[index], valueObj); + } else { + baseArray.push(valueObj); // nothing to merge + } + }); + } else if (base.hasOwnProperty(key) && typeof base[key] == "object" && typeof value == "object") { + // If we are overwriting an object with an object, do a merge of the properties. + base[key] = helpers.configMerge(base[key], value); + } else { + // can just overwrite the value in this case + base[key] = value; + } + } + }); + }); + + return base; + }, + getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) { + if (!value) { + return defaultValue; + } + + if (helpers.isArray(value) && index < value.length) { + return value[index]; + } + + return value; + }, indexOf = helpers.indexOf = function(arrayToSearch, item) { if (Array.prototype.indexOf) { return arrayToSearch.indexOf(item); @@ -330,6 +317,17 @@ min = helpers.min = function(array) { return Math.min.apply(Math, array); }, + sign = helpers.sign = function(x) { + if (Math.sign) { + return Math.sign(x); + } else { + x = +x; // convert to a number + if (x === 0 || isNaN(x)) { + return x; + } + return x > 0 ? 1 : -1; + } + }, cap = helpers.cap = function(valueToCap, maxValue, minValue) { if (isNumber(maxValue)) { if (valueToCap > maxValue) { @@ -360,9 +358,12 @@ return 0; } }, - toRadians = helpers.radians = function(degrees) { + toRadians = helpers.toRadians = function(degrees) { return degrees * (Math.PI / 180); }, + toDegrees = helpers.toDegrees = function(radians) { + return radians * (180 / Math.PI); + }, // Gets the angle from vertical upright to the point about a centre. getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint) { var distanceFromXCenter = anglePoint.x - centrePoint.x, @@ -393,11 +394,11 @@ fa = t * d01 / (d01 + d12), // scaling factor for triangle Ta fb = t * d12 / (d01 + d12); return { - next: { + previous: { x: MiddlePoint.x - fa * (AfterPoint.x - FirstPoint.x), y: MiddlePoint.y - fa * (AfterPoint.y - FirstPoint.y) }, - previous: { + next: { x: MiddlePoint.x + fb * (AfterPoint.x - FirstPoint.x), y: MiddlePoint.y + fb * (AfterPoint.y - FirstPoint.y) } @@ -482,6 +483,36 @@ }; }, + // Implementation of the nice number algorithm used in determining where axis labels will go + niceNum = helpers.niceNum = function(range, round) { + var exponent = Math.floor(Math.log10(range)); + var fraction = range / Math.pow(10, exponent); + var niceFraction; + + if (round) { + if (fraction < 1.5) { + niceFraction = 1; + } else if (fraction < 3) { + niceFraction = 2; + } else if (fraction < 7) { + niceFraction = 5; + } else { + niceFraction = 10; + } + } else { + if (fraction <= 1.0) { + niceFraction = 1; + } else if (fraction <= 2) { + niceFraction = 2; + } else if (fraction <= 5) { + niceFraction = 5; + } else { + niceFraction = 10; + } + } + + return niceFraction * Math.pow(10, exponent); + }, /* jshint ignore:start */ // Blows up jshint errors based on the new Function constructor //Templating methods @@ -959,10 +990,10 @@ redraw: noop, render: function(duration) { - if (this.options.animation) { + if (this.options.animation.duration !== 0) { var animation = new Chart.Animation(); - animation.numSteps = (duration || this.options.animationDuration) / 16.66; //60 fps - animation.easing = this.options.animationEasing; + animation.numSteps = (duration || this.options.animation.duration) / 16.66; //60 fps + animation.easing = this.options.animation.easing; // render function animation.render = function(chartInstance, animationObject) { @@ -1086,13 +1117,13 @@ var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {}; - Chart.defaults[chartName] = extend(baseDefaults, extensions.defaults); + Chart.defaults[chartName] = helpers.configMerge(baseDefaults, extensions.defaults); Chart.types[chartName] = ChartType; //Register this new chart type in the Chart prototype Chart.prototype[chartName] = function(config) { - helpers.extend(config.options, merge(Chart.defaults.global, Chart.defaults[chartName], config.options || {})); + config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[chartName], config.options || {}); return new ChartType(config, this); }; } else { @@ -1102,67 +1133,63 @@ }; Chart.Element = function(configuration) { - extend(this, { - _vm: {}, - }); extend(this, configuration); this.initialize.apply(this, arguments); }; extend(Chart.Element.prototype, { initialize: function() {}, - save: function() { - this._vm = clone(this); - delete this._vm._vm; - delete this._vm._start; - return this; - }, pivot: function() { - if (this._start) { - this._start = clone(this); - helpers.extend(this._start, this._vm); + if (!this._view) { + this._view = clone(this._model); } + this._start = clone(this._view); return this; }, transition: function(ease) { + if (!this._view) { + this._view = clone(this._model); + } if (!this._start) { - if (!this._vm) { - this.save(); - } - this._start = clone(this._vm); + this.pivot(); } - each(this, function(value, key) { + each(this._model, function(value, key) { - if (key[0] === '_' || !this.hasOwnProperty(key)) { + if (key[0] === '_' || !this._model.hasOwnProperty(key)) { // Only non-underscored properties } // Init if doesn't exist - else if (!this._vm[key]) { - this._vm[key] = value || null; + else if (!this._view[key]) { + if (typeof value === 'number') { + this._view[key] = value * ease; + } else { + this._view[key] = value || null; + } } // No unnecessary computations - else if (this[key] === this._vm[key]) { + else if (this._model[key] === this._view[key]) { // It's the same! Woohoo! } // Color transitions if possible else if (typeof value === 'string') { try { - var color = helpers.color(this._start[key]).mix(helpers.color(this[key]), ease); - this._vm[key] = color.rgbString(); + var color = helpers.color(this._start[key]).mix(helpers.color(this._model[key]), ease); + this._view[key] = color.rgbString(); } catch (err) { - this._vm[key] = value; + this._view[key] = value; } } // Number transitions else if (typeof value === 'number') { - - this._vm[key] = ((this[key] - this._start[key]) * ease) + this._start[key]; - } else { - // Everything else - this._vm[key] = value; + var startVal = this._start[key] !== undefined ? this._start[key] : 0; + this._view[key] = ((this._model[key] - startVal) * ease) + startVal; + } + // Everything else + else { + this._view[key] = value; } }, this); @@ -1174,12 +1201,12 @@ }, tooltipPosition: function() { return { - x: this.x, - y: this.y + x: this._model.x, + y: this._model.y }; }, hasValue: function() { - return isNumber(this.value); + return isNumber(this._model.x) && isNumber(this._model.y); } }); @@ -1188,16 +1215,21 @@ Chart.Point = Chart.Element.extend({ inRange: function(mouseX, mouseY) { - var vm = this._vm; + var vm = this._view; var hoverRange = vm.hoverRadius + vm.radius; return ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(hoverRange, 2)); }, inGroupRange: function(mouseX) { - var vm = this._vm; - return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + this.hoverRadius, 2)); + var vm = this._view; + + if (vm) { + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); + } else { + return false; + } }, tooltipPosition: function() { - var vm = this._vm; + var vm = this._view; return { x: vm.x, y: vm.y, @@ -1206,9 +1238,14 @@ }, draw: function() { - var vm = this._vm; + var vm = this._view; var ctx = this._chart.ctx; + + if (vm.skip) { + return; + } + if (vm.radius > 0 || vm.borderWidth > 0) { ctx.beginPath(); @@ -1231,35 +1268,75 @@ Chart.Line = Chart.Element.extend({ draw: function() { - var vm = this._vm; + var vm = this._view; var ctx = this._chart.ctx; + var first = this._children[0]; + var last = this._children[this._children.length - 1]; // Draw the background first (so the border is always on top) - helpers.each(vm._points, function(point, index) { + helpers.each(this._children, function(point, index) { + var previous = this.previousPoint(point, this._children, index); + var next = this.nextPoint(point, this._children, index); + + // First point only if (index === 0) { - ctx.moveTo(point._vm.x, point._vm.y); - } else { - if (vm._tension > 0 || 1) { - var previous = this.previousPoint(point, vm._points, index); + ctx.moveTo(point._view.x, point._view.y); + return; + } + + // Start Skip and drag along scale baseline + if (point._view.skip && vm.skipNull && !this._loop) { + ctx.lineTo(previous._view.x, point._view.y); + ctx.moveTo(next._view.x, point._view.y); + } + // End Skip Stright line from the base line + else if (previous._view.skip && vm.skipNull && !this._loop) { + ctx.moveTo(point._view.x, previous._view.y); + ctx.lineTo(point._view.x, point._view.y); + } + if (previous._view.skip && vm.skipNull) { + ctx.moveTo(point._view.x, point._view.y); + } + // Normal Bezier Curve + else { + if (vm.tension > 0) { ctx.bezierCurveTo( - previous._vm.controlPointNextX, - previous._vm.controlPointNextY, - point._vm.controlPointPreviousX, - point._vm.controlPointPreviousY, - point._vm.x, - point._vm.y + previous._view.controlPointNextX, + previous._view.controlPointNextY, + point._view.controlPointPreviousX, + point._view.controlPointPreviousY, + point._view.x, + point._view.y ); } else { - ctx.lineTo(point._vm.x, point._vm.y); + ctx.lineTo(point._view.x, point._view.y); } } }, this); - if (vm._points.length > 0) { + // For radial scales, loop back around to the first point + if (this._loop) { + if (vm.tension > 0 && !first._view.skip) { + + ctx.bezierCurveTo( + last._view.controlPointNextX, + last._view.controlPointNextY, + first._view.controlPointPreviousX, + first._view.controlPointPreviousY, + first._view.x, + first._view.y + ); + } else { + ctx.lineTo(first._view.x, first._view.y); + } + } + + // If we had points and want to fill this line, do so. + if (this._children.length > 0 && vm.fill) { //Round off the line by going to the base of the chart, back to the start, then fill. - ctx.lineTo(vm._points[vm._points.length - 1].x, vm.scaleZero); - ctx.lineTo(vm._points[0].x, vm.scaleZero); + ctx.lineTo(this._children[this._children.length - 1]._view.x, vm.scaleZero); + ctx.lineTo(this._children[0]._view.x, vm.scaleZero); ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; ctx.closePath(); ctx.fill(); @@ -1271,42 +1348,95 @@ ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; ctx.beginPath(); - helpers.each(vm._points, function(point, index) { + helpers.each(this._children, function(point, index) { + var previous = this.previousPoint(point, this._children, index); + var next = this.nextPoint(point, this._children, index); + + // First point only if (index === 0) { - ctx.moveTo(point._vm.x, point._vm.y); - } else { - if (vm._tension > 0 || 1) { - var previous = this.previousPoint(point, vm._points, index); + ctx.moveTo(point._view.x, point._view.y); + return; + } - ctx.bezierCurveTo( - previous._vm.controlPointNextX, - previous._vm.controlPointNextY, - point._vm.controlPointPreviousX, - point._vm.controlPointPreviousY, - point._vm.x, - point._vm.y - ); - } else { - ctx.lineTo(point._vm.x, point._vm.y); - } + // Start Skip and drag along scale baseline + if (point._view.skip && vm.skipNull && !this._loop) { + ctx.moveTo(previous._view.x, point._view.y); + ctx.moveTo(next._view.x, point._view.y); + return; + } + // End Skip Stright line from the base line + if (previous._view.skip && vm.skipNull && !this._loop) { + ctx.moveTo(point._view.x, previous._view.y); + ctx.moveTo(point._view.x, point._view.y); + return; + } + + if (previous._view.skip && vm.skipNull) { + ctx.moveTo(point._view.x, point._view.y); + return; + } + // Normal Bezier Curve + if (vm.tension > 0) { + ctx.bezierCurveTo( + previous._view.controlPointNextX, + previous._view.controlPointNextY, + point._view.controlPointPreviousX, + point._view.controlPointPreviousY, + point._view.x, + point._view.y + ); + } else { + ctx.lineTo(point._view.x, point._view.y); } }, this); + if (this._loop && !first._view.skip) { + if (vm.tension > 0) { + + ctx.bezierCurveTo( + last._view.controlPointNextX, + last._view.controlPointNextY, + first._view.controlPointPreviousX, + first._view.controlPointPreviousY, + first._view.x, + first._view.y + ); + } else { + ctx.lineTo(first._view.x, first._view.y); + } + } + ctx.stroke(); }, + nextPoint: function(point, collection, index) { + if (this.loop) { + return collection[index + 1] || collection[0]; + } + return collection[index + 1] || collection[collection.length - 1]; + }, previousPoint: function(point, collection, index) { - return helpers.findPreviousWhere(collection, function() { - return true; - }, index) || point; + if (this.loop) { + return collection[index - 1] || collection[collection.length - 1]; + } + return collection[index - 1] || collection[0]; }, }); Chart.Arc = Chart.Element.extend({ + inGroupRange: function(mouseX) { + var vm = this._view; + + if (vm) { + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); + } else { + return false; + } + }, inRange: function(chartX, chartY) { - var vm = this._vm; + var vm = this._view; var pointRelativePosition = helpers.getAngleFromPoint(vm, { x: chartX, @@ -1321,7 +1451,7 @@ //Ensure within the outside of the arc centre, but inside arc outer }, tooltipPosition: function() { - var vm = this._vm; + var vm = this._view; var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2), rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; @@ -1333,7 +1463,7 @@ draw: function() { var ctx = this._chart.ctx; - var vm = this._vm; + var vm = this._view; ctx.beginPath(); @@ -1359,10 +1489,10 @@ Chart.Rectangle = Chart.Element.extend({ draw: function() { - var vm = this._vm; + var ctx = this._chart.ctx; + var vm = this._view; - var ctx = this.ctx, - halfWidth = vm.width / 2, + var halfWidth = vm.width / 2, leftX = vm.x - halfWidth, rightX = vm.x + halfWidth, top = vm.base - (vm.base - vm.y), @@ -1394,11 +1524,11 @@ } }, height: function() { - var vm = this._vm; + var vm = this._view; return vm.base - vm.y; }, inRange: function(mouseX, mouseY) { - var vm = this._vm; + var vm = this._view; if (vm.y < vm.base) { return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base); } else { @@ -1406,11 +1536,11 @@ } }, inGroupRange: function(mouseX) { - var vm = this._vm; + var vm = this._view; return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2); }, tooltipPosition: function() { - var vm = this._vm; + var vm = this._view; if (vm.y < vm.base) { return { x: vm.x, @@ -1439,41 +1569,55 @@ initialize: function() { var options = this._options; extend(this, { - opacity: 0, - xPadding: options.tooltipXPadding, - yPadding: options.tooltipYPadding, - xOffset: options.tooltipXOffset, - backgroundColor: options.tooltipBackgroundColor, - textColor: options.tooltipFontColor, - _fontFamily: options.tooltipFontFamily, - _fontStyle: options.tooltipFontStyle, - fontSize: options.tooltipFontSize, - titleTextColor: options.tooltipTitleFontColor, - _titleFontFamily: options.tooltipTitleFontFamily, - _titleFontStyle: options.tooltipTitleFontStyle, - titleFontSize: options.tooltipTitleFontSize, - caretHeight: options.tooltipCaretSize, - cornerRadius: options.tooltipCornerRadius, - legendColorBackground: options.multiTooltipKeyBackground, - labels: [], - colors: [], + _model: { + // Positioning + xPadding: options.tooltips.xPadding, + yPadding: options.tooltips.yPadding, + xOffset: options.tooltips.xOffset, + + // Labels + textColor: options.tooltips.fontColor, + _fontFamily: options.tooltips.fontFamily, + _fontStyle: options.tooltips.fontStyle, + fontSize: options.tooltips.fontSize, + + // Title + titleTextColor: options.tooltips.titleFontColor, + _titleFontFamily: options.tooltips.titleFontFamily, + _titleFontStyle: options.tooltips.titleFontStyle, + titleFontSize: options.tooltips.titleFontSize, + + // Appearance + caretHeight: options.tooltips.caretSize, + cornerRadius: options.tooltips.cornerRadius, + backgroundColor: options.tooltips.backgroundColor, + opacity: 0, + legendColorBackground: options.tooltips.multiKeyBackground, + }, }); }, update: function() { var ctx = this._chart.ctx; - switch (this._options.hoverMode) { + switch (this._options.hover.mode) { case 'single': - helpers.extend(this, { - text: template(this._options.tooltipTemplate, this._active[0]), + 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] : '', + }), }); + var tooltipPosition = this._active[0].tooltipPosition(); - helpers.extend(this, { + helpers.extend(this._model, { x: Math.round(tooltipPosition.x), y: Math.round(tooltipPosition.y), caretPadding: tooltipPosition.padding }); + break; case 'label': @@ -1509,17 +1653,22 @@ if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()) { elements.push(dataCollection[dataIndex]); } - }); + }, this); helpers.each(elements, function(element) { - xPositions.push(element._vm.x); - yPositions.push(element._vm.y); + xPositions.push(element._view.x); + yPositions.push(element._view.y); //Include any colour information about the element - labels.push(helpers.template(this._options.multiTooltipTemplate, 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._vm.backgroundColor, - stroke: element._vm.borderColor + fill: element._view.backgroundColor, + stroke: element._view.borderColor }); }, this); @@ -1537,42 +1686,42 @@ }).call(this, dataIndex); // Apply for now - helpers.extend(this, { + helpers.extend(this._model, { x: medianPosition.x, y: medianPosition.y, labels: labels, - title: this._active.length ? this._active[0].label : '', + title: this._data.labels && this._data.labels.length ? this._data.labels[this._active[0]._index] : '', legendColors: colors, - legendBackgroundColor: this._options.multiTooltipKeyBackground, + legendBackgroundColor: this._options.tooltips.multiKeyBackground, }); // Calculate Appearance Tweaks - this.height = (labels.length * this.fontSize) + ((labels.length - 1) * (this.fontSize / 2)) + (this.yPadding * 2) + this.titleFontSize * 1.5; + this._model.height = (labels.length * this._model.fontSize) + ((labels.length - 1) * (this._model.fontSize / 2)) + (this._model.yPadding * 2) + this._model.titleFontSize * 1.5; var titleWidth = ctx.measureText(this.title).width, //Label has a legend square as well so account for this. - labelWidth = longestText(ctx, this.font, labels) + this.fontSize + 3, + labelWidth = longestText(ctx, this.font, labels) + this._model.fontSize + 3, longestTextWidth = max([labelWidth, titleWidth]); - this.width = longestTextWidth + (this.xPadding * 2); + this._model.width = longestTextWidth + (this._model.xPadding * 2); - var halfHeight = this.height / 2; + var halfHeight = this._model.height / 2; //Check to ensure the height will fit on the canvas - if (this.y - halfHeight < 0) { - this.y = halfHeight; - } else if (this.y + halfHeight > this._chart.height) { - this.y = this._chart.height - halfHeight; + if (this._model.y - halfHeight < 0) { + this._model.y = halfHeight; + } else if (this._model.y + halfHeight > this._chart.height) { + this._model.y = this._chart.height - halfHeight; } //Decide whether to align left or right based on position on canvas - if (this.x > this._chart.width / 2) { - this.x -= this.xOffset + this.width; + if (this._model.x > this._chart.width / 2) { + this._model.x -= this._model.xOffset + this._model.width; } else { - this.x += this.xOffset; + this._model.x += this._model.xOffset; } break; } @@ -1582,9 +1731,9 @@ draw: function() { var ctx = this._chart.ctx; - var vm = this._vm; + var vm = this._view; - switch (this._options.hoverMode) { + switch (this._options.hover.mode) { case 'single': ctx.font = fontString(vm.fontSize, vm._fontStyle, vm._fontFamily); @@ -1616,7 +1765,7 @@ // Custom Tooltips if (this._custom) { - this._custom(this._vm); + this._custom(this._view); } else { switch (vm.yAlign) { case "above": @@ -1682,8 +1831,8 @@ //ctx.clearRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize/2, vm.fontSize, vm.fontSize); //Instead we'll make a white filled block to put the legendColour palette over. - ctx.fillStyle = helpers.color(vm.legendBackgroundColor).alpha(vm.opacity).rgbString(); - ctx.fillRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize / 2, vm.fontSize, vm.fontSize); + ctx.fillStyle = helpers.color(vm.legendColors[index].stroke).alpha(vm.opacity).rgbString(); + ctx.fillRect(vm.x + vm.xPadding - 1, this.getLineHeight(index + 1) - vm.fontSize / 2 - 1, vm.fontSize + 2, vm.fontSize + 2); ctx.fillStyle = helpers.color(vm.legendColors[index].fill).alpha(vm.opacity).rgbString(); ctx.fillRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize / 2, vm.fontSize, vm.fontSize); @@ -1694,680 +1843,2095 @@ } }, getLineHeight: function(index) { - var baseLineHeight = this._vm.y - (this._vm.height / 2) + this._vm.yPadding, + var baseLineHeight = this._view.y - (this._view.height / 2) + this._view.yPadding, afterTitleIndex = index - 1; //If the index is zero, we're getting the title if (index === 0) { - return baseLineHeight + this._vm.titleFontSize / 2; + return baseLineHeight + this._view.titleFontSize / 2; } else { - return baseLineHeight + ((this._vm.fontSize * 1.5 * afterTitleIndex) + this._vm.fontSize / 2) + this._vm.titleFontSize * 1.5; + return baseLineHeight + ((this._view.fontSize * 1.5 * afterTitleIndex) + this._view.fontSize / 2) + this._view.titleFontSize * 1.5; } }, }); - Chart.Scale = Chart.Element.extend({ - initialize: function() { - this.fit(); - }, - buildYLabels: function() { - this.yLabels = []; + Chart.animationService = { + frameDuration: 17, + animations: [], + dropFrames: 0, + addAnimation: function(chartInstance, animationObject, duration) { - var stepDecimalPlaces = getDecimalPlaces(this.stepValue); + if (!duration) { + chartInstance.animating = true; + } - for (var i = 0; i <= this.steps; i++) { - this.yLabels.push(template(this.templateString, { - value: (this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces) - })); + 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.yLabelWidth = (this.display && this.showLabels) ? longestText(this.ctx, this.font, this.yLabels) + 10 : 0; - }, - addXLabel: function(label) { - this.xLabels.push(label); - this.valuesCount++; - this.fit(); - }, - removeXLabel: function() { - this.xLabels.shift(); - this.valuesCount--; - this.fit(); - }, - // Fitting loop to rotate x Labels and figure out what fits there, and also calculate how many Y steps to use - fit: function() { - // First we need the width of the yLabels, assuming the xLabels aren't rotated - // To do that we need the base line at the top and base of the chart, assuming there is no x label rotation - this.startPoint = (this.display) ? this.fontSize : 0; - this.endPoint = (this.display) ? this.height - (this.fontSize * 1.5) - 5 : this.height; // -5 to pad labels + this.animations.push({ + chartInstance: chartInstance, + animationObject: animationObject + }); - // Apply padding settings to the start and end point. - this.startPoint += this.padding; - this.endPoint -= this.padding; + // 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; + }); - // Cache the starting endpoint, excluding the space for x labels - var cachedEndPoint = this.endPoint; + 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() { - // Cache the starting height, so can determine if we need to recalculate the scale yAxis - var cachedHeight = this.endPoint - this.startPoint, - cachedYLabelWidth; + var startTime = Date.now(); + var framesToDrop = 0; - // Build the current yLabels so we have an idea of what size they'll be to start - /* - * This sets what is returned from calculateScaleRange as static properties of this class: - * - this.steps; - this.stepValue; - this.min; - this.max; - * - */ - this.calculateYRange(cachedHeight); + if (this.dropFrames > 1) { + framesToDrop = Math.floor(this.dropFrames); + this.dropFrames -= framesToDrop; + } - // With these properties set we can now build the array of yLabels - // and also the width of the largest yLabel - this.buildYLabels(); + for (var i = 0; i < this.animations.length; i++) { - this.calculateXLabelRotation(); + if (this.animations[i].animationObject.currentStep === null) { + this.animations[i].animationObject.currentStep = 0; + } - while ((cachedHeight > this.endPoint - this.startPoint)) { - cachedHeight = this.endPoint - this.startPoint; - cachedYLabelWidth = this.yLabelWidth; + this.animations[i].animationObject.currentStep += 1 + framesToDrop; + if (this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps) { + this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps; + } - this.calculateYRange(cachedHeight); - this.buildYLabels(); + this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject); - // Only go through the xLabel loop again if the yLabel width has changed - if (cachedYLabelWidth < this.yLabelWidth) { - this.endPoint = cachedEndPoint; - this.calculateXLabelRotation(); + 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--; } } - }, - calculateXLabelRotation: function() { - //Get the width of each grid by calculating the difference - //between x offsets between 0 and 1. - - this.ctx.font = this.font; - - var firstWidth = this.ctx.measureText(this.xLabels[0]).width, - lastWidth = this.ctx.measureText(this.xLabels[this.xLabels.length - 1]).width, - firstRotated, - lastRotated; - - - this.xScalePaddingRight = lastWidth / 2 + 3; - this.xScalePaddingLeft = (firstWidth / 2 > this.yLabelWidth) ? firstWidth / 2 : this.yLabelWidth; - - this.xLabelRotation = 0; - if (this.display) { - var originalLabelWidth = longestText(this.ctx, this.font, this.xLabels), - cosRotation, - firstRotatedWidth; - this.xLabelWidth = originalLabelWidth; - //Allow 3 pixels x2 padding either side for label readability - var xGridWidth = Math.floor(this.calculateX(1) - this.calculateX(0)) - 6; + var endTime = Date.now(); + var delay = endTime - startTime - this.frameDuration; + var frameDelay = delay / this.frameDuration; - //Max label rotate should be 90 - also act as a loop counter - while ((this.xLabelWidth > xGridWidth && this.xLabelRotation === 0) || (this.xLabelWidth > xGridWidth && this.xLabelRotation <= 90 && this.xLabelRotation > 0)) { - cosRotation = Math.cos(toRadians(this.xLabelRotation)); + if (frameDelay > 1) { + this.dropFrames += frameDelay; + } - firstRotated = cosRotation * firstWidth; - lastRotated = cosRotation * lastWidth; + // Do we have more stuff to animate? + if (this.animations.length > 0) { + helpers.requestAnimFrame.call(window, this.digestWrapper); + } + } + }; - // We're right aligning the text now. - if (firstRotated + this.fontSize / 2 > this.yLabelWidth) { - this.xScalePaddingLeft = firstRotated + this.fontSize / 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(); } - this.xScalePaddingRight = this.fontSize / 2; + }); + }, 50); + }; + })()); - this.xLabelRotation++; - this.xLabelWidth = cosRotation * originalLabelWidth; + if (amd) { + define(function() { + return Chart; + }); + } else if (typeof module === 'object' && module.exports) { + module.exports = Chart; + } - } - if (this.xLabelRotation > 0) { - this.endPoint -= Math.sin(toRadians(this.xLabelRotation)) * originalLabelWidth + 3; - } - } else { - this.xLabelWidth = 0; - this.xScalePaddingRight = this.padding; - this.xScalePaddingLeft = this.padding; - } + root.Chart = Chart; - }, - // Needs to be overidden in each Chart type - // Otherwise we need to pass all the data into the scale class - calculateYRange: noop, - drawingArea: function() { - return this.startPoint - this.endPoint; - }, - calculateY: function(value) { - var scalingFactor = this.drawingArea() / (this.min - this.max); - return this.endPoint - (scalingFactor * (value - this.min)); - }, - calculateX: function(index) { - var isRotated = (this.xLabelRotation > 0), - // innerWidth = (this.offsetGridLines) ? this.width - offsetLeft - this.padding : this.width - (offsetLeft + halfLabelWidth * 2) - this.padding, - innerWidth = this.width - (this.xScalePaddingLeft + this.xScalePaddingRight), - valueWidth = innerWidth / Math.max((this.valuesCount - ((this.offsetGridLines) ? 0 : 1)), 1), - valueOffset = (valueWidth * index) + this.xScalePaddingLeft; + Chart.noConflict = function() { + root.Chart = previous; + return Chart; + }; - if (this.offsetGridLines) { - valueOffset += (valueWidth / 2); - } +}).call(this); - return Math.round(valueOffset); - }, - update: function(newProps) { - helpers.extend(this, newProps); - this.fit(); - }, - draw: function() { - var ctx = this.ctx, - yLabelGap = (this.endPoint - this.startPoint) / this.steps, - xStart = Math.round(this.xScalePaddingLeft); - if (this.display) { - ctx.fillStyle = this.textColor; - ctx.font = this.font; - each(this.yLabels, function(labelString, index) { - var yLabelCenter = this.endPoint - (yLabelGap * index), - linePositionY = Math.round(yLabelCenter), - drawHorizontalLine = this.showHorizontalLines; - - ctx.textAlign = "right"; - ctx.textBaseline = "middle"; - if (this.showLabels) { - ctx.fillText(labelString, xStart - 10, yLabelCenter); - } +(function() { + "use strict"; - // This is X axis, so draw it - if (index === 0 && !drawHorizontalLine) { - drawHorizontalLine = true; - } + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; - if (drawHorizontalLine) { - ctx.beginPath(); - } + var defaultConfig = { - if (index > 0) { - // This is a grid line in the centre, so drop that - ctx.lineWidth = this.gridLineWidth; - ctx.strokeStyle = this.gridLineColor; - } else { - // This is the first line on the scale - ctx.lineWidth = this.lineWidth; - ctx.strokeStyle = this.lineColor; - } + stacked: false, - linePositionY += helpers.aliasPixel(ctx.lineWidth); + hover: { + mode: "label" + }, - if (drawHorizontalLine) { - ctx.moveTo(xStart, linePositionY); - ctx.lineTo(this.width, linePositionY); - ctx.stroke(); - ctx.closePath(); - } + 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 + + // 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, + }, - ctx.lineWidth = this.lineWidth; - ctx.strokeStyle = this.lineColor; - ctx.beginPath(); - ctx.moveTo(xStart - 5, linePositionY); - ctx.lineTo(xStart, linePositionY); - ctx.stroke(); - ctx.closePath(); + // 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", + + // 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); + // scale numbers + beginAtZero: false, + override: null, + + // label settings + labels: { + show: true, + template: "<%=value%>", + fontSize: 12, + fontStyle: "normal", + fontColor: "#666", + fontFamily: "Helvetica Neue", + } + }], + }, - each(this.xLabels, function(label, index) { - var xPos = this.calculateX(index) + aliasPixel(this.lineWidth), - // Check to see if line/bar here and decide where to place the line - linePos = this.calculateX(index - (this.offsetGridLines ? 0.5 : 0)) + aliasPixel(this.lineWidth), - isRotated = (this.xLabelRotation > 0), - drawVerticalLine = this.showVerticalLines; + }; - // This is Y axis, so draw it - if (index === 0 && !drawVerticalLine) { - drawVerticalLine = true; - } - if (drawVerticalLine) { - ctx.beginPath(); - } + Chart.Type.extend({ + name: "Bar", + defaults: defaultConfig, + initialize: function() { - if (index > 0) { - // This is a grid line in the centre, so drop that - ctx.lineWidth = this.gridLineWidth; - ctx.strokeStyle = this.gridLineColor; - } else { - // This is the first line on the scale - ctx.lineWidth = this.lineWidth; - ctx.strokeStyle = this.lineColor; - } + var _this = this; - if (drawVerticalLine) { - ctx.moveTo(linePos, this.endPoint); - ctx.lineTo(linePos, this.startPoint - 3); - ctx.stroke(); - ctx.closePath(); - } + // Events + helpers.bindEvents(this, this.options.events, this.events); + //Create a new bar for each piece of data + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + dataset.metaData = []; + helpers.each(dataset.data, function(dataPoint, index) { + dataset.metaData.push(new Chart.Rectangle({ + _chart: this.chart, + _datasetIndex: datasetIndex, + _index: index, + })); + }, this); - ctx.lineWidth = this.lineWidth; - ctx.strokeStyle = this.lineColor; + // 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); - // Small lines at the bottom of the base grid line - ctx.beginPath(); - ctx.moveTo(linePos, this.endPoint); - ctx.lineTo(linePos, this.endPoint + 5); - ctx.stroke(); - ctx.closePath(); + // Build and fit the scale. Needs to happen after the axis IDs have been set + this.buildScale(); - ctx.save(); - ctx.translate(xPos, (isRotated) ? this.endPoint + 12 : this.endPoint + 8); - ctx.rotate(toRadians(this.xLabelRotation) * -1); - ctx.font = this.font; - ctx.textAlign = (isRotated) ? "right" : "center"; - ctx.textBaseline = (isRotated) ? "middle" : "top"; - ctx.fillText(label, 0, 0); - ctx.restore(); - }, this); + // Create tooltip instance exclusively for this chart with some defaults. + this.tooltip = new Chart.Tooltip({ + _chart: this.chart, + _data: this.data, + _options: this.options, + }, this); - } - } + // Need to fit scales before we reset elements. + Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - }); + // So that we animate from the baseline + this.resetElements(); - Chart.RadialScale = Chart.Element.extend({ - initialize: function() { - this.size = min([this.height, this.width]); - this.drawingArea = (this.display) ? (this.size / 2) - (this.fontSize / 2 + this.backdropPaddingY) : (this.size / 2); + // Update the chart with the latest data. + this.update(); }, - calculateCenterOffset: function(value) { - // Take into account half font size + the yPadding of the top value - var scalingFactor = this.drawingArea / (this.max - this.min); + 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]; - return (value - this.min) * scalingFactor; - }, - update: function() { - if (!this.lineArc) { - this.setScaleSize(); - } else { - this.drawingArea = (this.display) ? (this.size / 2) - (this.fontSize / 2 + this.backdropPaddingY) : (this.size / 2); - } - this.buildYLabels(); - }, - buildYLabels: function() { - this.yLabels = []; + var yScalePoint; - var stepDecimalPlaces = getDecimalPlaces(this.stepValue); + 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); + } - for (var i = 0; i <= this.steps; i++) { - this.yLabels.push(template(this.templateString, { - value: (this.min + (i * this.stepValue)).toFixed(stepDecimalPlaces) - })); - } - }, - getCircumference: function() { - return ((Math.PI * 2) / this.valuesCount); + helpers.extend(bar, { + // Utility + _chart: this.chart, + _xScale: xScale, + _yScale: yScale, + _datasetIndex: datasetIndex, + _index: index, + + // Desired view properties + _model: { + x: xScale.calculateBarX(this.data.datasets.length, datasetIndex, index), + y: yScalePoint, + + // Appearance + base: yScale.calculateBarBase(datasetIndex, index), + width: xScale.calculateBarWidth(this.data.datasets.length), + backgroundColor: bar.custom && bar.custom.backgroundColor ? bar.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].backgroundColor, index, this.options.elements.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); }, - 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 - */ + update: function() { + // 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]; - // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. - // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points - var largestPossibleRadius = min([(this.height / 2 - this.pointLabelFontSize - 5), this.width / 2]), - pointPosition, - i, - textWidth, - halfTextWidth, - furthestRight = this.width, - furthestRightIndex, - furthestRightAngle, - furthestLeft = 0, - furthestLeftIndex, - furthestLeftAngle, - xProtrusionLeft, - xProtrusionRight, - radiusReductionRight, - radiusReductionLeft, - maxWidthRadius; - this.ctx.font = fontString(this.pointLabelFontSize, this.pointLabelFontStyle, this.pointLabelFontFamily); - for (i = 0; i < this.valuesCount; i++) { - // 5px to space the text slightly out - similar to what we do in the draw function. - pointPosition = this.getPointPosition(i, largestPossibleRadius); - textWidth = this.ctx.measureText(template(this.templateString, { - value: this.labels[i] - })).width + 5; - if (i === 0 || i === this.valuesCount / 2) { - // If we're at index zero, or exactly the middle, we're at exactly the top/bottom - // of the radar chart, so text will be aligned centrally, so we'll half it and compare - // w/left and right text sizes - halfTextWidth = textWidth / 2; - if (pointPosition.x + halfTextWidth > furthestRight) { - furthestRight = pointPosition.x + halfTextWidth; - furthestRightIndex = i; - } - if (pointPosition.x - halfTextWidth < furthestLeft) { - furthestLeft = pointPosition.x - halfTextWidth; - furthestLeftIndex = i; - } - } else if (i < this.valuesCount / 2) { - // Less than half the values means we'll left align the text - if (pointPosition.x + textWidth > furthestRight) { - furthestRight = pointPosition.x + textWidth; - furthestRightIndex = i; - } - } else if (i > this.valuesCount / 2) { - // More than half the values means we'll right align the text - if (pointPosition.x - textWidth < furthestLeft) { - furthestLeft = pointPosition.x - textWidth; - furthestLeftIndex = i; - } - } - } + helpers.extend(bar, { + // Utility + _chart: this.chart, + _xScale: xScale, + _yScale: yScale, + _datasetIndex: datasetIndex, + _index: index, - xProtrusionLeft = furthestLeft; + // 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); - xProtrusionRight = Math.ceil(furthestRight - this.width); - furthestRightAngle = this.getIndexAngle(furthestRightIndex); + this.render(); + }, + buildScale: function(labels) { + var self = this; - furthestLeftAngle = this.getIndexAngle(furthestLeftIndex); + // Function to determine the range of all the + var calculateYRange = function() { + this.min = null; + this.max = null; - radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2); + var positiveValues = []; + var negativeValues = []; - radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2); + if (self.options.stacked) { + helpers.each(self.data.datasets, function(dataset) { + if (dataset.yAxisID === this.id) { + helpers.each(dataset.data, function(value, index) { + positiveValues[index] = positiveValues[index] || 0; + negativeValues[index] = negativeValues[index] || 0; + + if (self.options.relativePoints) { + positiveValues[index] = 100; + } else { + if (value < 0) { + negativeValues[index] += value; + } else { + positiveValues[index] += value; + } + } + }, this); + } + }, this); - // Ensure we actually need to reduce the size of the chart - radiusReductionRight = (isNumber(radiusReductionRight)) ? radiusReductionRight : 0; - radiusReductionLeft = (isNumber(radiusReductionLeft)) ? radiusReductionLeft : 0; + var values = positiveValues.concat(negativeValues); + this.min = helpers.min(values); + this.max = helpers.max(values); - this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2; + } 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; + } - //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2]) - this.setCenterPoint(radiusReductionLeft, radiusReductionRight); + if (this.max === null) { + this.max = value; + } else if (value > this.max) { + this.max = value; + } + }, this); + } + }, this); + } + }; - }, - setCenterPoint: function(leftMovement, rightMovement) { + // Map of scale ID to scale object so we can lookup later + this.scales = {}; - var maxRight = this.width - rightMovement - this.drawingArea, - maxLeft = leftMovement + this.drawingArea; + // 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); - this.xCenter = (maxLeft + maxRight) / 2; - // Always vertically in the centre as the text height doesn't change - this.yCenter = (this.height / 2); - }, + 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); - getIndexAngle: function(index) { - var angleMultiplier = (Math.PI * 2) / this.valuesCount; - // Start from the top instead of right, so remove a quarter of the circle + if (self.options.stacked) { + return xAbsolute + barWidth / 2; + } - return index * angleMultiplier - (Math.PI / 2); - }, - getPointPosition: function(index, distanceFromCenter) { - var thisAngle = this.getIndexAngle(index); - return { - x: (Math.cos(thisAngle) * distanceFromCenter) + this.xCenter, - y: (Math.sin(thisAngle) * distanceFromCenter) + this.yCenter - }; - }, - draw: function() { - if (this.display) { - var ctx = this.ctx; - each(this.yLabels, function(label, index) { - // Don't draw a centre value - if (index > 0) { - var yCenterOffset = index * (this.drawingArea / this.steps), - yHeight = this.yCenter - yCenterOffset, - pointPosition; + return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * self.options.elements.bar.datasetSpacing) + barWidth / 2; + }, + }); + this.scales[xScale.id] = xScale; - // Draw circular lines around the scale - if (this.lineWidth > 0) { - ctx.strokeStyle = this.lineColor; - ctx.lineWidth = this.lineWidth; + // 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 (this.lineArc) { - ctx.beginPath(); - ctx.arc(this.xCenter, this.yCenter, yCenterOffset, 0, Math.PI * 2); - ctx.closePath(); - ctx.stroke(); + 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 { - ctx.beginPath(); - for (var i = 0; i < this.valuesCount; i++) { - pointPosition = this.getPointPosition(i, this.calculateCenterOffset(this.min + (index * this.stepValue))); - if (i === 0) { - ctx.moveTo(pointPosition.x, pointPosition.y); - } else { - ctx.lineTo(pointPosition.x, pointPosition.y); + 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; } } - ctx.closePath(); - ctx.stroke(); - } - } - if (this.showLabels) { - ctx.font = fontString(this.fontSize, this._fontStyle, this._fontFamily); - if (this.showLabelBackdrop) { - var labelWidth = ctx.measureText(label).width; - ctx.fillStyle = this.backdropColor; - ctx.fillRect( - this.xCenter - labelWidth / 2 - this.backdropPaddingX, - yHeight - this.fontSize / 2 - this.backdropPaddingY, - labelWidth + this.backdropPaddingX * 2, - this.fontSize + this.backdropPaddingY * 2 - ); } - ctx.textAlign = 'center'; - ctx.textBaseline = "middle"; - ctx.fillStyle = this.fontColor; - ctx.fillText(label, this.xCenter, yHeight); + + return this.getPixelForValue(base); } - } - }, this); - if (!this.lineArc) { - ctx.lineWidth = this.angleLineWidth; - ctx.strokeStyle = this.angleLineColor; - for (var i = this.valuesCount - 1; i >= 0; i--) { - if (this.angleLineWidth > 0) { - var outerPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max)); - ctx.beginPath(); - ctx.moveTo(this.xCenter, this.yCenter); - ctx.lineTo(outerPosition.x, outerPosition.y); - ctx.stroke(); - ctx.closePath(); + 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); } - // Extra 3px out for some label spacing - var pointLabelPosition = this.getPointPosition(i, this.calculateCenterOffset(this.max) + 5); - ctx.font = fontString(this.pointLabelFontSize, this.pointLabelFontStyle, this.pointLabelFontFamily); - ctx.fillStyle = this.pointLabelFontColor; - var labelsCount = this.labels.length, - halfLabelsCount = this.labels.length / 2, - quarterLabelsCount = halfLabelsCount / 2, - upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount), - exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount); - if (i === 0) { - ctx.textAlign = 'center'; - } else if (i === halfLabelsCount) { - ctx.textAlign = 'center'; - } else if (i < halfLabelsCount) { - ctx.textAlign = 'left'; - } else { - ctx.textAlign = 'right'; + 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); } - // Set the correct text baseline based on outer positioning - if (exactQuarter) { - ctx.textBaseline = 'middle'; - } else if (upperHalf) { - ctx.textBaseline = 'bottom'; - } else { - ctx.textBaseline = 'top'; + var offset = 0; + + for (var j = datasetIndex; j < self.data.datasets.length; j++) { + if (j === datasetIndex && value) { + offset += value; + } else { + offset = offset + value; + } } - ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y); - } - } + 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; } - } - }); - Chart.animationService = { - frameDuration: 17, - animations: [], - dropFrames: 0, - addAnimation: function(chartInstance, animationObject, duration) { + this.lastActive = this.lastActive || []; - if (!duration) { - chartInstance.animating = true; + // Find Active Elements + this.active = function() { + switch (this.options.hover.mode) { + case 'single': + return this.getElementAtEvent(e); + case 'label': + return this.getElementsAtEvent(e); + case 'dataset': + return this.getDatasetAtEvent(e); + default: + return e; + } + }.call(this); + + // On Hover hook + if (this.options.hover.onHover) { + this.options.hover.onHover.call(this, this.active); } - 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; + if (e.type == 'mouseup' || e.type == 'click') { + if (this.options.onClick) { + this.options.onClick.call(this, e, this.active); } } - this.animations.push({ - chartInstance: chartInstance, - animationObject: animationObject - }); + 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 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.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; - if (index) { - this.animations.splice(index, 1); - chartInstance.animating = false; + 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 + } } - }, - // calls startDigest with the proper context - digestWrapper: function() { - Chart.animationService.startDigest.call(Chart.animationService); - }, - startDigest: function() { - var startTime = Date.now(); - var framesToDrop = 0; + // 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.dropFrames > 1) { - framesToDrop = Math.floor(this.dropFrames); - this.dropFrames -= framesToDrop; + 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.35).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.35).rgbString()); + this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.active[0]._model.borderWidth); + break; + case 'label': + for (var i = 0; i < this.active.length; i++) { + dataset = this.data.datasets[this.active[i]._datasetIndex]; + index = this.active[i]._index; + + this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.35).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.35).rgbString()); + this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.active[i]._model.borderWidth); + } + break; + case 'dataset': + break; + default: + // Don't change anything + } } - for (var i = 0; i < this.animations.length; i++) { - if (this.animations[i].animationObject.currentStep === null) { - this.animations[i].animationObject.currentStep = 0; - } + // Built in Tooltips + if (this.options.tooltips.enabled) { - 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; - } + // The usual updates + this.tooltip.initialize(); - this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject); + // Active + if (this.active.length) { + this.tooltip._model.opacity = 1; - 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.extend(this.tooltip, { + _active: this.active, + }); + + this.tooltip.update(); + } else { + // Inactive + this.tooltip._model.opacity = 0; } } - var endTime = Date.now(); - var delay = endTime - startTime - this.frameDuration; - var frameDelay = delay / this.frameDuration; - if (frameDelay > 1) { - this.dropFrames += frameDelay; - } + this.tooltip.pivot(); + + // Hover animations + if (!this.animating) { + var changed; + + helpers.each(this.active, function(element, index) { + if (element !== this.lastActive[index]) { + changed = true; + } + }, this); + + // If entering, leaving, or changing elements, animate the change via pivot + if ((!this.lastActive.length && this.active.length) || + (this.lastActive.length && !this.active.length) || + (this.lastActive.length && this.active.length && changed)) { + + this.stop(); + this.render(this.options.hoverAnimationDuration); + } + } + + // Remember Last Active + this.lastActive = this.active; + return this; + }, + }); + + +}).call(this); + +(function() { + "use strict"; + + 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, + }, + + //The percentage of the chart that we cut out of the middle. + + cutoutPercentage: 50, + + }; + + Chart.Type.extend({ + //Passing in a name registers this chart in the Chart namespace + name: "Doughnut", + //Providing a defaults will also register the deafults in the chart namespace + defaults: defaultConfig, + //Initialize is fired when the chart is initialized - Data is passed in as a parameter + //Config is automatically merged by the core of Chart.js, and is available at this.options + initialize: function() { + + //Set up tooltip events on the chart + helpers.bindEvents(this, this.options.events, this.events); + + //Create a new bar for each piece of data + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + dataset.metaData = []; + helpers.each(dataset.data, function(dataPoint, index) { + dataset.metaData.push(new Chart.Arc({ + _chart: this.chart, + _datasetIndex: datasetIndex, + _index: index, + _model: {} + })); + }, this); + }, this); + + // Create tooltip instance exclusively for this chart with some defaults. + this.tooltip = new Chart.Tooltip({ + _chart: this.chart, + _data: this.data, + _options: this.options, + }, this); + + this.resetElements(); + + // Update the chart with the latest data. + this.update(); + + }, + + calculateCircumference: function(dataset, value) { + if (dataset.total > 0) { + return (Math.PI * 2) * (value / dataset.total); + } else { + return 0; + } + }, + resetElements: function() { + this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.elements.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); + }, + update: 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) { + + dataset.total = 0; + helpers.each(dataset.data, function(value) { + dataset.total += Math.abs(value); + }, this); + + + dataset.outerRadius = this.outerRadius - (this.radiusLength * datasetIndex); + + dataset.innerRadius = dataset.outerRadius - this.radiusLength; + + helpers.each(dataset.metaData, function(slice, index) { + + helpers.extend(slice, { + // Utility + _chart: this.chart, + _datasetIndex: datasetIndex, + _index: index, + + // Desired view properties + _model: { + x: this.chart.width / 2, + y: this.chart.height / 2, + circumference: this.calculateCircumference(dataset, dataset.data[index]), + outerRadius: dataset.outerRadius, + innerRadius: dataset.innerRadius, + + backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.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]) + }, + }); + + if (index === 0) { + slice._model.startAngle = Math.PI * -0.5; // use - PI / 2 instead of 3PI / 2 to make animations better. It means that we never deal with overflow during the transition function + } else { + slice._model.startAngle = dataset.metaData[index - 1]._model.endAngle; + } + + slice._model.endAngle = slice._model.startAngle + slice._model.circumference; + + + //Check to see if it's the last slice, if not get the next and update its start angle + if (index < dataset.data.length - 1) { + dataset.metaData[index + 1]._model.startAngle = slice._model.endAngle; + } + + slice.pivot(); + }, this); + + }, this); + + this.render(); + }, + draw: function(easeDecimal) { + easeDecimal = easeDecimal || 1; + this.clear(); + + this.eachElement(function(slice) { + slice.transition(easeDecimal).draw(); + }, this); + + this.tooltip.transition(easeDecimal).draw(); + }, + events: function(e) { + + // If exiting chart + if (e.type == 'mouseout') { + return this; + } + + this.lastActive = this.lastActive || []; + + // Find Active Elements + this.active = function() { + switch (this.options.hover.mode) { + case 'single': + return this.getSliceAtEvent(e); + case 'label': + return this.getSlicesAtEvent(e); + case 'dataset': + return this.getDatasetAtEvent(e); + default: + return e; + } + }.call(this); + + // On Hover hook + if (this.options.hover.onHover) { + this.options.hover.onHover.call(this, this.active); + } + + if (e.type == 'mouseup' || e.type == 'click') { + if (this.options.onClick) { + this.options.onClick.call(this, e, this.active); + } + } + + var dataset; + var index; + // Remove styling for last active (even if it may still be active) + if (this.lastActive.length) { + switch (this.options.hover.mode) { + case 'single': + dataset = this.data.datasets[this.lastActive[0]._datasetIndex]; + index = this.lastActive[0]._index; + + this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.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.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 + } + } + + // Built in hover styling + if (this.active.length && this.options.hover.mode) { + switch (this.options.hover.mode) { + case 'single': + dataset = this.data.datasets[this.active[0]._datasetIndex]; + index = this.active[0]._index; + + this.active[0]._model.radius = this.active[0].custom && this.active[0].custom.hoverRadius ? this.active[0].custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.active[0]._model.radius + 2); + this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.35).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.35).rgbString()); + this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[0]._model.borderWidth + 2); + break; + case 'label': + for (var i = 0; i < this.active.length; i++) { + dataset = this.data.datasets[this.active[i]._datasetIndex]; + index = this.active[i]._index; + + this.active[i]._model.radius = this.active[i].custom && this.active[i].custom.hoverRadius ? this.active[i].custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.active[i]._model.radius + 2); + this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.35).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.35).rgbString()); + this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[i]._model.borderWidth + 2); + } + break; + case 'dataset': + break; + default: + // Don't change anything + } + } + + + // Built in Tooltips + if (this.options.tooltips.enabled) { + + // The usual updates + this.tooltip.initialize(); + + // Active + if (this.active.length) { + this.tooltip._model.opacity = 1; + + helpers.extend(this.tooltip, { + _active: this.active, + }); + + this.tooltip.update(); + } else { + // Inactive + this.tooltip._model.opacity = 0; + } + } + + + // Hover animations + this.tooltip.pivot(); + + if (!this.animating) { + var changed; + + helpers.each(this.active, function(element, index) { + if (element !== this.lastActive[index]) { + changed = true; + } + }, this); + + // If entering, leaving, or changing elements, animate the change via pivot + if ((!this.lastActive.length && this.active.length) || + (this.lastActive.length && !this.active.length) || + (this.lastActive.length && this.active.length && changed)) { + + this.stop(); + this.render(this.options.hover.animationDuration); + } + } + + // Remember Last Active + this.lastActive = this.active; + return this; + }, + getSliceAtEvent: function(e) { + var elements = []; + + var location = helpers.getRelativePosition(e); + + this.eachElement(function(slice, index) { + if (slice.inRange(location.x, location.y)) { + elements.push(slice); + } + }, this); + return elements; + }, + /*getSlicesAtEvent: function(e) { + var elements = []; + + var location = helpers.getRelativePosition(e); + + this.eachElement(function(slice, index) { + if (slice.inGroupRange(location.x, location.y)) { + elements.push(slice); + } + }, this); + return elements; + },*/ + }); + + Chart.types.Doughnut.extend({ + name: "Pie", + defaults: helpers.merge(defaultConfig, { + cutoutPercentage: 0 + }) + }); + +}).call(this); + +(function() { + "use strict"; + + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; + + var defaultConfig = { + + stacked: false, + + hover: { + mode: "label" + }, + + scales: { + xAxes: [{ + 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 + + // grid line settings + gridLines: { + show: true, + color: "rgba(0, 0, 0, 0.05)", + lineWidth: 1, + drawOnChartArea: true, + drawTicks: true, + zeroLineWidth: 1, + zeroLineColor: "rgba(0,0,0,0.25)", + offsetGridLines: false, + }, + + // label settings + labels: { + show: true, + template: "<%=value%>", + fontSize: 12, + fontStyle: "normal", + fontColor: "#666", + fontFamily: "Helvetica Neue", + }, + }], + yAxes: [{ + 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", + + // grid line settings + gridLines: { + show: true, + color: "rgba(0, 0, 0, 0.05)", + lineWidth: 1, + drawOnChartArea: true, + drawTicks: true, // draw ticks extending towards the label + zeroLineWidth: 1, + zeroLineColor: "rgba(0,0,0,0.25)", + }, + + // scale numbers + beginAtZero: false, + override: null, + + // label settings + labels: { + show: true, + template: "<%=value%>", + fontSize: 12, + fontStyle: "normal", + fontColor: "#666", + fontFamily: "Helvetica Neue", + } + }], + }, + }; + + + Chart.Type.extend({ + name: "Line", + defaults: defaultConfig, + initialize: function() { + + var _this = this; + + // Events + helpers.bindEvents(this, this.options.events, this.events); + + // Create a new line and its points for each dataset and piece of data + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + + dataset.metaDataset = new Chart.Line({ + _chart: this.chart, + _datasetIndex: datasetIndex, + _points: dataset.metaData, + }); + + dataset.metaData = []; + + helpers.each(dataset.data, function(dataPoint, index) { + dataset.metaData.push(new Chart.Point({ + _datasetIndex: datasetIndex, + _index: index, + _chart: this.chart, + _model: { + x: 0, //xScale.getPixelForValue(null, index, true), + y: 0, //this.chartArea.bottom, + }, + })); + + }, this); + + // The line chart onlty supports a single x axis because the x axis is always a dataset axis + dataset.xAxisID = this.options.scales.xAxes[0].id; + + if (!dataset.yAxisID) { + dataset.yAxisID = this.options.scales.yAxes[0].id; + } + + }, this); + + // Build and fit the scale. Needs to happen after the axis IDs have been set + this.buildScale(); + + // Create tooltip instance exclusively for this chart with some defaults. + this.tooltip = new Chart.Tooltip({ + _chart: this.chart, + _data: this.data, + _options: this.options, + }, this); + + // Need to fit scales before we reset elements. + Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); + + // Reset so that we animation from the baseline + this.resetElements(); + + // Update that shiz + this.update(); + }, + nextPoint: function(collection, index) { + return collection[index + 1] || collection[index]; + }, + previousPoint: function(collection, index) { + return collection[index - 1] || collection[index]; + }, + resetElements: function() { + // Update the points + this.eachElement(function(point, index, dataset, datasetIndex) { + var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID]; + var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID]; + + var yScalePoint; + + if (yScale.min < 0 && yScale.max <0) { + // all less than 0. use the top + yScalePoint = yScale.getPixelForValue(yScale.max); + } else if (yScale.min > 0 && yScale.max > 0) { + yScalePoint = yScale.getPixelForValue(yScale.min); + } else { + yScalePoint = yScale.getPixelForValue(0); + } + + helpers.extend(point, { + // Utility + _chart: this.chart, + _xScale: xScale, + _yScale: yScale, + _datasetIndex: datasetIndex, + _index: index, + + // Desired view properties + _model: { + x: xScale.getPixelForValue(null, index, true), // value not used in dataset scale, but we want a consistent API between scales + y: yScalePoint, + + // Appearance + tension: point.custom && point.custom.tension ? point.custom.tension : this.options.elements.line.tension, + radius: point.custom && point.custom.radius ? point.custom.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: typeof this.data.datasets[datasetIndex].data[index] != 'number', + + // Tooltip + hoverRadius: point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointHitRadius, index, this.options.elements.point.hitRadius), + }, + }); + }, this); + + // Update control points for the bezier curve + this.eachElement(function(point, index, dataset, datasetIndex) { + var controlPoints = helpers.splineCurve( + this.previousPoint(dataset, index)._model, + point._model, + this.nextPoint(dataset, index)._model, + point._model.tension + ); + + point._model.controlPointPreviousX = controlPoints.previous.x; + point._model.controlPointNextX = controlPoints.next.x; + + // Prevent the bezier going outside of the bounds of the graph + + // Cap puter bezier handles to the upper/lower scale bounds + if (controlPoints.next.y > this.chartArea.bottom) { + point._model.controlPointNextY = this.chartArea.bottom; + } else if (controlPoints.next.y < this.chartArea.top) { + point._model.controlPointNextY = this.chartArea.top; + } else { + point._model.controlPointNextY = controlPoints.next.y; + } + + // Cap inner bezier handles to the upper/lower scale bounds + if (controlPoints.previous.y > this.chartArea.bottom) { + point._model.controlPointPreviousY = this.chartArea.bottom; + } else if (controlPoints.previous.y < this.chartArea.top) { + point._model.controlPointPreviousY = this.chartArea.top; + } else { + point._model.controlPointPreviousY = controlPoints.previous.y; + } + + // Now pivot the point for animation + point.pivot(); + }, this); + }, + update: function() { + + Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); + + // Update the lines + this.eachDataset(function(dataset, datasetIndex) { + var yScale = this.scales[dataset.yAxisID]; + + helpers.extend(dataset.metaDataset, { + // Utility + _scale: yScale, + _datasetIndex: datasetIndex, + // Data + _children: dataset.metaData, + // Model + _model: { + // Appearance + tension: dataset.tension || this.options.elements.line.tension, + backgroundColor: dataset.backgroundColor || this.options.elements.line.backgroundColor, + borderWidth: dataset.borderWidth || this.options.elements.line.borderWidth, + borderColor: dataset.borderColor || this.options.elements.line.borderColor, + fill: dataset.fill !== undefined ? dataset.fill : this.options.elements.line.fill, // use the value from the dataset if it was provided. else fall back to the default + skipNull: dataset.skipNull !== undefined ? dataset.skipNull : this.options.elements.line.skipNull, + drawNull: dataset.drawNull !== undefined ? dataset.drawNull : this.options.elements.line.drawNull, + // Scale + scaleTop: yScale.top, + scaleBottom: yScale.bottom, + scaleZero: yScale.getPixelForValue(0), + }, + }); + + dataset.metaDataset.pivot(); + }); + + // Update the points + this.eachElement(function(point, index, dataset, datasetIndex) { + var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID]; + var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID]; + + helpers.extend(point, { + // Utility + _chart: this.chart, + _xScale: xScale, + _yScale: yScale, + _datasetIndex: datasetIndex, + _index: index, + + // Desired view properties + _model: { + x: xScale.getPixelForValue(null, index, true), // value not used in dataset scale, but we want a consistent API between scales + y: yScale.getPointPixelForValue(this.data.datasets[datasetIndex].data[index], index, datasetIndex), + + // Appearance + tension: point.custom && point.custom.tension ? point.custom.tension : this.options.elements.line.tension, + radius: point.custom && point.custom.radius ? point.custom.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: typeof this.data.datasets[datasetIndex].data[index] != 'number', + + // Tooltip + hoverRadius: point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointHitRadius, index, this.options.elements.point.hitRadius), + }, + }); + }, this); + + + // Update control points for the bezier curve + this.eachElement(function(point, index, dataset, datasetIndex) { + var controlPoints = helpers.splineCurve( + this.previousPoint(dataset, index)._model, + point._model, + this.nextPoint(dataset, index)._model, + point._model.tension + ); + + point._model.controlPointPreviousX = controlPoints.previous.x; + point._model.controlPointNextX = controlPoints.next.x; + + // Prevent the bezier going outside of the bounds of the graph + + // Cap puter bezier handles to the upper/lower scale bounds + if (controlPoints.next.y > this.chartArea.bottom) { + point._model.controlPointNextY = this.chartArea.bottom; + } else if (controlPoints.next.y < this.chartArea.top) { + point._model.controlPointNextY = this.chartArea.top; + } else { + point._model.controlPointNextY = controlPoints.next.y; + } + + // Cap inner bezier handles to the upper/lower scale bounds + if (controlPoints.previous.y > this.chartArea.bottom) { + point._model.controlPointPreviousY = this.chartArea.bottom; + } else if (controlPoints.previous.y < this.chartArea.top) { + point._model.controlPointPreviousY = this.chartArea.top; + } else { + point._model.controlPointPreviousY = controlPoints.previous.y; + } + + // Now pivot the point for animation + point.pivot(); + }, this); + + this.render(); + }, + buildScale: function() { + var self = this; + + // Function to determine the range of all the + var calculateYRange = function() { + this.min = null; + this.max = null; + + var positiveValues = []; + var negativeValues = []; + + if (self.options.stacked) { + helpers.each(self.data.datasets, function(dataset) { + if (dataset.yAxisID === this.id) { + helpers.each(dataset.data, function(value, index) { + positiveValues[index] = positiveValues[index] || 0; + negativeValues[index] = negativeValues[index] || 0; + + if (self.options.relativePoints) { + positiveValues[index] = 100; + } else { + if (value < 0) { + negativeValues[index] += value; + } else { + positiveValues[index] += value; + } + } + }, this); + } + }, this); + + var values = positiveValues.concat(negativeValues); + this.min = helpers.min(values); + this.max = helpers.max(values); + } else { + helpers.each(self.data.datasets, function(dataset) { + if (dataset.yAxisID === this.id) { + helpers.each(dataset.data, function(value, index) { + if (this.min === null) { + this.min = value; + } else if (value < this.min) { + this.min = value; + } + + if (this.max === null) { + this.max = value; + } else if (value > this.max) { + this.max = value; + } + }, this); + } + }, this); + } + }; + + // Map of scale ID to scale object so we can lookup later + this.scales = {}; + + // Build the x axis. The line chart only supports a single x axis + var ScaleClass = Chart.scales.getScaleConstructor(this.options.scales.xAxes[0].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; + + // 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); + }, + draw: function(ease) { + + var easingDecimal = ease || 1; + this.clear(); + + // Draw all the scales + helpers.each(this.scales, function(scale) { + scale.draw(this.chartArea); + }, this); + + // reverse for-loop for proper stacking + for (var i = this.data.datasets.length - 1; i >= 0; i--) { + + var dataset = this.data.datasets[i]; + + // Transition Point Locations + helpers.each(dataset.metaData, function(point, index) { + point.transition(easingDecimal); + }, this); + + // Transition and Draw the line + dataset.metaDataset.transition(easingDecimal).draw(); + + // Draw the points + helpers.each(dataset.metaData, function(point) { + point.draw(); + }); + } + + // Finally draw the tooltip + this.tooltip.transition(easingDecimal).draw(); + }, + events: function(e) { + + // If exiting chart + if (e.type == 'mouseout') { + return this; + } + + this.lastActive = this.lastActive || []; + + // Find Active Elements + this.active = function() { + switch (this.options.hover.mode) { + case 'single': + return this.getElementAtEvent(e); + case 'label': + return this.getElementsAtEvent(e); + case 'dataset': + return this.getDatasetAtEvent(e); + default: + return e; + } + }.call(this); + + // On Hover hook + if (this.options.hover.onHover) { + this.options.hover.onHover.call(this, this.active); + } + + if (e.type == 'mouseup' || e.type == 'click') { + if (this.options.onClick) { + this.options.onClick.call(this, e, this.active); + } + } + + var dataset; + var index; + // Remove styling for last active (even if it may still be active) + if (this.lastActive.length) { + switch (this.options.hover.mode) { + case 'single': + dataset = this.data.datasets[this.lastActive[0]._datasetIndex]; + index = this.lastActive[0]._index; + + this.lastActive[0]._model.radius = this.lastActive[0].custom && this.lastActive[0].custom.radius ? this.lastActive[0].custom.pointRadius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, this.options.elements.point.radius); + this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, this.options.elements.point.backgroundColor); + this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, this.options.elements.point.borderColor); + this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.options.elements.point.borderWidth); + break; + case 'label': + for (var i = 0; i < this.lastActive.length; i++) { + dataset = this.data.datasets[this.lastActive[i]._datasetIndex]; + index = this.lastActive[i]._index; + + this.lastActive[i]._model.radius = this.lastActive[i].custom && this.lastActive[i].custom.radius ? this.lastActive[i].custom.pointRadius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, this.options.elements.point.radius); + this.lastActive[i]._model.backgroundColor = this.lastActive[i].custom && this.lastActive[i].custom.backgroundColor ? this.lastActive[i].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, this.options.elements.point.backgroundColor); + this.lastActive[i]._model.borderColor = this.lastActive[i].custom && this.lastActive[i].custom.borderColor ? this.lastActive[i].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, this.options.elements.point.borderColor); + this.lastActive[i]._model.borderWidth = this.lastActive[i].custom && this.lastActive[i].custom.borderWidth ? this.lastActive[i].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.options.elements.point.borderWidth); + } + break; + case 'dataset': + break; + default: + // Don't change anything + } + } + + // Built in hover styling + if (this.active.length && this.options.hover.mode) { + switch (this.options.hover.mode) { + case 'single': + dataset = this.data.datasets[this.active[0]._datasetIndex]; + index = this.active[0]._index; + + this.active[0]._model.radius = this.active[0].custom && this.active[0].custom.hoverRadius ? this.active[0].custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.active[0]._model.radius + 2); + this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.35).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.35).rgbString()); + this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[0]._model.borderWidth + 2); + break; + case 'label': + for (var i = 0; i < this.active.length; i++) { + dataset = this.data.datasets[this.active[i]._datasetIndex]; + index = this.active[i]._index; + + this.active[i]._model.radius = this.active[i].custom && this.active[i].custom.hoverRadius ? this.active[i].custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.active[i]._model.radius + 2); + this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.35).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.35).rgbString()); + this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[i]._model.borderWidth + 2); + } + break; + case 'dataset': + break; + default: + // Don't change anything + } + } + + + // Built in Tooltips + if (this.options.tooltips.enabled) { + + // The usual updates + this.tooltip.initialize(); + + // Active + if (this.active.length) { + this.tooltip._model.opacity = 1; + + helpers.extend(this.tooltip, { + _active: this.active, + }); + + this.tooltip.update(); + } else { + // Inactive + this.tooltip._model.opacity = 0; + } + } + + + // Hover animations + this.tooltip.pivot(); + + if (!this.animating) { + var changed; + + helpers.each(this.active, function(element, index) { + if (element !== this.lastActive[index]) { + changed = true; + } + }, this); + + // If entering, leaving, or changing elements, animate the change via pivot + if ((!this.lastActive.length && this.active.length) || + (this.lastActive.length && !this.active.length) || + (this.lastActive.length && this.active.length && changed)) { + + this.stop(); + this.render(this.options.hover.animationDuration); + } + } + + // Remember Last Active + this.lastActive = this.active; + return this; + }, + }); + + +}).call(this); + +(function() { + "use strict"; + + var root = this, + Chart = root.Chart, + //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, + + // grid line settings + gridLines: { + show: true, + color: "rgba(0, 0, 0, 0.05)", + lineWidth: 1, + }, + + // scale numbers + beginAtZero: true, + + // label settings + labels: { + show: true, + template: "<%=value%>", + fontSize: 12, + fontStyle: "normal", + fontColor: "#666", + fontFamily: "Helvetica Neue", + + //Boolean - Show a backdrop to the scale label + showLabelBackdrop: true, + + //String - The colour of the label backdrop + backdropColor: "rgba(255,255,255,0.75)", + + //Number - The backdrop padding above & below the label in pixels + backdropPaddingY: 2, + + //Number - The backdrop padding to the side of the label in pixels + backdropPaddingX: 2, + } + }, + + //Boolean - Whether to animate the rotation of the chart + animateRotate: true, + }; + + + Chart.Type.extend({ + //Passing in a name registers this chart in the Chart namespace + name: "PolarArea", + //Providing a defaults will also register the deafults in the chart namespace + defaults: defaultConfig, + //Initialize is fired when the chart is initialized - Data is passed in as a parameter + //Config is automatically merged by the core of Chart.js, and is available at this.options + initialize: function() { + + // Scale setup + var self = this; + var ScaleClass = Chart.scales.getScaleConstructor(this.options.scale.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); + + //Set up tooltip events on the chart + helpers.bindEvents(this, this.options.events, this.events); + + //Create a new bar for each piece of data + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + dataset.metaData = []; + helpers.each(dataset.data, function(dataPoint, index) { + dataset.metaData.push(new Chart.Arc({ + _chart: this.chart, + _datasetIndex: datasetIndex, + _index: index, + _model: {} + })); + }, this); + }, this); + + // Create tooltip instance exclusively for this chart with some defaults. + this.tooltip = new Chart.Tooltip({ + _chart: this.chart, + _data: this.data, + _options: this.options, + }, this); + + // Fit the scale before we animate + this.updateScaleRange(); + this.scale.calculateRange(); + Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); + + // so that we animate nicely + this.resetElements(); + + // Update the chart with the latest data. + this.update(); + + }, + updateScaleRange: function() { + helpers.extend(this.scale, { + size: helpers.min([this.chart.width, this.chart.height]), + xCenter: this.chart.width / 2, + yCenter: this.chart.height / 2 + }); + }, + resetElements: function() { + var circumference = 1 / this.data.datasets[0].data.length * 2; + + // Map new data to data points + helpers.each(this.data.datasets[0].metaData, function(slice, index) { + + var value = this.data.datasets[0].data[index]; + + var startAngle = Math.PI * 1.5 + (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: 0, + startAngle: Math.PI * 1.5, + endAngle: Math.PI * 1.5, + + 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(); + }, this); + }, + update: function() { + + this.updateScaleRange(); + this.scale.calculateRange(); + this.scale.generateTicks(); + this.scale.buildYLabels(); + + Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); + + var circumference = 1 / this.data.datasets[0].data.length * 2; + + // Map new data to data points + helpers.each(this.data.datasets[0].metaData, function(slice, index) { + + var value = this.data.datasets[0].data[index]; + + var startAngle = Math.PI * 1.5 + (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(); + }, + draw: function(ease) { + var easingDecimal = ease || 1; + + this.clear(); + + helpers.each(this.data.datasets[0].metaData, function(slice, index) { + slice.transition(easingDecimal).draw(); + }, this); + + this.scale.draw(); + + this.tooltip.transition(easingDecimal).draw(); + }, + events: function(e) { + + // If exiting chart + if (e.type == 'mouseout') { + return this; + } + + this.lastActive = this.lastActive || []; + + // Find Active Elements + this.active = function() { + switch (this.options.hover.mode) { + case 'single': + return this.getSliceAtEvent(e); + case 'label': + return this.getSlicesAtEvent(e); + case 'dataset': + return this.getDatasetAtEvent(e); + default: + return e; + } + }.call(this); + + // On Hover hook + if (this.options.hover.onHover) { + this.options.hover.onHover.call(this, this.active); + } + + if (e.type == 'mouseup' || e.type == 'click') { + if (this.options.onClick) { + this.options.onClick.call(this, e, this.active); + } + } + + var dataset; + var index; + // Remove styling for last active (even if it may still be active) + if (this.lastActive.length) { + switch (this.options.hover.mode) { + case 'single': + dataset = this.data.datasets[this.lastActive[0]._datasetIndex]; + index = this.lastActive[0]._index; + + this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.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.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 + } + } + + // Built in hover styling + if (this.active.length && this.options.hover.mode) { + switch (this.options.hover.mode) { + case 'single': + dataset = this.data.datasets[this.active[0]._datasetIndex]; + index = this.active[0]._index; + + this.active[0]._model.radius = this.active[0].custom && this.active[0].custom.hoverRadius ? this.active[0].custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.active[0]._model.radius + 2); + this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.35).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.35).rgbString()); + this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[0]._model.borderWidth + 2); + break; + case 'label': + for (var i = 0; i < this.active.length; i++) { + dataset = this.data.datasets[this.active[i]._datasetIndex]; + index = this.active[i]._index; + + this.active[i]._model.radius = this.active[i].custom && this.active[i].custom.hoverRadius ? this.active[i].custom.hoverRadius : helpers.getValueAtIndexOrDefault(dataset.pointHoverRadius, index, this.active[i]._model.radius + 2); + this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointHoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.35).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.35).rgbString()); + this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[i]._model.borderWidth + 2); + } + break; + case 'dataset': + break; + default: + // Don't change anything + } + } + + + // Built in Tooltips + if (this.options.tooltips.enabled) { + + // The usual updates + this.tooltip.initialize(); + + // Active + if (this.active.length) { + this.tooltip._model.opacity = 1; + + helpers.extend(this.tooltip, { + _active: this.active, + }); + + this.tooltip.update(); + } else { + // Inactive + this.tooltip._model.opacity = 0; + } + } + + + // Hover animations + this.tooltip.pivot(); + + if (!this.animating) { + var changed; + + helpers.each(this.active, function(element, index) { + if (element !== this.lastActive[index]) { + changed = true; + } + }, this); - // Do we have more stuff to animate? - if (this.animations.length > 0) { - helpers.requestAnimFrame.call(window, this.digestWrapper); + // 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); + } } - } - }; - // 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); - }; - })()); + // Remember Last Active + this.lastActive = this.active; + return this; + }, + getSliceAtEvent: function(e) { + var elements = []; + var location = helpers.getRelativePosition(e); - if (amd) { - define(function() { - return Chart; - }); - } else if (typeof module === 'object' && module.exports) { - module.exports = Chart; - } + this.eachElement(function(slice, index) { + if (slice.inRange(location.x, location.y)) { + elements.push(slice); + } + }, this); + return elements; + }, + /*getSlicesAtEvent: function(e) { + var elements = []; - root.Chart = Chart; + var location = helpers.getRelativePosition(e); - Chart.noConflict = function() { - root.Chart = previous; - return Chart; - }; + this.eachElement(function(slice, index) { + if (slice.inGroupRange(location.x, location.y)) { + elements.push(slice); + } + }, this); + return elements; + },*/ + }); }).call(this); @@ -2379,203 +3943,360 @@ helpers = Chart.helpers; - var defaultConfig = { - //Boolean - Whether the scale should start at zero, or an order of magnitude down from the lowest value - scaleBeginAtZero: true, - - //Boolean - Whether grid lines are shown across the chart - scaleShowGridLines: true, - //String - Colour of the grid lines - scaleGridLineColor: "rgba(0,0,0,.05)", + Chart.Type.extend({ + name: "Radar", + defaults: { - //Number - Width of the grid lines - scaleGridLineWidth: 1, + scale: { + scaleType: "radialLinear", + display: true, - //Boolean - Whether to show horizontal lines (except X axis) - scaleShowHorizontalLines: true, + //Boolean - Whether to animate scaling the chart from the centre + animate: false, - //Boolean - Whether to show vertical lines (except Y axis) - scaleShowVerticalLines: true, + lineArc: false, - //Number - Pixel width of the bar border - barBorderWidth: 2, + // grid line settings + gridLines: { + show: true, + color: "rgba(0, 0, 0, 0.05)", + lineWidth: 1, + }, - //Number - Spacing between each of the X value sets - barValueSpacing: 5, + angleLines: { + show: true, + color: "rgba(0,0,0,.1)", + lineWidth: 1 + }, - //Number - Spacing between data sets within X values - barDatasetSpacing: 1, + // scale numbers + beginAtZero: true, - //Boolean - Whether bars should be rendered on a percentage base - relativeBars: false, + // label settings + labels: { + show: true, + template: "<%=value%>", + fontSize: 12, + fontStyle: "normal", + fontColor: "#666", + fontFamily: "Helvetica Neue", - //String - A legend template - legendTemplate: "" + //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, - Chart.Type.extend({ - name: "Bar", - defaults: defaultConfig, - initialize: function(data) { + //Number - The backdrop padding to the side of the label in pixels + backdropPaddingX: 2, + }, - // Save data as a source for updating of values & methods - this.data = data; + pointLabels: { + //String - Point label font declaration + fontFamily: "'Arial'", - var options = this.options; + //String - Point label font weight + fontStyle: "normal", - var _this = this; + //Number - Point label font size in pixels + fontSize: 10, - // Custom Scale Methods and Options - this.ScaleClass = Chart.Scale.extend({ - offsetGridLines: true, - calculateBarBase: function(datasetIndex, index) { + //String - Point label font colour + fontColor: "#666", + }, + }, - var base = 0; + elements: { + line: { + tension: 0, // no bezier in radar + } + }, - if (_this.options.stacked) { - var bar = _this.data.datasets[datasetIndex].metaData[index]; - if (bar.value < 0) { - for (var i = 0; i < datasetIndex; i++) { - base += _this.data.datasets[i].metaData[index].value < base ? _this.data.datasets[i].metaData[index].value : 0; - } - } else { - for (var i = 0; i < datasetIndex; i++) { - base += _this.data.datasets[i].metaData[index].value > base ? _this.data.datasets[i].metaData[index].value : 0; - } - } - return this.calculateY(base); - } + //String - A legend template + legendTemplate: "" - base = this.endPoint; + }, - if (this.beginAtZero || ((this.min <= 0 && this.max >= 0) || (this.min >= 0 && this.max <= 0))) { - base = this.calculateY(0); - base += _this.options.scaleGridLineWidth; - } else if (this.min < 0 && this.max < 0) { - // All values are negative. Use the top as the base - base = this.startPoint; - } + initialize: function() { + // Events + helpers.bindEvents(this, this.options.events, this.events); - return base; + // Create a new line and its points for each dataset and piece of data + helpers.each(this.data.datasets, function(dataset, datasetIndex) { - }, - calculateBarX: function(datasetCount, datasetIndex, elementIndex) { - var xWidth = this.calculateBaseWidth(), - xAbsolute = this.calculateX(elementIndex) - (xWidth / 2), - barWidth = this.calculateBarWidth(datasetCount); + dataset.metaDataset = new Chart.Line({ + _chart: this.chart, + _datasetIndex: datasetIndex, + _points: dataset.metaData, + _loop: true + }); - if (_this.options.stacked) { - return xAbsolute + barWidth / 2; - } + dataset.metaData = []; - return xAbsolute + (barWidth * datasetIndex) + (datasetIndex * options.barDatasetSpacing) + barWidth / 2; - }, - calculateBarY: function(datasets, datasetIndex, barIndex, value) { + 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, + }, + })); - if (_this.options.stacked) { + }, this); + }, this); - var sumPos = 0, - sumNeg = 0; + // Build the scale. + this.buildScale(); - for (var i = 0; i < datasetIndex; i++) { - if (datasets[i].metaData[barIndex].value < 0) { - sumNeg += datasets[i].metaData[barIndex].value || 0; - } else { - sumPos += datasets[i].metaData[barIndex].value || 0; - } - } + // 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 (value < 0) { - return this.calculateY(sumNeg + value); - } else { - return this.calculateY(sumPos + value); - } + // Need to fit scales before we reset elements. + Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - /*if (options.relativeBars) { - offset = offset / sum * 100; - }*/ + // Reset so that we animation from the baseline + this.resetElements(); - return this.calculateY(0); - } + // 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() { - var offset = 0; + // 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: typeof this.data.datasets[datasetIndex].data[index] != 'number', + + // Tooltip + hoverRadius: point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointHitRadius, index, this.options.elements.point.hitRadius), + }, + }); + }, this); - for (i = datasetIndex; i < datasets.length; i++) { - if (i === datasetIndex && value) { - offset += value; - } else { - offset = offset + (datasets[i].metaData[barIndex].value); - } - } + // 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 + ); - return this.calculateY(value); - }, - calculateBaseWidth: function() { - return (this.calculateX(1) - this.calculateX(0)) - (2 * options.barValueSpacing); - }, - calculateBaseHeight: function() { - return (this.calculateY(1) - this.calculateY(0)); - }, - calculateBarWidth: function(datasetCount) { + point._model.controlPointPreviousX = this.scale.xCenter; + point._model.controlPointPreviousY = this.scale.yCenter; + point._model.controlPointNextX = this.scale.xCenter; + point._model.controlPointNextY = this.scale.yCenter; - //The padding between datasets is to the right of each bar, providing that there are more than 1 dataset - var baseWidth = this.calculateBaseWidth() - ((datasetCount - 1) * options.barDatasetSpacing); + // Now pivot the point for animation + point.pivot(); + }, this); + }, + update: function() { + Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - if (_this.options.stacked) { - return baseWidth; - } - return (baseWidth / datasetCount); - }, - }); + // Update the lines + this.eachDataset(function(dataset, datasetIndex) { + helpers.extend(dataset.metaDataset, { + // Utility + _datasetIndex: datasetIndex, - // Events - helpers.bindEvents(this, this.options.tooltipEvents, this.onHover); + // 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: this.scale.getPointPosition(0), + }, + }); - //Declare the extension of the default point, to cater for the options passed in to the constructor - this.BarClass = Chart.Rectangle.extend({ - ctx: this.chart.ctx, + dataset.metaDataset.pivot(); }); - // Build Scale - this.buildScale(this.data.labels); - - //Create a new bar for each piece of data - helpers.each(this.data.datasets, function(dataset, datasetIndex) { - dataset.metaData = []; - helpers.each(dataset.data, function(dataPoint, index) { - dataset.metaData.push(new this.BarClass()); - }, this); - }, this); + // 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])); - // Set defaults for bars - this.eachElement(function(bar, index, dataset, datasetIndex) { - helpers.extend(bar, { - base: this.scale.zeroPoint, - width: this.scale.calculateBarWidth(this.data.datasets.length), - x: this.scale.calculateBarX(this.data.datasets.length, datasetIndex, index), - y: this.scale.calculateBarY(this.data.datasets, datasetIndex, index, 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: typeof this.data.datasets[datasetIndex].data[index] != 'number', + + // Tooltip + hoverRadius: point.custom && point.custom.hoverRadius ? point.custom.hoverRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointHitRadius, index, this.options.elements.point.hitRadius), + }, }); - // Copy to view model - bar.save(); }, this); - // Create tooltip instance exclusively for this chart with some defaults. - this.tooltip = new Chart.Tooltip({ - _chart: this.chart, - _data: this.data, - _options: this.options, + + // Update control points for the bezier curve + this.eachElement(function(point, index, dataset, datasetIndex) { + var controlPoints = helpers.splineCurve( + this.previousPoint(dataset, index)._model, + point._model, + this.nextPoint(dataset, index)._model, + point._model.tension + ); + + point._model.controlPointPreviousX = controlPoints.previous.x; + point._model.controlPointNextX = controlPoints.next.x; + + // Prevent the bezier going outside of the bounds of the graph + + // Cap puter bezier handles to the upper/lower scale bounds + if (controlPoints.next.y > this.chartArea.bottom) { + point._model.controlPointNextY = this.chartArea.bottom; + } else if (controlPoints.next.y < this.chartArea.top) { + point._model.controlPointNextY = this.chartArea.top; + } else { + point._model.controlPointNextY = controlPoints.next.y; + } + + // Cap inner bezier handles to the upper/lower scale bounds + if (controlPoints.previous.y > this.chartArea.bottom) { + point._model.controlPointPreviousY = this.chartArea.bottom; + } else if (controlPoints.previous.y < this.chartArea.top) { + point._model.controlPointPreviousY = this.chartArea.top; + } else { + point._model.controlPointPreviousY = controlPoints.previous.y; + } + + // Now pivot the point for animation + point.pivot(); }, this); - // Update the chart with the latest data. - this.update(); + this.render(); + }, + 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]; + + // Transition Point Locations + helpers.each(dataset.metaData, function(point, index) { + point.transition(easingDecimal); + }, this); + + // Transition and Draw the line + dataset.metaDataset.transition(easingDecimal).draw(); + + // Draw the points + helpers.each(dataset.metaData, function(point) { + point.draw(); + }); + } + + // Finally draw the tooltip + this.tooltip.transition(easingDecimal).draw(); }, - onHover: function(e) { - + events: function(e) { // If exiting chart if (e.type == 'mouseout') { @@ -2586,7 +4307,7 @@ // Find Active Elements this.active = function() { - switch (this.options.hoverMode) { + switch (this.options.hover.mode) { case 'single': return this.getElementAtEvent(e); case 'label': @@ -2599,21 +4320,39 @@ }.call(this); // On Hover hook - if (this.options.onHover) { - this.options.onHover.call(this, this.active); + 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.hoverMode) { + switch (this.options.hover.mode) { case 'single': - this.lastActive[0].backgroundColor = this.data.datasets[this.lastActive[0]._datasetIndex].backgroundColor; - this.lastActive[0].borderColor = this.data.datasets[this.lastActive[0]._datasetIndex].borderColor; + dataset = this.data.datasets[this.lastActive[0]._datasetIndex]; + index = this.lastActive[0]._index; + + this.lastActive[0]._model.radius = this.lastActive[0].custom && this.lastActive[0].custom.radius ? this.lastActive[0].custom.pointRadius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, this.options.elements.point.radius); + this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, this.options.elements.point.backgroundColor); + this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, this.options.elements.point.borderColor); + this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.options.elements.point.borderWidth); break; case 'label': for (var i = 0; i < this.lastActive.length; i++) { - this.lastActive[i].backgroundColor = this.data.datasets[this.lastActive[i]._datasetIndex].backgroundColor; - this.lastActive[i].borderColor = this.data.datasets[this.lastActive[i]._datasetIndex].borderColor; + dataset = this.data.datasets[this.lastActive[i]._datasetIndex]; + index = this.lastActive[i]._index; + + this.lastActive[i]._model.radius = this.lastActive[i].custom && this.lastActive[i].custom.radius ? this.lastActive[i].custom.pointRadius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, this.options.elements.point.radius); + this.lastActive[i]._model.backgroundColor = this.lastActive[i].custom && this.lastActive[i].custom.backgroundColor ? this.lastActive[i].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, this.options.elements.point.backgroundColor); + this.lastActive[i]._model.borderColor = this.lastActive[i].custom && this.lastActive[i].custom.borderColor ? this.lastActive[i].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, this.options.elements.point.borderColor); + this.lastActive[i]._model.borderWidth = this.lastActive[i].custom && this.lastActive[i].custom.borderWidth ? this.lastActive[i].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.options.elements.point.borderWidth); } break; case 'dataset': @@ -2624,16 +4363,26 @@ } // Built in hover styling - if (this.active.length && this.options.hoverMode) { - switch (this.options.hoverMode) { + if (this.active.length && this.options.hover.mode) { + switch (this.options.hover.mode) { case 'single': - this.active[0].backgroundColor = this.data.datasets[this.active[0]._datasetIndex].hoverBackgroundColor || helpers.color(this.active[0].backgroundColor).saturate(0.8).darken(0.2).rgbString(); - this.active[0].borderColor = this.data.datasets[this.active[0]._datasetIndex].hoverBorderColor || helpers.color(this.active[0].borderColor).saturate(0.8).darken(0.2).rgbString(); + 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.35).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.35).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++) { - this.active[i].backgroundColor = this.data.datasets[this.active[i]._datasetIndex].hoverBackgroundColor || helpers.color(this.active[i].backgroundColor).saturate(0.8).darken(0.2).rgbString(); - this.active[i].borderColor = this.data.datasets[this.active[i]._datasetIndex].hoverBorderColor || helpers.color(this.active[i].borderColor).saturate(0.8).darken(0.2).rgbString(); + 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.35).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.35).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': @@ -2643,33 +4392,30 @@ } } - // Built in Tooltips - if (this.options.showTooltips) { + 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, { - opacity: 1, _active: this.active, }); this.tooltip.update(); } else { // Inactive - helpers.extend(this.tooltip, { - opacity: 0, - }); + this.tooltip._model.opacity = 0; } } - + // Hover animations this.tooltip.pivot(); - // Hover animations if (!this.animating) { var changed; @@ -2685,7 +4431,7 @@ (this.lastActive.length && this.active.length && changed)) { this.stop(); - this.render(this.options.hoverAnimationDuration); + this.render(this.options.hover.animationDuration); } } @@ -2693,404 +4439,1163 @@ this.lastActive = this.active; return this; }, - update: function() { + }); +}).call(this); - this.scale.update(); +(function() { + "use strict"; - this.eachElement(function(bar, index, dataset, datasetIndex) { - helpers.extend(bar, { - value: this.data.datasets[datasetIndex].data[index], - }); - bar.pivot(); - }, this); + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; - this.eachElement(function(bar, index, dataset, datasetIndex) { - helpers.extend(bar, { - base: this.scale.calculateBarBase(datasetIndex, index), - x: this.scale.calculateBarX(this.data.datasets.length, datasetIndex, index), - y: this.scale.calculateBarY(this.data.datasets, datasetIndex, index, this.data.datasets[datasetIndex].data[index]), - width: this.scale.calculateBarWidth(this.data.datasets.length), - label: this.data.labels[index], - datasetLabel: this.data.datasets[datasetIndex].label, - borderColor: this.data.datasets[datasetIndex].borderColor, - borderWidth: this.data.datasets[datasetIndex].borderWidth, - backgroundColor: this.data.datasets[datasetIndex].backgroundColor, - _datasetIndex: datasetIndex, - _index: index, + // 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 = 10; + var yPadding = 10; + + 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"; }); - bar.pivot(); - }, this); - this.render(); - }, - buildScale: function(labels) { - var self = this; + // Adjust the padding to take into account displaying labels + if (topScales.length === 0 || bottomScales.length === 0) { + var maxFontHeight = 0; - var dataTotal = function() { - var values = []; - var negativeValues = []; + 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); + } + }; - if (self.options.stacked) { - self.eachValue(function(value, index) { - values[index] = values[index] || 0; - negativeValues[index] = negativeValues[index] || 0; - if (self.options.relativeBars) { - values[index] = 100; + helpers.each(leftScales, maxFontHeightFunction); + helpers.each(rightScales, maxFontHeightFunction); + + if (topScales.length === 0) { + // Add padding so that we can handle drawing the top nicely + yPadding += 0.75 * maxFontHeight; // 0.75 since padding added on both sides + } + + if (bottomScales.length === 0) { + // Add padding so that we can handle drawing the bottom nicely + yPadding += 1.5 * maxFontHeight; + } + } + + // Essentially we now have any number of scales on each of the 4 sides. + // Our canvas looks like the following. + // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and + // B1 is the bottom axis + // |------------------------------------------------------| + // | | T1 | | + // |----|-----|-------------------------------------|-----| + // | | | | | + // | L1 | L2 | Chart area | R1 | + // | | | | | + // | | | | | + // |----|-----|-------------------------------------|-----| + // | | B1 | | + // | | | | + // |------------------------------------------------------| + + // What we do to find the best sizing, we do the following + // 1. Determine the minimum size of the chart area. + // 2. Split the remaining width equally between each vertical axis + // 3. Split the remaining height equally between each horizontal axis + // 4. Give each scale the maximum size it can be. The scale will return it's minimum size + // 5. Adjust the sizes of each axis based on it's minimum reported size. + // 6. Refit each axis + // 7. Position each axis in the final location + // 8. Tell the chart the final location of the chart area + + // Step 1 + var chartWidth = width / 2; // min 50% + var chartHeight = height / 2; // min 50% + var aspectRatio = chartHeight / chartWidth; + var screenAspectRatio; + + if (chartInstance.options.maintainAspectRatio) { + screenAspectRatio = height / width; + + if (aspectRatio != screenAspectRatio) { + chartHeight = chartWidth * screenAspectRatio; + aspectRatio = screenAspectRatio; + } + } + + chartWidth -= (2 * xPadding); + chartHeight -= (2 * yPadding); + + // Step 2 + var verticalScaleWidth = (width - chartWidth) / (leftScales.length + rightScales.length); + + // Step 3 + var horizontalScaleHeight = (height - chartHeight) / (topScales.length + bottomScales.length); + + // Step 4; + var minimumScaleSizes = []; + + var verticalScaleMinSizeFunction = function(scaleInstance) { + var minSize = scaleInstance.fit(verticalScaleWidth, chartHeight); + minimumScaleSizes.push({ + horizontal: false, + minSize: minSize, + scale: scaleInstance, + }); + }; + + var horizontalScaleMinSizeFunction = function(scaleInstance) { + var minSize = scaleInstance.fit(chartWidth, horizontalScaleHeight); + minimumScaleSizes.push({ + horizontal: true, + minSize: minSize, + scale: scaleInstance, + }); + }; + + // vertical scales + helpers.each(leftScales, verticalScaleMinSizeFunction); + helpers.each(rightScales, verticalScaleMinSizeFunction); + + // horizontal scales + helpers.each(topScales, horizontalScaleMinSizeFunction); + helpers.each(bottomScales, horizontalScaleMinSizeFunction); + + // Step 5 + var maxChartHeight = height - (2 * yPadding); + var maxChartWidth = width - (2 * xPadding); + + helpers.each(minimumScaleSizes, function(wrapper) { + if (wrapper.horizontal) { + maxChartHeight -= wrapper.minSize.height; + } else { + maxChartWidth -= wrapper.minSize.width; + } + }); + + // At this point, maxChartHeight and maxChartWidth are the size the chart area could + // be if the axes are drawn at their minimum sizes. + if (chartInstance.options.maintainAspectRatio) { + // Figure out what the real max size will be + var maxAspectRatio = maxChartHeight / maxChartWidth; + + if (maxAspectRatio != screenAspectRatio) { + // Need to adjust + if (maxChartHeight < maxChartWidth) { + maxChartWidth = maxChartHeight / screenAspectRatio; } else { - if (value < 0) { - negativeValues[index] += value; - } else { - values[index] += value; - } + maxChartHeight = maxChartWidth * screenAspectRatio; } - }); - return values.concat(negativeValues); + } } - self.eachValue(function(value, index) { - values.push(value); - }); + // Step 6 + var verticalScaleFitFunction = function(scaleInstance) { + var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { + return wrapper.scale === scaleInstance; + }); - return values; + if (wrapper) { + scaleInstance.fit(wrapper.minSize.width, maxChartHeight); + } + }; - }; + var horizontalScaleFitFunction = function(scaleInstance) { + var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { + return wrapper.scale === scaleInstance; + }); - var scaleOptions = { - templateString: this.options.scaleLabel, - height: this.chart.height, - width: this.chart.width, - ctx: this.chart.ctx, - textColor: this.options.scaleFontColor, - fontSize: this.options.scaleFontSize, - fontStyle: this.options.scaleFontStyle, - fontFamily: this.options.scaleFontFamily, - valuesCount: labels.length, - beginAtZero: this.options.scaleBeginAtZero, - integersOnly: this.options.scaleIntegersOnly, - calculateYRange: function(currentHeight) { - var updatedRanges = helpers.calculateScaleRange( - dataTotal(), - currentHeight, - this.fontSize, - this.beginAtZero, - this.integersOnly - ); - helpers.extend(this, updatedRanges); - }, - xLabels: labels, - font: helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily), - lineWidth: this.options.scaleLineWidth, - lineColor: this.options.scaleLineColor, - showHorizontalLines: this.options.scaleShowHorizontalLines, - showVerticalLines: this.options.scaleShowVerticalLines, - gridLineWidth: (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0, - gridLineColor: (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)", - padding: (this.options.showScale) ? 0 : this.options.borderWidth, - showLabels: this.options.scaleShowLabels, - display: this.options.showScale - }; + if (wrapper) { + scaleInstance.fit(maxChartWidth, wrapper.minSize.width); + } + }; + + helpers.each(leftScales, verticalScaleFitFunction); + helpers.each(rightScales, verticalScaleFitFunction); + helpers.each(topScales, horizontalScaleFitFunction); + helpers.each(bottomScales, horizontalScaleFitFunction); + + // Step 7 + var totalLeftWidth = xPadding; + var totalTopHeight = yPadding; + + // Calculate total width of all left axes + helpers.each(leftScales, function(scaleInstance) { + totalLeftWidth += scaleInstance.width; + }); - if (this.options.scaleOverride) { - helpers.extend(scaleOptions, { - calculateYRange: helpers.noop, - steps: this.options.scaleSteps, - stepValue: this.options.scaleStepWidth, - min: this.options.scaleStartValue, - max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) + // Calculate total height of all top axes + helpers.each(topScales, function(scaleInstance) { + totalTopHeight += scaleInstance.height; }); + + // Position the scales + var left = xPadding; + var top = yPadding; + var right = 0; + var bottom = 0; + + var verticalScalePlacer = function(scaleInstance) { + scaleInstance.left = left; + scaleInstance.right = left + scaleInstance.width; + scaleInstance.top = totalTopHeight; + scaleInstance.bottom = totalTopHeight + maxChartHeight; + + // Move to next point + left = scaleInstance.right; + }; + + var horizontalScalePlacer = function(scaleInstance) { + scaleInstance.left = totalLeftWidth; + scaleInstance.right = totalLeftWidth + maxChartWidth; + scaleInstance.top = top; + scaleInstance.bottom = top + scaleInstance.height; + + // Move to next point + top = scaleInstance.bottom; + }; + + helpers.each(leftScales, verticalScalePlacer); + helpers.each(topScales, horizontalScalePlacer); + + // Account for chart width and height + left += maxChartWidth; + top += maxChartHeight; + + helpers.each(rightScales, verticalScalePlacer); + helpers.each(bottomScales, horizontalScalePlacer); + + // Step 8 + chartInstance.chartArea = { + left: totalLeftWidth, + top: totalTopHeight, + right: totalLeftWidth + maxChartWidth, + bottom: totalTopHeight + maxChartHeight, + }; } + } + }; - this.scale = new this.ScaleClass(scaleOptions); + // 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; }, - // This should be incorportated into the init as something like a default value. "Reflow" seems like a weird word for a fredraw function - redraw: function() { - var base = this.scale.zeroPoint; - this.eachElement(function(element, index, datasetIndex) { - helpers.extend(element, { - y: base, - base: base - }); - }); - this.render(); + getScaleConstructor: function(scaleType) { + return this.constructors.hasOwnProperty(scaleType) ? this.constructors[scaleType] : undefined; + } + }; + + 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"; }, - draw: function(ease) { + generateTicks: function(width, height) { + // We need to decide how many ticks we are going to have. Each tick draws a grid line. + // There are two possibilities. The first is that the user has manually overridden the scale + // calculations in which case the job is easy. The other case is that we have to do it ourselves + // + // We assume at this point that the scale object has been updated with the following values + // by the chart. + // min: this is the minimum value of the scale + // max: this is the maximum value of the scale + // options: contains the options for the scale. This is referenced from the user settings + // rather than being cloned. This ensures that updates always propogate to a redraw + + // Reset the ticks array. Later on, we will draw a grid line at these positions + // The array simply contains the numerical value of the spots where ticks will be + this.ticks = []; + + if (this.options.override) { + // The user has specified the manual override. We use <= instead of < so that + // we get the final line + for (var i = 0; i <= this.options.override.steps; ++i) { + var value = this.options.override.start + (i * this.options.override.stepWidth); + ticks.push(value); + } + } else { + // Figure out what the max number of ticks we can support it is based on the size of + // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph - var easingDecimal = ease || 1; - this.clear(); + var maxTicks; + + if (this.isHorizontal()) { + maxTicks = Math.min(11, Math.ceil(width / 50)); + } else { + // The factor of 2 used to scale the font size has been experimentally determined. + maxTicks = Math.min(11, Math.ceil(height / (2 * this.options.labels.fontSize))); + } - this.scale.draw(easingDecimal); + // Make sure we always have at least 2 ticks + maxTicks = Math.max(2, maxTicks); + + // To get a "nice" value for the tick spacing, we will use the appropriately named + // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks + // for details. + + // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, + // do nothing since that would make the chart weird. If the user really wants a weird chart + // axis, they can manually override it + if (this.options.beginAtZero) { + var minSign = helpers.sign(this.min); + var maxSign = helpers.sign(this.max); + + if (minSign < 0 && maxSign < 0) { + // move the top up to 0 + this.max = 0; + } else if (minSign > 0 && maxSign > 0) { + // move the botttom down to 0 + this.min = 0; + } + } - //Draw all the bars for each dataset - this.eachElement(function(bar, index, datasetIndex) { - bar.transition(easingDecimal).draw(); + var niceRange = helpers.niceNum(this.max - this.min, false); + var spacing = helpers.niceNum(niceRange / (maxTicks - 1), true); + var niceMin = Math.floor(this.min / spacing) * spacing; + var niceMax = Math.ceil(this.max / spacing) * spacing; + + // Put the values into the ticks array + for (var j = niceMin; j <= niceMax; j += spacing) { + this.ticks.push(j); + } + } + + if (this.options.position == "left" || this.options.position == "right") { + // We are in a vertical orientation. The top value is the highest. So reverse the array + this.ticks.reverse(); + } + + // At this point, we need to update our max and min given the tick values since we have expanded the + // range of the scale + this.max = helpers.max(this.ticks); + this.min = helpers.min(this.ticks); + }, + buildLabels: function() { + // We assume that this has been run after ticks have been generated. We try to figure out + // a label for each tick. + this.labels = []; + + helpers.each(this.ticks, function(tick, index, ticks) { + var label; + + if (this.options.labels.userCallback) { + // If the user provided a callback for label generation, use that as first priority + label = this.options.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); + }, + 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)); + } - // Finally draw the tooltip - this.tooltip.transition(easingDecimal).draw(); + return pixel; + }, + // Fit this axis to the given size + // @param {number} maxWidth : the max width the axis can be + // @param {number} maxHeight: the max height the axis can be + // @return {object} minSize : the minimum size needed to draw the axis + fit: function(maxWidth, maxHeight) { + this.calculateRange(); + this.generateTicks(maxWidth, maxHeight); + this.buildLabels(); + + var minSize = { + width: 0, + height: 0, + }; + + if (this.isHorizontal()) { + minSize.width = maxWidth; // fill all the width + + // In a horizontal axis, we need some room for the scale to be drawn + // + // ----------------------------------------------------- + // | | | | | + // + minSize.height = this.options.gridLines.show ? 25 : 0; + } else { + minSize.height = maxHeight; // fill all the height + + // 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 ? 25 : 0; + } + + 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 { + // A vertical axis is more constrained by the width. Labels are the dominant factor + // here, so get that length first + var maxLabelWidth = maxWidth - minSize.width; + var largestTextWidth = helpers.longestText(this.ctx, labelFont, this.labels); + + if (largestTextWidth < maxLabelWidth) { + // We don't need all the room + minSize.width += largestTextWidth; + } else { + // Expand to max size + minSize.width = maxWidth; + } + } + } + + this.width = minSize.width; + this.height = minSize.height; + return minSize; + }, + // Actualy draw the scale on the canvas + // @param {rectangle} chartArea : the area of the chart to draw full grid lines on + draw: function(chartArea) { + if (this.options.display) { + + var setContextLineSettings; + var hasZero; + + // Make sure we draw text in the correct color + this.ctx.fillStyle = this.options.labels.fontColor; + + 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 - 10; + var yTickEnd = this.options.position == "bottom" ? this.top + 10 : 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; + } + + xValue += helpers.aliasPixel(this.ctx.lineWidth); + + // Draw the label area + this.ctx.beginPath(); + + if (this.options.gridLines.drawTicks) { + this.ctx.moveTo(xValue, yTickStart); + this.ctx.lineTo(xValue, yTickEnd); + } + + // Draw the chart area + if (this.options.gridLines.drawOnChartArea) { + this.ctx.moveTo(xValue, chartArea.top); + this.ctx.lineTo(xValue, chartArea.bottom); + } + + // Need to stroke in the loop because we are potentially changing line widths & colours + this.ctx.stroke(); + }, this); + } + + if (this.options.labels.show) { + // Draw the labels + + var labelStartY; + + if (this.options.position == "top") { + labelStartY = this.top; + } else { + // bottom side + labelStartY = this.top + 20; + } + + this.ctx.textAlign = "center"; + this.ctx.textBaseline = "top"; + 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) { + + // 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 - 10; + var xTickEnd = this.options.position == "right" ? this.left + 10 : this.right; + + helpers.each(this.ticks, function(tick, index) { + // Grid lines are horizontal + var yValue = this.getPixelForValue(tick); + + if (tick === 0 || (!hasZero && index === 0)) { + // Draw the 0 point specially or the bottom if there is no 0 + this.ctx.lineWidth = this.options.gridLines.zeroLineWidth; + this.ctx.strokeStyle = this.options.gridLines.zeroLineColor; + setContextLineSettings = true; // reset next time + } else if (setContextLineSettings) { + this.ctx.lineWidth = this.options.gridLines.lineWidth; + this.ctx.strokeStyle = this.options.gridLines.color; + setContextLineSettings = false; // use boolean to indicate that we only want to do this once + } + + yValue += helpers.aliasPixel(this.ctx.lineWidth); + + // Draw the label area + this.ctx.beginPath(); + + if (this.options.gridLines.drawTicks) { + this.ctx.moveTo(xTickStart, yValue); + this.ctx.lineTo(xTickEnd, yValue); + } + + // Draw the chart area + if (this.options.gridLines.drawOnChartArea) { + this.ctx.moveTo(chartArea.left, yValue); + this.ctx.lineTo(chartArea.right, yValue); + } + + this.ctx.stroke(); + }, this); + } + + if (this.options.labels.show) { + // Draw the labels + + var labelStartX; + var maxLabelWidth = this.width - 25; + + if (this.options.position == "left") { + labelStartX = this.left; + } else { + // right side + labelStartX = this.left + 20; + } + + this.ctx.textAlign = "left"; + this.ctx.textBaseline = "middle"; + this.ctx.font = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily); + + helpers.each(this.labels, function(label, index) { + var yValue = this.getPixelForValue(this.ticks[index]); + this.ctx.fillText(label, labelStartX, yValue, maxLabelWidth); + }, this); + } + } + } } }); + 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"; + }, + 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); + } -}).call(this); + return this.left + Math.round(valueOffset); + } else { + return this.top + (index * (this.height / this.max)); + } + }, + calculateLabelRotation: function(maxHeight) { + //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; -(function() { - "use strict"; + 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; - var root = this, - Chart = root.Chart, - //Cache a local reference to Chart.helpers - helpers = Chart.helpers; + this.paddingRight = lastWidth / 2 + 3; + this.paddingLeft = firstWidth / 2 + 3; - var defaultConfig = { - //Boolean - Whether we should show a stroke on each segment - segmentShowStroke: true, + this.labelRotation = 0; - //String - The colour of each segment stroke - segmentStrokeColor: "#fff", + if (this.options.display) { + var originalLabelWidth = helpers.longestText(this.ctx, labelFont, this.labels); + var cosRotation; + var sinRotation; + var firstRotatedWidth; - //Number - The width of each segment stroke - borderWidth: 2, + this.labelWidth = originalLabelWidth; - //The percentage of the chart that we cut out of the middle. - cutoutPercentage: 50, + //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; - // The duration of animations triggered by hover events - hoverAnimationDuration: 400, + //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)); - //String - Animation easing effect - animationEasing: "easeOutQuart", + firstRotated = cosRotation * firstWidth; + lastRotated = cosRotation * lastWidth; - //Boolean - Whether we animate the rotation of the Doughnut - animateRotate: 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; + } - //Boolean - Whether we animate scaling the Doughnut from the centre - animateScale: false, + this.paddingRight = this.options.labels.fontSize / 2; - //String - A legend template - legendTemplate: "" + if (sinRotation * originalLabelWidth > maxHeight) { + // go back one step + this.labelRotation--; + break; + } + + this.labelRotation++; + this.labelWidth = cosRotation * originalLabelWidth; + + } + } else { + this.labelWidth = 0; + this.paddingRight = this.padding; + this.paddingLeft = this.padding; + } + + }, + // 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.calculateLabelRotation(); + + 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 (this.isHorizontal()) { + minSize.width = maxWidth; + this.width = maxWidth; + + 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; + + minSize.width = Math.min(longestLabelWidth + 6, maxWidth); + } + + this.width = minSize.width; + this.height = minSize.height; + return minSize; + }, + // Actualy draw the scale on the canvas + // @param {rectangle} chartArea : the area of the chart to draw full grid lines on + draw: function(chartArea) { + if (this.options.display) { + + var setContextLineSettings; + + // Make sure we draw text in the correct color + this.ctx.fillStyle = this.options.labels.fontColor; + + if (this.isHorizontal()) { + setContextLineSettings = true; + var yTickStart = this.options.position == "bottom" ? this.top : this.bottom - 10; + var yTickEnd = this.options.position == "bottom" ? this.top + 10 : this.bottom; + var isRotated = this.labelRotation !== 0; + + helpers.each(this.labels, function(label, index) { + var xLineValue = this.getPixelForValue(label, index, false); // xvalues for grid lines + var xLabelValue = this.getPixelForValue(label, index, true); // x values for labels (need to consider offsetLabel option) + + if (this.options.gridLines.show) { + if (index === 0) { + // Draw the first index specially + this.ctx.lineWidth = this.options.gridLines.zeroLineWidth; + this.ctx.strokeStyle = this.options.gridLines.zeroLineColor; + setContextLineSettings = true; // reset next time + } else if (setContextLineSettings) { + this.ctx.lineWidth = this.options.gridLines.lineWidth; + this.ctx.strokeStyle = this.options.gridLines.color; + setContextLineSettings = false; + } - }; + xLineValue += helpers.aliasPixel(this.ctx.lineWidth); - Chart.Type.extend({ - //Passing in a name registers this chart in the Chart namespace - name: "Doughnut", - //Providing a defaults will also register the deafults in the chart namespace - defaults: defaultConfig, - //Initialize is fired when the chart is initialized - Data is passed in as a parameter - //Config is automatically merged by the core of Chart.js, and is available at this.options - initialize: function(data) { + // Draw the label area + this.ctx.beginPath(); - // Save data as a source for updating of values & methods - this.data = data; + if (this.options.gridLines.drawTicks) { + this.ctx.moveTo(xLineValue, yTickStart); + this.ctx.lineTo(xLineValue, yTickEnd); + } - // Slice Type and defaults - this.Slice = Chart.Arc.extend({ - _chart: this.chart, - x: this.chart.width / 2, - y: this.chart.height / 2 - }); + // Draw the chart area + if (this.options.gridLines.drawOnChartArea) { + this.ctx.moveTo(xLineValue, chartArea.top); + this.ctx.lineTo(xLineValue, chartArea.bottom); + } - //Set up tooltip events on the chart - if (this.options.showTooltips) { - helpers.bindEvents(this, this.options.tooltipEvents, this.onHover); - } + // Need to stroke in the loop because we are potentially changing line widths & colours + this.ctx.stroke(); + } - // Create new slice for each piece of data - this.data.metaData = []; - helpers.each(this.data.data, function(slice, index) { - var metaSlice = new this.Slice(); - if (typeof slice == 'number') { - helpers.extend(metaSlice, { - value: slice - }); + 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 { - helpers.extend(metaSlice, slice); - } - helpers.extend(metaSlice, { - startAngle: Math.PI * 1.5, - circumference: (this.options.animateRotate) ? 0 : this.calculateCircumference(metaSlice.value), - outerRadius: (this.options.animateScale) ? 0 : this.outerRadius, - innerRadius: (this.options.animateScale) ? 0 : (this.outerRadius / 100) * this.options.percentageInnerCutout, - }); - if (!metaSlice.backgroundColor) { - slice.backgroundColor = 'hsl(' + (360 * index / data.length) + ', 100%, 50%)'; - } - metaSlice.save(); - this.data.metaData.push(metaSlice); - }, this); + // Vertical + if (this.options.gridLines.show) {} - // 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.options.labels.show) { + // Draw the labels + } + } + } + } + }); + Chart.scales.registerScaleType("dataset", DatasetScale); - this.update(); + 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); }, - onHover: function(e) { - - // If exiting chart - if (e.type == 'mouseout') { - return this; + calculateCenterOffset: function(value) { + // Take into account half font size + the yPadding of the top value + var scalingFactor = this.drawingArea / (this.max - this.min); + return (value - this.min) * scalingFactor; + }, + update: function() { + if (!this.options.lineArc) { + this.setScaleSize(); + } else { + this.drawingArea = (this.options.display) ? (this.size / 2) - (this.fontSize / 2 + this.backdropPaddingY) : (this.size / 2); } - this.lastActive = this.lastActive || []; - - // Find Active Elements - this.active = this.getSliceAtEvent(e); + this.buildYLabels(); + }, + calculateRange: helpers.noop, // overridden in chart + generateTicks: function() { + // We need to decide how many ticks we are going to have. Each tick draws a grid line. + // There are two possibilities. The first is that the user has manually overridden the scale + // calculations in which case the job is easy. The other case is that we have to do it ourselves + // + // We assume at this point that the scale object has been updated with the following values + // by the chart. + // min: this is the minimum value of the scale + // max: this is the maximum value of the scale + // options: contains the options for the scale. This is referenced from the user settings + // rather than being cloned. This ensures that updates always propogate to a redraw + + // Reset the ticks array. Later on, we will draw a grid line at these positions + // The array simply contains the numerical value of the spots where ticks will be + this.ticks = []; + + if (this.options.override) { + // The user has specified the manual override. We use <= instead of < so that + // we get the final line + for (var i = 0; i <= this.options.override.steps; ++i) { + var value = this.options.override.start + (i * this.options.override.stepWidth); + ticks.push(value); + } + } else { + // Figure out what the max number of ticks we can support it is based on the size of + // the axis area. For now, we say that the minimum tick spacing in pixels must be 50 + // We also limit the maximum number of ticks to 11 which gives a nice 10 squares on + // the graph + + var maxTicks = Math.min(11, Math.ceil(this.drawingArea / (2 * this.options.labels.fontSize))); + + // Make sure we always have at least 2 ticks + maxTicks = Math.max(2, maxTicks); + + // To get a "nice" value for the tick spacing, we will use the appropriately named + // "nice number" algorithm. See http://stackoverflow.com/questions/8506881/nice-label-algorithm-for-charts-with-minimum-ticks + // for details. + + // If we are forcing it to begin at 0, but 0 will already be rendered on the chart, + // do nothing since that would make the chart weird. If the user really wants a weird chart + // axis, they can manually override it + if (this.options.beginAtZero) { + var minSign = helpers.sign(this.min); + var maxSign = helpers.sign(this.max); + + if (minSign < 0 && maxSign < 0) { + // move the top up to 0 + this.max = 0; + } else if (minSign > 0 && maxSign > 0) { + // move the botttom down to 0 + this.min = 0; + } + } - // On Hover hook - if (this.options.onHover) { - this.options.onHover.call(this, this.active); - } + 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; - // Remove styling for last active (even if it may still be active) - if (this.lastActive.length) { - this.lastActive[0].backgroundColor = this.data.data[this.lastActive[0]._index].backgroundColor; + // Put the values into the ticks array + for (var j = niceMin; j <= niceMax; j += spacing) { + this.ticks.push(j); + } } - // Built in hover styling - if (this.active.length && this.options.hoverMode) { - this.active[0].backgroundColor = this.data.data[this.active[0]._index].hoverBackgroundColor || helpers.color(this.data.data[this.active[0]._index].backgroundColor).saturate(0.5).darken(0.35).rgbString(); + 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(); } - // Built in Tooltips - if (this.options.showTooltips) { + // 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 = []; - // The usual updates - this.tooltip.initialize(); + helpers.each(this.ticks, function(tick, index, ticks) { + var label; - // Active - if (this.active.length) { - helpers.extend(this.tooltip, { - opacity: 1, - _active: this.active, + 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.tooltip.update(); - } else { - // Inactive - helpers.extend(this.tooltip, { - 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 + */ + + + // Get maximum radius of the polygon. Either half the height (minus the text width) or half the width. + // Use this to calculate the offset + change. - Make sure L/R protrusion is at least 0 to stop issues with centre points + var largestPossibleRadius = helpers.min([(this.height / 2 - this.options.pointLabels.fontSize - 5), this.width / 2]), + pointPosition, + i, + textWidth, + halfTextWidth, + furthestRight = this.width, + furthestRightIndex, + furthestRightAngle, + furthestLeft = 0, + furthestLeftIndex, + furthestLeftAngle, + xProtrusionLeft, + xProtrusionRight, + radiusReductionRight, + radiusReductionLeft, + maxWidthRadius; + this.ctx.font = helpers.fontString(this.options.pointLabels.fontSize, this.options.pointLabels.fontStyle, this.options.pointLabels.fontFamily); + for (i = 0; i < this.valuesCount; i++) { + // 5px to space the text slightly out - similar to what we do in the draw function. + pointPosition = this.getPointPosition(i, largestPossibleRadius); + textWidth = this.ctx.measureText(helpers.template(this.options.labels.template, { + value: this.labels[i] + })).width + 5; + if (i === 0 || i === this.valuesCount / 2) { + // If we're at index zero, or exactly the middle, we're at exactly the top/bottom + // of the radar chart, so text will be aligned centrally, so we'll half it and compare + // w/left and right text sizes + halfTextWidth = textWidth / 2; + if (pointPosition.x + halfTextWidth > furthestRight) { + furthestRight = pointPosition.x + halfTextWidth; + furthestRightIndex = i; + } + if (pointPosition.x - halfTextWidth < furthestLeft) { + furthestLeft = pointPosition.x - halfTextWidth; + furthestLeftIndex = i; + } + } else if (i < this.valuesCount / 2) { + // Less than half the values means we'll left align the text + if (pointPosition.x + textWidth > furthestRight) { + furthestRight = pointPosition.x + textWidth; + furthestRightIndex = i; + } + } else if (i > this.valuesCount / 2) { + // More than half the values means we'll right align the text + if (pointPosition.x - textWidth < furthestLeft) { + furthestLeft = pointPosition.x - textWidth; + furthestLeftIndex = i; + } } } + xProtrusionLeft = furthestLeft; - // Hover animations - this.tooltip.pivot(); + xProtrusionRight = Math.ceil(furthestRight - this.width); - if (!this.animating) { - var changed; + furthestRightAngle = this.getIndexAngle(furthestRightIndex); - helpers.each(this.active, function(element, index) { - if (element !== this.lastActive[index]) { - changed = true; - } - }, this); + furthestLeftAngle = this.getIndexAngle(furthestLeftIndex); - // 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)) { + radiusReductionRight = xProtrusionRight / Math.sin(furthestRightAngle + Math.PI / 2); - this.stop(); - this.render(this.options.hoverAnimationDuration); - } - } + radiusReductionLeft = xProtrusionLeft / Math.sin(furthestLeftAngle + Math.PI / 2); - // Remember Last Active - this.lastActive = this.active; - 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; - }, - getSliceAtEvent: function(e) { - var elements = []; + this.drawingArea = largestPossibleRadius - (radiusReductionLeft + radiusReductionRight) / 2; - var location = helpers.getRelativePosition(e); + //this.drawingArea = min([maxWidthRadius, (this.height - (2 * (this.pointLabelFontSize + 5)))/2]) + this.setCenterPoint(radiusReductionLeft, radiusReductionRight); - helpers.each(this.data.metaData, function(slice, index) { - if (slice.inRange(location.x, location.y)) elements.push(slice); - }, this); - return elements; - }, - calculateCircumference: function(value) { - if (this.total > 0) { - return (Math.PI * 2) * (value / this.total); - } else { - return 0; - } }, - update: function() { + setCenterPoint: function(leftMovement, rightMovement) { - // Calc Total - this.total = 0; - helpers.each(this.data.data, function(slice) { - this.total += Math.abs(slice.value); - }, this); + var maxRight = this.width - rightMovement - this.drawingArea, + maxLeft = leftMovement + this.drawingArea; + + this.xCenter = (maxLeft + maxRight) / 2; + // Always vertically in the centre as the text height doesn't change + this.yCenter = (this.height / 2); + }, - this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.borderWidth / 2) / 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 - // Map new data to data points - helpers.each(this.data.metaData, function(slice, index) { + 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; - var datapoint = this.data.data[index]; + // Draw circular lines around the scale + if (this.options.gridLines.show) { + ctx.strokeStyle = this.options.gridLines.color; + ctx.lineWidth = this.options.gridLines.lineWidth; - helpers.extend(slice, { - _index: index, - x: this.chart.width / 2, - y: this.chart.height / 2, - value: datapoint.value, - label: datapoint.label, - circumference: this.calculateCircumference(datapoint.value), - outerRadius: this.outerRadius, - innerRadius: (this.outerRadius / 100) * this.options.cutoutPercentage, - - backgroundColor: datapoint.backgroundColor, - hoverBackgroundColor: datapoint.hoverBackgroundColor || datapoint.backgroundColor, - borderWidth: this.options.borderWidth, - borderColor: this.options.segmentStrokeColor, - }); + 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(); + } + } - helpers.extend(slice, { - endAngle: slice.startAngle + slice.circumference, - }); + if (this.options.labels.show) { + ctx.font = helpers.fontString(this.options.labels.fontSize, this.options.labels.fontStyle, this.options.labels.fontFamily); - if (index === 0) { - slice.startAngle = Math.PI * 1.5; - } + 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 + ); + } - //Check to see if it's the last slice, if not get the next and update its start angle - if (index < this.data.data.length - 1) { - this.data.metaData[index + 1].startAngle = slice.endAngle; - } + ctx.textAlign = 'center'; + ctx.textBaseline = "middle"; + ctx.fillStyle = this.options.labels.fontColor; + ctx.fillText(label, this.xCenter, yHeight); + } + } + }, this); - slice.pivot(); + if (!this.options.lineArc) { + ctx.lineWidth = this.options.angleLines.lineWidth; + ctx.strokeStyle = this.options.angleLines.color; - }, this); + 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.render(); - }, - draw: function(easeDecimal) { - easeDecimal = easeDecimal || 1; - this.clear(); + var labelsCount = this.labels.length, + halfLabelsCount = this.labels.length / 2, + quarterLabelsCount = halfLabelsCount / 2, + upperHalf = (i < quarterLabelsCount || i > labelsCount - quarterLabelsCount), + exactQuarter = (i === quarterLabelsCount || i === labelsCount - quarterLabelsCount); + if (i === 0) { + ctx.textAlign = 'center'; + } else if (i === halfLabelsCount) { + ctx.textAlign = 'center'; + } else if (i < halfLabelsCount) { + ctx.textAlign = 'left'; + } else { + ctx.textAlign = 'right'; + } - helpers.each(this.data.metaData, function(slice, index) { - slice.transition(easeDecimal).draw(); - }, this); + // 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.tooltip.transition(easeDecimal).draw(); + ctx.fillText(this.labels[i], pointLabelPosition.x, pointLabelPosition.y); + } + } + } } }); - - Chart.types.Doughnut.extend({ - name: "Pie", - defaults: helpers.merge(defaultConfig, { - cutoutPercentage: 0 - }) - }); - + Chart.scales.registerScaleType("radialLinear", LinearRadialScale); }).call(this); (function() { @@ -3101,138 +5606,132 @@ helpers = Chart.helpers; var defaultConfig = { + hover: { + mode: 'single', + }, - ///Boolean - Whether grid lines are shown across the chart - scaleShowGridLines: true, - //String - Colour of the grid lines - scaleGridLineColor: "rgba(0,0,0,.05)", - //Number - Width of the grid lines - scaleGridLineWidth: 1, - //Boolean - Whether to show horizontal lines (except X axis) - scaleShowHorizontalLines: true, - //Boolean - Whether to show vertical lines (except Y axis) - scaleShowVerticalLines: true, - //Boolean - Whether to horizontally center the label and point dot inside the grid - offsetGridLines: false, - - - - //Boolean - Whether to stack the lines essentially creating a stacked area chart. - stacked: false, - - - //Number - Tension of the bezier curve between points - tension: 0.4, - - - //Number - Radius of each point dot in pixels - pointRadius: 3, - //Number - Pixel width of point dot border - pointBorderWidth: 1, - //Number - Pixel width of point on hover - pointHoverRadius: 5, - //Number - Pixel width of point dot border on hover - pointHoverBorderWidth: 2, - pointBackgroundColor: Chart.defaults.global.defaultColor, - pointBorderColor: Chart.defaults.global.defaultColor, + scales: { + xAxes: [{ + scaleType: "linear", // scatter should not use a dataset axis + display: true, + position: "bottom", + id: "x-axis-1", // need an ID so datasets can reference the scale + + // grid line settings + gridLines: { + show: true, + color: "rgba(0, 0, 0, 0.05)", + lineWidth: 1, + drawOnChartArea: true, + drawTicks: true, + zeroLineWidth: 1, + zeroLineColor: "rgba(0,0,0,0.25)", + }, - //Number - amount extra to add to the radius to cater for hit detection outside the drawn point - pointHitRadius: 6, + // scale numbers + beginAtZero: false, + integersOnly: false, + override: null, + + // 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", + + // 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 - Pixel width of dataset border - borderWidth: 2, - //Number - Pixel width of dataset border on hover - hoverBorderWidth: 2, + // scale numbers + beginAtZero: false, + integersOnly: false, + override: null, + + // label settings + labels: { + show: true, + template: "<%=value%>", + fontSize: 12, + fontStyle: "normal", + fontColor: "#666", + fontFamily: "Helvetica Neue", + } + }], + }, //String - A legend template legendTemplate: "", - + tooltips: { + template: "(<%= value.x %>, <%= value.y %>)", + multiTemplate: "<%if (datasetLabel){%><%=datasetLabel%>: <%}%>(<%= value.x %>, <%= value.y %>)", + }, }; Chart.Type.extend({ - name: "Line", + name: "Scatter", defaults: defaultConfig, initialize: function() { // Events helpers.bindEvents(this, this.options.events, this.events); - var _this = this; + //Custom Point Defaults + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + dataset.metaDataset = new Chart.Line({ + _chart: this.chart, + _datasetIndex: datasetIndex, + _points: dataset.metaData, + }); - // Build Scale - this.ScaleClass = Chart.Scale.extend({ - calculatePointY: function(index, datasetIndex) { + dataset.metaData = []; - var value = _this.data.datasets[datasetIndex].data[index]; + 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, + }, + })); - if (_this.options.stacked) { - var offsetPos = 0; - var offsetNeg = 0; - for (var i = 0; i < datasetIndex; i++) { - if (_this.data.datasets[i].data[index] < 0) { - offsetNeg += _this.data.datasets[i].data[index]; - } else { - offsetPos += _this.data.datasets[i].data[index]; - } - } - if (value < 0) { - return this.calculateY(offsetNeg + value); - } else { - return this.calculateY(offsetPos + value); - } - } + }, this); - return this.calculateY(value); + // The line chart onlty supports a single x axis because the x axis is always a dataset axis + if (!dataset.xAxisID) { + dataset.xAxisID = this.options.scales.xAxes[0].id; } - }); - this.buildScale(this.data.labels); + if (!dataset.yAxisID) { + dataset.yAxisID = this.options.scales.yAxes[0].id; + } - //Create a new line and its points for each dataset and piece of data - helpers.each(this.data.datasets, function(dataset, datasetIndex) { - dataset.metaDataset = new Chart.Line(); - dataset.metaData = []; - helpers.each(dataset.data, function(dataPoint, index) { - dataset.metaData.push(new Chart.Point()); - }, this); - }, this); - - // Set defaults for lines - this.eachDataset(function(dataset, datasetIndex) { - helpers.extend(dataset.metaDataset, { - _points: dataset.metaData, - _datasetIndex: datasetIndex, - _chart: this.chart, - }); - // Fill in dataset defaults from options - helpers.extend(dataset, helpers.merge(this.options, dataset)); - // Copy to view modele - dataset.metaDataset.save(); }, this); - // Set defaults for points - this.eachElement(function(point, index, dataset, datasetIndex) { - helpers.extend(point, { - x: this.scale.calculateX(index), - y: this.scale.calculateY(0), - _datasetIndex: datasetIndex, - _index: index, - _chart: this.chart - }); - - // Default bezier control points - helpers.extend(point, { - controlPointPreviousX: this.previousPoint(dataset, index).x, - controlPointPreviousY: this.nextPoint(dataset, index).y, - controlPointNextX: this.previousPoint(dataset, index).x, - controlPointNextY: this.nextPoint(dataset, index).y, - }); - // Copy to view model - point.save(); - }, this); + // 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({ @@ -3241,195 +5740,298 @@ _options: this.options, }, this); + // Need to fit scales before we reset elements. + Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); + + // Reset so that we animation from the baseline + this.resetElements(); + + // Update that shiz this.update(); }, nextPoint: function(collection, index) { - return collection[index - 1] || collection[index]; + return collection[index + 1] || collection[index]; }, previousPoint: function(collection, index) { - return collection[index + 1] || collection[index]; + return collection[index - 1] || collection[index]; }, - update: function() { + resetElements: function() { + // Update the points + this.eachElement(function(point, index, dataset, datasetIndex) { + var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID]; + var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID]; + + var yScalePoint; + + if (yScale.min < 0 && yScale.max < 0) { + // all less than 0. use the top + yScalePoint = yScale.getPixelForValue(yScale.max); + } else if (yScale.min > 0 && yScale.max > 0) { + yScalePoint = yScale.getPixelForValue(yScale.min); + } else { + yScalePoint = yScale.getPixelForValue(0); + } + + helpers.extend(point, { + // Utility + _chart: this.chart, + _xScale: xScale, + _yScale: yScale, + _datasetIndex: datasetIndex, + _index: index, + + // Desired view properties + _model: { + x: xScale.getPixelForValue(this.data.datasets[datasetIndex].data[index].x), // value not used in dataset scale, but we want a consistent API between scales + y: yScalePoint, + + // Appearance + tension: point.custom && point.custom.tension ? point.custom.tension : this.options.elements.line.tension, + radius: point.custom && point.custom.radius ? point.custom.pointRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointRadius, index, this.options.elements.point.radius), + backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBackgroundColor, index, this.options.elements.point.backgroundColor), + borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderColor, index, this.options.elements.point.borderColor), + borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderWidth, index, this.options.elements.point.borderWidth), + skip: (typeof this.data.datasets[datasetIndex].data[index].x != 'number') || (typeof this.data.datasets[datasetIndex].data[index].y != 'number'), + + // 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; + } - // Update the scale - this.scale.update(); + // Now pivot the point for animation + point.pivot(); + }, this); + }, + update: function() { + Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); // Update the lines this.eachDataset(function(dataset, datasetIndex) { + var yScale = this.scales[dataset.yAxisID]; + helpers.extend(dataset.metaDataset, { // Utility + _scale: yScale, _datasetIndex: datasetIndex, // Data - _points: dataset.metaData, - // Geometry - scaleTop: this.scale.startPoint, - scaleBottom: this.scale.endPoint, - scaleZero: this.scale.calculateY(0), - // Appearance - tension: dataset.tension || this.options.tension, - backgroundColor: dataset.backgroundColor || this.options.backgroundColor, - borderWidth: dataset.borderWidth || this.options.borderWidth, - borderColor: dataset.borderColor || this.options.borderColor, + _children: dataset.metaData, + // Model + _model: { + // Appearance + tension: dataset.tension || this.options.elements.line.tension, + backgroundColor: dataset.backgroundColor || this.options.elements.line.backgroundColor, + borderWidth: dataset.borderWidth || this.options.elements.line.borderWidth, + borderColor: dataset.borderColor || this.options.elements.line.borderColor, + fill: dataset.fill !== undefined ? dataset.fill : this.options.elements.line.fill, // use the value from the dataset if it was provided. else fall back to the default + skipNull: dataset.skipNull !== undefined ? dataset.skipNull : this.options.elements.line.skipNull, + drawNull: dataset.drawNull !== undefined ? dataset.drawNull : this.options.elements.line.drawNull, + // Scale + scaleTop: yScale.top, + scaleBottom: yScale.bottom, + scaleZero: yScale.getPixelForValue(0), + }, }); + dataset.metaDataset.pivot(); }); // Update the points this.eachElement(function(point, index, dataset, datasetIndex) { + var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID]; + var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID]; + helpers.extend(point, { // Utility _chart: this.chart, + _xScale: xScale, + _yScale: yScale, _datasetIndex: datasetIndex, _index: index, - // Data - label: this.data.labels[index], - value: this.data.datasets[datasetIndex].data[index], - datasetLabel: this.data.datasets[datasetIndex].label, - // Geometry - offsetGridLines: this.options.offsetGridLines, - x: this.scale.calculateX(index), - y: this.scale.calculatePointY(index, datasetIndex), - tension: this.data.datasets[datasetIndex].metaDataset.tension, - // Appearnce - radius: this.data.datasets[datasetIndex].pointRadius || this.options.pointRadius, - backgroundColor: this.data.datasets[datasetIndex].pointBackgroundColor || this.options.pointBackgroundColor, - borderWidth: this.data.datasets[datasetIndex].pointBorderWidth || this.options.pointBorderWidth, - // Tooltip - hoverRadius: this.data.datasets[datasetIndex].pointHitRadius || this.options.pointHitRadius, + + // Desired view properties + _model: { + x: xScale.getPixelForValue(this.data.datasets[datasetIndex].data[index].x), + y: yScale.getPixelForValue(this.data.datasets[datasetIndex].data[index].y), + + // Appearance + tension: point.custom && point.custom.tension ? point.custom.tension : this.options.elements.line.tension, + radius: point.custom && point.custom.radius ? point.custom.pointRadius : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointRadius, index, this.options.elements.point.radius), + backgroundColor: point.custom && point.custom.backgroundColor ? point.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBackgroundColor, index, this.options.elements.point.backgroundColor), + borderColor: point.custom && point.custom.borderColor ? point.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderColor, index, this.options.elements.point.borderColor), + borderWidth: point.custom && point.custom.borderWidth ? point.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].pointBorderWidth, index, this.options.elements.point.borderWidth), + skip: (typeof this.data.datasets[datasetIndex].data[index].x != 'number') || (typeof this.data.datasets[datasetIndex].data[index].y != 'number'), + + // 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), - point, - this.nextPoint(dataset, index), - point.tension + this.previousPoint(dataset, index)._model, + point._model, + this.nextPoint(dataset, index)._model, + point._model.tension ); - point.controlPointPreviousX = controlPoints.previous.x; - point.controlPointNextX = controlPoints.next.x; + 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.scale.endPoint) { - point.controlPointNextY = this.scale.endPoint; - } else if (controlPoints.next.y < this.scale.startPoint) { - point.controlPointNextY = this.scale.startPoint; + 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.controlPointNextY = controlPoints.next.y; + point._model.controlPointNextY = controlPoints.next.y; } // Cap inner bezier handles to the upper/lower scale bounds - if (controlPoints.previous.y > this.scale.endPoint) { - point.controlPointPreviousY = this.scale.endPoint; - } else if (controlPoints.previous.y < this.scale.startPoint) { - point.controlPointPreviousY = this.scale.startPoint; + 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.controlPointPreviousY = controlPoints.previous.y; + point._model.controlPointPreviousY = controlPoints.previous.y; } + // Now pivot the point for animation point.pivot(); }, this); this.render(); }, - buildScale: function(labels) { + buildScale: function() { var self = this; - var dataTotal = function() { - var values = []; - var negativeValues = []; - - if (self.options.stacked) { - self.eachValue(function(value, index) { - values[index] = values[index] || 0; - negativeValues[index] = negativeValues[index] || 0; - if (self.options.relativePoints) { - values[index] = 100; - } else { - if (value < 0) { - negativeValues[index] += value; - } else { - values[index] += value; + var calculateXRange = function() { + this.min = null; + this.max = null; + + helpers.each(self.data.datasets, function(dataset) { + // Only set the scale range for datasets that actually use this axis + if (dataset.xAxisID === this.id) { + helpers.each(dataset.data, function(value) { + if (this.min === null) { + this.min = value.x; + } else if (value.x < this.min) { + this.min = value.x; } - } - }); - return values.concat(negativeValues); - } - - self.eachValue(function(value, index) { - values.push(value); - }); - - return values; - + + if (this.max === null) { + this.max = value.x; + } else if (value.x > this.max) { + this.max = value.x; + } + }, this); + } + }, this); }; - var scaleOptions = { - templateString: this.options.scaleLabel, - height: this.chart.height, - width: this.chart.width, - ctx: this.chart.ctx, - textColor: this.options.scaleFontColor, - offsetGridLines: this.options.offsetGridLines, - fontSize: this.options.scaleFontSize, - fontStyle: this.options.scaleFontStyle, - fontFamily: this.options.scaleFontFamily, - valuesCount: labels.length, - beginAtZero: this.options.scaleBeginAtZero, - integersOnly: this.options.scaleIntegersOnly, - calculateYRange: function(currentHeight) { - var updatedRanges = helpers.calculateScaleRange( - dataTotal(), - currentHeight, - this.fontSize, - this.beginAtZero, - this.integersOnly - ); - helpers.extend(this, updatedRanges); - }, - xLabels: this.data.labels, - font: helpers.fontString(this.options.scaleFontSize, this.options.scaleFontStyle, this.options.scaleFontFamily), - lineWidth: this.options.scaleLineWidth, - lineColor: this.options.scaleLineColor, - showHorizontalLines: this.options.scaleShowHorizontalLines, - showVerticalLines: this.options.scaleShowVerticalLines, - gridLineWidth: (this.options.scaleShowGridLines) ? this.options.scaleGridLineWidth : 0, - gridLineColor: (this.options.scaleShowGridLines) ? this.options.scaleGridLineColor : "rgba(0,0,0,0)", - padding: (this.options.showScale) ? 0 : this.options.pointRadius + this.options.pointBorderWidth, - showLabels: this.options.scaleShowLabels, - display: this.options.showScale + var calculateYRange = function() { + this.min = null; + this.max = null; + + helpers.each(self.data.datasets, function(dataset) { + if (dataset.yAxisID === this.id) { + helpers.each(dataset.data, function(value) { + if (this.min === null) { + this.min = value.y; + } else if (value.y < this.min) { + this.min = value.y; + } + + if (this.max === null) { + this.max = value.y; + } else if (value.y > this.max) { + this.max = value.y; + } + }, this); + } + }, this); }; - if (this.options.scaleOverride) { - helpers.extend(scaleOptions, { - calculateYRange: helpers.noop, - steps: this.options.scaleSteps, - stepValue: this.options.scaleStepWidth, - min: this.options.scaleStartValue, - max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) + // Map of scale ID to scale object so we can lookup later + this.scales = {}; + + helpers.each(this.options.scales.xAxes, function(xAxisOptions) { + var ScaleClass = Chart.scales.getScaleConstructor(xAxisOptions.scaleType); + var scale = new ScaleClass({ + ctx: this.chart.ctx, + options: xAxisOptions, + calculateRange: calculateXRange, + id: xAxisOptions.id, }); - } - this.scale = new this.ScaleClass(scaleOptions); - }, - redraw: function() { + this.scales[scale.id] = scale; + }, this); + + 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, + id: yAxisOptions.id, + getPointPixelForValue: function(value, index, datasetIndex) { + return this.getPixelForValue(value); + } + }); + this.scales[scale.id] = scale; + }, this); }, draw: function(ease) { - var easingDecimal = ease || 1; this.clear(); - this.scale.draw(easingDecimal); + // Draw all the scales + helpers.each(this.scales, function(scale) { + scale.draw(this.chartArea); + }, this); // reverse for-loop for proper stacking for (var i = this.data.datasets.length - 1; i >= 0; i--) { var dataset = this.data.datasets[i]; - var datasetIndex = i; // Transition Point Locations helpers.each(dataset.metaData, function(point, index) { @@ -3449,7 +6051,6 @@ this.tooltip.transition(easingDecimal).draw(); }, events: function(e) { - // If exiting chart if (e.type == 'mouseout') { return this; @@ -3459,7 +6060,7 @@ // Find Active Elements this.active = function() { - switch (this.options.hoverMode) { + switch (this.options.hover.mode) { case 'single': return this.getElementAtEvent(e); case 'label': @@ -3472,36 +6073,33 @@ }.call(this); // On Hover hook - if (this.options.onHover) { - this.options.onHover.call(this, this.active); - } - - if (e.type == 'mouseup' || e.type == 'click') { - if (this.options.onClick) { - this.options.onClick.call(this, e, this.active); - } + if (this.options.hover.onHover) { + this.options.hover.onHover.call(this, this.active); } - var dataset; + var dataset; + var index; // Remove styling for last active (even if it may still be active) if (this.lastActive.length) { - switch (this.options.hoverMode) { + switch (this.options.hover.mode) { case 'single': dataset = this.data.datasets[this.lastActive[0]._datasetIndex]; + index = this.lastActive[0]._index; - this.lastActive[0].radius = dataset.pointRadius; - this.lastActive[0].backgroundColor = dataset.pointBackgroundColor; - this.lastActive[0].borderColor = dataset.pointBorderColor; - this.lastActive[0].borderWidth = dataset.pointBorderWidth; + this.lastActive[0]._model.radius = this.lastActive[0].custom && this.lastActive[0].custom.radius ? this.lastActive[0].custom.pointRadius : helpers.getValueAtIndexOrDefault(dataset.pointRadius, index, this.options.elements.point.radius); + this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.pointBackgroundColor, index, this.options.elements.point.backgroundColor); + this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.pointBorderColor, index, this.options.elements.point.borderColor); + this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.options.elements.point.borderWidth); break; case 'label': for (var i = 0; i < this.lastActive.length; i++) { dataset = this.data.datasets[this.lastActive[i]._datasetIndex]; + index = this.lastActive[i]._index; - this.lastActive[i].radius = dataset.pointRadius; - this.lastActive[i].backgroundColor = dataset.pointBackgroundColor; - this.lastActive[i].borderColor = dataset.pointBorderColor; - this.lastActive[i].borderWidth = dataset.pointBorderWidth; + 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': @@ -3512,24 +6110,26 @@ } // Built in hover styling - if (this.active.length && this.options.hoverMode) { - switch (this.options.hoverMode) { + 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].radius = dataset.pointHoverRadius || dataset.pointRadius + 2; - this.active[0].backgroundColor = dataset.pointHoverBackgroundColor || helpers.color(dataset.pointBackgroundColor).saturate(0.5).darken(0.35).rgbString(); - this.active[0].borderColor = dataset.pointHoverBorderColor || helpers.color(dataset.pointBorderColor).saturate(0.5).darken(0.35).rgbString(); - this.active[0].borderWidth = dataset.pointHoverBorderWidth || dataset.pointBorderWidth + 2; + 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.35).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.35).rgbString()); + this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.pointBorderWidth, index, this.active[0]._model.borderWidth + 2); break; case 'label': for (var i = 0; i < this.active.length; i++) { dataset = this.data.datasets[this.active[i]._datasetIndex]; + index = this.active[i]._index; - this.active[i].radius = dataset.pointHoverRadius || dataset.pointRadius + 2; - this.active[i].backgroundColor = dataset.pointHoverBackgroundColor || helpers.color(dataset.pointBackgroundColor).saturate(0.5).darken(0.35).rgbString(); - this.active[i].borderColor = dataset.pointHoverBorderColor || helpers.color(dataset.pointBorderColor).saturate(0.5).darken(0.35).rgbString(); - this.active[i].borderWidth = dataset.pointHoverBorderWidth || dataset.pointBorderWidth + 2; + 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.35).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.35).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': @@ -3539,30 +6139,27 @@ } } - // Built in Tooltips - if (this.options.showTooltips) { + 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, { - opacity: 1, _active: this.active, }); this.tooltip.update(); } else { // Inactive - helpers.extend(this.tooltip, { - opacity: 0, - }); + this.tooltip._model.opacity = 0; } } - // Hover animations this.tooltip.pivot(); @@ -3588,653 +6185,11 @@ // Remember Last Active this.lastActive = this.active; return this; + }, }); -}).call(this); - -(function(){ - "use strict"; - - var root = this, - Chart = root.Chart, - //Cache a local reference to Chart.helpers - helpers = Chart.helpers; - - var defaultConfig = { - //Boolean - Show a backdrop to the scale label - scaleShowLabelBackdrop : true, - - //String - The colour of the label backdrop - scaleBackdropColor : "rgba(255,255,255,0.75)", - - // Boolean - Whether the scale should begin at zero - scaleBeginAtZero : true, - - //Number - The backdrop padding above & below the label in pixels - scaleBackdropPaddingY : 2, - - //Number - The backdrop padding to the side of the label in pixels - scaleBackdropPaddingX : 2, - - //Boolean - Show line for each value in the scale - scaleShowLine : true, - - //Boolean - Stroke a line around each segment in the chart - segmentShowStroke : true, - - //String - The colour of the stroke on each segment. - segmentStrokeColor : "#fff", - - //Number - The width of the stroke value in pixels - segmentStrokeWidth : 2, - - //Number - Amount of animation steps - animationSteps : 100, - - //String - Animation easing effect. - animationEasing : "easeOutBounce", - - //Boolean - Whether to animate the rotation of the chart - animateRotate : true, - - //Boolean - Whether to animate scaling the chart from the centre - animateScale : false, - - //String - A legend template - legendTemplate : "" - }; - - - Chart.Type.extend({ - //Passing in a name registers this chart in the Chart namespace - name: "PolarArea", - //Providing a defaults will also register the deafults in the chart namespace - defaults : defaultConfig, - //Initialize is fired when the chart is initialized - Data is passed in as a parameter - //Config is automatically merged by the core of Chart.js, and is available at this.options - initialize: function(data){ - // Save data as a source for updating of values & methods - this.data = data; - - this.segments = []; - //Declare segment class as a chart instance specific class, so it can share props for this instance - this.SegmentArc = Chart.Arc.extend({ - showStroke : this.options.segmentShowStroke, - strokeWidth : this.options.segmentStrokeWidth, - strokeColor : this.options.segmentStrokeColor, - ctx : this.chart.ctx, - innerRadius : 0, - x : this.chart.width/2, - y : this.chart.height/2 - }); - this.scale = new Chart.RadialScale({ - display: this.options.showScale, - fontStyle: this.options.scaleFontStyle, - fontSize: this.options.scaleFontSize, - fontFamily: this.options.scaleFontFamily, - fontColor: this.options.scaleFontColor, - showLabels: this.options.scaleShowLabels, - showLabelBackdrop: this.options.scaleShowLabelBackdrop, - backdropColor: this.options.scaleBackdropColor, - backdropPaddingY : this.options.scaleBackdropPaddingY, - backdropPaddingX: this.options.scaleBackdropPaddingX, - lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0, - lineColor: this.options.scaleLineColor, - lineArc: true, - width: this.chart.width, - height: this.chart.height, - xCenter: this.chart.width/2, - yCenter: this.chart.height/2, - ctx : this.chart.ctx, - templateString: this.options.scaleLabel, - valuesCount: data.length - }); - - this.updateScaleRange(data); - - this.scale.update(); - - helpers.each(data,function(segment,index){ - this.addData(segment,index,true); - },this); - - //Set up tooltip events on the chart - if (this.options.showTooltips){ - helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ - var activeSegments = (evt.type !== 'mouseout') ? this.getSegmentsAtEvent(evt) : []; - helpers.each(this.segments,function(segment){ - segment.restore(["fillColor"]); - }); - helpers.each(activeSegments,function(activeSegment){ - activeSegment.fillColor = activeSegment.highlightColor; - }); - this.showTooltip(activeSegments); - }); - } - - this.render(); - }, - getSegmentsAtEvent : function(e){ - var segmentsArray = []; - - var location = helpers.getRelativePosition(e); - - helpers.each(this.segments,function(segment){ - if (segment.inRange(location.x,location.y)) segmentsArray.push(segment); - },this); - return segmentsArray; - }, - addData : function(segment, atIndex, silent){ - var index = atIndex || this.segments.length; - - this.segments.splice(index, 0, new this.SegmentArc({ - fillColor: segment.color, - highlightColor: segment.highlight || segment.color, - label: segment.label, - value: segment.value, - outerRadius: (this.options.animateScale) ? 0 : this.scale.calculateCenterOffset(segment.value), - circumference: (this.options.animateRotate) ? 0 : this.scale.getCircumference(), - startAngle: Math.PI * 1.5 - })); - if (!silent){ - this.reflow(); - this.update(); - } - }, - removeData: function(atIndex){ - var indexToDelete = (helpers.isNumber(atIndex)) ? atIndex : this.segments.length-1; - this.segments.splice(indexToDelete, 1); - this.reflow(); - this.update(); - }, - calculateTotal: function(data){ - this.total = 0; - helpers.each(data,function(segment){ - this.total += segment.value; - },this); - this.scale.valuesCount = this.segments.length; - }, - updateScaleRange: function(datapoints){ - var valuesArray = []; - helpers.each(datapoints,function(segment){ - valuesArray.push(segment.value); - }); - - var scaleSizes = (this.options.scaleOverride) ? - { - steps: this.options.scaleSteps, - stepValue: this.options.scaleStepWidth, - min: this.options.scaleStartValue, - max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) - } : - helpers.calculateScaleRange( - valuesArray, - helpers.min([this.chart.width, this.chart.height])/2, - this.options.scaleFontSize, - this.options.scaleBeginAtZero, - this.options.scaleIntegersOnly - ); - - helpers.extend( - this.scale, - scaleSizes, - { - size: helpers.min([this.chart.width, this.chart.height]), - xCenter: this.chart.width/2, - yCenter: this.chart.height/2 - } - ); - - }, - update : function(){ - - // Map new data to data points - if(this.data.length == this.segments.length){ - helpers.each(this.data, function(segment, i){ - helpers.extend(this.segments[i], { - fillColor: segment.color, - highlightColor: segment.highlight || segment.color, - label: segment.label, - value: segment.value, - }); - },this); - } else{ - // Data size changed without properly inserting, just redraw the chart - this.initialize(this.data); - } - - this.calculateTotal(this.segments); - - helpers.each(this.segments,function(segment){ - segment.save(); - }); - - this.reflow(); - this.render(); - }, - reflow : function(){ - helpers.extend(this.SegmentArc.prototype,{ - x : this.chart.width/2, - y : this.chart.height/2 - }); - this.updateScaleRange(this.segments); - this.scale.update(); - - helpers.extend(this.scale,{ - xCenter: this.chart.width/2, - yCenter: this.chart.height/2 - }); - - helpers.each(this.segments, function(segment){ - segment.update({ - outerRadius : this.scale.calculateCenterOffset(segment.value) - }); - }, this); - - }, - draw : function(ease){ - var easingDecimal = ease || 1; - //Clear & draw the canvas - this.clear(); - helpers.each(this.segments,function(segment, index){ - segment.transition({ - circumference : this.scale.getCircumference(), - outerRadius : this.scale.calculateCenterOffset(segment.value) - },easingDecimal); - - segment.endAngle = segment.startAngle + segment.circumference; - - // If we've removed the first segment we need to set the first one to - // start at the top. - if (index === 0){ - segment.startAngle = Math.PI * 1.5; - } - - //Check to see if it's the last segment, if not get the next and update the start angle - if (index < this.segments.length - 1){ - this.segments[index+1].startAngle = segment.endAngle; - } - segment.draw(); - }, this); - this.scale.draw(); - } - }); - -}).call(this); - -(function(){ - "use strict"; - - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; - - - - Chart.Type.extend({ - name: "Radar", - defaults:{ - //Boolean - Whether to show lines for each scale point - scaleShowLine : true, - - //Boolean - Whether we show the angle lines out of the radar - angleShowLineOut : true, - - //Boolean - Whether to show labels on the scale - scaleShowLabels : false, - - // Boolean - Whether the scale should begin at zero - scaleBeginAtZero : true, - - //String - Colour of the angle line - angleLineColor : "rgba(0,0,0,.1)", - - //Number - Pixel width of the angle line - angleLineWidth : 1, - - //String - Point label font declaration - pointLabelFontFamily : "'Arial'", - - //String - Point label font weight - pointLabelFontStyle : "normal", - - //Number - Point label font size in pixels - pointLabelFontSize : 10, - - //String - Point label font colour - pointLabelFontColor : "#666", - - //Boolean - Whether to show a dot for each point - pointDot : true, - - //Number - Radius of each point dot in pixels - pointDotRadius : 3, - - //Number - Pixel width of point dot stroke - pointDotStrokeWidth : 1, - - //Number - amount extra to add to the radius to cater for hit detection outside the drawn point - pointHitDetectionRadius : 20, - - //Boolean - Whether to show a stroke for datasets - datasetStroke : true, - - //Number - Pixel width of dataset stroke - datasetStrokeWidth : 2, - - //Boolean - Whether to fill the dataset with a colour - datasetFill : true, - - //String - A legend template - legendTemplate : "" - - }, - - initialize: function(data){ - // Save data as a source for updating of values & methods - this.data = data; - - this.PointClass = Chart.Point.extend({ - strokeWidth : this.options.pointDotStrokeWidth, - radius : this.options.pointDotRadius, - display: this.options.pointDot, - hitDetectionRadius : this.options.pointHitDetectionRadius, - ctx : this.chart.ctx - }); - - this.datasets = []; - - this.buildScale(data); - - //Set up tooltip events on the chart - if (this.options.showTooltips){ - helpers.bindEvents(this, this.options.tooltipEvents, function(evt){ - var activePointsCollection = (evt.type !== 'mouseout') ? this.getPointsAtEvent(evt) : []; - - this.eachPoints(function(point){ - point.restore(['fillColor', 'strokeColor']); - }); - helpers.each(activePointsCollection, function(activePoint){ - activePoint.fillColor = activePoint.highlightFill; - activePoint.strokeColor = activePoint.highlightStroke; - }); - - this.showTooltip(activePointsCollection); - }); - } - - //Iterate through each of the datasets, and build this into a property of the chart - helpers.each(data.datasets,function(dataset){ - - var datasetObject = { - label: dataset.label || null, - fillColor : dataset.fillColor, - strokeColor : dataset.strokeColor, - pointColor : dataset.pointColor, - pointStrokeColor : dataset.pointStrokeColor, - points : [] - }; - - this.datasets.push(datasetObject); - - helpers.each(dataset.data,function(dataPoint,index){ - //Add a new point for each piece of data, passing any required data to draw. - var pointPosition; - if (!this.scale.animation){ - pointPosition = this.scale.getPointPosition(index, this.scale.calculateCenterOffset(dataPoint)); - } - datasetObject.points.push(new this.PointClass({ - value : dataPoint, - label : data.labels[index], - datasetLabel: dataset.label, - x: (this.options.animation) ? this.scale.xCenter : pointPosition.x, - y: (this.options.animation) ? this.scale.yCenter : pointPosition.y, - strokeColor : dataset.pointStrokeColor, - fillColor : dataset.pointColor, - highlightFill : dataset.pointHighlightFill || dataset.pointColor, - highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor - })); - },this); - - },this); - - this.render(); - }, - eachPoints : function(callback){ - helpers.each(this.datasets,function(dataset){ - helpers.each(dataset.points,callback,this); - },this); - }, - - getPointsAtEvent : function(evt){ - var mousePosition = helpers.getRelativePosition(evt), - fromCenter = helpers.getAngleFromPoint({ - x: this.scale.xCenter, - y: this.scale.yCenter - }, mousePosition); - - var anglePerIndex = (Math.PI * 2) /this.scale.valuesCount, - pointIndex = Math.round((fromCenter.angle - Math.PI * 1.5) / anglePerIndex), - activePointsCollection = []; - - // If we're at the top, make the pointIndex 0 to get the first of the array. - if (pointIndex >= this.scale.valuesCount || pointIndex < 0){ - pointIndex = 0; - } - - if (fromCenter.distance <= this.scale.drawingArea){ - helpers.each(this.datasets, function(dataset){ - activePointsCollection.push(dataset.points[pointIndex]); - }); - } - - return activePointsCollection; - }, - - buildScale : function(data){ - this.scale = new Chart.RadialScale({ - display: this.options.showScale, - fontStyle: this.options.scaleFontStyle, - fontSize: this.options.scaleFontSize, - fontFamily: this.options.scaleFontFamily, - fontColor: this.options.scaleFontColor, - showLabels: this.options.scaleShowLabels, - showLabelBackdrop: this.options.scaleShowLabelBackdrop, - backdropColor: this.options.scaleBackdropColor, - backdropPaddingY : this.options.scaleBackdropPaddingY, - backdropPaddingX: this.options.scaleBackdropPaddingX, - lineWidth: (this.options.scaleShowLine) ? this.options.scaleLineWidth : 0, - lineColor: this.options.scaleLineColor, - angleLineColor : this.options.angleLineColor, - angleLineWidth : (this.options.angleShowLineOut) ? this.options.angleLineWidth : 0, - // Point labels at the edge of each line - pointLabelFontColor : this.options.pointLabelFontColor, - pointLabelFontSize : this.options.pointLabelFontSize, - pointLabelFontFamily : this.options.pointLabelFontFamily, - pointLabelFontStyle : this.options.pointLabelFontStyle, - height : this.chart.height, - width: this.chart.width, - xCenter: this.chart.width/2, - yCenter: this.chart.height/2, - ctx : this.chart.ctx, - templateString: this.options.scaleLabel, - labels: data.labels, - valuesCount: data.datasets[0].data.length - }); - - this.scale.setScaleSize(); - this.updateScaleRange(data.datasets); - this.scale.buildYLabels(); - }, - updateScaleRange: function(datasets){ - var valuesArray = (function(){ - var totalDataArray = []; - helpers.each(datasets,function(dataset){ - if (dataset.data){ - totalDataArray = totalDataArray.concat(dataset.data); - } - else { - helpers.each(dataset.points, function(point){ - totalDataArray.push(point.value); - }); - } - }); - return totalDataArray; - })(); - - - var scaleSizes = (this.options.scaleOverride) ? - { - steps: this.options.scaleSteps, - stepValue: this.options.scaleStepWidth, - min: this.options.scaleStartValue, - max: this.options.scaleStartValue + (this.options.scaleSteps * this.options.scaleStepWidth) - } : - helpers.calculateScaleRange( - valuesArray, - helpers.min([this.chart.width, this.chart.height])/2, - this.options.scaleFontSize, - this.options.scaleBeginAtZero, - this.options.scaleIntegersOnly - ); - - helpers.extend( - this.scale, - scaleSizes - ); - - }, - addData : function(valuesArray,label){ - //Map the values array for each of the datasets - this.scale.valuesCount++; - helpers.each(valuesArray,function(value,datasetIndex){ - var pointPosition = this.scale.getPointPosition(this.scale.valuesCount, this.scale.calculateCenterOffset(value)); - this.datasets[datasetIndex].points.push(new this.PointClass({ - value : value, - label : label, - datasetLabel: this.datasets[datasetIndex].label, - x: pointPosition.x, - y: pointPosition.y, - strokeColor : this.datasets[datasetIndex].pointStrokeColor, - fillColor : this.datasets[datasetIndex].pointColor - })); - },this); - - this.scale.labels.push(label); - - this.reflow(); - - this.update(); - }, - removeData : function(){ - this.scale.valuesCount--; - this.scale.labels.shift(); - helpers.each(this.datasets,function(dataset){ - dataset.points.shift(); - },this); - this.reflow(); - this.update(); - }, - update : function(){ - //Iterate through each of the datasets, and build this into a property of the chart - helpers.each(this.data.datasets,function(dataset,datasetIndex){ - - helpers.extend(this.datasets[datasetIndex], { - label : dataset.label || null, - fillColor : dataset.fillColor, - strokeColor : dataset.strokeColor, - pointColor : dataset.pointColor, - pointStrokeColor : dataset.pointStrokeColor, - }); - - helpers.each(dataset.data,function(dataPoint,index){ - helpers.extend(this.datasets[datasetIndex].points[index], { - value : dataPoint, - label : this.data.labels[index], - datasetLabel: dataset.label, - strokeColor : dataset.pointStrokeColor, - fillColor : dataset.pointColor, - highlightFill : dataset.pointHighlightFill || dataset.pointColor, - highlightStroke : dataset.pointHighlightStroke || dataset.pointStrokeColor - }); - },this); - - },this); - - this.eachPoints(function(point){ - point.save(); - }); - this.reflow(); - this.render(); - }, - reflow: function(){ - helpers.extend(this.scale, { - width : this.chart.width, - height: this.chart.height, - size : helpers.min([this.chart.width, this.chart.height]), - xCenter: this.chart.width/2, - yCenter: this.chart.height/2 - }); - this.updateScaleRange(this.datasets); - this.scale.setScaleSize(); - this.scale.buildYLabels(); - }, - draw : function(ease){ - var easeDecimal = ease || 1, - ctx = this.chart.ctx; - this.clear(); - this.scale.draw(); - - helpers.each(this.datasets,function(dataset){ - - //Transition each point first so that the line and point drawing isn't out of sync - helpers.each(dataset.points,function(point,index){ - if (point.hasValue()){ - point.transition(this.scale.getPointPosition(index, this.scale.calculateCenterOffset(point.value)), easeDecimal); - } - },this); - - - - //Draw the line between all the points - ctx.lineWidth = this.options.datasetStrokeWidth; - ctx.strokeStyle = dataset.strokeColor; - ctx.beginPath(); - helpers.each(dataset.points,function(point,index){ - if (index === 0){ - ctx.moveTo(point.x,point.y); - } - else{ - ctx.lineTo(point.x,point.y); - } - },this); - ctx.closePath(); - ctx.stroke(); - - ctx.fillStyle = dataset.fillColor; - ctx.fill(); - - //Now draw the points over the line - //A little inefficient double looping, but better than the line - //lagging behind the point positions - helpers.each(dataset.points,function(point){ - if (point.hasValue()){ - point.draw(); - } - }); - - },this); - - } - - }); - - - - - }).call(this); !function e(r,t,n){function a(i,u){if(!t[i]){if(!r[i]){var l="function"==typeof require&&require;if(!u&&l)return l(i,!0);if(s)return s(i,!0);var h=new Error("Cannot find module '"+i+"'");throw h.code="MODULE_NOT_FOUND",h}var o=t[i]={exports:{}};r[i][0].call(o.exports,function(e){var t=r[i][1][e];return a(t?t:e)},o,o.exports,e,r,t,n)}return t[i].exports}for(var s="function"==typeof require&&require,i=0;i=n?n/12.92:Math.pow((n+.055)/1.055,2.4)}return.2126*r[0]+.7152*r[1]+.0722*r[2]},contrast:function(e){var r=this.luminosity(),t=e.luminosity();return r>t?(r+.05)/(t+.05):(t+.05)/(r+.05)},level:function(e){var r=this.contrast(e);return r>=7.1?"AAA":r>=4.5?"AA":""},dark:function(){var e=this.values.rgb,r=(299*e[0]+587*e[1]+114*e[2])/1e3;return 128>r},light:function(){return!this.dark()},negate:function(){for(var e=[],r=0;3>r;r++)e[r]=255-this.values.rgb[r];return this.setValues("rgb",e),this},lighten:function(e){return this.values.hsl[2]+=this.values.hsl[2]*e,this.setValues("hsl",this.values.hsl),this},darken:function(e){return this.values.hsl[2]-=this.values.hsl[2]*e,this.setValues("hsl",this.values.hsl),this},saturate:function(e){return this.values.hsl[1]+=this.values.hsl[1]*e,this.setValues("hsl",this.values.hsl),this},desaturate:function(e){return this.values.hsl[1]-=this.values.hsl[1]*e,this.setValues("hsl",this.values.hsl),this},whiten:function(e){return this.values.hwb[1]+=this.values.hwb[1]*e,this.setValues("hwb",this.values.hwb),this},blacken:function(e){return this.values.hwb[2]+=this.values.hwb[2]*e,this.setValues("hwb",this.values.hwb),this},greyscale:function(){var e=this.values.rgb,r=.3*e[0]+.59*e[1]+.11*e[2];return this.setValues("rgb",[r,r,r]),this},clearer:function(e){return this.setValues("alpha",this.values.alpha-this.values.alpha*e),this},opaquer:function(e){return this.setValues("alpha",this.values.alpha+this.values.alpha*e),this},rotate:function(e){var r=this.values.hsl[0];return r=(r+e)%360,r=0>r?360+r:r,this.values.hsl[0]=r,this.setValues("hsl",this.values.hsl),this},mix:function(e,r){r=1-(null==r?.5:r);for(var t=2*r-1,n=this.alpha()-e.alpha(),a=((t*n==-1?t:(t+n)/(1+t*n))+1)/2,s=1-a,i=this.rgbArray(),u=e.rgbArray(),l=0;lr&&(r+=360),n=(u+l)/2,t=l==u?0:.5>=n?h/(l+u):h/(2-l-u),[r,100*t,100*n]}function s(e){var r,t,n,a=e[0],s=e[1],i=e[2],u=Math.min(a,s,i),l=Math.max(a,s,i),h=l-u;return t=0==l?0:h/l*1e3/10,l==u?r=0:a==l?r=(s-i)/h:s==l?r=2+(i-a)/h:i==l&&(r=4+(a-s)/h),r=Math.min(60*r,360),0>r&&(r+=360),n=l/255*1e3/10,[r,t,n]}function i(e){var r=e[0],t=e[1],n=e[2],s=a(e)[0],i=1/255*Math.min(r,Math.min(t,n)),n=1-1/255*Math.max(r,Math.max(t,n));return[s,100*i,100*n]}function u(e){var r,t,n,a,s=e[0]/255,i=e[1]/255,u=e[2]/255;return a=Math.min(1-s,1-i,1-u),r=(1-s-a)/(1-a)||0,t=(1-i-a)/(1-a)||0,n=(1-u-a)/(1-a)||0,[100*r,100*t,100*n,100*a]}function l(e){return X[JSON.stringify(e)]}function h(e){var r=e[0]/255,t=e[1]/255,n=e[2]/255;r=r>.04045?Math.pow((r+.055)/1.055,2.4):r/12.92,t=t>.04045?Math.pow((t+.055)/1.055,2.4):t/12.92,n=n>.04045?Math.pow((n+.055)/1.055,2.4):n/12.92;var a=.4124*r+.3576*t+.1805*n,s=.2126*r+.7152*t+.0722*n,i=.0193*r+.1192*t+.9505*n;return[100*a,100*s,100*i]}function o(e){var r,t,n,a=h(e),s=a[0],i=a[1],u=a[2];return s/=95.047,i/=100,u/=108.883,s=s>.008856?Math.pow(s,1/3):7.787*s+16/116,i=i>.008856?Math.pow(i,1/3):7.787*i+16/116,u=u>.008856?Math.pow(u,1/3):7.787*u+16/116,r=116*i-16,t=500*(s-i),n=200*(i-u),[r,t,n]}function c(e){return J(o(e))}function v(e){var r,t,n,a,s,i=e[0]/360,u=e[1]/100,l=e[2]/100;if(0==u)return s=255*l,[s,s,s];t=.5>l?l*(1+u):l+u-l*u,r=2*l-t,a=[0,0,0];for(var h=0;3>h;h++)n=i+1/3*-(h-1),0>n&&n++,n>1&&n--,s=1>6*n?r+6*(t-r)*n:1>2*n?t:2>3*n?r+(t-r)*(2/3-n)*6:r,a[h]=255*s;return a}function f(e){var r,t,n=e[0],a=e[1]/100,s=e[2]/100;return s*=2,a*=1>=s?s:2-s,t=(s+a)/2,r=2*a/(s+a),[n,100*r,100*t]}function d(e){return i(v(e))}function p(e){return u(v(e))}function m(e){return l(v(e))}function y(e){var r=e[0]/60,t=e[1]/100,n=e[2]/100,a=Math.floor(r)%6,s=r-Math.floor(r),i=255*n*(1-t),u=255*n*(1-t*s),l=255*n*(1-t*(1-s)),n=255*n;switch(a){case 0:return[n,l,i];case 1:return[u,n,i];case 2:return[i,n,l];case 3:return[i,u,n];case 4:return[l,i,n];case 5:return[n,i,u]}}function w(e){var r,t,n=e[0],a=e[1]/100,s=e[2]/100;return t=(2-a)*s,r=a*s,r/=1>=t?t:2-t,r=r||0,t/=2,[n,100*r,100*t]}function k(e){return i(y(e))}function M(e){return u(y(e))}function S(e){return l(y(e))}function x(e){var t,n,a,s,i=e[0]/360,u=e[1]/100,l=e[2]/100,h=u+l;switch(h>1&&(u/=h,l/=h),t=Math.floor(6*i),n=1-l,a=6*i-t,0!=(1&t)&&(a=1-a),s=u+a*(n-u),t){default:case 6:case 0:r=n,g=s,b=u;break;case 1:r=s,g=n,b=u;break;case 2:r=u,g=n,b=s;break;case 3:r=u,g=s,b=n;break;case 4:r=s,g=u,b=n;break;case 5:r=n,g=u,b=s}return[255*r,255*g,255*b]}function V(e){return a(x(e))}function q(e){return s(x(e))}function A(e){return u(x(e))}function C(e){return l(x(e))}function F(e){var r,t,n,a=e[0]/100,s=e[1]/100,i=e[2]/100,u=e[3]/100;return r=1-Math.min(1,a*(1-u)+u),t=1-Math.min(1,s*(1-u)+u),n=1-Math.min(1,i*(1-u)+u),[255*r,255*t,255*n]}function N(e){return a(F(e))}function z(e){return s(F(e))}function I(e){return i(F(e))}function O(e){return l(F(e))}function E(e){var r,t,n,a=e[0]/100,s=e[1]/100,i=e[2]/100;return r=3.2406*a+-1.5372*s+i*-.4986,t=a*-.9689+1.8758*s+.0415*i,n=.0557*a+s*-.204+1.057*i,r=r>.0031308?1.055*Math.pow(r,1/2.4)-.055:r=12.92*r,t=t>.0031308?1.055*Math.pow(t,1/2.4)-.055:t=12.92*t,n=n>.0031308?1.055*Math.pow(n,1/2.4)-.055:n=12.92*n,r=Math.min(Math.max(0,r),1),t=Math.min(Math.max(0,t),1),n=Math.min(Math.max(0,n),1),[255*r,255*t,255*n]}function H(e){var r,t,n,a=e[0],s=e[1],i=e[2];return a/=95.047,s/=100,i/=108.883,a=a>.008856?Math.pow(a,1/3):7.787*a+16/116,s=s>.008856?Math.pow(s,1/3):7.787*s+16/116,i=i>.008856?Math.pow(i,1/3):7.787*i+16/116,r=116*s-16,t=500*(a-s),n=200*(s-i),[r,t,n]}function U(e){return J(H(e))}function j(e){var r,t,n,a,s=e[0],i=e[1],u=e[2];return 8>=s?(t=100*s/903.3,a=7.787*(t/100)+16/116):(t=100*Math.pow((s+16)/116,3),a=Math.pow(t/100,1/3)),r=.008856>=r/95.047?r=95.047*(i/500+a-16/116)/7.787:95.047*Math.pow(i/500+a,3),n=.008859>=n/108.883?n=108.883*(a-u/200-16/116)/7.787:108.883*Math.pow(a-u/200,3),[r,t,n]}function J(e){var r,t,n,a=e[0],s=e[1],i=e[2];return r=Math.atan2(i,s),t=360*r/2/Math.PI,0>t&&(t+=360),n=Math.sqrt(s*s+i*i),[a,n,t]}function R(e){return E(j(e))}function $(e){var r,t,n,a=e[0],s=e[1],i=e[2];return n=i/360*2*Math.PI,r=s*Math.cos(n),t=s*Math.sin(n),[a,r,t]}function D(e){return j($(e))}function P(e){return R($(e))}function _(e){return W[e]}function L(e){return a(_(e))}function T(e){return s(_(e))}function B(e){return i(_(e))}function G(e){return u(_(e))}function K(e){return o(_(e))}function Q(e){return h(_(e))}t.exports={rgb2hsl:a,rgb2hsv:s,rgb2hwb:i,rgb2cmyk:u,rgb2keyword:l,rgb2xyz:h,rgb2lab:o,rgb2lch:c,hsl2rgb:v,hsl2hsv:f,hsl2hwb:d,hsl2cmyk:p,hsl2keyword:m,hsv2rgb:y,hsv2hsl:w,hsv2hwb:k,hsv2cmyk:M,hsv2keyword:S,hwb2rgb:x,hwb2hsl:V,hwb2hsv:q,hwb2cmyk:A,hwb2keyword:C,cmyk2rgb:F,cmyk2hsl:N,cmyk2hsv:z,cmyk2hwb:I,cmyk2keyword:O,keyword2rgb:_,keyword2hsl:L,keyword2hsv:T,keyword2hwb:B,keyword2cmyk:G,keyword2lab:K,keyword2xyz:Q,xyz2rgb:E,xyz2lab:H,xyz2lch:U,lab2xyz:j,lab2rgb:R,lab2lch:J,lch2lab:$,lch2xyz:D,lch2rgb:P};var W={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]},X={};for(var Y in W)X[JSON.stringify(W[Y])]=Y},{}],3:[function(e,r,t){var n=e("./conversions"),a=function(){return new h};for(var s in n){a[s+"Raw"]=function(e){return function(r){return"number"==typeof r&&(r=Array.prototype.slice.call(arguments)),n[e](r)}}(s);var i=/(\w+)2(\w+)/.exec(s),u=i[1],l=i[2];a[u]=a[u]||{},a[u][l]=a[s]=function(e){return function(r){"number"==typeof r&&(r=Array.prototype.slice.call(arguments));var t=n[e](r);if("string"==typeof t||void 0===t)return t;for(var a=0;ar||e[3]&&e[3]<1?c(e,r):"rgb("+e[0]+", "+e[1]+", "+e[2]+")"}function c(e,r){return void 0===r&&(r=void 0!==e[3]?e[3]:1),"rgba("+e[0]+", "+e[1]+", "+e[2]+", "+r+")"}function g(e,r){if(1>r||e[3]&&e[3]<1)return v(e,r);var t=Math.round(e[0]/255*100),n=Math.round(e[1]/255*100),a=Math.round(e[2]/255*100);return"rgb("+t+"%, "+n+"%, "+a+"%)"}function v(e,r){var t=Math.round(e[0]/255*100),n=Math.round(e[1]/255*100),a=Math.round(e[2]/255*100);return"rgba("+t+"%, "+n+"%, "+a+"%, "+(r||e[3]||1)+")"}function f(e,r){return 1>r||e[3]&&e[3]<1?d(e,r):"hsl("+e[0]+", "+e[1]+"%, "+e[2]+"%)"}function d(e,r){return void 0===r&&(r=void 0!==e[3]?e[3]:1),"hsla("+e[0]+", "+e[1]+"%, "+e[2]+"%, "+r+")"}function b(e,r){return void 0===r&&(r=void 0!==e[3]?e[3]:1),"hwb("+e[0]+", "+e[1]+"%, "+e[2]+"%"+(void 0!==r&&1!==r?", "+r:"")+")"}function p(e){return k[e.slice(0,3)]}function m(e,r,t){return Math.min(Math.max(r,e),t)}function y(e){var r=e.toString(16).toUpperCase();return r.length<2?"0"+r:r}var w=e("color-name");r.exports={getRgba:n,getHsla:a,getRgb:i,getHsl:u,getHwb:s,getAlpha:l,hexString:h,rgbString:o,rgbaString:c,percentString:g,percentaString:v,hslString:f,hslaString:d,hwbString:b,keyword:p};var k={};for(var M in w)k[w[M]]=M},{"color-name":5}],5:[function(e,r,t){r.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/Chart.min.js b/Chart.min.js index 9490ea1e8..f9d5c4179 100644 --- a/Chart.min.js +++ b/Chart.min.js @@ -1,13 +1,14 @@ /*! * Chart.js * http://chartjs.org/ - * Version: 1.0.2 + * 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,i=t.Chart,e=function(t){this.canvas=t.canvas,this.ctx=t;var i=function(t,i){return t["offset"+i]?t["offset"+i]:document.defaultView.getComputedStyle(t).getPropertyValue(i)},e=this.width=i(t.canvas,"Width")||t.canvas.width,s=this.height=i(t.canvas,"Height")||t.canvas.height;return t.canvas.width=e,t.canvas.height=s,e=this.width=t.canvas.width,s=this.height=t.canvas.height,this.aspectRatio=this.width/this.height,a.retinaScale(this),this};e.defaults={global:{animation:{duration:1e3,easing:"easeOutQuart",onProgress:function(){},onComplete:function(){}},responsive:!1,maintainAspectRatio:!0,events:["mousemove","mouseout","click","touchstart","touchmove","touchend"],hover:{mode:"label",onHover:null,animationDuration:400},onClick:null,tooltips:{enabled:!0,custom:null,backgroundColor:"rgba(0,0,0,0.8)",fontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",fontSize:14,fontStyle:"normal",fontColor:"#fff",titleFontFamily:"'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",titleFontSize:14,titleFontStyle:"bold",titleFontColor:"#fff",yPadding:6,xPadding:6,caretSize:8,cornerRadius:6,xOffset:10,template:"<%if (label){%><%=label%>: <%}%><%= value %>",multiTemplate:"<%if (datasetLabel){%><%=datasetLabel%>: <%}%><%= value %>",multiKeyBackground:"#fff"},defaultColor:"rgba(0,0,0,0.1)"}},e.types={};var a=e.helpers={},s=a.each=function(t,i,e){var a=Array.prototype.slice.call(arguments,3);if(t)if(t.length===+t.length){var s;for(s=0;s=0;a--){var s=t[a];if(i(s))return s}},a.inherits=function(t){var i=this,e=t&&t.hasOwnProperty("constructor")?t.constructor:function(){return i.apply(this,arguments)},a=function(){this.constructor=e};return a.prototype=i.prototype,e.prototype=new a,e.extend=h,t&&o(e.prototype,t),e.__super__=i.prototype,e}),l=a.noop=function(){},c=a.uid=function(){var t=0;return function(){return"chart-"+t++}}(),u=a.warn=function(t){window.console&&"function"==typeof window.console.warn&&console.warn(t)},d=a.amd="function"==typeof define&&define.amd,p=a.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},g=a.max=function(t){return Math.max.apply(Math,t)},f=a.min=function(t){return Math.min.apply(Math,t)},v=(a.sign=function(t){return Math.sign?Math.sign(t):(t=+t,0===t||isNaN(t)?t:t>0?1:-1)},a.cap=function(t,i,e){if(p(i)){if(t>i)return i}else if(p(e)&&e>t)return e;return t},a.getDecimalPlaces=function(t){if(t%1!==0&&p(t)){var i=t.toString();if(i.indexOf("e-")<0)return i.split(".")[1].length;if(i.indexOf(".")<0)return parseInt(i.split("e-")[1]);var e=i.split(".")[1].split("e-");return e[0].length+parseInt(e[1])}return 0},a.toRadians=function(t){return t*(Math.PI/180)},a.toDegrees=function(t){return t*(180/Math.PI)},a.getAngleFromPoint=function(t,i){var e=i.x-t.x,a=i.y-t.y,s=Math.sqrt(e*e+a*a),n=2*Math.PI+Math.atan2(a,e);return 0>e&&0>a&&(n+=2*Math.PI),{angle:n,distance:s}},a.aliasPixel=function(t){return t%2===0?0:.5},a.splineCurve=function(t,i,e,a){var s=Math.sqrt(Math.pow(i.x-t.x,2)+Math.pow(i.y-t.y,2)),n=Math.sqrt(Math.pow(e.x-i.x,2)+Math.pow(e.y-i.y,2)),o=a*s/(s+n),r=a*n/(s+n);return{next:{x:i.x-o*(e.x-t.x),y:i.y-o*(e.y-t.y)},previous:{x:i.x+r*(e.x-t.x),y:i.y+r*(e.y-t.y)}}},a.calculateOrderOfMagnitude=function(t){return Math.floor(Math.log(t)/Math.LN10)}),m=(a.calculateScaleRange=function(t,i,e,a,s){var n=2,o=Math.floor(i/(1.5*e)),r=n>=o,h=g(t),l=f(t);h===l&&(h+=.5,l>=.5&&!a?l-=.5:h+=.5);for(var c=Math.abs(h-l),u=v(c),d=Math.ceil(h/(1*Math.pow(10,u)))*Math.pow(10,u),p=a?0:Math.floor(l/(1*Math.pow(10,u)))*Math.pow(10,u),m=d-p,b=Math.pow(10,u),x=Math.round(m/b);(x>o||o>2*x)&&!r;)if(x>o)b*=2,x=Math.round(m/b),x%1!==0&&(r=!0);else if(s&&u>=0){if(b/2%1!==0)break;b/=2,x=Math.round(m/b)}else b/=2,x=Math.round(m/b);return r&&(x=n,b=m/x),{steps:x,stepValue:b,min:p,max:p+x*b}},a.niceNum=function(t,i){var e,a=Math.floor(Math.log10(t)),s=t/Math.pow(10,a);return e=i?1.5>s?1:3>s?2:7>s?5:10:1>=s?1:2>=s?2:5>=s?5:10,e*Math.pow(10,a)},a.template=function(t,i){function e(t,i){var e=/\W/.test(t)?new Function("obj","var p=[],print=function(){p.push.apply(p,arguments);};with(obj){p.push('"+t.replace(/[\r\t\n]/g," ").split("<%").join(" ").replace(/((^|%>)[^\t]*)'/g,"$1\r").replace(/\t=(.*?)%>/g,"',$1,'").split(" ").join("');").split("%>").join("p.push('").split("\r").join("\\'")+"');}return p.join('');"):a[t]=a[t];return i?e(i):e}if(t instanceof Function)return t(i);var a={};return e(t,i)}),b=(a.generateLabels=function(t,i,e,a){var n=new Array(i);return t&&s(n,function(i,s){n[s]=m(t,{value:e+a*(s+1)})}),n},a.easingEffects={linear:function(t){return t},easeInQuad:function(t){return t*t},easeOutQuad:function(t){return-1*t*(t-2)},easeInOutQuad:function(t){return(t/=.5)<1?.5*t*t:-0.5*(--t*(t-2)-1)},easeInCubic:function(t){return t*t*t},easeOutCubic:function(t){return 1*((t=t/1-1)*t*t+1)},easeInOutCubic:function(t){return(t/=.5)<1?.5*t*t*t:.5*((t-=2)*t*t+2)},easeInQuart:function(t){return t*t*t*t},easeOutQuart:function(t){return-1*((t=t/1-1)*t*t*t-1)},easeInOutQuart:function(t){return(t/=.5)<1?.5*t*t*t*t:-0.5*((t-=2)*t*t*t-2)},easeInQuint:function(t){return 1*(t/=1)*t*t*t*t},easeOutQuint:function(t){return 1*((t=t/1-1)*t*t*t*t+1)},easeInOutQuint:function(t){return(t/=.5)<1?.5*t*t*t*t*t:.5*((t-=2)*t*t*t*t+2)},easeInSine:function(t){return-1*Math.cos(t/1*(Math.PI/2))+1},easeOutSine:function(t){return 1*Math.sin(t/1*(Math.PI/2))},easeInOutSine:function(t){return-0.5*(Math.cos(Math.PI*t/1)-1)},easeInExpo:function(t){return 0===t?1:1*Math.pow(2,10*(t/1-1))},easeOutExpo:function(t){return 1===t?1:1*(-Math.pow(2,-10*t/1)+1)},easeInOutExpo:function(t){return 0===t?0:1===t?1:(t/=.5)<1?.5*Math.pow(2,10*(t-1)):.5*(-Math.pow(2,-10*--t)+2)},easeInCirc:function(t){return t>=1?t:-1*(Math.sqrt(1-(t/=1)*t)-1)},easeOutCirc:function(t){return 1*Math.sqrt(1-(t=t/1-1)*t)},easeInOutCirc:function(t){return(t/=.5)<1?-0.5*(Math.sqrt(1-t*t)-1):.5*(Math.sqrt(1-(t-=2)*t)+1)},easeInElastic:function(t){var i=1.70158,e=0,a=1;return 0===t?0:1==(t/=1)?1:(e||(e=.3),at?-.5*a*Math.pow(2,10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e):a*Math.pow(2,-10*(t-=1))*Math.sin(2*(1*t-i)*Math.PI/e)*.5+1)},easeInBack:function(t){var i=1.70158;return 1*(t/=1)*t*((i+1)*t-i)},easeOutBack:function(t){var i=1.70158;return 1*((t=t/1-1)*t*((i+1)*t+i)+1)},easeInOutBack:function(t){var i=1.70158;return(t/=.5)<1?.5*t*t*(((i*=1.525)+1)*t-i):.5*((t-=2)*t*(((i*=1.525)+1)*t+i)+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=a.requestAnimFrame=function(){return window.requestAnimationFrame||window.webkitRequestAnimationFrame||window.mozRequestAnimationFrame||window.oRequestAnimationFrame||window.msRequestAnimationFrame||function(t){return window.setTimeout(t,1e3/60)}}(),y=(a.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),a.animationLoop=function(t,i,e,a,s,n){var o=0,r=b[e]||b.linear,h=function(){o++;var e=o/i,l=r(e);t.call(n,l,e,o),a.call(n,l,e),i>o?n.animationFrame=x(h):s.apply(n)};x(h)},a.getRelativePosition=function(t){var i,e,a=t.originalEvent||t,s=t.currentTarget||t.srcElement,n=s.getBoundingClientRect();return a.touches?(i=a.touches[0].clientX-n.left,e=a.touches[0].clientY-n.top):(i=a.clientX-n.left,e=a.clientY-n.top),{x:i,y:e}},a.addEvent=function(t,i,e){t.addEventListener?t.addEventListener(i,e):t.attachEvent?t.attachEvent("on"+i,e):t["on"+i]=e}),w=a.removeEvent=function(t,i,e){t.removeEventListener?t.removeEventListener(i,e,!1):t.detachEvent?t.detachEvent("on"+i,e):t["on"+i]=l},C=(a.bindEvents=function(t,i,e){t.events||(t.events={}),s(i,function(i){t.events[i]=function(){e.apply(t,arguments)},y(t.chart.canvas,i,t.events[i])})},a.unbindEvents=function(t,i){s(i,function(i,e){w(t.chart.canvas,e,i)})}),k=a.getMaximumWidth=function(t){var i=t.parentNode,e=parseInt(S(i,"padding-left"))+parseInt(S(i,"padding-right"));return i.clientWidth-e},A=a.getMaximumHeight=function(t){var i=t.parentNode,e=parseInt(S(i,"padding-bottom"))+parseInt(S(i,"padding-top"));return i.clientHeight-e},S=a.getStyle=function(t,i){return t.currentStyle?t.currentStyle[i]:document.defaultView.getComputedStyle(t,null).getPropertyValue(i)},P=(a.getMaximumSize=a.getMaximumWidth,a.retinaScale=function(t){var i=t.ctx,e=t.canvas.width,a=t.canvas.height;window.devicePixelRatio&&(i.canvas.style.width=e+"px",i.canvas.style.height=a+"px",i.canvas.height=a*window.devicePixelRatio,i.canvas.width=e*window.devicePixelRatio,i.scale(window.devicePixelRatio,window.devicePixelRatio))}),M=a.clear=function(t){t.ctx.clearRect(0,0,t.width,t.height)},_=a.fontString=function(t,i,e){return i+" "+t+"px "+e},R=a.longestText=function(t,i,e){t.font=i;var a=0;return s(e,function(i){var e=t.measureText(i).width;a=e>a?e:a}),a},I=a.drawRoundedRectangle=function(t,i,e,a,s,n){t.beginPath(),t.moveTo(i+n,e),t.lineTo(i+a-n,e),t.quadraticCurveTo(i+a,e,i+a,e+n),t.lineTo(i+a,e+s-n),t.quadraticCurveTo(i+a,e+s,i+a-n,e+s),t.lineTo(i+n,e+s),t.quadraticCurveTo(i,e+s,i,e+s-n),t.lineTo(i,e+n),t.quadraticCurveTo(i,e,i+n,e),t.closePath()};a.color=function(t){return window.Color?window.Color(t):(console.log("Color.js not found!"),t)},a.isArray=function(t){return Array.isArray?Array.isArray(t):"[object Array]"===Object.prototype.toString.call(arg)};e.instances={},e.Type=function(t,i){this.data=t.data,this.options=t.options,this.chart=i,this.id=c(),e.instances[this.id]=this,this.options.responsive&&this.resize(),this.initialize.call(this)},o(e.Type.prototype,{initialize:function(){return this},clear:function(){return M(this.chart),this},stop:function(){return e.animationService.cancelAnimation(this),this},resize:function(){this.stop();var t=this.chart.canvas,i=k(this.chart.canvas),e=this.options.maintainAspectRatio?i/this.chart.aspectRatio:A(this.chart.canvas);return t.width=this.chart.width=i,t.height=this.chart.height=e,P(this.chart),this},redraw:l,render:function(t){if(0!==this.options.animation.duration){var i=new e.Animation;i.numSteps=(t||this.options.animation.duration)/16.66,i.easing=this.options.animation.easing,i.render=function(t,i){var e=a.easingEffects[i.easing],s=i.currentStep/i.numSteps,n=e(s);t.draw(n,s,i.currentStep)},i.onAnimationProgress=this.options.onAnimationProgress,i.onAnimationComplete=this.options.onAnimationComplete,e.animationService.addAnimation(this,i,t)}else this.draw(),this.options.onAnimationComplete.call(this);return this},eachElement:function(t){a.each(this.data.datasets,function(i,e){a.each(i.metaData,t,this,i.metaData,e)},this)},eachValue:function(t){a.each(this.data.datasets,function(i,e){a.each(i.data,t,this,e)},this)},eachDataset:function(t){a.each(this.data.datasets,t,this)},getElementsAtEvent:function(t){for(var i,e=[],s=a.getRelativePosition(t),n=function(t){e.push(t.metaData[i])},o=0;o0||t.borderWidth>0)&&(i.beginPath(),i.arc(t.x,t.y,t.radius,0,2*Math.PI),i.closePath(),i.strokeStyle=t.borderColor||e.defaults.global.defaultColor,i.lineWidth=t.borderWidth||e.defaults.global.defaultColor,i.fillStyle=t.backgroundColor||e.defaults.global.defaultColor,i.fill(),i.stroke())}}),e.Line=e.Element.extend({draw:function(){var t=this._vm,i=this._chart.ctx;a.each(t._points,function(e,a){if(0===a)i.moveTo(e._vm.x,e._vm.y);else if(t._tension>0,0)i.lineTo(e._vm.x,e._vm.y);else{var s=this.previousPoint(e,t._points,a);i.bezierCurveTo(s._vm.controlPointNextX,s._vm.controlPointNextY,e._vm.controlPointPreviousX,e._vm.controlPointPreviousY,e._vm.x,e._vm.y)}},this),t._points.length>0&&(i.lineTo(t._points[t._points.length-1].x,t.scaleZero),i.lineTo(t._points[0].x,t.scaleZero),i.fillStyle=t.backgroundColor||e.defaults.global.defaultColor,i.closePath(),i.fill()),i.lineWidth=t.borderWidth||e.defaults.global.defaultColor,i.strokeStyle=t.borderColor||e.defaults.global.defaultColor,i.beginPath(),a.each(t._points,function(e,a){if(0===a)i.moveTo(e._vm.x,e._vm.y);else if(t._tension>0,0)i.lineTo(e._vm.x,e._vm.y);else{var s=this.previousPoint(e,t._points,a);i.bezierCurveTo(s._vm.controlPointNextX,s._vm.controlPointNextY,e._vm.controlPointPreviousX,e._vm.controlPointPreviousY,e._vm.x,e._vm.y)}},this),i.stroke()},previousPoint:function(t,i,e){return a.findPreviousWhere(i,function(){return!0},e)||t}}),e.Arc=e.Element.extend({inRange:function(t,i){var e=this._vm,s=a.getAngleFromPoint(e,{x:t,y:i}),n=s.angle>=e.startAngle&&s.angle<=e.endAngle,o=s.distance>=e.innerRadius&&s.distance<=e.outerRadius;return n&&o},tooltipPosition:function(){var t=this._vm,i=t.startAngle+(t.endAngle-t.startAngle)/2,e=(t.outerRadius-t.innerRadius)/2+t.innerRadius;return{x:t.x+Math.cos(i)*e,y:t.y+Math.sin(i)*e}},draw:function(){var t=this._chart.ctx,i=this._vm;t.beginPath(),t.arc(i.x,i.y,i.outerRadius,i.startAngle,i.endAngle),t.arc(i.x,i.y,i.innerRadius,i.endAngle,i.startAngle,!0),t.closePath(),t.strokeStyle=i.borderColor,t.lineWidth=i.borderWidth,t.fillStyle=i.backgroundColor,t.fill(),t.lineJoin="bevel",i.borderWidth&&t.stroke()}}),e.Rectangle=e.Element.extend({draw:function(){var t=this._vm,i=this.ctx,e=t.width/2,a=t.x-e,s=t.x+e,n=t.base-(t.base-t.y),o=t.borderWidth/2;t.borderWidth&&(a+=o,s-=o,n+=o),i.beginPath(),i.fillStyle=t.backgroundColor,i.strokeStyle=t.borderColor,i.lineWidth=t.borderWidth,i.moveTo(a,t.base),i.lineTo(a,n),i.lineTo(s,n),i.lineTo(s,t.base),i.fill(),t.borderWidth&&i.stroke()},height:function(){var t=this._vm;return t.base-t.y},inRange:function(t,i){var e=this._vm;return e.y=e.x-e.width/2&&t<=e.x+e.width/2&&i>=e.y&&i<=e.base:t>=e.x-e.width/2&&t<=e.x+e.width/2&&i>=e.base&&i<=e.y},inGroupRange:function(t){var i=this._vm;return t>=i.x-i.width/2&&t<=i.x+i.width/2},tooltipPosition:function(){var t=this._vm;return t.y=0&&(e=this._data.datasets[h].metaData,s=r(e,this._active[0]),-1===s);h--);var l=function(t){var i,e,r,h,l,c=[],u=[],d=[];return a.each(this._data.datasets,function(t){i=t.metaData,i[s]&&i[s].hasValue()&&c.push(i[s])}),a.each(c,function(t){u.push(t._vm.x),d.push(t._vm.y),n.push(a.template(this._options.tooltips.multiTemplate,t)),o.push({fill:t._vm.backgroundColor,stroke:t._vm.borderColor})},this),l=f(d),r=g(d),h=f(u),e=g(u),{x:h>this._chart.width/2?h:e,y:(l+r)/2}}.call(this,s);a.extend(this,{x:l.x,y:l.y,labels:n,title:this._active.length?this._active[0].label:"",legendColors:o,legendBackgroundColor:this._options.tooltips.multiKeyBackground}),this.height=n.length*this.fontSize+(n.length-1)*(this.fontSize/2)+2*this.yPadding+1.5*this.titleFontSize;var c=t.measureText(this.title).width,u=R(t,this.font,n)+this.fontSize+3,d=g([u,c]);this.width=d+2*this.xPadding;var p=this.height/2;this.y-p<0?this.y=p:this.y+p>this._chart.height&&(this.y=this._chart.height-p),this.x>this._chart.width/2?this.x-=this.xOffset+this.width:this.x+=this.xOffset}return this},draw:function(){var t=this._chart.ctx,i=this._vm;switch(this._options.hover.mode){case"single":t.font=_(i.fontSize,i._fontStyle,i._fontFamily),i.xAlign="center",i.yAlign="above";var e=i.caretPadding||2,s=t.measureText(i.text).width+2*i.xPadding,n=i.fontSize+2*i.yPadding,o=n+i.caretHeight+e;i.x+s/2>this._chart.width?i.xAlign="left":i.x-s/2<0&&(i.xAlign="right"),i.y-o<0&&(i.yAlign="below");var r=i.x-s/2,h=i.y-o;if(t.fillStyle=a.color(i.backgroundColor).alpha(i.opacity).rgbString(),this._custom)this._custom(this._vm);else{switch(i.yAlign){case"above":t.beginPath(),t.moveTo(i.x,i.y-e),t.lineTo(i.x+i.caretHeight,i.y-(e+i.caretHeight)),t.lineTo(i.x-i.caretHeight,i.y-(e+i.caretHeight)),t.closePath(),t.fill();break;case"below":h=i.y+e+i.caretHeight,t.beginPath(),t.moveTo(i.x,i.y+e),t.lineTo(i.x+i.caretHeight,i.y+e+i.caretHeight),t.lineTo(i.x-i.caretHeight,i.y+e+i.caretHeight),t.closePath(),t.fill()}switch(i.xAlign){case"left":r=i.x-s+(i.cornerRadius+i.caretHeight);break;case"right":r=i.x-(i.cornerRadius+i.caretHeight)}I(t,r,h,s,n,i.cornerRadius),t.fill(),t.fillStyle=a.color(i.textColor).alpha(i.opacity).rgbString(),t.textAlign="center",t.textBaseline="middle",t.fillText(i.text,r+s/2,h+n/2)}break;case"label":I(t,i.x,i.y-i.height/2,i.width,i.height,i.cornerRadius),t.fillStyle=a.color(i.backgroundColor).alpha(i.opacity).rgbString(),t.fill(),t.closePath(),t.textAlign="left",t.textBaseline="middle",t.fillStyle=a.color(i.titleTextColor).alpha(i.opacity).rgbString(),t.font=_(i.fontSize,i._titleFontStyle,i._titleFontFamily),t.fillText(i.title,i.x+i.xPadding,this.getLineHeight(0)),t.font=_(i.fontSize,i._fontStyle,i._fontFamily),a.each(i.labels,function(e,s){t.fillStyle=a.color(i.textColor).alpha(i.opacity).rgbString(),t.fillText(e,i.x+i.xPadding+i.fontSize+3,this.getLineHeight(s+1)),t.fillStyle=a.color(i.legendBackgroundColor).alpha(i.opacity).rgbString(),t.fillRect(i.x+i.xPadding,this.getLineHeight(s+1)-i.fontSize/2,i.fontSize,i.fontSize),t.fillStyle=a.color(i.legendColors[s].fill).alpha(i.opacity).rgbString(),t.fillRect(i.x+i.xPadding,this.getLineHeight(s+1)-i.fontSize/2,i.fontSize,i.fontSize)},this)}},getLineHeight:function(t){var i=this._vm.y-this._vm.height/2+this._vm.yPadding,e=t-1;return 0===t?i+this._vm.titleFontSize/2:i+(1.5*this._vm.fontSize*e+this._vm.fontSize/2)+1.5*this._vm.titleFontSize}}),e.animationService={frameDuration:17,animations:[],dropFrames:0,addAnimation:function(t,i,e){e||(t.animating=!0);for(var s=0;s1&&(i=Math.floor(this.dropFrames),this.dropFrames-=i);for(var e=0;ethis.animations[e].animationObject.numSteps&&(this.animations[e].animationObject.currentStep=this.animations[e].animationObject.numSteps),this.animations[e].animationObject.render(this.animations[e].chartInstance,this.animations[e].animationObject),this.animations[e].animationObject.currentStep==this.animations[e].animationObject.numSteps&&(this.animations[e].chartInstance.animating=!1,this.animations.splice(e,1),e--);var s=Date.now(),n=s-t-this.frameDuration,o=n/this.frameDuration;o>1&&(this.dropFrames+=o),this.animations.length>0&&a.requestAnimFrame.call(window,this.digestWrapper)}},a.addEvent(window,"resize",function(){var t;return function(){clearTimeout(t),t=setTimeout(function(){s(e.instances,function(t){t.options.responsive&&(t.resize(),t.update(),t.render())})},50)}}()),d?define(function(){return e}):"object"==typeof module&&module.exports&&(module.exports=e),t.Chart=e,e.noConflict=function(){return t.Chart=i,e}}).call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,a={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},beginAtZero:!1,integersOnly:!1,override:null,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,integersOnly:!1,override:null,labels:{show:!0,template:"<%=value%>",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue"}}]},bars:{borderWidth:2,valueSpacing:5,datasetSpacing:1},legendTemplate:'
    <% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
'};i.Type.extend({name:"Bar",defaults:a,initialize:function(){e.bindEvents(this,this.options.events,this.onHover),this.BarClass=i.Rectangle.extend({ctx:this.chart.ctx}),e.each(this.data.datasets,function(t,i){t.metaData=[],e.each(t.data,function(i,e){t.metaData.push(new this.BarClass)},this),t.xAxisID=this.options.scales.xAxes[0].id,t.yAxisID||(t.yAxisID=this.options.scales.yAxes[0].id)},this),this.buildScale(),i.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height),this.eachElement(function(t,i,a,s){var n=this.scales[this.data.datasets[s].xAxisID],o=this.scales[this.data.datasets[s].yAxisID];e.extend(t,{base:o.getPixelForValue(0),width:n.calculateBarWidth(this.data.datasets.length),x:n.calculateBarX(this.data.datasets.length,s,i),y:o.calculateBarY(this.data.datasets,s,i,this.data.datasets[s].data[i]),_datasetIndex:s,_index:i}),t.save()},this),this.tooltip=new i.Tooltip({_chart:this.chart,_data:this.data,_options:this.options},this),this.update()},onHover:function(t){if("mouseout"==t.type)return this;if(this.lastActive=this.lastActive||[],this.active=function(){switch(this.options.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.onHover&&this.options.onHover.call(this,this.active),this.lastActive.length)switch(this.options.hover.mode){case"single":this.lastActive[0].backgroundColor=this.data.datasets[this.lastActive[0]._datasetIndex].backgroundColor,this.lastActive[0].borderColor=this.data.datasets[this.lastActive[0]._datasetIndex].borderColor;break;case"label":for(var i=0;ie?i[s]+=e:t[s]+=e},this)},this);var s=t.concat(i);this.min=e.min(s),this.max=e.max(s)}else e.each(a.data.datasets,function(t){t.yAxisID===this.id&&e.each(t.data,function(t,i){null===this.min?this.min=t:tthis.max&&(this.max=t)},this)},this)};this.scales={};var n=i.scales.getScaleConstructor(this.options.scales.xAxes[0].scaleType),o=new n({ctx:this.chart.ctx,options:this.options.scales.xAxes[0],id:this.options.scales.xAxes[0].id,calculateRange:function(){this.labels=a.data.labels,this.min=0,this.max=this.labels.length},calculateBaseWidth:function(){return this.getPixelForValue(null,1,!0)-this.getPixelForValue(null,0,!0)-2*a.options.bars.valueSpacing},calculateBarWidth:function(t){var i=this.calculateBaseWidth()-(t-1)*a.options.bars.datasetSpacing;return a.options.stacked?i:i/t},calculateBarX:function(t,i,e){var s=this.calculateBaseWidth(),n=this.getPixelForValue(null,e,!0)-s/2,o=this.calculateBarWidth(t);return a.options.stacked?n+o/2:n+o*i+i*a.options.bars.datasetSpacing+o/2}});this.scales[o.id]=o,e.each(this.options.scales.yAxes,function(t){var e=i.scales.getScaleConstructor(t.scaleType),n=new e({ctx:this.chart.ctx,options:t,calculateRange:s,calculateBarBase:function(t,i){var e=0;if(a.options.stacked){var s=a.data.datasets[t].metaData[i];if(s.value<0)for(var n=0;t>n;n++)a.data.datasets[n].yAxisID===this.id&&(e+=a.data.datasets[n].metaData[i].valuen;n++)a.data.datasets[n].yAxisID===this.id&&(e+=a.data.datasets[n].metaData[i].value>e?a.data.datasets[n].metaData[i].value:0); -return this.getPixelForValue(e)}return e=this.getPixelForValue(this.min),this.beginAtZero||this.min<=0&&this.max>=0||this.min>=0&&this.max<=0?(e=this.getPixelForValue(0),e+=this.options.gridLines.lineWidth):this.min<0&&this.max<0&&(e=this.getPixelForValue(this.max)),e},calculateBarY:function(t,i,e,s){if(a.options.stacked){for(var n=0,o=0,r=0;i>r;r++)t[r].metaData[e].value<0?o+=t[r].metaData[e].value||0:n+=t[r].metaData[e].value||0;return this.getPixelForValue(0>s?o+s:n+s)}var h=0;for(r=i;r=0?n=s.getPixelForValue(0):s.min<0&&s.max<0&&(n=s.getPixelForValue(s.max)),e.extend(t,{y:n,base:n})}),this.render()},draw:function(t){var i=t||1;this.clear(),e.each(this.scales,function(t){t.draw(this.chartArea)},this),this.eachElement(function(t,e,a){t.transition(i).draw()},this),this.tooltip.transition(i).draw()}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,a={segments:{showStroke:!0,strokeColor:"#fff",borderWidth:2},hover:{animationDuration:400},animation:{animateRotate:!0,animateScale:!1},cutoutPercentage:50,legendTemplate:'
    <% for (var i=0; i
  • <%if(segments[i].label){%><%=segments[i].label%><%}%>
  • <%}%>
'};i.Type.extend({name:"Doughnut",defaults:a,initialize:function(){this.Slice=i.Arc.extend({_chart:this.chart,x:this.chart.width/2,y:this.chart.height/2}),this.options.tooltips.enabled&&e.bindEvents(this,this.options.events,this.onHover),this.data.metaData=[],e.each(this.data.data,function(t,i){var a=new this.Slice;"number"==typeof t?e.extend(a,{value:t}):e.extend(a,t),e.extend(a,{startAngle:1.5*Math.PI,circumference:this.options.animation.animateRotate?0:this.calculateCircumference(a.value),outerRadius:this.options.animation.animateScale?0:this.outerRadius,innerRadius:this.options.animation.animateScale?0:this.outerRadius/100*this.options.percentageInnerCutout}),a.backgroundColor||(t.backgroundColor="hsl("+360*i/this.data.data.length+", 100%, 50%)"),a.save(),this.data.metaData.push(a)},this),this.tooltip=new i.Tooltip({_chart:this.chart,_data:this.data,_options:this.options},this),this.update()},onHover:function(t){if("mouseout"==t.type)return this;if(this.lastActive=this.lastActive||[],this.active=this.getSliceAtEvent(t),this.options.onHover&&this.options.onHover.call(this,this.active),this.lastActive.length&&(this.lastActive[0].backgroundColor=this.data.data[this.lastActive[0]._index].backgroundColor),this.active.length&&this.options.hover.mode&&(this.active[0].backgroundColor=this.data.data[this.active[0]._index].hoverBackgroundColor||e.color(this.data.data[this.active[0]._index].backgroundColor).saturate(.5).darken(.35).rgbString()),this.options.tooltips.enabled&&(this.tooltip.initialize(),this.active.length?(e.extend(this.tooltip,{opacity:1,_active:this.active}),this.tooltip.update()):e.extend(this.tooltip,{opacity:0})),this.tooltip.pivot(),!this.animating){var i;e.each(this.active,function(t,e){t!==this.lastActive[e]&&(i=!0)},this),(!this.lastActive.length&&this.active.length||this.lastActive.length&&!this.active.length||this.lastActive.length&&this.active.length&&i)&&(this.stop(),this.render(this.options.hover.animationDuration))}return this.lastActive=this.active,this},getSliceAtEvent:function(t){var i=[],a=e.getRelativePosition(t);return e.each(this.data.metaData,function(t,e){t.inRange(a.x,a.y)&&i.push(t)},this),i},calculateCircumference:function(t){return this.total>0?2*Math.PI*(t/this.total):0},update:function(){this.total=0,e.each(this.data.data,function(t){this.total+=Math.abs(t.value)},this),this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segments.borderWidth/2)/2,e.each(this.data.metaData,function(t,i){var a=this.data.data[i];e.extend(t,{_index:i,x:this.chart.width/2,y:this.chart.height/2,value:a.value,label:a.label,circumference:this.calculateCircumference(a.value),outerRadius:this.outerRadius,innerRadius:this.outerRadius/100*this.options.cutoutPercentage,backgroundColor:a.backgroundColor,hoverBackgroundColor:a.hoverBackgroundColor||a.backgroundColor,borderWidth:this.options.segments.borderWidth,borderColor:this.options.segments.strokeColor}),e.extend(t,{endAngle:t.startAngle+t.circumference}),0===i&&(t.startAngle=1.5*Math.PI),i",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%>",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue"}}]},stacked:!1,points:{radius:3,borderWidth:1,hoverRadius:5,hoverBorderWidth:2,backgroundColor:i.defaults.global.defaultColor,borderColor:i.defaults.global.defaultColor,hitRadius:6},lines:{tension:.4},borderWidth:2,hoverBorderWidth:2,legendTemplate:'
    <% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
'};i.Type.extend({name:"Line",defaults:a,initialize:function(){e.bindEvents(this,this.options.events,this.events);e.each(this.data.datasets,function(t,a){t.metaDataset=new i.Line,t.metaData=[],e.each(t.data,function(e,a){t.metaData.push(new i.Point)},this),t.xAxisID=this.options.scales.xAxes[0].id,t.yAxisID||(t.yAxisID=this.options.scales.yAxes[0].id)},this),this.buildScale(),i.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height),this.eachDataset(function(t,i){e.extend(t.metaDataset,{_points:t.metaData,_datasetIndex:i,_chart:this.chart}),e.extend(t,e.merge(this.options,t)),t.metaDataset.save()},this),this.eachElement(function(t,i,a,s){var n=this.scales[this.data.datasets[s].xAxisID];e.extend(t,{x:n.getPixelForValue(null,i,!0),y:this.chartArea.bottom,_datasetIndex:s,_index:i,_chart:this.chart}),e.extend(t,{controlPointPreviousX:this.previousPoint(a,i).x,controlPointPreviousY:this.nextPoint(a,i).y,controlPointNextX:this.previousPoint(a,i).x,controlPointNextY:this.nextPoint(a,i).y}),t.save()},this),this.tooltip=new i.Tooltip({_chart:this.chart,_data:this.data,_options:this.options},this),this.update()},nextPoint:function(t,i){return t[i-1]||t[i]},previousPoint:function(t,i){return t[i+1]||t[i]},update:function(){i.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height),this.eachDataset(function(t,i){var a=this.scales[t.yAxisID];e.extend(t.metaDataset,{_datasetIndex:i,_points:t.metaData,scaleTop:a.top,scaleBottom:a.bottom,scaleZero:a.getPixelForValue(0),tension:t.tension||this.options.lines.tension,backgroundColor:t.backgroundColor||this.options.backgroundColor,borderWidth:t.borderWidth||this.options.borderWidth,borderColor:t.borderColor||this.options.borderColor}),t.metaDataset.pivot()}),this.eachElement(function(t,i,a,s){var n=this.scales[this.data.datasets[s].xAxisID],o=this.scales[this.data.datasets[s].yAxisID];e.extend(t,{_chart:this.chart,_datasetIndex:s,_index:i,label:this.data.labels[i],value:this.data.datasets[s].data[i],datasetLabel:this.data.datasets[s].label,offsetGridLines:this.options.offsetGridLines,x:n.getPixelForValue(null,i,!0),y:o.getPointPixelForValue(this.data.datasets[s].data[i],i,s),tension:this.data.datasets[s].metaDataset.tension,radius:this.data.datasets[s].pointRadius||this.options.points.radius,backgroundColor:this.data.datasets[s].pointBackgroundColor||this.options.points.backgroundColor,borderWidth:this.data.datasets[s].pointBorderWidth||this.options.pointsborderWidth,hoverRadius:this.data.datasets[s].pointHitRadius||this.options.points.hitRadius})},this),this.eachElement(function(t,i,a,s){var n=e.splineCurve(this.previousPoint(a,i),t,this.nextPoint(a,i),t.tension);t.controlPointPreviousX=n.previous.x,t.controlPointNextX=n.next.x,n.next.y>this.chartArea.bottom?t.controlPointNextY=this.chartArea.bottom:n.next.ythis.chartArea.bottom?t.controlPointPreviousY=this.chartArea.bottom:n.previous.ye?a[s]+=e:i[s]+=e},this)},this);var s=i.concat(a);this.min=e.min(s),this.max=e.max(s)}else e.each(t.data.datasets,function(t){t.yAxisID===this.id&&e.each(t.data,function(t,i){null===this.min?this.min=t:tthis.max&&(this.max=t)},this)},this)};this.scales={};var s=i.scales.getScaleConstructor(this.options.scales.xAxes[0].scaleType),n=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[n.id]=n,e.each(this.options.scales.yAxes,function(e){var s=i.scales.getScaleConstructor(e.scaleType),n=new s({ctx:this.chart.ctx,options:e,calculateRange:a,getPointPixelForValue:function(i,e,a){if(t.options.stacked){for(var s=0,n=0,o=0;a>o;++o)t.data.datasets[o].data[e]<0?n+=t.data.datasets[o].data[e]:s+=t.data.datasets[o].data[e];return this.getPixelForValue(0>i?n+i:s+i)}return this.getPixelForValue(i)},id:e.id});this.scales[n.id]=n},this)},redraw:function(){},draw:function(t){var i=t||1;this.clear(),e.each(this.scales,function(t){t.draw(this.chartArea)},this);for(var a=this.data.datasets.length-1;a>=0;a--){var s=this.data.datasets[a];e.each(s.metaData,function(t,e){t.transition(i)},this),s.metaDataset.transition(i).draw(),e.each(s.metaData,function(t){t.draw()})}this.tooltip.transition(i).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.onHover&&this.options.onHover.call(this,this.active),("mouseup"==t.type||"click"==t.type)&&this.options.onClick&&this.options.onClick.call(this,t,this.active);var i;if(this.lastActive.length)switch(this.options.hover.mode){case"single":i=this.data.datasets[this.lastActive[0]._datasetIndex],this.lastActive[0].radius=i.pointRadius,this.lastActive[0].backgroundColor=i.pointBackgroundColor,this.lastActive[0].borderColor=i.pointBorderColor,this.lastActive[0].borderWidth=i.pointBorderWidth;break;case"label":for(var a=0;a",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue",showLabelBackdrop:!0,backdropColor:"rgba(255,255,255,0.75)",backdropPaddingY:2,backdropPaddingX:2}},animateRotate:!0,legendTemplate:'
    <% for (var i=0; i
  • <%if(segments[i].label){%><%=segments[i].label%><%}%>
  • <%}%>
'};i.Type.extend({name:"PolarArea",defaults:a,initialize:function(){var t=this,a=i.scales.getScaleConstructor(this.options.scale.scaleType);this.scale=new a({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,e.each(t.data.data,function(t){null===this.min?this.min=t.value:t.valuethis.max&&(this.max=t.value)},this)}}),this.Slice=i.Arc.extend(),this.options.showTooltips&&e.bindEvents(this,this.options.events,this.onHover),this.data.metaData=[],e.each(this.data.data,function(t,i){var a=new this.Slice({_chart:this.chart,innerRadius:0,startAngle:1.5*Math.PI,endAngle:1.5*Math.PI,x:this.chart.width/2,y:this.chart.height/2});"number"==typeof t?e.extend(a,{value:t}):e.extend(a,t),a.backgroundColor||(t.backgroundColor="hsl("+360*i/this.data.data.length+", 100%, 50%)"),a.save(),this.data.metaData.push(a)},this),this.tooltip=new i.Tooltip({_chart:this.chart,_data:this.data,_options:this.options},this),this.update()},updateScaleRange:function(){e.extend(this.scale,{size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2})},update:function(){this.updateScaleRange(),this.scale.calculateRange(),this.scale.generateTicks(),this.scale.buildYLabels(),this.outerRadius=(e.min([this.chart.width,this.chart.height])-this.options.segment.borderWidth/2)/2;var t=1/this.data.data.length*2;e.each(this.data.metaData,function(i,a){var s=this.data.data[a],n=1.5*Math.PI+Math.PI*t*a,o=n+t*Math.PI;e.extend(i,{_index:a,x:this.chart.width/2,y:this.chart.height/2,value:s.value,label:s.label,innerRadius:0,outerRadius:this.scale.calculateCenterOffset(i.value),startAngle:n,endAngle:o,backgroundColor:s.backgroundColor,hoverBackgroundColor:s.hoverBackgroundColor||s.backgroundColor,borderWidth:this.options.borderWidth,borderColor:this.options.segmentStrokeColor}),i.pivot()},this),this.render()},draw:function(t){var i=t||1;this.clear(),e.each(this.data.metaData,function(t,e){t.transition(i).draw()},this),this.scale.draw()}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers;i.Type.extend({name:"Radar",defaults:{scale:{scaleType:"radialLinear",display:!0,animate:!1,lineArc:!1,gridLines:{show:!0,color:"rgba(0, 0, 0, 0.05)",lineWidth:1},angleLines:{show:!0,color:"rgba(0,0,0,.1)",lineWidth:1},beginAtZero:!0,labels:{show:!0,template:"<%=value%>",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"}},pointDot:!0,pointRadius:3,pointBorderWidth:1,pointHoverRadius:5,pointHoverBorderWidth:2,pointBackgroundColor:i.defaults.global.defaultColor,pointBorderColor:i.defaults.global.defaultColor,pointHitRadius:20,datasetStroke:!0,datasetStrokeWidth:2,datasetFill:!0,legendTemplate:'
    <% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
'},initialize:function(){this.PointClass=i.Point.extend({display:this.options.pointDot,_chart:this.chart}),this.datasets=[],this.buildScale(this.data),this.options.showTooltips&&e.bindEvents(this,this.options.events,function(t){var i="mouseout"!==t.type?this.getPointsAtEvent(t):[];this.eachPoints(function(t){t.restore(["fillColor","strokeColor"])}),e.each(i,function(t){t.fillColor=t.highlightFill,t.strokeColor=t.highlightStroke}),this.showTooltip(i)}),e.each(this.data.datasets,function(t){var i={label:t.label||null,fillColor:t.fillColor,strokeColor:t.strokeColor,pointColor:t.pointColor,pointStrokeColor:t.pointStrokeColor,points:[]};this.datasets.push(i),e.each(t.data,function(e,a){var s;this.scale.animation||(s=this.scale.getPointPosition(a,this.scale.calculateCenterOffset(e))),i.points.push(new this.PointClass({value:e,label:this.data.labels[a],datasetLabel:t.label,x:this.options.animation?this.scale.xCenter:s.x,y:this.options.animation?this.scale.yCenter:s.y,strokeColor:t.pointStrokeColor,fillColor:t.pointColor,highlightFill:t.pointHighlightFill||t.pointColor,highlightStroke:t.pointHighlightStroke||t.pointStrokeColor,radius:t.pointRadius||this.options.pointRadius,backgroundColor:t.pointBackgroundColor||this.options.pointBackgroundColor,borderWidth:t.pointBorderWidth||this.options.pointBorderWidth,hoverRadius:t.pointHitRadius||this.options.pointHitRadius}))},this)},this),this.render()},eachPoints:function(t){e.each(this.datasets,function(i){e.each(i.points,t,this)},this)},getPointsAtEvent:function(t){var i=e.getRelativePosition(t),a=e.getAngleFromPoint({x:this.scale.xCenter,y:this.scale.yCenter},i),s=2*Math.PI/this.scale.valuesCount,n=Math.round((a.angle-1.5*Math.PI)/s),o=[];return(n>=this.scale.valuesCount||0>n)&&(n=0),a.distance<=this.scale.drawingArea&&e.each(this.datasets,function(t){o.push(t.points[n])}),o},buildScale:function(t){var a=this,s=i.scales.getScaleConstructor(this.options.scale.scaleType);this.scale=new s({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:t.labels,valuesCount:t.datasets[0].data.length,calculateRange:function(){this.min=null,this.max=null,e.each(a.data.datasets,function(t){t.yAxisID===this.id&&e.each(t.data,function(t,i){null===this.min?this.min=t:tthis.max&&(this.max=t)},this)},this)}}),this.scale.setScaleSize(),this.scale.calculateRange(),this.scale.generateTicks(),this.scale.buildYLabels()},addData:function(t,i){this.scale.valuesCount++,e.each(t,function(t,e){var a=this.scale.getPointPosition(this.scale.valuesCount,this.scale.calculateCenterOffset(t));this.datasets[e].points.push(new this.PointClass({value:t,label:i,datasetLabel:this.datasets[e].label,x:a.x,y:a.y,strokeColor:this.datasets[e].pointStrokeColor,fillColor:this.datasets[e].pointColor}))},this),this.scale.labels.push(i),this.reflow(),this.update()},removeData:function(){this.scale.valuesCount--,this.scale.labels.shift(),e.each(this.datasets,function(t){t.points.shift()},this),this.reflow(),this.update()},update:function(){e.each(this.data.datasets,function(t,i){e.extend(this.datasets[i],{label:t.label||null,fillColor:t.fillColor,strokeColor:t.strokeColor,pointColor:t.pointColor,pointStrokeColor:t.pointStrokeColor}),e.each(t.data,function(a,s){e.extend(this.datasets[i].points[s],{value:a,label:this.data.labels[s],datasetLabel:t.label,strokeColor:t.pointStrokeColor,fillColor:t.pointColor,highlightFill:t.pointHighlightFill||t.pointColor,highlightStroke:t.pointHighlightStroke||t.pointStrokeColor})},this)},this),this.eachPoints(function(t){t.save()}),this.reflow(),this.render()},reflow:function(){e.extend(this.scale,{width:this.chart.width,height:this.chart.height,size:e.min([this.chart.width,this.chart.height]),xCenter:this.chart.width/2,yCenter:this.chart.height/2}),this.scale.calculateRange(),this.scale.generateTicks(),this.scale.buildYLabels()},draw:function(t){var i=t||1,a=this.chart.ctx;this.clear(),this.scale.draw(),e.each(this.datasets,function(t){e.each(t.points,function(t,e){t.hasValue()&&t.transition(i)},this),a.lineWidth=this.options.datasetStrokeWidth,a.strokeStyle=t.strokeColor,a.beginPath(),e.each(t.points,function(t,i){0===i?a.moveTo(t.x,t.y):a.lineTo(t.x,t.y)},this),a.closePath(),a.stroke(),a.fillStyle=t.fillColor,a.fill(),e.each(t.points,function(t){t.hasValue()&&t.draw()})},this)}})}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers;i.scaleService={fitScalesForChart:function(t,i,a){var s=10,n=10;if(t){var o=e.where(t.scales,function(t){return"left"==t.options.position}),r=e.where(t.scales,function(t){return"right"==t.options.position}),h=e.where(t.scales,function(t){return"top"==t.options.position}),l=e.where(t.scales,function(t){return"bottom"==t.options.position});if(0===h.length||0===l.length){var c=0,u=function(t){t.options.labels.show&&(c=Math.max(c,t.options.labels.fontSize))};e.each(o,u),e.each(r,u),0===h.length&&(n+=.75*c),0===l.length&&(n+=1.5*c)}var d,p=i/2,g=a/2,f=g/p;t.options.maintainAspectRatio&&(d=a/i,f!=d&&(g=p*d,f=d)),p-=2*s,g-=2*n;var v=(i-p)/(o.length+r.length),m=(a-g)/(h.length+l.length),b=[],x=function(t){var i=t.fit(v,g);b.push({horizontal:!1,minSize:i,scale:t})},y=function(t){var i=t.fit(p,m);b.push({horizontal:!0,minSize:i,scale:t})};e.each(o,x),e.each(r,x),e.each(h,y),e.each(l,y);var w=a-2*n,C=i-2*s;if(e.each(b,function(t){t.horizontal?w-=t.minSize.height:C-=t.minSize.width}),t.options.maintainAspectRatio){var k=w/C;k!=d&&(C>w?C=w/d:w=C*d)}var A=function(t){var i=e.findNextWhere(b,function(i){return i.scale===t});i&&t.fit(i.minSize.width,w)},S=function(t){var i=e.findNextWhere(b,function(i){return i.scale===t});i&&t.fit(C,i.minSize.width)};e.each(o,A),e.each(r,A),e.each(h,S),e.each(l,S);var P=s,M=n;e.each(o,function(t){P+=t.width}),e.each(h,function(t){M+=t.height});var _=s,R=n,I=function(t){t.left=_,t.right=_+t.width,t.top=M,t.bottom=M+w,_=t.right},z=function(t){t.left=P,t.right=P+C,t.top=R,t.bottom=R+t.height,R=t.bottom};e.each(o,I),e.each(h,z),_+=C,R+=w,e.each(r,I),e.each(l,z),t.chartArea={left:P,top:M,right:P+C,bottom:M+w}}}},i.scales={constructors:{},registerScaleType:function(t,i){this.constructors[t]=i},getScaleConstructor:function(t){return this.constructors.hasOwnProperty(t)?this.constructors[t]:void 0}};var a=i.Element.extend({calculateRange:e.noop,isHorizontal:function(){return"top"==this.options.position||"bottom"==this.options.position},generateTicks:function(t,i){if(this.ticks=[],this.options.override)for(var a=0;a<=this.options.override.steps;++a){var s=this.options.override.start+a*this.options.override.stepWidth;ticks.push(s)}else{var n;if(n=this.isHorizontal()?Math.min(11,Math.ceil(t/50)):Math.min(11,Math.ceil(i/(2*this.options.labels.fontSize))),n=Math.max(2,n),this.options.beginAtZero){var o=e.sign(this.min),r=e.sign(this.max);0>o&&0>r?this.max=0:o>0&&r>0&&(this.min=0)}for(var h=e.niceNum(this.max-this.min,!1),l=e.niceNum(h/(n-1),!0),c=Math.floor(this.min/l)*l,u=Math.ceil(this.max/l)*l,d=c;u>=d;d+=l)this.ticks.push(d)}("left"==this.options.position||"right"==this.options.position)&&this.ticks.reverse(),this.max=e.max(this.ticks),this.min=e.min(this.ticks)},buildLabels:function(){this.labels=[],e.each(this.ticks,function(t,i,a){var s;this.options.labels.userCallback?s=this.options.lables.userCallback(t,i,a):this.options.labels.template&&(s=e.template(this.options.labels.template,{value:t})),this.labels.push(s?s:"")},this)},getPixelForValue:function(t){var i,e=this.max-this.min;return i=this.isHorizontal()?this.left+this.width/e*(t-this.min):this.bottom-this.height/e*(t-this.min)},fit:function(t,i){this.calculateRange(),this.generateTicks(t,i),this.buildLabels();var a={width:0,height:0};if(this.isHorizontal()?(a.width=t,a.height=this.options.gridLines.show?25:0):(a.height=i,a.width=this.options.gridLines.show?25:0),this.options.labels.show){var s=e.fontString(this.options.labels.fontSize,this.options.labels.fontStyle,this.options.labels.fontFamily);if(this.isHorizontal()){var n=(i-a.height,1.5*this.options.labels.fontSize);a.height=Math.min(i,a.height+n)}else{var o=t-a.width,r=e.longestText(this.ctx,s,this.labels);o>r?a.width+=r:a.width=t}}return this.width=a.width,this.height=a.height,a},draw:function(t){if(this.options.display){var i,a;if(this.ctx.fillStyle=this.options.labels.fontColor,this.isHorizontal()){if(this.options.gridLines.show){i=!0,a=void 0!==e.findNextWhere(this.ticks,function(t){return 0===t});var s="bottom"==this.options.position?this.top:this.bottom-10,n="bottom"==this.options.position?this.top+10:this.bottom;e.each(this.ticks,function(o,r){var h=this.getPixelForValue(o);0===o||!a&&0===r?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,i=!0):i&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,i=!1),h+=e.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(h,s),this.ctx.lineTo(h,n)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(h,t.top),this.ctx.lineTo(h,t.bottom)),this.ctx.stroke()},this)}if(this.options.labels.show){var o;o="top"==this.options.position?this.top:this.top+20,this.ctx.textAlign="center",this.ctx.textBaseline="top",this.ctx.font=e.fontString(this.options.labels.fontSize,this.options.labels.fontStyle,this.options.labels.fontFamily),e.each(this.labels,function(t,i){var e=this.getPixelForValue(this.ticks[i]);this.ctx.fillText(t,e,o)},this)}}else{if(this.options.gridLines.show){i=!0,a=void 0!==e.findNextWhere(this.ticks,function(t){return 0===t});var r="right"==this.options.position?this.left:this.right-10,h="right"==this.options.position?this.left+10:this.right;e.each(this.ticks,function(s,n){var o=this.getPixelForValue(s);0===s||!a&&0===n?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,i=!0):i&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,i=!1),o+=e.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(r,o),this.ctx.lineTo(h,o)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(t.left,o),this.ctx.lineTo(t.right,o)),this.ctx.stroke()},this)}if(this.options.labels.show){var l,c=this.width-25;l="left"==this.options.position?this.left:this.left+20,this.ctx.textAlign="left",this.ctx.textBaseline="middle",this.ctx.font=e.fontString(this.options.labels.fontSize,this.options.labels.fontStyle,this.options.labels.fontFamily),e.each(this.labels,function(t,i){var e=this.getPixelForValue(this.ticks[i]);this.ctx.fillText(t,l,e,c)},this)}}}}});i.scales.registerScaleType("linear",a);var s=i.Element.extend({calculateRange:e.noop,isHorizontal:function(){return"top"==this.options.position||"bottom"==this.options.position},getPixelForValue:function(t,i,e){if(this.isHorizontal()){var a=(this.labelRotation>0,this.width-(this.paddingLeft+this.paddingRight)),s=a/Math.max(this.max-(this.options.gridLines.offsetGridLines?0:1),1),n=s*i+this.paddingLeft;return this.options.gridLines.offsetGridLines&&e&&(n+=s/2),this.left+Math.round(n)}return this.top+i*(this.height/this.max)},calculateLabelRotation:function(t){var i=e.fontString(this.options.labels.fontSize,this.options.labels.fontStyle,this.options.labels.fontFamily);this.ctx.font=i;var a,s,n=this.ctx.measureText(this.labels[0]).width,o=this.ctx.measureText(this.labels[this.labels.length-1]).width;if(this.paddingRight=o/2+3,this.paddingLeft=n/2+3,this.labelRotation=0,this.options.display){var r,h,l=e.longestText(this.ctx,i,this.labels);this.labelWidth=l;for(var c=Math.floor(this.getPixelForValue(0,1)-this.getPixelForValue(0,0))-6;this.labelWidth>c&&0===this.labelRotation||this.labelWidth>c&&this.labelRotation<=90&&this.labelRotation>0;){if(r=Math.cos(e.toRadians(this.labelRotation)),h=Math.sin(e.toRadians(this.labelRotation)),a=r*n,s=r*o,a+this.options.labels.fontSize/2>this.yLabelWidth&&(this.paddingLeft=a+this.options.labels.fontSize/2),this.paddingRight=this.options.labels.fontSize/2,h*l>t){this.labelRotation--;break}this.labelRotation++,this.labelWidth=r*l}}else this.labelWidth=0,this.paddingRight=this.padding,this.paddingLeft=this.padding},fit:function(t,i){this.calculateRange(),this.calculateLabelRotation();var a={width:0,height:0},s=e.fontString(this.options.labels.fontSize,this.options.labels.fontStyle,this.options.labels.fontFamily),n=e.longestText(this.ctx,s,this.labels);if(this.isHorizontal()){a.width=t,this.width=t;var o=Math.cos(e.toRadians(this.labelRotation))*n+1.5*this.options.labels.fontSize;a.height=Math.min(o,i)}else a.height=i,this.height=i,a.width=Math.min(n+6,t);return this.width=a.width,this.height=a.height,a},draw:function(t){if(this.options.display){var i;if(this.ctx.fillStyle=this.options.labels.fontColor,this.isHorizontal()){i=!0;var a="bottom"==this.options.position?this.top:this.bottom-10,s="bottom"==this.options.position?this.top+10:this.bottom,n=0!==this.labelRotation;e.each(this.labels,function(o,r){var h=this.getPixelForValue(o,r,!1),l=this.getPixelForValue(o,r,!0);this.options.gridLines.show&&(0===r?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,i=!0):i&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,i=!1),h+=e.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(h,a),this.ctx.lineTo(h,s)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(h,t.top),this.ctx.lineTo(h,t.bottom)),this.ctx.stroke()),this.options.labels.show&&(this.ctx.save(),this.ctx.translate(l,n?this.top+12:this.top+8),this.ctx.rotate(-1*e.toRadians(this.labelRotation)),this.ctx.font=this.font,this.ctx.textAlign=n?"right":"center",this.ctx.textBaseline=n?"middle":"top",this.ctx.fillText(o,0,0),this.ctx.restore())},this)}else this.options.gridLines.show,this.options.labels.show}}});i.scales.registerScaleType("dataset",s);var n=i.Element.extend({initialize:function(){ -this.size=e.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 i=this.drawingArea/(this.max-this.min);return(t-this.min)*i},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:e.noop,generateTicks:function(){if(this.ticks=[],this.options.override)for(var t=0;t<=this.options.override.steps;++t){var i=this.options.override.start+t*this.options.override.stepWidth;ticks.push(i)}else{var a=Math.min(11,Math.ceil(this.drawingArea/(2*this.options.labels.fontSize)));if(a=Math.max(2,a),this.options.beginAtZero){var s=e.sign(this.min),n=e.sign(this.max);0>s&&0>n?this.max=0:s>0&&n>0&&(this.min=0)}for(var o=e.niceNum(this.max-this.min,!1),r=e.niceNum(o/(a-1),!0),h=Math.floor(this.min/r)*r,l=Math.ceil(this.max/r)*r,c=h;l>=c;c+=r)this.ticks.push(c)}("left"==this.options.position||"right"==this.options.position)&&this.ticks.reverse(),this.max=e.max(this.ticks),this.min=e.min(this.ticks)},buildYLabels:function(){this.yLabels=[],e.each(this.ticks,function(t,i,a){var s;this.options.labels.userCallback?s=this.options.labels.userCallback(t,i,a):this.options.labels.template&&(s=e.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,i,a,s,n,o,r,h,l,c,u,d,p=e.min([this.height/2-this.options.pointLabels.fontSize-5,this.width/2]),g=this.width,f=0;for(this.ctx.font=e.fontString(this.options.pointLabels.fontSize,this.options.pointLabels.fontStyle,this.options.pointLabels.fontFamily),i=0;ig&&(g=t.x+s,n=i),t.x-sg&&(g=t.x+a,n=i):i>this.valuesCount/2&&t.x-a0){var s,n=a*(this.drawingArea/Math.max(this.ticks.length,1)),o=this.yCenter-n;if(this.options.gridLines.show)if(t.strokeStyle=this.options.gridLines.color,t.lineWidth=this.options.gridLines.lineWidth,this.options.lineArc)t.beginPath(),t.arc(this.xCenter,this.yCenter,n,0,2*Math.PI),t.closePath(),t.stroke();else{t.beginPath();for(var r=0;r=0;i--){if(this.options.angleLines.show){var a=this.getPointPosition(i,this.calculateCenterOffset(this.max));t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(a.x,a.y),t.stroke(),t.closePath()}var s=this.getPointPosition(i,this.calculateCenterOffset(this.max)+5);t.font=e.fontString(this.options.pointLabels.fontSize,this.options.pointLabels.fontStyle,this.options.pointLabels.fontFamily),t.fillStyle=this.options.pointLabels.fontColor;var n=this.labels.length,o=this.labels.length/2,r=o/2,h=r>i||i>n-r,l=i===r||i===n-r;0===i?t.textAlign="center":i===o?t.textAlign="center":o>i?t.textAlign="left":t.textAlign="right",l?t.textBaseline="middle":h?t.textBaseline="bottom":t.textBaseline="top",t.fillText(this.labels[i],s.x,s.y)}}}}});i.scales.registerScaleType("radialLinear",n)}.call(this),function(){"use strict";var t=this,i=t.Chart,e=i.helpers,a={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%>",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%>",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue"}}]},tension:.4,pointRadius:4,pointBorderWidth:1,pointHoverRadius:20,borderWidth:2,legendTemplate:'
    <% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
',tooltips:{template:"(<%= dataX %>, <%= dataY %>)",multiTemplate:"<%if (datasetLabel){%><%=datasetLabel%>: <%}%>(<%= dataX %>, <%= dataY %>)"}};i.Type.extend({name:"Scatter",defaults:a,initialize:function(){this.PointClass=i.Point.extend({_chart:this.chart,offsetGridLines:this.options.offsetGridLines,borderWidth:this.options.pointBorderWidth,radius:this.options.pointRadius,hoverRadius:this.options.pointHoverRadius}),e.bindEvents(this,this.options.events,this.events),this.buildScale(),i.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height),e.each(this.data.datasets,function(t,a){t.metaDataset=new i.Line,t.metaData=[],e.each(t.data,function(i,e){t.metaData.push(new this.PointClass)},this),t.xAxisID||(t.xAxisID=this.options.scales.xAxes[0].id),t.yAxisID||(t.yAxisID=this.options.scales.yAxes[0].id)},this),this.eachDataset(function(t,i){t=e.merge(this.options,t),e.extend(t.metaDataset,{_points:t.metaData,_datasetIndex:i,_chart:this.chart}),t.metaDataset.save()},this),this.eachElement(function(t,i,a,s){var n=this.scales[this.data.datasets[s].xAxisID];e.extend(t,{x:n.getPixelForValue(i),y:this.chartArea.bottom,_datasetIndex:s,_index:i,_chart:this.chart}),e.extend(t,{controlPointPreviousX:this.previousPoint(a,i).x,controlPointPreviousY:this.nextPoint(a,i).y,controlPointNextX:this.previousPoint(a,i).x,controlPointNextY:this.nextPoint(a,i).y}),t.save()},this),this.tooltip=new i.Tooltip({_chart:this.chart,_data:this.data,_options:this.options},this),this.update()},nextPoint:function(t,i){return t[i-1]||t[i]},previousPoint:function(t,i){return t[i+1]||t[i]},events:function(t){if("mouseout"==t.type)return this;if(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.onHover&&this.options.onHover.call(this,this.active),this.lastActive.length)switch(this.options.hover.mode){case"single":this.lastActive[0].backgroundColor=this.data.datasets[this.lastActive[0]._datasetIndex].pointBackgroundColor,this.lastActive[0].borderColor=this.data.datasets[this.lastActive[0]._datasetIndex].pointBorderColor,this.lastActive[0].borderWidth=this.data.datasets[this.lastActive[0]._datasetIndex].pointBorderWidth;break;case"label":for(var i=0;ithis.chartArea.bottom?t.controlPointNextY=this.chartArea.bottom:n.next.ythis.chartArea.bottom?t.controlPointPreviousY=this.chartArea.bottom:n.previous.ythis.max&&(this.max=t.x)},this)},this)},s=function(){this.min=null,this.max=null,e.each(t.data.datasets,function(t){t.yAxisID===this.id&&e.each(t.data,function(t){null===this.min?this.min=t.y:t.ythis.max&&(this.max=t.y)},this)},this)};this.scales={},e.each(this.options.scales.xAxes,function(t){var e=i.scales.getScaleConstructor(t.scaleType),s=new e({ctx:this.chart.ctx,options:t,calculateRange:a,id:t.id});this.scales[s.id]=s},this),e.each(this.options.scales.yAxes,function(t){var e=i.scales.getScaleConstructor(t.scaleType),a=new e({ctx:this.chart.ctx,options:t,calculateRange:s,id:t.id});this.scales[a.id]=a},this)},redraw:function(){},draw:function(t){var i=t||1;this.clear(),e.each(this.scales,function(t){t.draw(this.chartArea)},this),this.eachDataset(function(t,a){e.each(t.metaData,function(t,e){t.transition(i)},this),t.metaDataset.transition(i).draw(),e.each(t.metaData,function(t){t.draw()})},this),this.tooltip.transition(i).draw()}})}.call(this),!function t(i,e,a){function s(o,r){if(!e[o]){if(!i[o]){var h="function"==typeof require&&require;if(!r&&h)return h(o,!0);if(n)return n(o,!0);var l=new Error("Cannot find module '"+o+"'");throw l.code="MODULE_NOT_FOUND",l}var c=e[o]={exports:{}};i[o][0].call(c.exports,function(t){var e=i[o][1][t];return s(e?e:t)},c,c.exports,t,i,e,a)}return e[o].exports}for(var n="function"==typeof require&&require,o=0;o=a?a/12.92:Math.pow((a+.055)/1.055,2.4)}return.2126*i[0]+.7152*i[1]+.0722*i[2]},contrast:function(t){var i=this.luminosity(),e=t.luminosity();return i>e?(i+.05)/(e+.05):(e+.05)/(i+.05)},level:function(t){var i=this.contrast(t);return i>=7.1?"AAA":i>=4.5?"AA":""},dark:function(){var t=this.values.rgb,i=(299*t[0]+587*t[1]+114*t[2])/1e3;return 128>i},light:function(){return!this.dark()},negate:function(){for(var t=[],i=0;3>i;i++)t[i]=255-this.values.rgb[i];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,i=.3*t[0]+.59*t[1]+.11*t[2];return this.setValues("rgb",[i,i,i]),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 i=this.values.hsl[0];return i=(i+t)%360,i=0>i?360+i:i,this.values.hsl[0]=i,this.setValues("hsl",this.values.hsl),this},mix:function(t,i){i=1-(null==i?.5:i);for(var e=2*i-1,a=this.alpha()-t.alpha(),s=((e*a==-1?e:(e+a)/(1+e*a))+1)/2,n=1-s,o=this.rgbArray(),r=t.rgbArray(),h=0;hi&&(i+=360),a=(r+h)/2,e=h==r?0:.5>=a?l/(h+r):l/(2-h-r),[i,100*e,100*a]}function s(t){var i,e,a,s=t[0],n=t[1],o=t[2],r=Math.min(s,n,o),h=Math.max(s,n,o),l=h-r;return e=0==h?0:l/h*1e3/10,h==r?i=0:s==h?i=(n-o)/l:n==h?i=2+(o-s)/l:o==h&&(i=4+(s-n)/l),i=Math.min(60*i,360),0>i&&(i+=360),a=h/255*1e3/10,[i,e,a]}function n(t){var i=t[0],e=t[1],s=t[2],n=a(t)[0],o=1/255*Math.min(i,Math.min(e,s)),s=1-1/255*Math.max(i,Math.max(e,s));return[n,100*o,100*s]}function o(t){var i,e,a,s,n=t[0]/255,o=t[1]/255,r=t[2]/255;return s=Math.min(1-n,1-o,1-r),i=(1-n-s)/(1-s)||0,e=(1-o-s)/(1-s)||0,a=(1-r-s)/(1-s)||0,[100*i,100*e,100*a,100*s]}function h(t){return $[JSON.stringify(t)]}function l(t){var i=t[0]/255,e=t[1]/255,a=t[2]/255;i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92,e=e>.04045?Math.pow((e+.055)/1.055,2.4):e/12.92,a=a>.04045?Math.pow((a+.055)/1.055,2.4):a/12.92;var s=.4124*i+.3576*e+.1805*a,n=.2126*i+.7152*e+.0722*a,o=.0193*i+.1192*e+.9505*a;return[100*s,100*n,100*o]}function c(t){var i,e,a,s=l(t),n=s[0],o=s[1],r=s[2];return n/=95.047,o/=100,r/=108.883,n=n>.008856?Math.pow(n,1/3):7.787*n+16/116,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,i=116*o-16,e=500*(n-o),a=200*(o-r),[i,e,a]}function u(t){return H(c(t))}function d(t){var i,e,a,s,n,o=t[0]/360,r=t[1]/100,h=t[2]/100;if(0==r)return n=255*h,[n,n,n];e=.5>h?h*(1+r):h+r-h*r,i=2*h-e,s=[0,0,0];for(var l=0;3>l;l++)a=o+1/3*-(l-1),0>a&&a++,a>1&&a--,n=1>6*a?i+6*(e-i)*a:1>2*a?e:2>3*a?i+(e-i)*(2/3-a)*6:i,s[l]=255*n;return s}function p(t){var i,e,a=t[0],s=t[1]/100,n=t[2]/100;return n*=2,s*=1>=n?n:2-n,e=(n+s)/2,i=2*s/(n+s),[a,100*i,100*e]}function f(t){return n(d(t))}function v(t){return o(d(t))}function m(t){return h(d(t))}function x(t){var i=t[0]/60,e=t[1]/100,a=t[2]/100,s=Math.floor(i)%6,n=i-Math.floor(i),o=255*a*(1-e),r=255*a*(1-e*n),h=255*a*(1-e*(1-n)),a=255*a;switch(s){case 0:return[a,h,o];case 1:return[r,a,o];case 2:return[o,a,h];case 3:return[o,r,a];case 4:return[h,o,a];case 5:return[a,o,r]}}function y(t){var i,e,a=t[0],s=t[1]/100,n=t[2]/100;return e=(2-s)*n,i=s*n,i/=1>=e?e:2-e,i=i||0,e/=2,[a,100*i,100*e]}function w(t){return n(x(t))}function C(t){return o(x(t))}function k(t){return h(x(t))}function A(t){var i,e,a,s,n=t[0]/360,o=t[1]/100,h=t[2]/100,l=o+h;switch(l>1&&(o/=l,h/=l),i=Math.floor(6*n),e=1-h,a=6*n-i,0!=(1&i)&&(a=1-a),s=o+a*(e-o),i){default:case 6:case 0:r=e,g=s,b=o;break;case 1:r=s,g=e,b=o;break;case 2:r=o,g=e,b=s;break;case 3:r=o,g=s,b=e;break;case 4:r=s,g=o,b=e;break;case 5:r=e,g=o,b=s}return[255*r,255*g,255*b]}function S(t){return a(A(t))}function P(t){return s(A(t))}function M(t){return o(A(t))}function _(t){return h(A(t))}function R(t){var i,e,a,s=t[0]/100,n=t[1]/100,o=t[2]/100,r=t[3]/100;return i=1-Math.min(1,s*(1-r)+r),e=1-Math.min(1,n*(1-r)+r),a=1-Math.min(1,o*(1-r)+r),[255*i,255*e,255*a]}function I(t){return a(R(t))}function z(t){return s(R(t))}function T(t){return n(R(t))}function W(t){return h(R(t))}function L(t){var i,e,a,s=t[0]/100,n=t[1]/100,o=t[2]/100;return i=3.2406*s+-1.5372*n+o*-.4986,e=s*-.9689+1.8758*n+.0415*o,a=.0557*s+n*-.204+1.057*o,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:i=12.92*i,e=e>.0031308?1.055*Math.pow(e,1/2.4)-.055:e=12.92*e,a=a>.0031308?1.055*Math.pow(a,1/2.4)-.055:a=12.92*a,i=Math.min(Math.max(0,i),1),e=Math.min(Math.max(0,e),1),a=Math.min(Math.max(0,a),1),[255*i,255*e,255*a]}function F(t){var i,e,a,s=t[0],n=t[1],o=t[2];return s/=95.047,n/=100,o/=108.883,s=s>.008856?Math.pow(s,1/3):7.787*s+16/116,n=n>.008856?Math.pow(n,1/3):7.787*n+16/116,o=o>.008856?Math.pow(o,1/3):7.787*o+16/116,i=116*n-16,e=500*(s-n),a=200*(n-o),[i,e,a]}function D(t){return H(F(t))}function B(t){var i,e,a,s,n=t[0],o=t[1],r=t[2];return 8>=n?(e=100*n/903.3,s=7.787*(e/100)+16/116):(e=100*Math.pow((n+16)/116,3),s=Math.pow(e/100,1/3)),i=.008856>=i/95.047?i=95.047*(o/500+s-16/116)/7.787:95.047*Math.pow(o/500+s,3),a=.008859>=a/108.883?a=108.883*(s-r/200-16/116)/7.787:108.883*Math.pow(s-r/200,3),[i,e,a]}function H(t){var i,e,a,s=t[0],n=t[1],o=t[2];return i=Math.atan2(o,n),e=360*i/2/Math.PI,0>e&&(e+=360),a=Math.sqrt(n*n+o*o),[s,a,e]}function O(t){return L(B(t))}function E(t){var i,e,a,s=t[0],n=t[1],o=t[2];return a=o/360*2*Math.PI,i=n*Math.cos(a),e=n*Math.sin(a),[s,i,e]}function V(t){return B(E(t))}function N(t){return O(E(t))}function q(t){return U[t]}function Y(t){return a(q(t))}function j(t){return s(q(t))}function X(t){return n(q(t))}function Z(t){return o(q(t))}function G(t){return c(q(t))}function Q(t){return l(q(t))}i.exports={rgb2hsl:a,rgb2hsv:s,rgb2hwb:n,rgb2cmyk:o,rgb2keyword:h,rgb2xyz:l,rgb2lab:c,rgb2lch:u,hsl2rgb:d,hsl2hsv:p,hsl2hwb:f,hsl2cmyk:v,hsl2keyword:m,hsv2rgb:x,hsv2hsl:y,hsv2hwb:w,hsv2cmyk:C,hsv2keyword:k,hwb2rgb:A,hwb2hsl:S,hwb2hsv:P,hwb2cmyk:M,hwb2keyword:_,cmyk2rgb:R,cmyk2hsl:I,cmyk2hsv:z,cmyk2hwb:T,cmyk2keyword:W,keyword2rgb:q,keyword2hsl:Y,keyword2hsv:j,keyword2hwb:X,keyword2cmyk:Z,keyword2lab:G,keyword2xyz:Q,xyz2rgb:L,xyz2lab:F,xyz2lch:D,lab2xyz:B,lab2rgb:O,lab2lch:H,lch2lab:E,lch2xyz:V,lch2rgb:N};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,i,e){var a=t("./conversions"),s=function(){return new l};for(var n in a){s[n+"Raw"]=function(t){return function(i){return"number"==typeof i&&(i=Array.prototype.slice.call(arguments)),a[t](i)}}(n);var o=/(\w+)2(\w+)/.exec(n),r=o[1],h=o[2];s[r]=s[r]||{},s[r][h]=s[n]=function(t){return function(i){"number"==typeof i&&(i=Array.prototype.slice.call(arguments));var e=a[t](i);if("string"==typeof e||void 0===e)return e;for(var s=0;si||t[3]&&t[3]<1?u(t,i):"rgb("+t[0]+", "+t[1]+", "+t[2]+")"}function u(t,i){return void 0===i&&(i=void 0!==t[3]?t[3]:1),"rgba("+t[0]+", "+t[1]+", "+t[2]+", "+i+")"}function d(t,i){if(1>i||t[3]&&t[3]<1)return p(t,i);var e=Math.round(t[0]/255*100),a=Math.round(t[1]/255*100),s=Math.round(t[2]/255*100);return"rgb("+e+"%, "+a+"%, "+s+"%)"}function p(t,i){var e=Math.round(t[0]/255*100),a=Math.round(t[1]/255*100),s=Math.round(t[2]/255*100);return"rgba("+e+"%, "+a+"%, "+s+"%, "+(i||t[3]||1)+")"}function g(t,i){return 1>i||t[3]&&t[3]<1?f(t,i):"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"}function f(t,i){return void 0===i&&(i=void 0!==t[3]?t[3]:1),"hsla("+t[0]+", "+t[1]+"%, "+t[2]+"%, "+i+")"}function v(t,i){return void 0===i&&(i=void 0!==t[3]?t[3]:1),"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==i&&1!==i?", "+i:"")+")"}function m(t){return w[t.slice(0,3)]}function b(t,i,e){return Math.min(Math.max(i,t),e)}function x(t){var i=t.toString(16).toUpperCase();return i.length<2?"0"+i:i}var y=t("color-name");i.exports={getRgba:a,getHsla:s,getRgb:o,getHsl:r,getHwb:n,getAlpha:h,hexString:l,rgbString:c,rgbaString:u,percentString:d,percentaString:p,hslString:g,hslaString:f,hwbString:v,keyword:m};var w={};for(var C in y)w[y[C]]=C},{"color-name":5}],5:[function(t,i,e){i.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:{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:6,hoverRadius:4,hoverBorderWidth:2},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=l,t&&n(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)},v=s.amd="function"==typeof define&&define.amd,m=s.isNumber=function(t){return!isNaN(parseFloat(t))&&isFinite(t)},p=s.max=function(t){return Math.max.apply(Math,t)},g=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.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=2*Math.PI+Math.atan2(o,i);return 0>i&&0>o&&(a+=2*Math.PI),{angle:a,distance:s}},s.aliasPixel=function(t){return t%2===0?0:.5},s.splineCurve=function(t,e,i,o){var s=Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2)),a=Math.sqrt(Math.pow(i.x-e.x,2)+Math.pow(i.y-e.y,2)),r=o*s/(s+a),n=o*a/(s+a);return{previous:{x:e.x-r*(i.x-t.x),y:e.y-r*(i.y-t.y)},next:{x:e.x+n*(i.x-t.x),y:e.y+n*(i.y-t.y)}}},s.calculateOrderOfMagnitude=function(t){return Math.floor(Math.log(t)/Math.LN10)}),b=(s.calculateScaleRange=function(t,e,i,o,s){var a=2,r=Math.floor(e/(1.5*i)),n=a>=r,h=p(t),l=g(t);h===l&&(h+=.5,l>=.5&&!o?l-=.5:h+=.5);for(var c=Math.abs(h-l),d=f(c),u=Math.ceil(h/(1*Math.pow(10,d)))*Math.pow(10,d),v=o?0:Math.floor(l/(1*Math.pow(10,d)))*Math.pow(10,d),m=u-v,b=Math.pow(10,d),x=Math.round(m/b);(x>r||r>2*x)&&!n;)if(x>r)b*=2,x=Math.round(m/b),x%1!==0&&(n=!0);else if(s&&d>=0){if(b/2%1!==0)break;b/=2,x=Math.round(m/b)}else b/=2,x=Math.round(m/b);return n&&(x=a,b=m/x),{steps:x,stepValue:b,min:v,max:v+x*b}},s.niceNum=function(t,e){var i,o=Math.floor(Math.log10(t)),s=t/Math.pow(10,o);return i=e?1.5>s?1:3>s?2:7>s?5:10:1>=s?1:2>=s?2:5>=s?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)}}(),y=(s.cancelAnimFrame=function(){return window.cancelAnimationFrame||window.webkitCancelAnimationFrame||window.mozCancelAnimationFrame||window.oCancelAnimationFrame||window.msCancelAnimationFrame||function(t){return window.clearTimeout(t,1e3/60)}}(),s.animationLoop=function(t,e,i,o,s,a){var r=0,n=x[i]||x.linear,h=function(){r++;var i=r/e,l=n(i);t.call(a,l,i,r),o.call(a,l,i),e>r?a.animationFrame=A(h):s.apply(a)};A(h)},s.getRelativePosition=function(t){var e,i,o=t.originalEvent||t,s=t.currentTarget||t.srcElement,a=s.getBoundingClientRect();return o.touches?(e=o.touches[0].clientX-a.left,i=o.touches[0].clientY-a.top):(e=o.clientX-a.left,i=o.clientY-a.top),{x:e,y:i}},s.addEvent=function(t,e,i){t.addEventListener?t.addEventListener(e,i):t.attachEvent?t.attachEvent("on"+e,i):t["on"+e]=i}),C=s.removeEvent=function(t,e,i){t.removeEventListener?t.removeEventListener(e,i,!1):t.detachEvent?t.detachEvent("on"+e,i):t["on"+e]=c},_=(s.bindEvents=function(t,e,i){t.events||(t.events={}),a(e,function(e){t.events[e]=function(){i.apply(t,arguments)},y(t.chart.canvas,e,t.events[e])})},s.unbindEvents=function(t,e){a(e,function(e,i){C(t.chart.canvas,i,e)})}),w=s.getMaximumWidth=function(t){var e=t.parentNode,i=parseInt(S(e,"padding-left"))+parseInt(S(e,"padding-right"));return e.clientWidth-i},k=s.getMaximumHeight=function(t){var e=t.parentNode,i=parseInt(S(e,"padding-bottom"))+parseInt(S(e,"padding-top"));return e.clientHeight-i},S=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))}),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)},n(i.Type.prototype,{initialize:function(){return this},clear:function(){return I(this.chart),this},stop:function(){return i.animationService.cancelAnimation(this),this},resize:function(){this.stop();var t=this.chart.canvas,e=w(this.chart.canvas),i=this.options.maintainAspectRatio?e/this.chart.aspectRatio:k(this.chart.canvas);return t.width=this.chart.width=e,t.height=this.chart.height=i,P(this.chart),this},redraw:c,render:function(t){if(0!==this.options.animation.duration){var e=new i.Animation;e.numSteps=(t||this.options.animation.duration)/16.66,e.easing=this.options.animation.easing,e.render=function(t,e){var i=s.easingEffects[e.easing],o=e.currentStep/e.numSteps,a=i(o);t.draw(a,o,e.currentStep)},e.onAnimationProgress=this.options.onAnimationProgress,e.onAnimationComplete=this.options.onAnimationComplete,i.animationService.addAnimation(this,e,t)}else this.draw(),this.options.onAnimationComplete.call(this);return this},eachElement:function(t){s.each(this.data.datasets,function(e,i){s.each(e.metaData,t,this,e.metaData,i)},this)},eachValue:function(t){s.each(this.data.datasets,function(e,i){s.each(e.data,t,this,i)},this)},eachDataset:function(t){s.each(this.data.datasets,t,this)},getElementsAtEvent:function(t){for(var e,i=[],o=s.getRelativePosition(t),a=function(t){i.push(t.metaData[e])},r=0;r0||t.borderWidth>0)&&(e.beginPath(),e.arc(t.x,t.y,t.radius,0,2*Math.PI),e.closePath(),e.strokeStyle=t.borderColor||i.defaults.global.defaultColor,e.lineWidth=t.borderWidth||i.defaults.global.defaultColor,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)=i.startAngle&&o.angle<=i.endAngle,r=o.distance>=i.innerRadius&&o.distance<=i.outerRadius;return a&&r},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),r=e.borderWidth/2;e.borderWidth&&(o+=r,s-=r,a+=r),t.beginPath(),t.fillStyle=e.backgroundColor,t.strokeStyle=e.borderColor,t.lineWidth=e.borderWidth,t.moveTo(o,e.base),t.lineTo(o,a),t.lineTo(s,a),t.lineTo(s,e.base),t.fill(),e.borderWidth&&t.stroke()},height:function(){var t=this._view;return t.base-t.y},inRange:function(t,e){var i=this._view;return i.y=i.x-i.width/2&&t<=i.x+i.width/2&&e>=i.y&&e<=i.base:t>=i.x-i.width/2&&t<=i.x+i.width/2&&e>=i.base&&e<=i.y},inGroupRange:function(t){var e=this._view;return t>=e.x-e.width/2&&t<=e.x+e.width/2},tooltipPosition:function(){var t=this._view;return t.y=0&&(i=this._data.datasets[n].metaData,o=h(i,this._active[0]),-1===o);n--);var l=function(t){var e,i,n,h,l,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(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]})),r.push({fill:t._view.backgroundColor,stroke:t._view.borderColor})},this),l=g(u),n=p(u),h=g(d),i=p(d),{x:h>this._chart.width/2?h:i,y:(l+n)/2}}.call(this,o);s.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:r,legendBackgroundColor:this._options.tooltips.multiKeyBackground}),this._model.height=a.length*this._model.fontSize+(a.length-1)*(this._model.fontSize/2)+2*this._model.yPadding+1.5*this._model.titleFontSize;var c=t.measureText(this.title).width,d=D(t,this.font,a)+this._model.fontSize+3,u=p([d,c]);this._model.width=u+2*this._model.xPadding;var v=this._model.height/2;this._model.y-v<0?this._model.y=v:this._model.y+v>this._chart.height&&(this._model.y=this._chart.height-v),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,r=a+e.caretHeight+i;e.x+o/2>this._chart.width?e.xAlign="left":e.x-o/2<0&&(e.xAlign="right"),e.y-r<0&&(e.yAlign="below");var n=e.x-o/2,h=e.y-r;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":h=e.y+i+e.caretHeight,t.beginPath(),t.moveTo(e.x,e.y+i),t.lineTo(e.x+e.caretHeight,e.y+i+e.caretHeight),t.lineTo(e.x-e.caretHeight,e.y+i+e.caretHeight),t.closePath(),t.fill()}switch(e.xAlign){case"left":n=e.x-o+(e.cornerRadius+e.caretHeight);break;case"right":n=e.x-(e.cornerRadius+e.caretHeight)}O(t,n,h,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,n+o/2,h+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,r=a/this.frameDuration;r>1&&(this.dropFrames+=r),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)}}()),v?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,r=this.scales[this.data.datasets[s].xAxisID],n=this.scales[this.data.datasets[s].yAxisID];a=n.getPixelForValue(n.min<0&&n.max<0?n.max:n.min>0&&n.max>0?n.min:0),i.extend(t,{_chart:this.chart,_xScale:r,_yScale:n,_datasetIndex:s,_index:e,_model:{x:r.calculateBarX(this.data.datasets.length,s,e),y:a,base:n.calculateBarBase(s,e),width:r.calculateBarWidth(this.data.datasets.length),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[s].backgroundColor,e,this.options.elements.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(){e.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height),this.eachElement(function(t,e,o,s){var a=this.scales[this.data.datasets[s].xAxisID],r=this.scales[this.data.datasets[s].yAxisID];i.extend(t,{_chart:this.chart,_xScale:a,_yScale:r,_datasetIndex:s,_index:e,_model:{x:a.calculateBarX(this.data.datasets.length,s,e),y:r.calculateBarY(s,e),base:r.calculateBarBase(s,e),width:a.calculateBarWidth(this.data.datasets.length),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[s].backgroundColor,e,this.options.elements.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()},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),r=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,r=this.calculateBarWidth(t);return o.options.stacked?a+r/2:a+r*e+e*o.options.elements.bar.datasetSpacing+r/2}});this.scales[r.id]=r,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 r=0;t>r;r++)o.data.datasets[r].yAxisID===this.id&&(i+=o.data.datasets[r].data[e]>0?o.data.datasets[r].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,r=0;t>r;r++)o.data.datasets[r].data[e]<0?a+=o.data.datasets[r].data[e]||0:s+=o.data.datasets[r].data[e]||0;return this.getPixelForValue(0>i?a+i:s+i)}for(var n=0,h=t;h0?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(){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%>",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue"}}]}};e.Type.extend({name:"Line",defaults:o,initialize:function(){i.bindEvents(this,this.options.events,this.events),i.each(this.data.datasets,function(t,o){t.metaDataset=new e.Line({_chart:this.chart,_datasetIndex:o,_points:t.metaData}),t.metaData=[],i.each(t.data,function(i,s){t.metaData.push(new e.Point({_datasetIndex:o,_index:s,_chart:this.chart,_model:{x:0,y:0}}))},this),t.xAxisID=this.options.scales.xAxes[0].id,t.yAxisID||(t.yAxisID=this.options.scales.yAxes[0].id)},this),this.buildScale(),this.tooltip=new e.Tooltip({_chart:this.chart,_data:this.data,_options:this.options},this),e.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height),this.resetElements(),this.update()},nextPoint:function(t,e){return t[e+1]||t[e]},previousPoint:function(t,e){return t[e-1]||t[e]},resetElements:function(){this.eachElement(function(t,e,o,s){var a,r=this.scales[this.data.datasets[s].xAxisID],n=this.scales[this.data.datasets[s].yAxisID];a=n.getPixelForValue(n.min<0&&n.max<0?n.max:n.min>0&&n.max>0?n.min:0),i.extend(t,{_chart:this.chart,_xScale:r,_yScale:n,_datasetIndex:s,_index:e,_model:{x:r.getPixelForValue(null,e,!0),y:a,tension:t.custom&&t.custom.tension?t.custom.tension:this.options.elements.line.tension,radius:t.custom&&t.custom.radius?t.custom.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:"number"!=typeof 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.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,r=0;o>r;++r)t.data.datasets[r].data[i]<0?a+=t.data.datasets[r].data[i]:s+=t.data.datasets[r].data[i];return this.getPixelForValue(0>e?a+e:s+e)}return this.getPixelForValue(e)},id:i.id});this.scales[a.id]=a},this)},draw:function(t){var e=t||1;this.clear(),i.each(this.scales,function(t){t.draw(this.chartArea)},this);for(var o=this.data.datasets.length-1;o>=0;o--){var s=this.data.datasets[o];i.each(s.metaData,function(t,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",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(){var t=1/this.data.datasets[0].data.length*2;i.each(this.data.datasets[0].metaData,function(e,o){var s=(this.data.datasets[0].data[o],1.5*Math.PI+Math.PI*t*o);s+t*Math.PI;i.extend(e,{_index:o,_model:{x:this.chart.width/2,y:this.chart.height/2,innerRadius:0,outerRadius:0,startAngle:1.5*Math.PI,endAngle:1.5*Math.PI,backgroundColor:e.custom&&e.custom.backgroundColor?e.custom.backgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[0].backgroundColor,o,this.options.elements.slice.backgroundColor),hoverBackgroundColor:e.custom&&e.custom.hoverBackgroundColor?e.custom.hoverBackgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[0].hoverBackgroundColor,o,this.options.elements.slice.hoverBackgroundColor),borderWidth:e.custom&&e.custom.borderWidth?e.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[0].borderWidth,o,this.options.elements.slice.borderWidth),borderColor:e.custom&&e.custom.borderColor?e.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[0].borderColor,o,this.options.elements.slice.borderColor),label:i.getValueAtIndexOrDefault(this.data.datasets[0].labels,o,this.data.datasets[0].labels[o])}}),e.pivot(); +},this)},update:function(){this.updateScaleRange(),this.scale.calculateRange(),this.scale.generateTicks(),this.scale.buildYLabels(),e.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height);var t=1/this.data.datasets[0].data.length*2;i.each(this.data.datasets[0].metaData,function(e,o){var s=this.data.datasets[0].data[o],a=1.5*Math.PI+Math.PI*t*o,r=a+t*Math.PI;i.extend(e,{_index:o,_model:{x:this.chart.width/2,y:this.chart.height/2,innerRadius:0,outerRadius:this.scale.calculateCenterOffset(s),startAngle:a,endAngle:r,backgroundColor:e.custom&&e.custom.backgroundColor?e.custom.backgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[0].backgroundColor,o,this.options.elements.slice.backgroundColor),hoverBackgroundColor:e.custom&&e.custom.hoverBackgroundColor?e.custom.hoverBackgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[0].hoverBackgroundColor,o,this.options.elements.slice.hoverBackgroundColor),borderWidth:e.custom&&e.custom.borderWidth?e.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[0].borderWidth,o,this.options.elements.slice.borderWidth),borderColor:e.custom&&e.custom.borderColor?e.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[0].borderColor,o,this.options.elements.slice.borderColor),label:i.getValueAtIndexOrDefault(this.data.datasets[0].labels,o,this.data.datasets[0].labels[o])}}),e.pivot(),console.log(e)},this),this.render()},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:"number"!=typeof 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(){e.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height),this.eachDataset(function(t,e){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:this.scale.getPointPosition(0)}}),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:"number"!=typeof 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;sy?C=y/u:y=C*u)}var w=function(t){var e=i.findNextWhere(b,function(e){return e.scale===t});e&&t.fit(e.minSize.width,y)},k=function(t){var e=i.findNextWhere(b,function(e){return e.scale===t});e&&t.fit(C,e.minSize.width)};i.each(r,w),i.each(n,w),i.each(h,k),i.each(l,k);var S=s,P=a;i.each(r,function(t){S+=t.width}),i.each(h,function(t){P+=t.height});var I=s,W=a,D=function(t){t.left=I,t.right=I+t.width,t.top=P,t.bottom=P+y,I=t.right},O=function(t){t.left=S,t.right=S+C,t.top=W,t.bottom=W+t.height,W=t.bottom};i.each(r,D),i.each(h,O),I+=C,W+=y,i.each(n,D),i.each(l,O),t.chartArea={left:S,top:P,right:S+C,bottom:P+y}}}},e.scales={constructors:{},registerScaleType:function(t,e){this.constructors[t]=e},getScaleConstructor:function(t){return this.constructors.hasOwnProperty(t)?this.constructors[t]:void 0}};var o=e.Element.extend({calculateRange:i.noop,isHorizontal:function(){return"top"==this.options.position||"bottom"==this.options.position},generateTicks:function(t,e){if(this.ticks=[],this.options.override)for(var o=0;o<=this.options.override.steps;++o){var s=this.options.override.start+o*this.options.override.stepWidth;ticks.push(s)}else{var a;if(a=this.isHorizontal()?Math.min(11,Math.ceil(t/50)):Math.min(11,Math.ceil(e/(2*this.options.labels.fontSize))),a=Math.max(2,a),this.options.beginAtZero){var r=i.sign(this.min),n=i.sign(this.max);0>r&&0>n?this.max=0:r>0&&n>0&&(this.min=0)}for(var h=i.niceNum(this.max-this.min,!1),l=i.niceNum(h/(a-1),!0),c=Math.floor(this.min/l)*l,d=Math.ceil(this.max/l)*l,u=c;d>=u;u+=l)this.ticks.push(u)}("left"==this.options.position||"right"==this.options.position)&&this.ticks.reverse(),this.max=i.max(this.ticks),this.min=i.min(this.ticks)},buildLabels:function(){this.labels=[],i.each(this.ticks,function(t,e,o){var s;this.options.labels.userCallback?s=this.options.lables.userCallback(t,e,o):this.options.labels.template&&(s=i.template(this.options.labels.template,{value:t})),this.labels.push(s?s:"")},this)},getPixelForValue:function(t){var e,i=this.max-this.min;return e=this.isHorizontal()?this.left+this.width/i*(t-this.min):this.bottom-this.height/i*(t-this.min)},fit:function(t,e){this.calculateRange(),this.generateTicks(t,e),this.buildLabels();var o={width:0,height:0};if(this.isHorizontal()?(o.width=t,o.height=this.options.gridLines.show?25:0):(o.height=e,o.width=this.options.gridLines.show?25: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 r=t-o.width,n=i.longestText(this.ctx,s,this.labels);r>n?o.width+=n:o.width=t}}return this.width=o.width,this.height=o.height,o},draw:function(t){if(this.options.display){var e,o;if(this.ctx.fillStyle=this.options.labels.fontColor,this.isHorizontal()){if(this.options.gridLines.show){e=!0,o=void 0!==i.findNextWhere(this.ticks,function(t){return 0===t});var s="bottom"==this.options.position?this.top:this.bottom-10,a="bottom"==this.options.position?this.top+10:this.bottom;i.each(this.ticks,function(r,n){var h=this.getPixelForValue(r);0===r||!o&&0===n?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,e=!0):e&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,e=!1),h+=i.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(h,s),this.ctx.lineTo(h,a)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(h,t.top),this.ctx.lineTo(h,t.bottom)),this.ctx.stroke()},this)}if(this.options.labels.show){var r;r="top"==this.options.position?this.top:this.top+20,this.ctx.textAlign="center",this.ctx.textBaseline="top",this.ctx.font=i.fontString(this.options.labels.fontSize,this.options.labels.fontStyle,this.options.labels.fontFamily),i.each(this.labels,function(t,e){var i=this.getPixelForValue(this.ticks[e]);this.ctx.fillText(t,i,r)},this)}}else{if(this.options.gridLines.show){e=!0,o=void 0!==i.findNextWhere(this.ticks,function(t){return 0===t});var n="right"==this.options.position?this.left:this.right-10,h="right"==this.options.position?this.left+10:this.right;i.each(this.ticks,function(s,a){var r=this.getPixelForValue(s);0===s||!o&&0===a?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,e=!0):e&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,e=!1),r+=i.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(n,r),this.ctx.lineTo(h,r)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(t.left,r),this.ctx.lineTo(t.right,r)),this.ctx.stroke()},this)}if(this.options.labels.show){var l,c=this.width-25;l="left"==this.options.position?this.left:this.left+20,this.ctx.textAlign="left",this.ctx.textBaseline="middle",this.ctx.font=i.fontString(this.options.labels.fontSize,this.options.labels.fontStyle,this.options.labels.fontFamily),i.each(this.labels,function(t,e){var i=this.getPixelForValue(this.ticks[e]);this.ctx.fillText(t,l,i,c)},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){var e=i.fontString(this.options.labels.fontSize,this.options.labels.fontStyle,this.options.labels.fontFamily);this.ctx.font=e;var o,s,a=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=a/2+3,this.labelRotation=0,this.options.display){var n,h,l=i.longestText(this.ctx,e,this.labels);this.labelWidth=l;for(var c=Math.floor(this.getPixelForValue(0,1)-this.getPixelForValue(0,0))-6;this.labelWidth>c&&0===this.labelRotation||this.labelWidth>c&&this.labelRotation<=90&&this.labelRotation>0;){if(n=Math.cos(i.toRadians(this.labelRotation)),h=Math.sin(i.toRadians(this.labelRotation)),o=n*a,s=n*r,o+this.options.labels.fontSize/2>this.yLabelWidth&&(this.paddingLeft=o+this.options.labels.fontSize/2),this.paddingRight=this.options.labels.fontSize/2,h*l>t){this.labelRotation--;break}this.labelRotation++,this.labelWidth=n*l}}else this.labelWidth=0,this.paddingRight=this.padding,this.paddingLeft=this.padding},fit:function(t,e){this.calculateRange(),this.calculateLabelRotation();var o={width:0,height:0},s=i.fontString(this.options.labels.fontSize,this.options.labels.fontStyle,this.options.labels.fontFamily),a=i.longestText(this.ctx,s,this.labels);if(this.isHorizontal()){o.width=t,this.width=t;var r=Math.cos(i.toRadians(this.labelRotation))*a+1.5*this.options.labels.fontSize;o.height=Math.min(r,e)}else o.height=e,this.height=e,o.width=Math.min(a+6,t);return this.width=o.width,this.height=o.height,o},draw:function(t){if(this.options.display){var e;if(this.ctx.fillStyle=this.options.labels.fontColor,this.isHorizontal()){e=!0;var o="bottom"==this.options.position?this.top:this.bottom-10,s="bottom"==this.options.position?this.top+10:this.bottom,a=0!==this.labelRotation;i.each(this.labels,function(r,n){var h=this.getPixelForValue(r,n,!1),l=this.getPixelForValue(r,n,!0);this.options.gridLines.show&&(0===n?(this.ctx.lineWidth=this.options.gridLines.zeroLineWidth,this.ctx.strokeStyle=this.options.gridLines.zeroLineColor,e=!0):e&&(this.ctx.lineWidth=this.options.gridLines.lineWidth,this.ctx.strokeStyle=this.options.gridLines.color,e=!1),h+=i.aliasPixel(this.ctx.lineWidth),this.ctx.beginPath(),this.options.gridLines.drawTicks&&(this.ctx.moveTo(h,o),this.ctx.lineTo(h,s)),this.options.gridLines.drawOnChartArea&&(this.ctx.moveTo(h,t.top),this.ctx.lineTo(h,t.bottom)),this.ctx.stroke()),this.options.labels.show&&(this.ctx.save(),this.ctx.translate(l,a?this.top+12:this.top+8),this.ctx.rotate(-1*i.toRadians(this.labelRotation)),this.ctx.font=this.font,this.ctx.textAlign=a?"right":"center",this.ctx.textBaseline=a?"middle":"top",this.ctx.fillText(r,0,0),this.ctx.restore())},this)}else this.options.gridLines.show,this.options.labels.show}}});e.scales.registerScaleType("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 r=i.niceNum(this.max-this.min,!1),n=i.niceNum(r/(o-1),!0),h=Math.floor(this.min/n)*n,l=Math.ceil(this.max/n)*n,c=h;l>=c;c+=n)this.ticks.push(c)}("left"==this.options.position||"right"==this.options.position)&&this.ticks.reverse(),this.max=i.max(this.ticks),this.min=i.min(this.ticks)},buildYLabels:function(){this.yLabels=[],i.each(this.ticks,function(t,e,o){var s;this.options.labels.userCallback?s=this.options.labels.userCallback(t,e,o):this.options.labels.template&&(s=i.template(this.options.labels.template,{value:t})),this.yLabels.push(s?s:"")},this)},getCircumference:function(){return 2*Math.PI/this.valuesCount},setScaleSize:function(){var t,e,o,s,a,r,n,h,l,c,d,u,v=i.min([this.height/2-this.options.pointLabels.fontSize-5,this.width/2]),m=this.width,p=0;for(this.ctx.font=i.fontString(this.options.pointLabels.fontSize,this.options.pointLabels.fontStyle,this.options.pointLabels.fontFamily),e=0;em&&(m=t.x+s,a=e),t.x-sm&&(m=t.x+o,a=e):e>this.valuesCount/2&&t.x-o0){var s,a=o*(this.drawingArea/Math.max(this.ticks.length,1)),r=this.yCenter-a;if(this.options.gridLines.show)if(t.strokeStyle=this.options.gridLines.color,t.lineWidth=this.options.gridLines.lineWidth,this.options.lineArc)t.beginPath(),t.arc(this.xCenter,this.yCenter,a,0,2*Math.PI),t.closePath(),t.stroke();else{t.beginPath();for(var n=0;n=0;e--){if(this.options.angleLines.show){var o=this.getPointPosition(e,this.calculateCenterOffset(this.max));t.beginPath(),t.moveTo(this.xCenter,this.yCenter),t.lineTo(o.x,o.y),t.stroke(),t.closePath()}var s=this.getPointPosition(e,this.calculateCenterOffset(this.max)+5);t.font=i.fontString(this.options.pointLabels.fontSize,this.options.pointLabels.fontStyle,this.options.pointLabels.fontFamily),t.fillStyle=this.options.pointLabels.fontColor;var a=this.labels.length,r=this.labels.length/2,n=r/2,h=n>e||e>a-n,l=e===n||e===a-n;0===e?t.textAlign="center":e===r?t.textAlign="center":r>e?t.textAlign="left":t.textAlign="right",l?t.textBaseline="middle":h?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%>",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%>",fontSize:12,fontStyle:"normal",fontColor:"#666",fontFamily:"Helvetica Neue"}}]},legendTemplate:'
    <% for (var i=0; i
  • <%if(datasets[i].label){%><%=datasets[i].label%><%}%>
  • <%}%>
',tooltips:{template:"(<%= value.x %>, <%= value.y %>)",multiTemplate:"<%if (datasetLabel){%><%=datasetLabel%>: <%}%>(<%= value.x %>, <%= value.y %>)"}};e.Type.extend({name:"Scatter",defaults:o,initialize:function(){i.bindEvents(this,this.options.events,this.events),i.each(this.data.datasets,function(t,o){t.metaDataset=new e.Line({_chart:this.chart,_datasetIndex:o,_points:t.metaData}),t.metaData=[],i.each(t.data,function(i,s){t.metaData.push(new e.Point({_datasetIndex:o,_index:s,_chart:this.chart,_model:{x:0,y:0}}))},this),t.xAxisID||(t.xAxisID=this.options.scales.xAxes[0].id),t.yAxisID||(t.yAxisID=this.options.scales.yAxes[0].id)},this),this.buildScale(),this.tooltip=new e.Tooltip({_chart:this.chart,_data:this.data,_options:this.options},this),e.scaleService.fitScalesForChart(this,this.chart.width,this.chart.height),this.resetElements(),this.update()},nextPoint:function(t,e){return t[e+1]||t[e]},previousPoint:function(t,e){return t[e-1]||t[e]},resetElements:function(){this.eachElement(function(t,e,o,s){var a,r=this.scales[this.data.datasets[s].xAxisID],n=this.scales[this.data.datasets[s].yAxisID];a=n.getPixelForValue(n.min<0&&n.max<0?n.max:n.min>0&&n.max>0?n.min:0),i.extend(t,{_chart:this.chart,_xScale:r,_yScale:n,_datasetIndex:s,_index:e,_model:{x:r.getPixelForValue(this.data.datasets[s].data[e].x),y:a,tension:t.custom&&t.custom.tension?t.custom.tension:this.options.elements.line.tension,radius:t.custom&&t.custom.radius?t.custom.pointRadius:i.getValueAtIndexOrDefault(this.data.datasets[s].pointRadius,e,this.options.elements.point.radius),backgroundColor:t.custom&&t.custom.backgroundColor?t.custom.backgroundColor:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBackgroundColor,e,this.options.elements.point.backgroundColor),borderColor:t.custom&&t.custom.borderColor?t.custom.borderColor:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBorderColor,e,this.options.elements.point.borderColor),borderWidth:t.custom&&t.custom.borderWidth?t.custom.borderWidth:i.getValueAtIndexOrDefault(this.data.datasets[s].pointBorderWidth,e,this.options.elements.point.borderWidth),skip:"number"!=typeof this.data.datasets[s].data[e].x||"number"!=typeof 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.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,r=this.rgbArray(),n=t.rgbArray(),h=0;he&&(e+=360),o=(n+h)/2,i=h==n?0:.5>=o?l/(h+n):l/(2-h-n),[e,100*i,100*o]}function s(t){var e,i,o,s=t[0],a=t[1],r=t[2],n=Math.min(s,a,r),h=Math.max(s,a,r),l=h-n;return i=0==h?0:l/h*1e3/10,h==n?e=0:s==h?e=(a-r)/l:a==h?e=2+(r-s)/l:r==h&&(e=4+(s-a)/l),e=Math.min(60*e,360),0>e&&(e+=360),o=h/255*1e3/10,[e,i,o]}function a(t){var e=t[0],i=t[1],s=t[2],a=o(t)[0],r=1/255*Math.min(e,Math.min(i,s)),s=1-1/255*Math.max(e,Math.max(i,s));return[a,100*r,100*s]}function n(t){var e,i,o,s,a=t[0]/255,r=t[1]/255,n=t[2]/255;return s=Math.min(1-a,1-r,1-n),e=(1-a-s)/(1-s)||0,i=(1-r-s)/(1-s)||0,o=(1-n-s)/(1-s)||0,[100*e,100*i,100*o,100*s]}function h(t){return $[JSON.stringify(t)]}function l(t){var e=t[0]/255,i=t[1]/255,o=t[2]/255;e=e>.04045?Math.pow((e+.055)/1.055,2.4):e/12.92,i=i>.04045?Math.pow((i+.055)/1.055,2.4):i/12.92,o=o>.04045?Math.pow((o+.055)/1.055,2.4):o/12.92;var s=.4124*e+.3576*i+.1805*o,a=.2126*e+.7152*i+.0722*o,r=.0193*e+.1192*i+.9505*o;return[100*s,100*a,100*r]}function c(t){var e,i,o,s=l(t),a=s[0],r=s[1],n=s[2];return a/=95.047,r/=100,n/=108.883,a=a>.008856?Math.pow(a,1/3):7.787*a+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,n=n>.008856?Math.pow(n,1/3):7.787*n+16/116,e=116*r-16,i=500*(a-r),o=200*(r-n),[e,i,o]}function d(t){return L(c(t))}function u(t){var e,i,o,s,a,r=t[0]/360,n=t[1]/100,h=t[2]/100;if(0==n)return a=255*h,[a,a,a];i=.5>h?h*(1+n):h+n-h*n,e=2*h-i,s=[0,0,0];for(var l=0;3>l;l++)o=r+1/3*-(l-1),0>o&&o++,o>1&&o--,a=1>6*o?e+6*(i-e)*o:1>2*o?i:2>3*o?e+(i-e)*(2/3-o)*6:e,s[l]=255*a;return s}function v(t){var e,i,o=t[0],s=t[1]/100,a=t[2]/100;return a*=2,s*=1>=a?a:2-a,i=(a+s)/2,e=2*s/(a+s),[o,100*e,100*i]}function m(t){return a(u(t))}function p(t){return n(u(t))}function f(t){return h(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),r=255*o*(1-i),n=255*o*(1-i*a),h=255*o*(1-i*(1-a)),o=255*o;switch(s){case 0:return[o,h,r];case 1:return[n,o,r];case 2:return[r,o,h];case 3:return[r,n,o];case 4:return[h,r,o];case 5:return[o,r,n]}}function 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 y(t){return a(x(t))}function C(t){return n(x(t))}function _(t){return h(x(t))}function w(t){var e,i,o,s,a=t[0]/360,n=t[1]/100,h=t[2]/100,l=n+h;switch(l>1&&(n/=l,h/=l),e=Math.floor(6*a),i=1-h,o=6*a-e,0!=(1&e)&&(o=1-o),s=n+o*(i-n),e){default:case 6:case 0:r=i,g=s,b=n;break;case 1:r=s,g=i,b=n;break;case 2:r=n,g=i,b=s;break;case 3:r=n,g=s,b=i;break;case 4:r=s,g=n,b=i;break;case 5:r=i,g=n,b=s}return[255*r,255*g,255*b]}function k(t){return o(w(t))}function S(t){return s(w(t))}function P(t){return n(w(t))}function I(t){return h(w(t))}function W(t){var e,i,o,s=t[0]/100,a=t[1]/100,r=t[2]/100,n=t[3]/100;return e=1-Math.min(1,s*(1-n)+n),i=1-Math.min(1,a*(1-n)+n),o=1-Math.min(1,r*(1-n)+n),[255*e,255*i,255*o]}function D(t){return o(W(t))}function O(t){return s(W(t))}function R(t){return a(W(t))}function V(t){return h(W(t))}function B(t){var e,i,o,s=t[0]/100,a=t[1]/100,r=t[2]/100;return e=3.2406*s+-1.5372*a+r*-.4986,i=s*-.9689+1.8758*a+.0415*r,o=.0557*s+a*-.204+1.057*r,e=e>.0031308?1.055*Math.pow(e,1/2.4)-.055:e=12.92*e,i=i>.0031308?1.055*Math.pow(i,1/2.4)-.055:i=12.92*i,o=o>.0031308?1.055*Math.pow(o,1/2.4)-.055:o=12.92*o,e=Math.min(Math.max(0,e),1),i=Math.min(Math.max(0,i),1),o=Math.min(Math.max(0,o),1),[255*e,255*i,255*o]}function M(t){var e,i,o,s=t[0],a=t[1],r=t[2];return s/=95.047,a/=100,r/=108.883,s=s>.008856?Math.pow(s,1/3):7.787*s+16/116,a=a>.008856?Math.pow(a,1/3):7.787*a+16/116,r=r>.008856?Math.pow(r,1/3):7.787*r+16/116,e=116*a-16,i=500*(s-a),o=200*(a-r),[e,i,o]}function T(t){return L(M(t))}function z(t){var e,i,o,s,a=t[0],r=t[1],n=t[2];return 8>=a?(i=100*a/903.3,s=7.787*(i/100)+16/116):(i=100*Math.pow((a+16)/116,3),s=Math.pow(i/100,1/3)),e=.008856>=e/95.047?e=95.047*(r/500+s-16/116)/7.787:95.047*Math.pow(r/500+s,3),o=.008859>=o/108.883?o=108.883*(s-n/200-16/116)/7.787:108.883*Math.pow(s-n/200,3),[e,i,o]}function L(t){var e,i,o,s=t[0],a=t[1],r=t[2];return e=Math.atan2(r,a),i=360*e/2/Math.PI,0>i&&(i+=360),o=Math.sqrt(a*a+r*r),[s,o,i]}function F(t){return B(z(t))}function E(t){var e,i,o,s=t[0],a=t[1],r=t[2];return o=r/360*2*Math.PI,e=a*Math.cos(o),i=a*Math.sin(o),[s,e,i]}function 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 l(Y(t))}e.exports={rgb2hsl:o,rgb2hsv:s,rgb2hwb:a,rgb2cmyk:n,rgb2keyword:h,rgb2xyz:l,rgb2lab:c,rgb2lch:d,hsl2rgb:u,hsl2hsv:v,hsl2hwb:m,hsl2cmyk:p,hsl2keyword:f,hsv2rgb:x,hsv2hsl:A,hsv2hwb:y,hsv2cmyk:C,hsv2keyword:_,hwb2rgb:w,hwb2hsl:k,hwb2hsv:S,hwb2cmyk:P,hwb2keyword:I,cmyk2rgb:W,cmyk2hsl:D,cmyk2hsv:O,cmyk2hwb:R,cmyk2keyword:V,keyword2rgb:Y,keyword2hsl:q,keyword2hsv:j,keyword2hwb:X,keyword2cmyk:Z,keyword2lab:Q,keyword2xyz:G,xyz2rgb:B,xyz2lab:M,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 l};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 r=/(\w+)2(\w+)/.exec(a),n=r[1],h=r[2];s[n]=s[n]||{},s[n][h]=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 v(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 v(t,e){var i=Math.round(t[0]/255*100),o=Math.round(t[1]/255*100),s=Math.round(t[2]/255*100);return"rgba("+i+"%, "+o+"%, "+s+"%, "+(e||t[3]||1)+")"}function m(t,e){return 1>e||t[3]&&t[3]<1?p(t,e):"hsl("+t[0]+", "+t[1]+"%, "+t[2]+"%)"}function p(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hsla("+t[0]+", "+t[1]+"%, "+t[2]+"%, "+e+")"}function g(t,e){return void 0===e&&(e=void 0!==t[3]?t[3]:1),"hwb("+t[0]+", "+t[1]+"%, "+t[2]+"%"+(void 0!==e&&1!==e?", "+e:"")+")"}function f(t){return y[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:r,getHsl:n,getHwb:a,getAlpha:h,hexString:l,rgbString:c,rgbaString:d,percentString:u,percentaString:v,hslString:m,hslaString:p,hwbString:g,keyword:f};var y={};for(var C in A)y[A[C]]=C},{"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