From b5c69984e3e757f89e4b0b0fc30a525af4bf78b6 Mon Sep 17 00:00:00 2001 From: Evert Timberg Date: Sat, 13 Jun 2015 10:15:21 -0400 Subject: [PATCH] Change spaces to tabs throughout --- src/charts/chart.bar.js | 808 +++++----- src/charts/chart.doughnut.js | 724 ++++----- src/core/core.animation.js | 184 +-- src/core/core.js | 2374 ++++++++++++++--------------- src/core/core.scale.js | 498 +++--- src/core/core.tooltip.js | 540 +++---- src/elements/element.arc.js | 118 +- src/elements/element.line.js | 304 ++-- src/elements/element.point.js | 108 +- src/elements/element.rectangle.js | 142 +- 10 files changed, 2900 insertions(+), 2900 deletions(-) diff --git a/src/charts/chart.bar.js b/src/charts/chart.bar.js index e16161bb2..b77fe97fe 100644 --- a/src/charts/chart.bar.js +++ b/src/charts/chart.bar.js @@ -1,408 +1,408 @@ (function() { - "use strict"; - - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; - - var defaultConfig = { - hover: { - mode: "label" - }, - - scales: { - xAxes: [{ - type: "category", // scatter should not use a dataset axis - display: true, - position: "bottom", - id: "x-axis-1", // need an ID so datasets can reference the scale - - categorySpacing: 10, - spacing: 1, - - // grid line settings - gridLines: { - show: true, - color: "rgba(0, 0, 0, 0.05)", - lineWidth: 1, - drawOnChartArea: true, - drawTicks: true, - zeroLineWidth: 1, - zeroLineColor: "rgba(0,0,0,0.25)", - offsetGridLines: true, - }, - - // label settings - labels: { - show: true, - template: "<%=value%>", - fontSize: 12, - fontStyle: "normal", - fontColor: "#666", - fontFamily: "Helvetica Neue", - }, - }], - yAxes: [{ - type: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance - display: true, - position: "left", - id: "y-axis-1", - - spacing: 1, - - // grid line settings - gridLines: { - show: true, - color: "rgba(0, 0, 0, 0.05)", - lineWidth: 1, - drawOnChartArea: true, - drawTicks: true, // draw ticks extending towards the label - zeroLineWidth: 1, - zeroLineColor: "rgba(0,0,0,0.25)", - }, - - // scale numbers - beginAtZero: false, - override: null, - - // label settings - labels: { - show: true, - template: "<%=value%>", - fontSize: 12, - fontStyle: "normal", - fontColor: "#666", - fontFamily: "Helvetica Neue", - } - }], - }, - - }; - - - Chart.Type.extend({ - name: "Bar", - defaults: defaultConfig, - initialize: function() { - - var _this = this; - - // Events - helpers.bindEvents(this, this.options.events, this.events); - - //Create a new bar for each piece of data - helpers.each(this.data.datasets, function(dataset, datasetIndex) { - dataset.metaData = []; - helpers.each(dataset.data, function(dataPoint, index) { - dataset.metaData.push(new Chart.Rectangle({ - _chart: this.chart, - _datasetIndex: datasetIndex, - _index: index, - })); - }, this); - - // The bar chart only supports a single x axis because the x axis is always a dataset axis - dataset.xAxisID = this.options.scales.xAxes[0].id; - - if (!dataset.yAxisID) { - dataset.yAxisID = this.options.scales.yAxes[0].id; - } - }, this); - - // Build and fit the scale. Needs to happen after the axis IDs have been set - this.buildScale(); - - // Create tooltip instance exclusively for this chart with some defaults. - this.tooltip = new Chart.Tooltip({ - _chart: this.chart, - _data: this.data, - _options: this.options, - }, this); - - // Need to fit scales before we reset elements. - Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - - // So that we animate from the baseline - this.resetElements(); - - // Update the chart with the latest data. - this.update(); - }, - resetElements: function() { - // Update the points - this.eachElement(function(bar, index, dataset, datasetIndex) { - var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID]; - var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID]; - - var yScalePoint; - - if (yScale.min < 0 && yScale.max < 0) { - // all less than 0. use the top - yScalePoint = yScale.getPixelForValue(yScale.max); - } else if (yScale.min > 0 && yScale.max > 0) { - yScalePoint = yScale.getPixelForValue(yScale.min); - } else { - yScalePoint = yScale.getPixelForValue(0); - } - - helpers.extend(bar, { - // Utility - _chart: this.chart, - _xScale: xScale, - _yScale: yScale, - _datasetIndex: datasetIndex, - _index: index, - - // Desired view properties - _model: { - x: xScale.calculateBarX(this.data.datasets.length, datasetIndex, index), - y: yScalePoint, - - // Appearance - base: yScale.calculateBarBase(datasetIndex, index), - width: xScale.calculateBarWidth(this.data.datasets.length), - backgroundColor: bar.custom && bar.custom.backgroundColor ? bar.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].backgroundColor, index, this.options.elements.rectangle.backgroundColor), - borderColor: bar.custom && bar.custom.borderColor ? bar.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderColor, index, this.options.elements.rectangle.borderColor), - borderWidth: bar.custom && bar.custom.borderWidth ? bar.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderWidth, index, this.options.elements.rectangle.borderWidth), - - // Tooltip - label: this.data.labels[index], - datasetLabel: this.data.datasets[datasetIndex].label, - }, - }); - bar.pivot(); - }, this); - }, - update: function(animationDuration) { - // Update the scale sizes - Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); - - // Update the points - this.eachElement(function(bar, index, dataset, datasetIndex) { - var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID]; - var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID]; - - helpers.extend(bar, { - // Utility - _chart: this.chart, - _xScale: xScale, - _yScale: yScale, - _datasetIndex: datasetIndex, - _index: index, - - // Desired view properties - _model: { - x: xScale.calculateBarX(this.data.datasets.length, datasetIndex, index), - y: yScale.calculateBarY(datasetIndex, index), - - // Appearance - base: yScale.calculateBarBase(datasetIndex, index), - width: xScale.calculateBarWidth(this.data.datasets.length), - backgroundColor: bar.custom && bar.custom.backgroundColor ? bar.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].backgroundColor, index, this.options.elements.rectangle.backgroundColor), - borderColor: bar.custom && bar.custom.borderColor ? bar.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderColor, index, this.options.elements.rectangle.borderColor), - borderWidth: bar.custom && bar.custom.borderWidth ? bar.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderWidth, index, this.options.elements.rectangle.borderWidth), - - // Tooltip - label: this.data.labels[index], - datasetLabel: this.data.datasets[datasetIndex].label, - }, - }); - bar.pivot(); - }, this); - - - this.render(animationDuration); - }, - buildScale: function(labels) { - var self = this; - - // 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.scaleService.getScaleConstructor(this.options.scales.xAxes[0].type); - var xScale = new ScaleClass({ - ctx: this.chart.ctx, - options: this.options.scales.xAxes[0], - id: this.options.scales.xAxes[0].id, - data: this.data, - }); - this.scales[xScale.id] = xScale; - - // Build up all the y scales - helpers.each(this.options.scales.yAxes, function(yAxisOptions) { - var ScaleClass = Chart.scaleService.getScaleConstructor(yAxisOptions.type); - var scale = new ScaleClass({ - ctx: this.chart.ctx, - options: yAxisOptions, - data: this.data, - id: yAxisOptions.id, - }); - - this.scales[scale.id] = scale; - }, this); - }, - draw: function(ease) { - - var easingDecimal = ease || 1; - this.clear(); - - // Draw all the scales - helpers.each(this.scales, function(scale) { - scale.draw(this.chartArea); - }, this); - - //Draw all the bars for each dataset - this.eachElement(function(bar, index, datasetIndex) { - bar.transition(easingDecimal).draw(); - }, this); - - // Finally draw the tooltip - this.tooltip.transition(easingDecimal).draw(); - }, - events: function(e) { - - - - this.lastActive = this.lastActive || []; - - // Find Active Elements - if (e.type == 'mouseout') { - this.active = []; - } else { - this.active = function() { - switch (this.options.hover.mode) { - case 'single': - return this.getElementAtEvent(e); - case 'label': - return this.getElementsAtEvent(e); - case 'dataset': - return this.getDatasetAtEvent(e); - default: - return e; - } - }.call(this); - } - - // On Hover hook - if (this.options.hover.onHover) { - this.options.hover.onHover.call(this, this.active); - } - - if (e.type == 'mouseup' || e.type == 'click') { - if (this.options.onClick) { - this.options.onClick.call(this, e, this.active); - } - } - - var dataset; - var index; - // Remove styling for last active (even if it may still be active) - if (this.lastActive.length) { - switch (this.options.hover.mode) { - case 'single': - dataset = this.data.datasets[this.lastActive[0]._datasetIndex]; - index = this.lastActive[0]._index; - - this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.rectangle.backgroundColor); - this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.rectangle.borderColor); - this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.rectangle.borderWidth); - break; - case 'label': - for (var i = 0; i < this.lastActive.length; i++) { - dataset = this.data.datasets[this.lastActive[i]._datasetIndex]; - index = this.lastActive[i]._index; - - this.lastActive[i]._model.backgroundColor = this.lastActive[i].custom && this.lastActive[i].custom.backgroundColor ? this.lastActive[i].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.rectangle.backgroundColor); - this.lastActive[i]._model.borderColor = this.lastActive[i].custom && this.lastActive[i].custom.borderColor ? this.lastActive[i].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.rectangle.borderColor); - this.lastActive[i]._model.borderWidth = this.lastActive[i].custom && this.lastActive[i].custom.borderWidth ? this.lastActive[i].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.rectangle.borderWidth); - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } - - // Built in hover styling - if (this.active.length && this.options.hover.mode) { - switch (this.options.hover.mode) { - case 'single': - dataset = this.data.datasets[this.active[0]._datasetIndex]; - index = this.active[0]._index; - - this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); - this.active[0]._model.borderColor = this.active[0].custom && this.active[0].custom.hoverBorderColor ? this.active[0].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(this.active[0]._model.borderColor).saturate(0.5).darken(0.1).rgbString()); - this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.active[0]._model.borderWidth); - break; - case 'label': - for (var i = 0; i < this.active.length; i++) { - dataset = this.data.datasets[this.active[i]._datasetIndex]; - index = this.active[i]._index; - - this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); - this.active[i]._model.borderColor = this.active[i].custom && this.active[i].custom.hoverBorderColor ? this.active[i].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(this.active[i]._model.borderColor).saturate(0.5).darken(0.1).rgbString()); - this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.active[i]._model.borderWidth); - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } - - - // Built in Tooltips - if (this.options.tooltips.enabled) { - - // The usual updates - this.tooltip.initialize(); - - // Active - if (this.active.length) { - this.tooltip._model.opacity = 1; - - helpers.extend(this.tooltip, { - _active: this.active, - }); - - this.tooltip.update(); - } else { - // Inactive - this.tooltip._model.opacity = 0; - } - } - - - this.tooltip.pivot(); - - // Hover animations - if (!this.animating) { - var changed; - - helpers.each(this.active, function(element, index) { - if (element !== this.lastActive[index]) { - changed = true; - } - }, this); - - // If entering, leaving, or changing elements, animate the change via pivot - if ((!this.lastActive.length && this.active.length) || - (this.lastActive.length && !this.active.length) || - (this.lastActive.length && this.active.length && changed)) { - - this.stop(); - this.render(this.options.hoverAnimationDuration); - } - } - - // Remember Last Active - this.lastActive = this.active; - return this; - }, - }); + "use strict"; + + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; + + var defaultConfig = { + hover: { + mode: "label" + }, + + scales: { + xAxes: [{ + type: "category", // scatter should not use a dataset axis + display: true, + position: "bottom", + id: "x-axis-1", // need an ID so datasets can reference the scale + + categorySpacing: 10, + spacing: 1, + + // grid line settings + gridLines: { + show: true, + color: "rgba(0, 0, 0, 0.05)", + lineWidth: 1, + drawOnChartArea: true, + drawTicks: true, + zeroLineWidth: 1, + zeroLineColor: "rgba(0,0,0,0.25)", + offsetGridLines: true, + }, + + // label settings + labels: { + show: true, + template: "<%=value%>", + fontSize: 12, + fontStyle: "normal", + fontColor: "#666", + fontFamily: "Helvetica Neue", + }, + }], + yAxes: [{ + type: "linear", // only linear but allow scale type registration. This allows extensions to exist solely for log scale for instance + display: true, + position: "left", + id: "y-axis-1", + + spacing: 1, + + // grid line settings + gridLines: { + show: true, + color: "rgba(0, 0, 0, 0.05)", + lineWidth: 1, + drawOnChartArea: true, + drawTicks: true, // draw ticks extending towards the label + zeroLineWidth: 1, + zeroLineColor: "rgba(0,0,0,0.25)", + }, + + // scale numbers + beginAtZero: false, + override: null, + + // label settings + labels: { + show: true, + template: "<%=value%>", + fontSize: 12, + fontStyle: "normal", + fontColor: "#666", + fontFamily: "Helvetica Neue", + } + }], + }, + + }; + + + Chart.Type.extend({ + name: "Bar", + defaults: defaultConfig, + initialize: function() { + + var _this = this; + + // Events + helpers.bindEvents(this, this.options.events, this.events); + + //Create a new bar for each piece of data + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + dataset.metaData = []; + helpers.each(dataset.data, function(dataPoint, index) { + dataset.metaData.push(new Chart.Rectangle({ + _chart: this.chart, + _datasetIndex: datasetIndex, + _index: index, + })); + }, this); + + // The bar chart only supports a single x axis because the x axis is always a dataset axis + dataset.xAxisID = this.options.scales.xAxes[0].id; + + if (!dataset.yAxisID) { + dataset.yAxisID = this.options.scales.yAxes[0].id; + } + }, this); + + // Build and fit the scale. Needs to happen after the axis IDs have been set + this.buildScale(); + + // Create tooltip instance exclusively for this chart with some defaults. + this.tooltip = new Chart.Tooltip({ + _chart: this.chart, + _data: this.data, + _options: this.options, + }, this); + + // Need to fit scales before we reset elements. + Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); + + // So that we animate from the baseline + this.resetElements(); + + // Update the chart with the latest data. + this.update(); + }, + resetElements: function() { + // Update the points + this.eachElement(function(bar, index, dataset, datasetIndex) { + var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID]; + var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID]; + + var yScalePoint; + + if (yScale.min < 0 && yScale.max < 0) { + // all less than 0. use the top + yScalePoint = yScale.getPixelForValue(yScale.max); + } else if (yScale.min > 0 && yScale.max > 0) { + yScalePoint = yScale.getPixelForValue(yScale.min); + } else { + yScalePoint = yScale.getPixelForValue(0); + } + + helpers.extend(bar, { + // Utility + _chart: this.chart, + _xScale: xScale, + _yScale: yScale, + _datasetIndex: datasetIndex, + _index: index, + + // Desired view properties + _model: { + x: xScale.calculateBarX(this.data.datasets.length, datasetIndex, index), + y: yScalePoint, + + // Appearance + base: yScale.calculateBarBase(datasetIndex, index), + width: xScale.calculateBarWidth(this.data.datasets.length), + backgroundColor: bar.custom && bar.custom.backgroundColor ? bar.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].backgroundColor, index, this.options.elements.rectangle.backgroundColor), + borderColor: bar.custom && bar.custom.borderColor ? bar.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderColor, index, this.options.elements.rectangle.borderColor), + borderWidth: bar.custom && bar.custom.borderWidth ? bar.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderWidth, index, this.options.elements.rectangle.borderWidth), + + // Tooltip + label: this.data.labels[index], + datasetLabel: this.data.datasets[datasetIndex].label, + }, + }); + bar.pivot(); + }, this); + }, + update: function(animationDuration) { + // Update the scale sizes + Chart.scaleService.fitScalesForChart(this, this.chart.width, this.chart.height); + + // Update the points + this.eachElement(function(bar, index, dataset, datasetIndex) { + var xScale = this.scales[this.data.datasets[datasetIndex].xAxisID]; + var yScale = this.scales[this.data.datasets[datasetIndex].yAxisID]; + + helpers.extend(bar, { + // Utility + _chart: this.chart, + _xScale: xScale, + _yScale: yScale, + _datasetIndex: datasetIndex, + _index: index, + + // Desired view properties + _model: { + x: xScale.calculateBarX(this.data.datasets.length, datasetIndex, index), + y: yScale.calculateBarY(datasetIndex, index), + + // Appearance + base: yScale.calculateBarBase(datasetIndex, index), + width: xScale.calculateBarWidth(this.data.datasets.length), + backgroundColor: bar.custom && bar.custom.backgroundColor ? bar.custom.backgroundColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].backgroundColor, index, this.options.elements.rectangle.backgroundColor), + borderColor: bar.custom && bar.custom.borderColor ? bar.custom.borderColor : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderColor, index, this.options.elements.rectangle.borderColor), + borderWidth: bar.custom && bar.custom.borderWidth ? bar.custom.borderWidth : helpers.getValueAtIndexOrDefault(this.data.datasets[datasetIndex].borderWidth, index, this.options.elements.rectangle.borderWidth), + + // Tooltip + label: this.data.labels[index], + datasetLabel: this.data.datasets[datasetIndex].label, + }, + }); + bar.pivot(); + }, this); + + + this.render(animationDuration); + }, + buildScale: function(labels) { + var self = this; + + // 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.scaleService.getScaleConstructor(this.options.scales.xAxes[0].type); + var xScale = new ScaleClass({ + ctx: this.chart.ctx, + options: this.options.scales.xAxes[0], + id: this.options.scales.xAxes[0].id, + data: this.data, + }); + this.scales[xScale.id] = xScale; + + // Build up all the y scales + helpers.each(this.options.scales.yAxes, function(yAxisOptions) { + var ScaleClass = Chart.scaleService.getScaleConstructor(yAxisOptions.type); + var scale = new ScaleClass({ + ctx: this.chart.ctx, + options: yAxisOptions, + data: this.data, + id: yAxisOptions.id, + }); + + this.scales[scale.id] = scale; + }, this); + }, + draw: function(ease) { + + var easingDecimal = ease || 1; + this.clear(); + + // Draw all the scales + helpers.each(this.scales, function(scale) { + scale.draw(this.chartArea); + }, this); + + //Draw all the bars for each dataset + this.eachElement(function(bar, index, datasetIndex) { + bar.transition(easingDecimal).draw(); + }, this); + + // Finally draw the tooltip + this.tooltip.transition(easingDecimal).draw(); + }, + events: function(e) { + + + + this.lastActive = this.lastActive || []; + + // Find Active Elements + if (e.type == 'mouseout') { + this.active = []; + } else { + this.active = function() { + switch (this.options.hover.mode) { + case 'single': + return this.getElementAtEvent(e); + case 'label': + return this.getElementsAtEvent(e); + case 'dataset': + return this.getDatasetAtEvent(e); + default: + return e; + } + }.call(this); + } + + // On Hover hook + if (this.options.hover.onHover) { + this.options.hover.onHover.call(this, this.active); + } + + if (e.type == 'mouseup' || e.type == 'click') { + if (this.options.onClick) { + this.options.onClick.call(this, e, this.active); + } + } + + var dataset; + var index; + // Remove styling for last active (even if it may still be active) + if (this.lastActive.length) { + switch (this.options.hover.mode) { + case 'single': + dataset = this.data.datasets[this.lastActive[0]._datasetIndex]; + index = this.lastActive[0]._index; + + this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.rectangle.backgroundColor); + this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.rectangle.borderColor); + this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.rectangle.borderWidth); + break; + case 'label': + for (var i = 0; i < this.lastActive.length; i++) { + dataset = this.data.datasets[this.lastActive[i]._datasetIndex]; + index = this.lastActive[i]._index; + + this.lastActive[i]._model.backgroundColor = this.lastActive[i].custom && this.lastActive[i].custom.backgroundColor ? this.lastActive[i].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.rectangle.backgroundColor); + this.lastActive[i]._model.borderColor = this.lastActive[i].custom && this.lastActive[i].custom.borderColor ? this.lastActive[i].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.rectangle.borderColor); + this.lastActive[i]._model.borderWidth = this.lastActive[i].custom && this.lastActive[i].custom.borderWidth ? this.lastActive[i].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.rectangle.borderWidth); + } + break; + case 'dataset': + break; + default: + // Don't change anything + } + } + + // Built in hover styling + if (this.active.length && this.options.hover.mode) { + switch (this.options.hover.mode) { + case 'single': + dataset = this.data.datasets[this.active[0]._datasetIndex]; + index = this.active[0]._index; + + this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); + this.active[0]._model.borderColor = this.active[0].custom && this.active[0].custom.hoverBorderColor ? this.active[0].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(this.active[0]._model.borderColor).saturate(0.5).darken(0.1).rgbString()); + this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.active[0]._model.borderWidth); + break; + case 'label': + for (var i = 0; i < this.active.length; i++) { + dataset = this.data.datasets[this.active[i]._datasetIndex]; + index = this.active[i]._index; + + this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); + this.active[i]._model.borderColor = this.active[i].custom && this.active[i].custom.hoverBorderColor ? this.active[i].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, helpers.color(this.active[i]._model.borderColor).saturate(0.5).darken(0.1).rgbString()); + this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.active[i]._model.borderWidth); + } + break; + case 'dataset': + break; + default: + // Don't change anything + } + } + + + // Built in Tooltips + if (this.options.tooltips.enabled) { + + // The usual updates + this.tooltip.initialize(); + + // Active + if (this.active.length) { + this.tooltip._model.opacity = 1; + + helpers.extend(this.tooltip, { + _active: this.active, + }); + + this.tooltip.update(); + } else { + // Inactive + this.tooltip._model.opacity = 0; + } + } + + + this.tooltip.pivot(); + + // Hover animations + if (!this.animating) { + var changed; + + helpers.each(this.active, function(element, index) { + if (element !== this.lastActive[index]) { + changed = true; + } + }, this); + + // If entering, leaving, or changing elements, animate the change via pivot + if ((!this.lastActive.length && this.active.length) || + (this.lastActive.length && !this.active.length) || + (this.lastActive.length && this.active.length && changed)) { + + this.stop(); + this.render(this.options.hoverAnimationDuration); + } + } + + // Remember Last Active + this.lastActive = this.active; + return this; + }, + }); }).call(this); diff --git a/src/charts/chart.doughnut.js b/src/charts/chart.doughnut.js index 17b0802d8..47884f298 100644 --- a/src/charts/chart.doughnut.js +++ b/src/charts/chart.doughnut.js @@ -1,365 +1,365 @@ (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, - }, - - hover: { - mode: 'single' - }, - - //The percentage of the chart that we cut out of the middle. - - cutoutPercentage: 50, - - }; - - Chart.Type.extend({ - //Passing in a name registers this chart in the Chart namespace - name: "Doughnut", - //Providing a defaults will also register the deafults in the chart namespace - defaults: defaultConfig, - //Initialize is fired when the chart is initialized - Data is passed in as a parameter - //Config is automatically merged by the core of Chart.js, and is available at this.options - initialize: function() { - - //Set up tooltip events on the chart - helpers.bindEvents(this, this.options.events, this.events); - - //Create a new bar for each piece of data - helpers.each(this.data.datasets, function(dataset, datasetIndex) { - dataset.metaData = []; - helpers.each(dataset.data, function(dataPoint, index) { - dataset.metaData.push(new Chart.Arc({ - _chart: this.chart, - _datasetIndex: datasetIndex, - _index: index, - _model: {} - })); - }, this); - }, this); - - // Create tooltip instance exclusively for this chart with some defaults. - this.tooltip = new Chart.Tooltip({ - _chart: this.chart, - _data: this.data, - _options: this.options, - }, this); - - this.resetElements(); - - // Update the chart with the latest data. - this.update(); - - }, - - calculateCircumference: function(dataset, value) { - if (dataset.total > 0) { - return (Math.PI * 2) * (value / dataset.total); - } else { - return 0; - } - }, - resetElements: function() { - this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.elements.arc.borderWidth / 2) / 2; - this.innerRadius = this.options.cutoutPercentage ? (this.outerRadius / 100) * (this.options.cutoutPercentage) : 1; - this.radiusLength = (this.outerRadius - this.innerRadius) / this.data.datasets.length; - - // Update the points - helpers.each(this.data.datasets, function(dataset, datasetIndex) { - // So that calculateCircumference works - dataset.total = 0; - helpers.each(dataset.data, function(value) { - dataset.total += Math.abs(value); - }, this); - - dataset.outerRadius = this.outerRadius - (this.radiusLength * datasetIndex); - dataset.innerRadius = dataset.outerRadius - this.radiusLength; - - helpers.each(dataset.metaData, function(slice, index) { - helpers.extend(slice, { - _model: { - x: this.chart.width / 2, - y: this.chart.height / 2, - startAngle: Math.PI * -0.5, // use - PI / 2 instead of 3PI / 2 to make animations better. It means that we never deal with overflow during the transition function - circumference: (this.options.animation.animateRotate) ? 0 : this.calculateCircumference(metaSlice.value), - outerRadius: (this.options.animation.animateScale) ? 0 : dataset.outerRadius, - innerRadius: (this.options.animation.animateScale) ? 0 : dataset.innerRadius, - - backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor), - hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, this.options.elements.arc.hoverBackgroundColor), - borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth), - borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor), - - label: helpers.getValueAtIndexOrDefault(dataset.label, index, this.data.labels[index]) - }, - }); - - slice.pivot(); - }, this); - - }, this); - }, - update: function(animationDuration) { - - this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.elements.arc.borderWidth / 2) / 2; - this.innerRadius = this.options.cutoutPercentage ? (this.outerRadius / 100) * (this.options.cutoutPercentage) : 1; - this.radiusLength = (this.outerRadius - this.innerRadius) / this.data.datasets.length; - - - // Update the points - helpers.each(this.data.datasets, function(dataset, datasetIndex) { - - dataset.total = 0; - helpers.each(dataset.data, function(value) { - dataset.total += Math.abs(value); - }, this); - - - dataset.outerRadius = this.outerRadius - (this.radiusLength * datasetIndex); - - dataset.innerRadius = dataset.outerRadius - this.radiusLength; - - helpers.each(dataset.metaData, function(slice, index) { - - helpers.extend(slice, { - // Utility - _chart: this.chart, - _datasetIndex: datasetIndex, - _index: index, - - // Desired view properties - _model: { - x: this.chart.width / 2, - y: this.chart.height / 2, - circumference: this.calculateCircumference(dataset, dataset.data[index]), - outerRadius: dataset.outerRadius, - innerRadius: dataset.innerRadius, - - backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor), - hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, this.options.elements.arc.hoverBackgroundColor), - borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth), - borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor), - - label: helpers.getValueAtIndexOrDefault(dataset.label, index, this.data.labels[index]) - }, - }); - - if (index === 0) { - slice._model.startAngle = Math.PI * -0.5; // use - PI / 2 instead of 3PI / 2 to make animations better. It means that we never deal with overflow during the transition function - } else { - slice._model.startAngle = dataset.metaData[index - 1]._model.endAngle; - } - - slice._model.endAngle = slice._model.startAngle + slice._model.circumference; - - - //Check to see if it's the last slice, if not get the next and update its start angle - if (index < dataset.data.length - 1) { - dataset.metaData[index + 1]._model.startAngle = slice._model.endAngle; - } - - slice.pivot(); - }, this); - - }, this); - - this.render(animationDuration); - }, - draw: function(easeDecimal) { - easeDecimal = easeDecimal || 1; - this.clear(); - - this.eachElement(function(slice) { - slice.transition(easeDecimal).draw(); - }, this); - - this.tooltip.transition(easeDecimal).draw(); - }, - events: function(e) { - - this.lastActive = this.lastActive || []; - - // Find Active Elements - if (e.type == 'mouseout') { - this.active = []; - } else { - - this.active = function() { - switch (this.options.hover.mode) { - case 'single': - return this.getSliceAtEvent(e); - case 'label': - return this.getSlicesAtEvent(e); - case 'dataset': - return this.getDatasetAtEvent(e); - default: - return e; - } - }.call(this); - } - - // On Hover hook - if (this.options.hover.onHover) { - this.options.hover.onHover.call(this, this.active); - } - - if (e.type == 'mouseup' || e.type == 'click') { - if (this.options.onClick) { - this.options.onClick.call(this, e, this.active); - } - } - - var dataset; - var index; - // Remove styling for last active (even if it may still be active) - if (this.lastActive.length) { - switch (this.options.hover.mode) { - case 'single': - dataset = this.data.datasets[this.lastActive[0]._datasetIndex]; - index = this.lastActive[0]._index; - - this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor); - this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor); - this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth); - break; - case 'label': - for (var i = 0; i < this.lastActive.length; i++) { - dataset = this.data.datasets[this.lastActive[i]._datasetIndex]; - index = this.lastActive[i]._index; - - this.lastActive[i]._model.backgroundColor = this.lastActive[i].custom && this.lastActive[i].custom.backgroundColor ? this.lastActive[i].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor); - this.lastActive[i]._model.borderColor = this.lastActive[i].custom && this.lastActive[i].custom.borderColor ? this.lastActive[i].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor); - this.lastActive[i]._model.borderWidth = this.lastActive[i].custom && this.lastActive[i].custom.borderWidth ? this.lastActive[i].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth); - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } - - // Built in hover styling - if (this.active.length && this.options.hover.mode) { - switch (this.options.hover.mode) { - case 'single': - dataset = this.data.datasets[this.active[0]._datasetIndex]; - index = this.active[0]._index; - - this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); - this.active[0]._model.borderColor = this.active[0].custom && this.active[0].custom.hoverBorderColor ? this.active[0].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, this.active[0]._model.borderColor); - this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, this.active[0]._model.borderWidth); - break; - case 'label': - for (var i = 0; i < this.active.length; i++) { - dataset = this.data.datasets[this.active[i]._datasetIndex]; - index = this.active[i]._index; - - this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); - this.active[i]._model.borderColor = this.active[i].custom && this.active[i].custom.hoverBorderColor ? this.active[i].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, this.active[0]._model.borderColor); - this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, this.active[i]._model.borderWidth); - } - break; - case 'dataset': - break; - default: - // Don't change anything - } - } - - - // Built in Tooltips - if (this.options.tooltips.enabled) { - - // The usual updates - this.tooltip.initialize(); - - // Active - if (this.active.length) { - this.tooltip._model.opacity = 1; - - helpers.extend(this.tooltip, { - _active: this.active, - }); - - this.tooltip.update(); - } else { - // Inactive - this.tooltip._model.opacity = 0; - } - } - - - // Hover animations - this.tooltip.pivot(); - - if (!this.animating) { - var changed; - - helpers.each(this.active, function(element, index) { - if (element !== this.lastActive[index]) { - changed = true; - } - }, this); - - // If entering, leaving, or changing elements, animate the change via pivot - if ((!this.lastActive.length && this.active.length) || - (this.lastActive.length && !this.active.length) || - (this.lastActive.length && this.active.length && changed)) { - - this.stop(); - this.render(this.options.hover.animationDuration); - } - } - - // Remember Last Active - this.lastActive = this.active; - return this; - }, - getSliceAtEvent: function(e) { - var elements = []; - - var location = helpers.getRelativePosition(e); - - this.eachElement(function(slice, index) { - if (slice.inRange(location.x, location.y)) { - elements.push(slice); - } - }, this); - return elements; - }, - /*getSlicesAtEvent: function(e) { - var elements = []; - - var location = helpers.getRelativePosition(e); - - this.eachElement(function(slice, index) { - if (slice.inGroupRange(location.x, location.y)) { - elements.push(slice); - } - }, this); - return elements; - },*/ - }); - - Chart.types.Doughnut.extend({ - name: "Pie", - defaults: helpers.merge(defaultConfig, { - cutoutPercentage: 0 - }) - }); + "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, + }, + + hover: { + mode: 'single' + }, + + //The percentage of the chart that we cut out of the middle. + + cutoutPercentage: 50, + + }; + + Chart.Type.extend({ + //Passing in a name registers this chart in the Chart namespace + name: "Doughnut", + //Providing a defaults will also register the deafults in the chart namespace + defaults: defaultConfig, + //Initialize is fired when the chart is initialized - Data is passed in as a parameter + //Config is automatically merged by the core of Chart.js, and is available at this.options + initialize: function() { + + //Set up tooltip events on the chart + helpers.bindEvents(this, this.options.events, this.events); + + //Create a new bar for each piece of data + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + dataset.metaData = []; + helpers.each(dataset.data, function(dataPoint, index) { + dataset.metaData.push(new Chart.Arc({ + _chart: this.chart, + _datasetIndex: datasetIndex, + _index: index, + _model: {} + })); + }, this); + }, this); + + // Create tooltip instance exclusively for this chart with some defaults. + this.tooltip = new Chart.Tooltip({ + _chart: this.chart, + _data: this.data, + _options: this.options, + }, this); + + this.resetElements(); + + // Update the chart with the latest data. + this.update(); + + }, + + calculateCircumference: function(dataset, value) { + if (dataset.total > 0) { + return (Math.PI * 2) * (value / dataset.total); + } else { + return 0; + } + }, + resetElements: function() { + this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.elements.arc.borderWidth / 2) / 2; + this.innerRadius = this.options.cutoutPercentage ? (this.outerRadius / 100) * (this.options.cutoutPercentage) : 1; + this.radiusLength = (this.outerRadius - this.innerRadius) / this.data.datasets.length; + + // Update the points + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + // So that calculateCircumference works + dataset.total = 0; + helpers.each(dataset.data, function(value) { + dataset.total += Math.abs(value); + }, this); + + dataset.outerRadius = this.outerRadius - (this.radiusLength * datasetIndex); + dataset.innerRadius = dataset.outerRadius - this.radiusLength; + + helpers.each(dataset.metaData, function(slice, index) { + helpers.extend(slice, { + _model: { + x: this.chart.width / 2, + y: this.chart.height / 2, + startAngle: Math.PI * -0.5, // use - PI / 2 instead of 3PI / 2 to make animations better. It means that we never deal with overflow during the transition function + circumference: (this.options.animation.animateRotate) ? 0 : this.calculateCircumference(metaSlice.value), + outerRadius: (this.options.animation.animateScale) ? 0 : dataset.outerRadius, + innerRadius: (this.options.animation.animateScale) ? 0 : dataset.innerRadius, + + backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor), + hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, this.options.elements.arc.hoverBackgroundColor), + borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth), + borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor), + + label: helpers.getValueAtIndexOrDefault(dataset.label, index, this.data.labels[index]) + }, + }); + + slice.pivot(); + }, this); + + }, this); + }, + update: function(animationDuration) { + + this.outerRadius = (helpers.min([this.chart.width, this.chart.height]) - this.options.elements.arc.borderWidth / 2) / 2; + this.innerRadius = this.options.cutoutPercentage ? (this.outerRadius / 100) * (this.options.cutoutPercentage) : 1; + this.radiusLength = (this.outerRadius - this.innerRadius) / this.data.datasets.length; + + + // Update the points + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + + dataset.total = 0; + helpers.each(dataset.data, function(value) { + dataset.total += Math.abs(value); + }, this); + + + dataset.outerRadius = this.outerRadius - (this.radiusLength * datasetIndex); + + dataset.innerRadius = dataset.outerRadius - this.radiusLength; + + helpers.each(dataset.metaData, function(slice, index) { + + helpers.extend(slice, { + // Utility + _chart: this.chart, + _datasetIndex: datasetIndex, + _index: index, + + // Desired view properties + _model: { + x: this.chart.width / 2, + y: this.chart.height / 2, + circumference: this.calculateCircumference(dataset, dataset.data[index]), + outerRadius: dataset.outerRadius, + innerRadius: dataset.innerRadius, + + backgroundColor: slice.custom && slice.custom.backgroundColor ? slice.custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor), + hoverBackgroundColor: slice.custom && slice.custom.hoverBackgroundColor ? slice.custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, this.options.elements.arc.hoverBackgroundColor), + borderWidth: slice.custom && slice.custom.borderWidth ? slice.custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth), + borderColor: slice.custom && slice.custom.borderColor ? slice.custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor), + + label: helpers.getValueAtIndexOrDefault(dataset.label, index, this.data.labels[index]) + }, + }); + + if (index === 0) { + slice._model.startAngle = Math.PI * -0.5; // use - PI / 2 instead of 3PI / 2 to make animations better. It means that we never deal with overflow during the transition function + } else { + slice._model.startAngle = dataset.metaData[index - 1]._model.endAngle; + } + + slice._model.endAngle = slice._model.startAngle + slice._model.circumference; + + + //Check to see if it's the last slice, if not get the next and update its start angle + if (index < dataset.data.length - 1) { + dataset.metaData[index + 1]._model.startAngle = slice._model.endAngle; + } + + slice.pivot(); + }, this); + + }, this); + + this.render(animationDuration); + }, + draw: function(easeDecimal) { + easeDecimal = easeDecimal || 1; + this.clear(); + + this.eachElement(function(slice) { + slice.transition(easeDecimal).draw(); + }, this); + + this.tooltip.transition(easeDecimal).draw(); + }, + events: function(e) { + + this.lastActive = this.lastActive || []; + + // Find Active Elements + if (e.type == 'mouseout') { + this.active = []; + } else { + + this.active = function() { + switch (this.options.hover.mode) { + case 'single': + return this.getSliceAtEvent(e); + case 'label': + return this.getSlicesAtEvent(e); + case 'dataset': + return this.getDatasetAtEvent(e); + default: + return e; + } + }.call(this); + } + + // On Hover hook + if (this.options.hover.onHover) { + this.options.hover.onHover.call(this, this.active); + } + + if (e.type == 'mouseup' || e.type == 'click') { + if (this.options.onClick) { + this.options.onClick.call(this, e, this.active); + } + } + + var dataset; + var index; + // Remove styling for last active (even if it may still be active) + if (this.lastActive.length) { + switch (this.options.hover.mode) { + case 'single': + dataset = this.data.datasets[this.lastActive[0]._datasetIndex]; + index = this.lastActive[0]._index; + + this.lastActive[0]._model.backgroundColor = this.lastActive[0].custom && this.lastActive[0].custom.backgroundColor ? this.lastActive[0].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor); + this.lastActive[0]._model.borderColor = this.lastActive[0].custom && this.lastActive[0].custom.borderColor ? this.lastActive[0].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor); + this.lastActive[0]._model.borderWidth = this.lastActive[0].custom && this.lastActive[0].custom.borderWidth ? this.lastActive[0].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth); + break; + case 'label': + for (var i = 0; i < this.lastActive.length; i++) { + dataset = this.data.datasets[this.lastActive[i]._datasetIndex]; + index = this.lastActive[i]._index; + + this.lastActive[i]._model.backgroundColor = this.lastActive[i].custom && this.lastActive[i].custom.backgroundColor ? this.lastActive[i].custom.backgroundColor : helpers.getValueAtIndexOrDefault(dataset.backgroundColor, index, this.options.elements.arc.backgroundColor); + this.lastActive[i]._model.borderColor = this.lastActive[i].custom && this.lastActive[i].custom.borderColor ? this.lastActive[i].custom.borderColor : helpers.getValueAtIndexOrDefault(dataset.borderColor, index, this.options.elements.arc.borderColor); + this.lastActive[i]._model.borderWidth = this.lastActive[i].custom && this.lastActive[i].custom.borderWidth ? this.lastActive[i].custom.borderWidth : helpers.getValueAtIndexOrDefault(dataset.borderWidth, index, this.options.elements.arc.borderWidth); + } + break; + case 'dataset': + break; + default: + // Don't change anything + } + } + + // Built in hover styling + if (this.active.length && this.options.hover.mode) { + switch (this.options.hover.mode) { + case 'single': + dataset = this.data.datasets[this.active[0]._datasetIndex]; + index = this.active[0]._index; + + this.active[0]._model.backgroundColor = this.active[0].custom && this.active[0].custom.hoverBackgroundColor ? this.active[0].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[0]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); + this.active[0]._model.borderColor = this.active[0].custom && this.active[0].custom.hoverBorderColor ? this.active[0].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, this.active[0]._model.borderColor); + this.active[0]._model.borderWidth = this.active[0].custom && this.active[0].custom.hoverBorderWidth ? this.active[0].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, this.active[0]._model.borderWidth); + break; + case 'label': + for (var i = 0; i < this.active.length; i++) { + dataset = this.data.datasets[this.active[i]._datasetIndex]; + index = this.active[i]._index; + + this.active[i]._model.backgroundColor = this.active[i].custom && this.active[i].custom.hoverBackgroundColor ? this.active[i].custom.hoverBackgroundColor : helpers.getValueAtIndexOrDefault(dataset.hoverBackgroundColor, index, helpers.color(this.active[i]._model.backgroundColor).saturate(0.5).darken(0.1).rgbString()); + this.active[i]._model.borderColor = this.active[i].custom && this.active[i].custom.hoverBorderColor ? this.active[i].custom.hoverBorderColor : helpers.getValueAtIndexOrDefault(dataset.hoverBorderColor, index, this.active[0]._model.borderColor); + this.active[i]._model.borderWidth = this.active[i].custom && this.active[i].custom.hoverBorderWidth ? this.active[i].custom.hoverBorderWidth : helpers.getValueAtIndexOrDefault(dataset.hoverBorderWidth, index, this.active[i]._model.borderWidth); + } + break; + case 'dataset': + break; + default: + // Don't change anything + } + } + + + // Built in Tooltips + if (this.options.tooltips.enabled) { + + // The usual updates + this.tooltip.initialize(); + + // Active + if (this.active.length) { + this.tooltip._model.opacity = 1; + + helpers.extend(this.tooltip, { + _active: this.active, + }); + + this.tooltip.update(); + } else { + // Inactive + this.tooltip._model.opacity = 0; + } + } + + + // Hover animations + this.tooltip.pivot(); + + if (!this.animating) { + var changed; + + helpers.each(this.active, function(element, index) { + if (element !== this.lastActive[index]) { + changed = true; + } + }, this); + + // If entering, leaving, or changing elements, animate the change via pivot + if ((!this.lastActive.length && this.active.length) || + (this.lastActive.length && !this.active.length) || + (this.lastActive.length && this.active.length && changed)) { + + this.stop(); + this.render(this.options.hover.animationDuration); + } + } + + // Remember Last Active + this.lastActive = this.active; + return this; + }, + getSliceAtEvent: function(e) { + var elements = []; + + var location = helpers.getRelativePosition(e); + + this.eachElement(function(slice, index) { + if (slice.inRange(location.x, location.y)) { + elements.push(slice); + } + }, this); + return elements; + }, + /*getSlicesAtEvent: function(e) { + var elements = []; + + var location = helpers.getRelativePosition(e); + + this.eachElement(function(slice, index) { + if (slice.inGroupRange(location.x, location.y)) { + elements.push(slice); + } + }, this); + return elements; + },*/ + }); + + Chart.types.Doughnut.extend({ + name: "Pie", + defaults: helpers.merge(defaultConfig, { + cutoutPercentage: 0 + }) + }); }).call(this); diff --git a/src/core/core.animation.js b/src/core/core.animation.js index 1794febd7..f8812b5d9 100644 --- a/src/core/core.animation.js +++ b/src/core/core.animation.js @@ -11,117 +11,117 @@ (function() { - "use strict"; + "use strict"; - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; - Chart.defaults.global.animation = { - duration: 1000, - easing: "easeOutQuart", - onProgress: function() {}, - onComplete: function() {}, - }; + Chart.defaults.global.animation = { + duration: 1000, + easing: "easeOutQuart", + onProgress: function() {}, + onComplete: function() {}, + }; - Chart.Animation = Chart.Element.extend({ - currentStep: null, // the current animation step - numSteps: 60, // default number of steps - easing: "", // the easing to use for this animation - render: null, // render function used by the animation service + Chart.Animation = Chart.Element.extend({ + currentStep: null, // the current animation step + numSteps: 60, // default number of steps + easing: "", // the easing to use for this animation + render: null, // render function used by the animation service - onAnimationProgress: null, // user specified callback to fire on each step of the animation - onAnimationComplete: null, // user specified callback to fire when the animation finishes - }); + onAnimationProgress: null, // user specified callback to fire on each step of the animation + onAnimationComplete: null, // user specified callback to fire when the animation finishes + }); - Chart.animationService = { - frameDuration: 17, - animations: [], - dropFrames: 0, - addAnimation: function(chartInstance, animationObject, duration) { + Chart.animationService = { + frameDuration: 17, + animations: [], + dropFrames: 0, + addAnimation: function(chartInstance, animationObject, duration) { - if (!duration) { - chartInstance.animating = true; - } + if (!duration) { + chartInstance.animating = true; + } - for (var index = 0; index < this.animations.length; ++index) { - if (this.animations[index].chartInstance === chartInstance) { - // replacing an in progress animation - this.animations[index].animationObject = animationObject; - return; - } - } + for (var index = 0; index < this.animations.length; ++index) { + if (this.animations[index].chartInstance === chartInstance) { + // replacing an in progress animation + this.animations[index].animationObject = animationObject; + return; + } + } - this.animations.push({ - chartInstance: chartInstance, - animationObject: animationObject - }); + this.animations.push({ + chartInstance: chartInstance, + animationObject: animationObject + }); - // If there are no animations queued, manually kickstart a digest, for lack of a better word - if (this.animations.length == 1) { - helpers.requestAnimFrame.call(window, this.digestWrapper); - } - }, - // Cancel the animation for a given chart instance - cancelAnimation: function(chartInstance) { - var index = helpers.findNextWhere(this.animations, function(animationWrapper) { - return animationWrapper.chartInstance === chartInstance; - }); + // If there are no animations queued, manually kickstart a digest, for lack of a better word + if (this.animations.length == 1) { + helpers.requestAnimFrame.call(window, this.digestWrapper); + } + }, + // Cancel the animation for a given chart instance + cancelAnimation: function(chartInstance) { + var index = helpers.findNextWhere(this.animations, function(animationWrapper) { + return animationWrapper.chartInstance === chartInstance; + }); - if (index) { - this.animations.splice(index, 1); - chartInstance.animating = false; - } - }, - // calls startDigest with the proper context - digestWrapper: function() { - Chart.animationService.startDigest.call(Chart.animationService); - }, - startDigest: function() { + if (index) { + this.animations.splice(index, 1); + chartInstance.animating = false; + } + }, + // calls startDigest with the proper context + digestWrapper: function() { + Chart.animationService.startDigest.call(Chart.animationService); + }, + startDigest: function() { - var startTime = Date.now(); - var framesToDrop = 0; + var startTime = Date.now(); + var framesToDrop = 0; - if (this.dropFrames > 1) { - framesToDrop = Math.floor(this.dropFrames); - this.dropFrames -= framesToDrop; - } + if (this.dropFrames > 1) { + framesToDrop = Math.floor(this.dropFrames); + this.dropFrames -= framesToDrop; + } - for (var i = 0; i < this.animations.length; i++) { + for (var i = 0; i < this.animations.length; i++) { - if (this.animations[i].animationObject.currentStep === null) { - this.animations[i].animationObject.currentStep = 0; - } + if (this.animations[i].animationObject.currentStep === null) { + this.animations[i].animationObject.currentStep = 0; + } - this.animations[i].animationObject.currentStep += 1 + framesToDrop; - if (this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps) { - this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps; - } + this.animations[i].animationObject.currentStep += 1 + framesToDrop; + if (this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps) { + this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps; + } - this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject); + this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject); - if (this.animations[i].animationObject.currentStep == this.animations[i].animationObject.numSteps) { - // executed the last frame. Remove the animation. - this.animations[i].chartInstance.animating = false; - this.animations.splice(i, 1); - // Keep the index in place to offset the splice - i--; - } - } + if (this.animations[i].animationObject.currentStep == this.animations[i].animationObject.numSteps) { + // executed the last frame. Remove the animation. + this.animations[i].chartInstance.animating = false; + this.animations.splice(i, 1); + // Keep the index in place to offset the splice + i--; + } + } - var endTime = Date.now(); - var delay = endTime - startTime - this.frameDuration; - var frameDelay = delay / this.frameDuration; + var endTime = Date.now(); + var delay = endTime - startTime - this.frameDuration; + var frameDelay = delay / this.frameDuration; - if (frameDelay > 1) { - this.dropFrames += frameDelay; - } + if (frameDelay > 1) { + this.dropFrames += frameDelay; + } - // Do we have more stuff to animate? - if (this.animations.length > 0) { - helpers.requestAnimFrame.call(window, this.digestWrapper); - } - } - }; + // Do we have more stuff to animate? + if (this.animations.length > 0) { + helpers.requestAnimFrame.call(window, this.digestWrapper); + } + } + }; }).call(this); diff --git a/src/core/core.js b/src/core/core.js index b2dcc0045..c01dfbaa9 100755 --- a/src/core/core.js +++ b/src/core/core.js @@ -11,1192 +11,1192 @@ (function() { - "use strict"; - - //Declare root variable - window in the browser, global on the server - var root = this, - previous = root.Chart; - - //Occupy the global variable of Chart, and create a simple base class - var Chart = function(context) { - var chart = this; - - // Support a jQuery'd canvas element - if (context.length && context[0].getContext) { - context = context[0]; - } - - // Support a canvas domnode - if (context.getContext) { - context = context.getContext("2d"); - } - - this.canvas = context.canvas; - - this.ctx = context; - - //Variables global to the chart - var computeDimension = function(element, dimension) { - if (element['offset' + dimension]) { - return element['offset' + dimension]; - } else { - return document.defaultView.getComputedStyle(element).getPropertyValue(dimension); - } - }; - - var width = this.width = computeDimension(context.canvas, 'Width') || context.canvas.width; - var height = this.height = computeDimension(context.canvas, 'Height') || context.canvas.height; - - // Firefox requires this to work correctly - context.canvas.width = width; - context.canvas.height = height; - - width = this.width = context.canvas.width; - height = this.height = context.canvas.height; - this.aspectRatio = this.width / this.height; - //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. - helpers.retinaScale(this); - - return this; - }; - - var defaultColor = 'rgba(0,0,0,0.1)'; - - //Globally expose the defaults to allow for user updating/changing - Chart.defaults = { - global: { - responsive: true, - maintainAspectRatio: true, - events: ["mousemove", "mouseout", "click", "touchstart", "touchmove", "touchend"], - hover: { - onHover: null, - mode: 'single', - animationDuration: 400, - }, - onClick: null, - defaultColor: defaultColor, - - // Element defaults defined in element extensions - elements: {} - }, - }; - - //Create a dictionary of chart types, to allow for extension of existing types - Chart.types = {}; - - //Global Chart helpers object for utility methods and classes - var helpers = Chart.helpers = {}; - - //-- Basic js utility methods - var each = helpers.each = function(loopable, callback, self) { - var additionalArgs = Array.prototype.slice.call(arguments, 3); - // Check to see if null or undefined firstly. - if (loopable) { - if (loopable.length === +loopable.length) { - var i; - for (i = 0; i < loopable.length; i++) { - callback.apply(self, [loopable[i], i].concat(additionalArgs)); - } - } else { - for (var item in loopable) { - callback.apply(self, [loopable[item], item].concat(additionalArgs)); - } - } - } - }, - clone = helpers.clone = function(obj) { - var objClone = {}; - each(obj, function(value, key) { - if (obj.hasOwnProperty(key)) { - if (typeof value === 'object' && value !== null) { - objClone[key] = clone(value); - } else { - objClone[key] = value; - } - } - }); - return objClone; - }, - extend = helpers.extend = function(base) { - each(Array.prototype.slice.call(arguments, 1), function(extensionObject) { - each(extensionObject, function(value, key) { - if (extensionObject.hasOwnProperty(key)) { - base[key] = value; - } - }); - }); - return base; - }, - merge = helpers.merge = function(base, master) { - //Merge properties in left object over to a shallow clone of object right. - var args = Array.prototype.slice.call(arguments, 0); - args.unshift({}); - return extend.apply(null, args); - }, - // Need a special merge function to chart configs since they are now grouped - configMerge = helpers.configMerge = function(_base) { - var base = clone(_base); - helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) { - helpers.each(extension, function(value, key) { - if (extension.hasOwnProperty(key)) { - if (base.hasOwnProperty(key) && helpers.isArray(base[key]) && helpers.isArray(value)) { - // In this case we have an array of objects replacing another array. Rather than doing a strict replace, - // merge. This allows easy scale option merging - var baseArray = base[key]; - - helpers.each(value, function(valueObj, index) { - if (index < baseArray.length) { - baseArray[index] = helpers.configMerge(baseArray[index], valueObj); - } else { - baseArray.push(valueObj); // nothing to merge - } - }); - } else if (base.hasOwnProperty(key) && typeof base[key] == "object" && base[key] !== null && typeof value == "object") { - // If we are overwriting an object with an object, do a merge of the properties. - base[key] = helpers.configMerge(base[key], value); - } else { - // can just overwrite the value in this case - base[key] = value; - } - } - }); - }); - - return base; - }, - getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) { - if (!value) { - return defaultValue; - } - - if (helpers.isArray(value) && index < value.length) { - return value[index]; - } - - return value; - }, - indexOf = helpers.indexOf = function(arrayToSearch, item) { - if (Array.prototype.indexOf) { - return arrayToSearch.indexOf(item); - } else { - for (var i = 0; i < arrayToSearch.length; i++) { - if (arrayToSearch[i] === item) return i; - } - return -1; - } - }, - where = helpers.where = function(collection, filterCallback) { - var filtered = []; - - helpers.each(collection, function(item) { - if (filterCallback(item)) { - filtered.push(item); - } - }); - - return filtered; - }, - findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) { - // Default to start of the array - if (!startIndex) { - startIndex = -1; - } - for (var i = startIndex + 1; i < arrayToSearch.length; i++) { - var currentItem = arrayToSearch[i]; - if (filterCallback(currentItem)) { - return currentItem; - } - } - }, - findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) { - // Default to end of the array - if (!startIndex) { - startIndex = arrayToSearch.length; - } - for (var i = startIndex - 1; i >= 0; i--) { - var currentItem = arrayToSearch[i]; - if (filterCallback(currentItem)) { - return currentItem; - } - } - }, - inherits = helpers.inherits = function(extensions) { - //Basic javascript inheritance based on the model created in Backbone.js - var parent = this; - var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function() { - return parent.apply(this, arguments); - }; - - var Surrogate = function() { - this.constructor = ChartElement; - }; - Surrogate.prototype = parent.prototype; - ChartElement.prototype = new Surrogate(); - - ChartElement.extend = inherits; - - if (extensions) extend(ChartElement.prototype, extensions); - - ChartElement.__super__ = parent.prototype; - - return ChartElement; - }, - noop = helpers.noop = function() {}, - uid = helpers.uid = (function() { - var id = 0; - return function() { - return "chart-" + id++; - }; - })(), - warn = helpers.warn = function(str) { - //Method for warning of errors - if (window.console && typeof window.console.warn === "function") console.warn(str); - }, - amd = helpers.amd = (typeof define === 'function' && define.amd), - //-- Math methods - isNumber = helpers.isNumber = function(n) { - return !isNaN(parseFloat(n)) && isFinite(n); - }, - max = helpers.max = function(array) { - return Math.max.apply(Math, array); - }, - min = helpers.min = function(array) { - return Math.min.apply(Math, array); - }, - sign = helpers.sign = function(x) { - if (Math.sign) { - return Math.sign(x); - } else { - x = +x; // convert to a number - if (x === 0 || isNaN(x)) { - return x; - } - return x > 0 ? 1 : -1; - } - }, - log10 = helpers.log10 = function(x) { - if (Math.log10) { - return Math.log10(x) - } else { - return Math.log(x) / Math.LN10; - } - }, - cap = helpers.cap = function(valueToCap, maxValue, minValue) { - if (isNumber(maxValue)) { - if (valueToCap > maxValue) { - return maxValue; - } - } else if (isNumber(minValue)) { - if (valueToCap < minValue) { - return minValue; - } - } - return valueToCap; - }, - getDecimalPlaces = helpers.getDecimalPlaces = function(num) { - if (num % 1 !== 0 && isNumber(num)) { - var s = num.toString(); - if (s.indexOf("e-") < 0) { - // no exponent, e.g. 0.01 - return s.split(".")[1].length; - } else if (s.indexOf(".") < 0) { - // no decimal point, e.g. 1e-9 - return parseInt(s.split("e-")[1]); - } else { - // exponent and decimal point, e.g. 1.23e-9 - var parts = s.split(".")[1].split("e-"); - return parts[0].length + parseInt(parts[1]); - } - } else { - return 0; - } - }, - toRadians = helpers.toRadians = function(degrees) { - return degrees * (Math.PI / 180); - }, - toDegrees = helpers.toDegrees = function(radians) { - return radians * (180 / Math.PI); - }, - // Gets the angle from vertical upright to the point about a centre. - getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint) { - var distanceFromXCenter = anglePoint.x - centrePoint.x, - distanceFromYCenter = anglePoint.y - centrePoint.y, - radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); - - var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); - - if (angle < (-0.5 * Math.PI)) { - angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] - } - - return { - angle: angle, - distance: radialDistanceFromCenter - }; - }, - aliasPixel = helpers.aliasPixel = function(pixelWidth) { - return (pixelWidth % 2 === 0) ? 0 : 0.5; - }, - splineCurve = helpers.splineCurve = function(FirstPoint, MiddlePoint, AfterPoint, t) { - //Props to Rob Spencer at scaled innovation for his post on splining between points - //http://scaledinnovation.com/analytics/splines/aboutSplines.html - var d01 = Math.sqrt(Math.pow(MiddlePoint.x - FirstPoint.x, 2) + Math.pow(MiddlePoint.y - FirstPoint.y, 2)), - d12 = Math.sqrt(Math.pow(AfterPoint.x - MiddlePoint.x, 2) + Math.pow(AfterPoint.y - MiddlePoint.y, 2)), - fa = t * d01 / (d01 + d12), // scaling factor for triangle Ta - fb = t * d12 / (d01 + d12); - return { - previous: { - x: MiddlePoint.x - fa * (AfterPoint.x - FirstPoint.x), - y: MiddlePoint.y - fa * (AfterPoint.y - FirstPoint.y) - }, - next: { - x: MiddlePoint.x + fb * (AfterPoint.x - FirstPoint.x), - y: MiddlePoint.y + fb * (AfterPoint.y - FirstPoint.y) - } - }; - }, - calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val) { - return Math.floor(Math.log(val) / Math.LN10); - }, - calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly) { - - //Set a minimum step of two - a point at the top of the graph, and a point at the base - var minSteps = 2, - maxSteps = Math.floor(drawingSize / (textSize * 1.5)), - skipFitting = (minSteps >= maxSteps); - - var maxValue = max(valuesArray), - minValue = min(valuesArray); - - // We need some degree of seperation here to calculate the scales if all the values are the same - // Adding/minusing 0.5 will give us a range of 1. - if (maxValue === minValue) { - maxValue += 0.5; - // So we don't end up with a graph with a negative start value if we've said always start from zero - if (minValue >= 0.5 && !startFromZero) { - minValue -= 0.5; - } else { - // Make up a whole number above the values - maxValue += 0.5; - } - } - - var valueRange = Math.abs(maxValue - minValue), - rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange), - graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude), - graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude), - graphRange = graphMax - graphMin, - stepValue = Math.pow(10, rangeOrderOfMagnitude), - numberOfSteps = Math.round(graphRange / stepValue); - - //If we have more space on the graph we'll use it to give more definition to the data - while ((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) { - if (numberOfSteps > maxSteps) { - stepValue *= 2; - numberOfSteps = Math.round(graphRange / stepValue); - // Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps. - if (numberOfSteps % 1 !== 0) { - skipFitting = true; - } - } - //We can fit in double the amount of scale points on the scale - else { - //If user has declared ints only, and the step value isn't a decimal - if (integersOnly && rangeOrderOfMagnitude >= 0) { - //If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float - if (stepValue / 2 % 1 === 0) { - stepValue /= 2; - numberOfSteps = Math.round(graphRange / stepValue); - } - //If it would make it a float break out of the loop - else { - break; - } - } - //If the scale doesn't have to be an int, make the scale more granular anyway. - else { - stepValue /= 2; - numberOfSteps = Math.round(graphRange / stepValue); - } - - } - } - - if (skipFitting) { - numberOfSteps = minSteps; - stepValue = graphRange / numberOfSteps; - } - return { - steps: numberOfSteps, - stepValue: stepValue, - min: graphMin, - max: graphMin + (numberOfSteps * stepValue) - }; - - }, - // Implementation of the nice number algorithm used in determining where axis labels will go - niceNum = helpers.niceNum = function(range, round) { - var exponent = Math.floor(helpers.log10(range)); - var fraction = range / Math.pow(10, exponent); - var niceFraction; - - if (round) { - if (fraction < 1.5) { - niceFraction = 1; - } else if (fraction < 3) { - niceFraction = 2; - } else if (fraction < 7) { - niceFraction = 5; - } else { - niceFraction = 10; - } - } else { - if (fraction <= 1.0) { - niceFraction = 1; - } else if (fraction <= 2) { - niceFraction = 2; - } else if (fraction <= 5) { - niceFraction = 5; - } else { - niceFraction = 10; - } - } - - return niceFraction * Math.pow(10, exponent); - }, - /* jshint ignore:start */ - // Blows up jshint errors based on the new Function constructor - //Templating methods - //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/ - template = helpers.template = function(templateString, valuesObject) { - - // If templateString is function rather than string-template - call the function for valuesObject - - if (templateString instanceof Function) { - return templateString(valuesObject); - } - - var cache = {}; - - function tmpl(str, data) { - // Figure out if we're getting a template, or if we need to - // load the template - and be sure to cache the result. - var fn = !/\W/.test(str) ? - cache[str] = cache[str] : - - // Generate a reusable function that will serve as a template - // generator (and which will be cached). - new Function("obj", - "var p=[],print=function(){p.push.apply(p,arguments);};" + - - // Introduce the data as local variables using with(){} - "with(obj){p.push('" + - - // Convert the template into pure JavaScript - str - .replace(/[\r\t\n]/g, " ") - .split("<%").join("\t") - .replace(/((^|%>)[^\t]*)'/g, "$1\r") - .replace(/\t=(.*?)%>/g, "',$1,'") - .split("\t").join("');") - .split("%>").join("p.push('") - .split("\r").join("\\'") + - "');}return p.join('');" - ); - - // Provide some basic currying to the user - return data ? fn(data) : fn; - } - return tmpl(templateString, valuesObject); - }, - /* jshint ignore:end */ - generateLabels = helpers.generateLabels = function(templateString, numberOfSteps, graphMin, stepValue) { - var labelsArray = new Array(numberOfSteps); - if (templateString) { - each(labelsArray, function(val, index) { - labelsArray[index] = template(templateString, { - value: (graphMin + (stepValue * (index + 1))) - }); - }); - } - return labelsArray; - }, - //--Animation methods - //Easing functions adapted from Robert Penner's easing equations - //http://www.robertpenner.com/easing/ - easingEffects = helpers.easingEffects = { - linear: function(t) { - return t; - }, - easeInQuad: function(t) { - return t * t; - }, - easeOutQuad: function(t) { - return -1 * t * (t - 2); - }, - easeInOutQuad: function(t) { - if ((t /= 1 / 2) < 1) { - return 1 / 2 * t * t; - } - return -1 / 2 * ((--t) * (t - 2) - 1); - }, - easeInCubic: function(t) { - return t * t * t; - }, - easeOutCubic: function(t) { - return 1 * ((t = t / 1 - 1) * t * t + 1); - }, - easeInOutCubic: function(t) { - if ((t /= 1 / 2) < 1) { - return 1 / 2 * t * t * t; - } - return 1 / 2 * ((t -= 2) * t * t + 2); - }, - easeInQuart: function(t) { - return t * t * t * t; - }, - easeOutQuart: function(t) { - return -1 * ((t = t / 1 - 1) * t * t * t - 1); - }, - easeInOutQuart: function(t) { - if ((t /= 1 / 2) < 1) { - return 1 / 2 * t * t * t * t; - } - return -1 / 2 * ((t -= 2) * t * t * t - 2); - }, - easeInQuint: function(t) { - return 1 * (t /= 1) * t * t * t * t; - }, - easeOutQuint: function(t) { - return 1 * ((t = t / 1 - 1) * t * t * t * t + 1); - }, - easeInOutQuint: function(t) { - if ((t /= 1 / 2) < 1) { - return 1 / 2 * t * t * t * t * t; - } - return 1 / 2 * ((t -= 2) * t * t * t * t + 2); - }, - easeInSine: function(t) { - return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1; - }, - easeOutSine: function(t) { - return 1 * Math.sin(t / 1 * (Math.PI / 2)); - }, - easeInOutSine: function(t) { - return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1); - }, - easeInExpo: function(t) { - return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1)); - }, - easeOutExpo: function(t) { - return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1); - }, - easeInOutExpo: function(t) { - if (t === 0) { - return 0; - } - if (t === 1) { - return 1; - } - if ((t /= 1 / 2) < 1) { - return 1 / 2 * Math.pow(2, 10 * (t - 1)); - } - return 1 / 2 * (-Math.pow(2, -10 * --t) + 2); - }, - easeInCirc: function(t) { - if (t >= 1) { - return t; - } - return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1); - }, - easeOutCirc: function(t) { - return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t); - }, - easeInOutCirc: function(t) { - if ((t /= 1 / 2) < 1) { - return -1 / 2 * (Math.sqrt(1 - t * t) - 1); - } - return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1); - }, - easeInElastic: function(t) { - var s = 1.70158; - var p = 0; - var a = 1; - if (t === 0) { - return 0; - } - if ((t /= 1) == 1) { - return 1; - } - if (!p) { - p = 1 * 0.3; - } - if (a < Math.abs(1)) { - a = 1; - s = p / 4; - } else { - s = p / (2 * Math.PI) * Math.asin(1 / a); - } - return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); - }, - easeOutElastic: function(t) { - var s = 1.70158; - var p = 0; - var a = 1; - if (t === 0) { - return 0; - } - if ((t /= 1) == 1) { - return 1; - } - if (!p) { - p = 1 * 0.3; - } - if (a < Math.abs(1)) { - a = 1; - s = p / 4; - } else { - s = p / (2 * Math.PI) * Math.asin(1 / a); - } - return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1; - }, - easeInOutElastic: function(t) { - var s = 1.70158; - var p = 0; - var a = 1; - if (t === 0) { - return 0; - } - if ((t /= 1 / 2) == 2) { - return 1; - } - if (!p) { - p = 1 * (0.3 * 1.5); - } - if (a < Math.abs(1)) { - a = 1; - s = p / 4; - } else { - s = p / (2 * Math.PI) * Math.asin(1 / a); - } - if (t < 1) { - return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); - } - return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1; - }, - easeInBack: function(t) { - var s = 1.70158; - return 1 * (t /= 1) * t * ((s + 1) * t - s); - }, - easeOutBack: function(t) { - var s = 1.70158; - return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1); - }, - easeInOutBack: function(t) { - var s = 1.70158; - if ((t /= 1 / 2) < 1) { - return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)); - } - return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); - }, - easeInBounce: function(t) { - return 1 - easingEffects.easeOutBounce(1 - t); - }, - easeOutBounce: function(t) { - if ((t /= 1) < (1 / 2.75)) { - return 1 * (7.5625 * t * t); - } else if (t < (2 / 2.75)) { - return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75); - } else if (t < (2.5 / 2.75)) { - return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375); - } else { - return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375); - } - }, - easeInOutBounce: function(t) { - if (t < 1 / 2) { - return easingEffects.easeInBounce(t * 2) * 0.5; - } - return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5; - } - }, - //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ - requestAnimFrame = helpers.requestAnimFrame = (function() { - return window.requestAnimationFrame || - window.webkitRequestAnimationFrame || - window.mozRequestAnimationFrame || - window.oRequestAnimationFrame || - window.msRequestAnimationFrame || - function(callback) { - return window.setTimeout(callback, 1000 / 60); - }; - })(), - cancelAnimFrame = helpers.cancelAnimFrame = (function() { - return window.cancelAnimationFrame || - window.webkitCancelAnimationFrame || - window.mozCancelAnimationFrame || - window.oCancelAnimationFrame || - window.msCancelAnimationFrame || - function(callback) { - return window.clearTimeout(callback, 1000 / 60); - }; - })(), - animationLoop = helpers.animationLoop = function(callback, totalSteps, easingString, onProgress, onComplete, chartInstance) { - - var currentStep = 0, - easingFunction = easingEffects[easingString] || easingEffects.linear; - - var animationFrame = function() { - currentStep++; - var stepDecimal = currentStep / totalSteps; - var easeDecimal = easingFunction(stepDecimal); - - callback.call(chartInstance, easeDecimal, stepDecimal, currentStep); - onProgress.call(chartInstance, easeDecimal, stepDecimal); - if (currentStep < totalSteps) { - chartInstance.animationFrame = requestAnimFrame(animationFrame); - } else { - onComplete.apply(chartInstance); - } - }; - requestAnimFrame(animationFrame); - }, - //-- DOM methods - getRelativePosition = helpers.getRelativePosition = function(evt) { - var mouseX, mouseY; - var e = evt.originalEvent || evt, - canvas = evt.currentTarget || evt.srcElement, - boundingRect = canvas.getBoundingClientRect(); - - if (e.touches) { - mouseX = e.touches[0].clientX - boundingRect.left; - mouseY = e.touches[0].clientY - boundingRect.top; - - } else { - mouseX = e.clientX - boundingRect.left; - mouseY = e.clientY - boundingRect.top; - } - - return { - x: mouseX, - y: mouseY - }; - - }, - addEvent = helpers.addEvent = function(node, eventType, method) { - if (node.addEventListener) { - node.addEventListener(eventType, method); - } else if (node.attachEvent) { - node.attachEvent("on" + eventType, method); - } else { - node["on" + eventType] = method; - } - }, - removeEvent = helpers.removeEvent = function(node, eventType, handler) { - if (node.removeEventListener) { - node.removeEventListener(eventType, handler, false); - } else if (node.detachEvent) { - node.detachEvent("on" + eventType, handler); - } else { - node["on" + eventType] = noop; - } - }, - bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler) { - // Create the events object if it's not already present - if (!chartInstance.events) chartInstance.events = {}; - - each(arrayOfEvents, function(eventName) { - chartInstance.events[eventName] = function() { - handler.apply(chartInstance, arguments); - }; - addEvent(chartInstance.chart.canvas, eventName, chartInstance.events[eventName]); - }); - }, - unbindEvents = helpers.unbindEvents = function(chartInstance, arrayOfEvents) { - each(arrayOfEvents, function(handler, eventName) { - removeEvent(chartInstance.chart.canvas, eventName, handler); - }); - }, - getMaximumWidth = helpers.getMaximumWidth = function(domNode) { - var container = domNode.parentNode, - padding = parseInt(getStyle(container, 'padding-left')) + parseInt(getStyle(container, 'padding-right')); - // TODO = check cross browser stuff with this. - return container.clientWidth - padding; - }, - getMaximumHeight = helpers.getMaximumHeight = function(domNode) { - var container = domNode.parentNode, - padding = parseInt(getStyle(container, 'padding-bottom')) + parseInt(getStyle(container, 'padding-top')); - // TODO = check cross browser stuff with this. - return container.clientHeight - padding; - }, - getStyle = helpers.getStyle = function(el, property) { - return el.currentStyle ? - el.currentStyle[property] : - document.defaultView.getComputedStyle(el, null).getPropertyValue(property); - }, - getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support - retinaScale = helpers.retinaScale = function(chart) { - var ctx = chart.ctx, - width = chart.canvas.width, - height = chart.canvas.height; - - if (window.devicePixelRatio) { - ctx.canvas.style.width = width + "px"; - ctx.canvas.style.height = height + "px"; - ctx.canvas.height = height * window.devicePixelRatio; - ctx.canvas.width = width * window.devicePixelRatio; - ctx.scale(window.devicePixelRatio, window.devicePixelRatio); - } - }, - //-- Canvas methods - clear = helpers.clear = function(chart) { - chart.ctx.clearRect(0, 0, chart.width, chart.height); - }, - fontString = helpers.fontString = function(pixelSize, fontStyle, fontFamily) { - return fontStyle + " " + pixelSize + "px " + fontFamily; - }, - longestText = helpers.longestText = function(ctx, font, arrayOfStrings) { - ctx.font = font; - var longest = 0; - each(arrayOfStrings, function(string) { - var textWidth = ctx.measureText(string).width; - longest = (textWidth > longest) ? textWidth : longest; - }); - return longest; - }, - drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx, x, y, width, height, radius) { - ctx.beginPath(); - ctx.moveTo(x + radius, y); - ctx.lineTo(x + width - radius, y); - ctx.quadraticCurveTo(x + width, y, x + width, y + radius); - ctx.lineTo(x + width, y + height - radius); - ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); - ctx.lineTo(x + radius, y + height); - ctx.quadraticCurveTo(x, y + height, x, y + height - radius); - ctx.lineTo(x, y + radius); - ctx.quadraticCurveTo(x, y, x + radius, y); - ctx.closePath(); - }, - color = helpers.color = function(color) { - if (!window.Color) { - console.log('Color.js not found!'); - return color; - } - return window.Color(color); - }, - isArray = helpers.isArray = function(obj) { - if (!Array.isArray) { - return Object.prototype.toString.call(arg) === '[object Array]'; - } - return Array.isArray(obj); - }; - - //Store a reference to each instance - allowing us to globally resize chart instances on window resize. - //Destroy method on the chart will remove the instance of the chart from this reference. - Chart.instances = {}; - - Chart.Type = function(config, instance) { - this.data = config.data; - this.options = config.options; - this.chart = instance; - this.id = uid(); - //Add the chart instance to the global namespace - Chart.instances[this.id] = this; - - // Initialize is always called when a chart type is created - // By default it is a no op, but it should be extended - if (this.options.responsive) { - this.resize(); - } - this.initialize.call(this); - }; - - //Core methods that'll be a part of every chart type - extend(Chart.Type.prototype, { - initialize: function() { - return this; - }, - clear: function() { - clear(this.chart); - return this; - }, - stop: function() { - // Stops any current animation loop occuring - Chart.animationService.cancelAnimation(this); - return this; - }, - resize: function() { - this.stop(); - var canvas = this.chart.canvas, - newWidth = getMaximumWidth(this.chart.canvas), - newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas); - - canvas.width = this.chart.width = newWidth; - canvas.height = this.chart.height = newHeight; - - retinaScale(this.chart); - - return this; - }, - redraw: noop, - render: function(duration) { - - if (this.options.animation.duration !== 0 || duration) { - var animation = new Chart.Animation(); - animation.numSteps = (duration || this.options.animation.duration) / 16.66; //60 fps - animation.easing = this.options.animation.easing; - - // render function - animation.render = function(chartInstance, animationObject) { - var easingFunction = helpers.easingEffects[animationObject.easing]; - var stepDecimal = animationObject.currentStep / animationObject.numSteps; - var easeDecimal = easingFunction(stepDecimal); - - chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep); - }; - - // user events - animation.onAnimationProgress = this.options.onAnimationProgress; - animation.onAnimationComplete = this.options.onAnimationComplete; - - Chart.animationService.addAnimation(this, animation, duration); - } else { - this.draw(); - this.options.onAnimationComplete.call(this); - } - return this; - }, - eachElement: function(callback) { - helpers.each(this.data.datasets, function(dataset, datasetIndex) { - helpers.each(dataset.metaData, callback, this, dataset.metaData, datasetIndex); - }, this); - }, - eachValue: function(callback) { - helpers.each(this.data.datasets, function(dataset, datasetIndex) { - helpers.each(dataset.data, callback, this, datasetIndex); - }, this); - }, - eachDataset: function(callback) { - helpers.each(this.data.datasets, callback, this); - }, - getElementsAtEvent: function(e) { - var elementsArray = [], - eventPosition = helpers.getRelativePosition(e), - datasetIterator = function(dataset) { - elementsArray.push(dataset.metaData[elementIndex]); - }, - elementIndex; - - for (var datasetIndex = 0; datasetIndex < this.data.datasets.length; datasetIndex++) { - for (elementIndex = 0; elementIndex < this.data.datasets[datasetIndex].metaData.length; elementIndex++) { - if (this.data.datasets[datasetIndex].metaData[elementIndex].inGroupRange(eventPosition.x, eventPosition.y)) { - helpers.each(this.data.datasets, datasetIterator); - } - } - } - - return elementsArray.length ? elementsArray : []; - }, - // Get the single element that was clicked on - // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was drawn - getElementAtEvent: function(e) { - var element = []; - var eventPosition = helpers.getRelativePosition(e); - - for (var datasetIndex = 0; datasetIndex < this.data.datasets.length; ++datasetIndex) { - for (var elementIndex = 0; elementIndex < this.data.datasets[datasetIndex].metaData.length; ++elementIndex) { - if (this.data.datasets[datasetIndex].metaData[elementIndex].inRange(eventPosition.x, eventPosition.y)) { - element.push(this.data.datasets[datasetIndex].metaData[elementIndex]); - return element; - } - } - } - - return []; - }, - generateLegend: function() { - return template(this.options.legendTemplate, this); - }, - destroy: function() { - this.clear(); - unbindEvents(this, this.events); - var canvas = this.chart.canvas; - - // Reset canvas height/width attributes starts a fresh with the canvas context - canvas.width = this.chart.width; - canvas.height = this.chart.height; - - // < IE9 doesn't support removeProperty - if (canvas.style.removeProperty) { - canvas.style.removeProperty('width'); - canvas.style.removeProperty('height'); - } else { - canvas.style.removeAttribute('width'); - canvas.style.removeAttribute('height'); - } - - delete Chart.instances[this.id]; - }, - toBase64Image: function() { - return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments); - } - }); - - Chart.Type.extend = function(extensions) { - - var parent = this; - - var ChartType = function() { - return parent.apply(this, arguments); - }; - - //Copy the prototype object of the this class - ChartType.prototype = clone(parent.prototype); - //Now overwrite some of the properties in the base class with the new extensions - extend(ChartType.prototype, extensions); - - ChartType.extend = Chart.Type.extend; - - if (extensions.name || parent.prototype.name) { - - var chartName = extensions.name || parent.prototype.name; - //Assign any potential default values of the new chart type - - //If none are defined, we'll use a clone of the chart type this is being extended from. - //I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart - //doesn't define some defaults of their own. - - var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {}; - - Chart.defaults[chartName] = helpers.configMerge(baseDefaults, extensions.defaults); - - Chart.types[chartName] = ChartType; - - //Register this new chart type in the Chart prototype - Chart.prototype[chartName] = function(config) { - config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[chartName], config.options || {}); - return new ChartType(config, this); - }; - } else { - warn("Name not provided for this chart, so it hasn't been registered"); - } - return parent; - }; - - Chart.Element = function(configuration) { - extend(this, configuration); - this.initialize.apply(this, arguments); - }; - extend(Chart.Element.prototype, { - initialize: function() {}, - pivot: function() { - if (!this._view) { - this._view = clone(this._model); - } - this._start = clone(this._view); - return this; - }, - transition: function(ease) { - if (!this._view) { - this._view = clone(this._model); - } - if (!this._start) { - this.pivot(); - } - - each(this._model, function(value, key) { - - if (key[0] === '_' || !this._model.hasOwnProperty(key)) { - // Only non-underscored properties - } - - // Init if doesn't exist - else if (!this._view[key]) { - if (typeof value === 'number') { - this._view[key] = value * ease; - } else { - this._view[key] = value || null; - } - } - - // No unnecessary computations - else if (this._model[key] === this._view[key]) { - // It's the same! Woohoo! - } - - // Color transitions if possible - else if (typeof value === 'string') { - try { - var color = helpers.color(this._start[key]).mix(helpers.color(this._model[key]), ease); - this._view[key] = color.rgbString(); - } catch (err) { - this._view[key] = value; - } - } - // Number transitions - else if (typeof value === 'number') { - var startVal = this._start[key] !== undefined ? this._start[key] : 0; - this._view[key] = ((this._model[key] - startVal) * ease) + startVal; - } - // Everything else - else { - this._view[key] = value; - } - - }, this); - - if (ease === 1) { - delete this._start; - } - return this; - }, - tooltipPosition: function() { - return { - x: this._model.x, - y: this._model.y - }; - }, - hasValue: function() { - return isNumber(this._model.x) && isNumber(this._model.y); - } - }); - - Chart.Element.extend = inherits; - - - // Attach global event to resize each chart instance when the browser resizes - helpers.addEvent(window, "resize", (function() { - // Basic debounce of resize function so it doesn't hurt performance when resizing browser. - var timeout; - return function() { - clearTimeout(timeout); - timeout = setTimeout(function() { - each(Chart.instances, function(instance) { - // If the responsive flag is set in the chart instance config - // Cascade the resize event down to the chart. - if (instance.options.responsive) { - instance.resize(); - instance.update(); - } - }); - }, 50); - }; - })()); - - - if (amd) { - define(function() { - return Chart; - }); - } else if (typeof module === 'object' && module.exports) { - module.exports = Chart; - } - - root.Chart = Chart; - - Chart.noConflict = function() { - root.Chart = previous; - return Chart; - }; + "use strict"; + + //Declare root variable - window in the browser, global on the server + var root = this, + previous = root.Chart; + + //Occupy the global variable of Chart, and create a simple base class + var Chart = function(context) { + var chart = this; + + // Support a jQuery'd canvas element + if (context.length && context[0].getContext) { + context = context[0]; + } + + // Support a canvas domnode + if (context.getContext) { + context = context.getContext("2d"); + } + + this.canvas = context.canvas; + + this.ctx = context; + + //Variables global to the chart + var computeDimension = function(element, dimension) { + if (element['offset' + dimension]) { + return element['offset' + dimension]; + } else { + return document.defaultView.getComputedStyle(element).getPropertyValue(dimension); + } + }; + + var width = this.width = computeDimension(context.canvas, 'Width') || context.canvas.width; + var height = this.height = computeDimension(context.canvas, 'Height') || context.canvas.height; + + // Firefox requires this to work correctly + context.canvas.width = width; + context.canvas.height = height; + + width = this.width = context.canvas.width; + height = this.height = context.canvas.height; + this.aspectRatio = this.width / this.height; + //High pixel density displays - multiply the size of the canvas height/width by the device pixel ratio, then scale. + helpers.retinaScale(this); + + return this; + }; + + var defaultColor = 'rgba(0,0,0,0.1)'; + + //Globally expose the defaults to allow for user updating/changing + Chart.defaults = { + global: { + responsive: true, + maintainAspectRatio: true, + events: ["mousemove", "mouseout", "click", "touchstart", "touchmove", "touchend"], + hover: { + onHover: null, + mode: 'single', + animationDuration: 400, + }, + onClick: null, + defaultColor: defaultColor, + + // Element defaults defined in element extensions + elements: {} + }, + }; + + //Create a dictionary of chart types, to allow for extension of existing types + Chart.types = {}; + + //Global Chart helpers object for utility methods and classes + var helpers = Chart.helpers = {}; + + //-- Basic js utility methods + var each = helpers.each = function(loopable, callback, self) { + var additionalArgs = Array.prototype.slice.call(arguments, 3); + // Check to see if null or undefined firstly. + if (loopable) { + if (loopable.length === +loopable.length) { + var i; + for (i = 0; i < loopable.length; i++) { + callback.apply(self, [loopable[i], i].concat(additionalArgs)); + } + } else { + for (var item in loopable) { + callback.apply(self, [loopable[item], item].concat(additionalArgs)); + } + } + } + }, + clone = helpers.clone = function(obj) { + var objClone = {}; + each(obj, function(value, key) { + if (obj.hasOwnProperty(key)) { + if (typeof value === 'object' && value !== null) { + objClone[key] = clone(value); + } else { + objClone[key] = value; + } + } + }); + return objClone; + }, + extend = helpers.extend = function(base) { + each(Array.prototype.slice.call(arguments, 1), function(extensionObject) { + each(extensionObject, function(value, key) { + if (extensionObject.hasOwnProperty(key)) { + base[key] = value; + } + }); + }); + return base; + }, + merge = helpers.merge = function(base, master) { + //Merge properties in left object over to a shallow clone of object right. + var args = Array.prototype.slice.call(arguments, 0); + args.unshift({}); + return extend.apply(null, args); + }, + // Need a special merge function to chart configs since they are now grouped + configMerge = helpers.configMerge = function(_base) { + var base = clone(_base); + helpers.each(Array.prototype.slice.call(arguments, 1), function(extension) { + helpers.each(extension, function(value, key) { + if (extension.hasOwnProperty(key)) { + if (base.hasOwnProperty(key) && helpers.isArray(base[key]) && helpers.isArray(value)) { + // In this case we have an array of objects replacing another array. Rather than doing a strict replace, + // merge. This allows easy scale option merging + var baseArray = base[key]; + + helpers.each(value, function(valueObj, index) { + if (index < baseArray.length) { + baseArray[index] = helpers.configMerge(baseArray[index], valueObj); + } else { + baseArray.push(valueObj); // nothing to merge + } + }); + } else if (base.hasOwnProperty(key) && typeof base[key] == "object" && base[key] !== null && typeof value == "object") { + // If we are overwriting an object with an object, do a merge of the properties. + base[key] = helpers.configMerge(base[key], value); + } else { + // can just overwrite the value in this case + base[key] = value; + } + } + }); + }); + + return base; + }, + getValueAtIndexOrDefault = helpers.getValueAtIndexOrDefault = function(value, index, defaultValue) { + if (!value) { + return defaultValue; + } + + if (helpers.isArray(value) && index < value.length) { + return value[index]; + } + + return value; + }, + indexOf = helpers.indexOf = function(arrayToSearch, item) { + if (Array.prototype.indexOf) { + return arrayToSearch.indexOf(item); + } else { + for (var i = 0; i < arrayToSearch.length; i++) { + if (arrayToSearch[i] === item) return i; + } + return -1; + } + }, + where = helpers.where = function(collection, filterCallback) { + var filtered = []; + + helpers.each(collection, function(item) { + if (filterCallback(item)) { + filtered.push(item); + } + }); + + return filtered; + }, + findNextWhere = helpers.findNextWhere = function(arrayToSearch, filterCallback, startIndex) { + // Default to start of the array + if (!startIndex) { + startIndex = -1; + } + for (var i = startIndex + 1; i < arrayToSearch.length; i++) { + var currentItem = arrayToSearch[i]; + if (filterCallback(currentItem)) { + return currentItem; + } + } + }, + findPreviousWhere = helpers.findPreviousWhere = function(arrayToSearch, filterCallback, startIndex) { + // Default to end of the array + if (!startIndex) { + startIndex = arrayToSearch.length; + } + for (var i = startIndex - 1; i >= 0; i--) { + var currentItem = arrayToSearch[i]; + if (filterCallback(currentItem)) { + return currentItem; + } + } + }, + inherits = helpers.inherits = function(extensions) { + //Basic javascript inheritance based on the model created in Backbone.js + var parent = this; + var ChartElement = (extensions && extensions.hasOwnProperty("constructor")) ? extensions.constructor : function() { + return parent.apply(this, arguments); + }; + + var Surrogate = function() { + this.constructor = ChartElement; + }; + Surrogate.prototype = parent.prototype; + ChartElement.prototype = new Surrogate(); + + ChartElement.extend = inherits; + + if (extensions) extend(ChartElement.prototype, extensions); + + ChartElement.__super__ = parent.prototype; + + return ChartElement; + }, + noop = helpers.noop = function() {}, + uid = helpers.uid = (function() { + var id = 0; + return function() { + return "chart-" + id++; + }; + })(), + warn = helpers.warn = function(str) { + //Method for warning of errors + if (window.console && typeof window.console.warn === "function") console.warn(str); + }, + amd = helpers.amd = (typeof define === 'function' && define.amd), + //-- Math methods + isNumber = helpers.isNumber = function(n) { + return !isNaN(parseFloat(n)) && isFinite(n); + }, + max = helpers.max = function(array) { + return Math.max.apply(Math, array); + }, + min = helpers.min = function(array) { + return Math.min.apply(Math, array); + }, + sign = helpers.sign = function(x) { + if (Math.sign) { + return Math.sign(x); + } else { + x = +x; // convert to a number + if (x === 0 || isNaN(x)) { + return x; + } + return x > 0 ? 1 : -1; + } + }, + log10 = helpers.log10 = function(x) { + if (Math.log10) { + return Math.log10(x) + } else { + return Math.log(x) / Math.LN10; + } + }, + cap = helpers.cap = function(valueToCap, maxValue, minValue) { + if (isNumber(maxValue)) { + if (valueToCap > maxValue) { + return maxValue; + } + } else if (isNumber(minValue)) { + if (valueToCap < minValue) { + return minValue; + } + } + return valueToCap; + }, + getDecimalPlaces = helpers.getDecimalPlaces = function(num) { + if (num % 1 !== 0 && isNumber(num)) { + var s = num.toString(); + if (s.indexOf("e-") < 0) { + // no exponent, e.g. 0.01 + return s.split(".")[1].length; + } else if (s.indexOf(".") < 0) { + // no decimal point, e.g. 1e-9 + return parseInt(s.split("e-")[1]); + } else { + // exponent and decimal point, e.g. 1.23e-9 + var parts = s.split(".")[1].split("e-"); + return parts[0].length + parseInt(parts[1]); + } + } else { + return 0; + } + }, + toRadians = helpers.toRadians = function(degrees) { + return degrees * (Math.PI / 180); + }, + toDegrees = helpers.toDegrees = function(radians) { + return radians * (180 / Math.PI); + }, + // Gets the angle from vertical upright to the point about a centre. + getAngleFromPoint = helpers.getAngleFromPoint = function(centrePoint, anglePoint) { + var distanceFromXCenter = anglePoint.x - centrePoint.x, + distanceFromYCenter = anglePoint.y - centrePoint.y, + radialDistanceFromCenter = Math.sqrt(distanceFromXCenter * distanceFromXCenter + distanceFromYCenter * distanceFromYCenter); + + var angle = Math.atan2(distanceFromYCenter, distanceFromXCenter); + + if (angle < (-0.5 * Math.PI)) { + angle += 2.0 * Math.PI; // make sure the returned angle is in the range of (-PI/2, 3PI/2] + } + + return { + angle: angle, + distance: radialDistanceFromCenter + }; + }, + aliasPixel = helpers.aliasPixel = function(pixelWidth) { + return (pixelWidth % 2 === 0) ? 0 : 0.5; + }, + splineCurve = helpers.splineCurve = function(FirstPoint, MiddlePoint, AfterPoint, t) { + //Props to Rob Spencer at scaled innovation for his post on splining between points + //http://scaledinnovation.com/analytics/splines/aboutSplines.html + var d01 = Math.sqrt(Math.pow(MiddlePoint.x - FirstPoint.x, 2) + Math.pow(MiddlePoint.y - FirstPoint.y, 2)), + d12 = Math.sqrt(Math.pow(AfterPoint.x - MiddlePoint.x, 2) + Math.pow(AfterPoint.y - MiddlePoint.y, 2)), + fa = t * d01 / (d01 + d12), // scaling factor for triangle Ta + fb = t * d12 / (d01 + d12); + return { + previous: { + x: MiddlePoint.x - fa * (AfterPoint.x - FirstPoint.x), + y: MiddlePoint.y - fa * (AfterPoint.y - FirstPoint.y) + }, + next: { + x: MiddlePoint.x + fb * (AfterPoint.x - FirstPoint.x), + y: MiddlePoint.y + fb * (AfterPoint.y - FirstPoint.y) + } + }; + }, + calculateOrderOfMagnitude = helpers.calculateOrderOfMagnitude = function(val) { + return Math.floor(Math.log(val) / Math.LN10); + }, + calculateScaleRange = helpers.calculateScaleRange = function(valuesArray, drawingSize, textSize, startFromZero, integersOnly) { + + //Set a minimum step of two - a point at the top of the graph, and a point at the base + var minSteps = 2, + maxSteps = Math.floor(drawingSize / (textSize * 1.5)), + skipFitting = (minSteps >= maxSteps); + + var maxValue = max(valuesArray), + minValue = min(valuesArray); + + // We need some degree of seperation here to calculate the scales if all the values are the same + // Adding/minusing 0.5 will give us a range of 1. + if (maxValue === minValue) { + maxValue += 0.5; + // So we don't end up with a graph with a negative start value if we've said always start from zero + if (minValue >= 0.5 && !startFromZero) { + minValue -= 0.5; + } else { + // Make up a whole number above the values + maxValue += 0.5; + } + } + + var valueRange = Math.abs(maxValue - minValue), + rangeOrderOfMagnitude = calculateOrderOfMagnitude(valueRange), + graphMax = Math.ceil(maxValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude), + graphMin = (startFromZero) ? 0 : Math.floor(minValue / (1 * Math.pow(10, rangeOrderOfMagnitude))) * Math.pow(10, rangeOrderOfMagnitude), + graphRange = graphMax - graphMin, + stepValue = Math.pow(10, rangeOrderOfMagnitude), + numberOfSteps = Math.round(graphRange / stepValue); + + //If we have more space on the graph we'll use it to give more definition to the data + while ((numberOfSteps > maxSteps || (numberOfSteps * 2) < maxSteps) && !skipFitting) { + if (numberOfSteps > maxSteps) { + stepValue *= 2; + numberOfSteps = Math.round(graphRange / stepValue); + // Don't ever deal with a decimal number of steps - cancel fitting and just use the minimum number of steps. + if (numberOfSteps % 1 !== 0) { + skipFitting = true; + } + } + //We can fit in double the amount of scale points on the scale + else { + //If user has declared ints only, and the step value isn't a decimal + if (integersOnly && rangeOrderOfMagnitude >= 0) { + //If the user has said integers only, we need to check that making the scale more granular wouldn't make it a float + if (stepValue / 2 % 1 === 0) { + stepValue /= 2; + numberOfSteps = Math.round(graphRange / stepValue); + } + //If it would make it a float break out of the loop + else { + break; + } + } + //If the scale doesn't have to be an int, make the scale more granular anyway. + else { + stepValue /= 2; + numberOfSteps = Math.round(graphRange / stepValue); + } + + } + } + + if (skipFitting) { + numberOfSteps = minSteps; + stepValue = graphRange / numberOfSteps; + } + return { + steps: numberOfSteps, + stepValue: stepValue, + min: graphMin, + max: graphMin + (numberOfSteps * stepValue) + }; + + }, + // Implementation of the nice number algorithm used in determining where axis labels will go + niceNum = helpers.niceNum = function(range, round) { + var exponent = Math.floor(helpers.log10(range)); + var fraction = range / Math.pow(10, exponent); + var niceFraction; + + if (round) { + if (fraction < 1.5) { + niceFraction = 1; + } else if (fraction < 3) { + niceFraction = 2; + } else if (fraction < 7) { + niceFraction = 5; + } else { + niceFraction = 10; + } + } else { + if (fraction <= 1.0) { + niceFraction = 1; + } else if (fraction <= 2) { + niceFraction = 2; + } else if (fraction <= 5) { + niceFraction = 5; + } else { + niceFraction = 10; + } + } + + return niceFraction * Math.pow(10, exponent); + }, + /* jshint ignore:start */ + // Blows up jshint errors based on the new Function constructor + //Templating methods + //Javascript micro templating by John Resig - source at http://ejohn.org/blog/javascript-micro-templating/ + template = helpers.template = function(templateString, valuesObject) { + + // If templateString is function rather than string-template - call the function for valuesObject + + if (templateString instanceof Function) { + return templateString(valuesObject); + } + + var cache = {}; + + function tmpl(str, data) { + // Figure out if we're getting a template, or if we need to + // load the template - and be sure to cache the result. + var fn = !/\W/.test(str) ? + cache[str] = cache[str] : + + // Generate a reusable function that will serve as a template + // generator (and which will be cached). + new Function("obj", + "var p=[],print=function(){p.push.apply(p,arguments);};" + + + // Introduce the data as local variables using with(){} + "with(obj){p.push('" + + + // Convert the template into pure JavaScript + str + .replace(/[\r\t\n]/g, " ") + .split("<%").join("\t") + .replace(/((^|%>)[^\t]*)'/g, "$1\r") + .replace(/\t=(.*?)%>/g, "',$1,'") + .split("\t").join("');") + .split("%>").join("p.push('") + .split("\r").join("\\'") + + "');}return p.join('');" + ); + + // Provide some basic currying to the user + return data ? fn(data) : fn; + } + return tmpl(templateString, valuesObject); + }, + /* jshint ignore:end */ + generateLabels = helpers.generateLabels = function(templateString, numberOfSteps, graphMin, stepValue) { + var labelsArray = new Array(numberOfSteps); + if (templateString) { + each(labelsArray, function(val, index) { + labelsArray[index] = template(templateString, { + value: (graphMin + (stepValue * (index + 1))) + }); + }); + } + return labelsArray; + }, + //--Animation methods + //Easing functions adapted from Robert Penner's easing equations + //http://www.robertpenner.com/easing/ + easingEffects = helpers.easingEffects = { + linear: function(t) { + return t; + }, + easeInQuad: function(t) { + return t * t; + }, + easeOutQuad: function(t) { + return -1 * t * (t - 2); + }, + easeInOutQuad: function(t) { + if ((t /= 1 / 2) < 1) { + return 1 / 2 * t * t; + } + return -1 / 2 * ((--t) * (t - 2) - 1); + }, + easeInCubic: function(t) { + return t * t * t; + }, + easeOutCubic: function(t) { + return 1 * ((t = t / 1 - 1) * t * t + 1); + }, + easeInOutCubic: function(t) { + if ((t /= 1 / 2) < 1) { + return 1 / 2 * t * t * t; + } + return 1 / 2 * ((t -= 2) * t * t + 2); + }, + easeInQuart: function(t) { + return t * t * t * t; + }, + easeOutQuart: function(t) { + return -1 * ((t = t / 1 - 1) * t * t * t - 1); + }, + easeInOutQuart: function(t) { + if ((t /= 1 / 2) < 1) { + return 1 / 2 * t * t * t * t; + } + return -1 / 2 * ((t -= 2) * t * t * t - 2); + }, + easeInQuint: function(t) { + return 1 * (t /= 1) * t * t * t * t; + }, + easeOutQuint: function(t) { + return 1 * ((t = t / 1 - 1) * t * t * t * t + 1); + }, + easeInOutQuint: function(t) { + if ((t /= 1 / 2) < 1) { + return 1 / 2 * t * t * t * t * t; + } + return 1 / 2 * ((t -= 2) * t * t * t * t + 2); + }, + easeInSine: function(t) { + return -1 * Math.cos(t / 1 * (Math.PI / 2)) + 1; + }, + easeOutSine: function(t) { + return 1 * Math.sin(t / 1 * (Math.PI / 2)); + }, + easeInOutSine: function(t) { + return -1 / 2 * (Math.cos(Math.PI * t / 1) - 1); + }, + easeInExpo: function(t) { + return (t === 0) ? 1 : 1 * Math.pow(2, 10 * (t / 1 - 1)); + }, + easeOutExpo: function(t) { + return (t === 1) ? 1 : 1 * (-Math.pow(2, -10 * t / 1) + 1); + }, + easeInOutExpo: function(t) { + if (t === 0) { + return 0; + } + if (t === 1) { + return 1; + } + if ((t /= 1 / 2) < 1) { + return 1 / 2 * Math.pow(2, 10 * (t - 1)); + } + return 1 / 2 * (-Math.pow(2, -10 * --t) + 2); + }, + easeInCirc: function(t) { + if (t >= 1) { + return t; + } + return -1 * (Math.sqrt(1 - (t /= 1) * t) - 1); + }, + easeOutCirc: function(t) { + return 1 * Math.sqrt(1 - (t = t / 1 - 1) * t); + }, + easeInOutCirc: function(t) { + if ((t /= 1 / 2) < 1) { + return -1 / 2 * (Math.sqrt(1 - t * t) - 1); + } + return 1 / 2 * (Math.sqrt(1 - (t -= 2) * t) + 1); + }, + easeInElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if ((t /= 1) == 1) { + return 1; + } + if (!p) { + p = 1 * 0.3; + } + if (a < Math.abs(1)) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return -(a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); + }, + easeOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if ((t /= 1) == 1) { + return 1; + } + if (!p) { + p = 1 * 0.3; + } + if (a < Math.abs(1)) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + return a * Math.pow(2, -10 * t) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) + 1; + }, + easeInOutElastic: function(t) { + var s = 1.70158; + var p = 0; + var a = 1; + if (t === 0) { + return 0; + } + if ((t /= 1 / 2) == 2) { + return 1; + } + if (!p) { + p = 1 * (0.3 * 1.5); + } + if (a < Math.abs(1)) { + a = 1; + s = p / 4; + } else { + s = p / (2 * Math.PI) * Math.asin(1 / a); + } + if (t < 1) { + return -0.5 * (a * Math.pow(2, 10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p)); + } + return a * Math.pow(2, -10 * (t -= 1)) * Math.sin((t * 1 - s) * (2 * Math.PI) / p) * 0.5 + 1; + }, + easeInBack: function(t) { + var s = 1.70158; + return 1 * (t /= 1) * t * ((s + 1) * t - s); + }, + easeOutBack: function(t) { + var s = 1.70158; + return 1 * ((t = t / 1 - 1) * t * ((s + 1) * t + s) + 1); + }, + easeInOutBack: function(t) { + var s = 1.70158; + if ((t /= 1 / 2) < 1) { + return 1 / 2 * (t * t * (((s *= (1.525)) + 1) * t - s)); + } + return 1 / 2 * ((t -= 2) * t * (((s *= (1.525)) + 1) * t + s) + 2); + }, + easeInBounce: function(t) { + return 1 - easingEffects.easeOutBounce(1 - t); + }, + easeOutBounce: function(t) { + if ((t /= 1) < (1 / 2.75)) { + return 1 * (7.5625 * t * t); + } else if (t < (2 / 2.75)) { + return 1 * (7.5625 * (t -= (1.5 / 2.75)) * t + 0.75); + } else if (t < (2.5 / 2.75)) { + return 1 * (7.5625 * (t -= (2.25 / 2.75)) * t + 0.9375); + } else { + return 1 * (7.5625 * (t -= (2.625 / 2.75)) * t + 0.984375); + } + }, + easeInOutBounce: function(t) { + if (t < 1 / 2) { + return easingEffects.easeInBounce(t * 2) * 0.5; + } + return easingEffects.easeOutBounce(t * 2 - 1) * 0.5 + 1 * 0.5; + } + }, + //Request animation polyfill - http://www.paulirish.com/2011/requestanimationframe-for-smart-animating/ + requestAnimFrame = helpers.requestAnimFrame = (function() { + return window.requestAnimationFrame || + window.webkitRequestAnimationFrame || + window.mozRequestAnimationFrame || + window.oRequestAnimationFrame || + window.msRequestAnimationFrame || + function(callback) { + return window.setTimeout(callback, 1000 / 60); + }; + })(), + cancelAnimFrame = helpers.cancelAnimFrame = (function() { + return window.cancelAnimationFrame || + window.webkitCancelAnimationFrame || + window.mozCancelAnimationFrame || + window.oCancelAnimationFrame || + window.msCancelAnimationFrame || + function(callback) { + return window.clearTimeout(callback, 1000 / 60); + }; + })(), + animationLoop = helpers.animationLoop = function(callback, totalSteps, easingString, onProgress, onComplete, chartInstance) { + + var currentStep = 0, + easingFunction = easingEffects[easingString] || easingEffects.linear; + + var animationFrame = function() { + currentStep++; + var stepDecimal = currentStep / totalSteps; + var easeDecimal = easingFunction(stepDecimal); + + callback.call(chartInstance, easeDecimal, stepDecimal, currentStep); + onProgress.call(chartInstance, easeDecimal, stepDecimal); + if (currentStep < totalSteps) { + chartInstance.animationFrame = requestAnimFrame(animationFrame); + } else { + onComplete.apply(chartInstance); + } + }; + requestAnimFrame(animationFrame); + }, + //-- DOM methods + getRelativePosition = helpers.getRelativePosition = function(evt) { + var mouseX, mouseY; + var e = evt.originalEvent || evt, + canvas = evt.currentTarget || evt.srcElement, + boundingRect = canvas.getBoundingClientRect(); + + if (e.touches) { + mouseX = e.touches[0].clientX - boundingRect.left; + mouseY = e.touches[0].clientY - boundingRect.top; + + } else { + mouseX = e.clientX - boundingRect.left; + mouseY = e.clientY - boundingRect.top; + } + + return { + x: mouseX, + y: mouseY + }; + + }, + addEvent = helpers.addEvent = function(node, eventType, method) { + if (node.addEventListener) { + node.addEventListener(eventType, method); + } else if (node.attachEvent) { + node.attachEvent("on" + eventType, method); + } else { + node["on" + eventType] = method; + } + }, + removeEvent = helpers.removeEvent = function(node, eventType, handler) { + if (node.removeEventListener) { + node.removeEventListener(eventType, handler, false); + } else if (node.detachEvent) { + node.detachEvent("on" + eventType, handler); + } else { + node["on" + eventType] = noop; + } + }, + bindEvents = helpers.bindEvents = function(chartInstance, arrayOfEvents, handler) { + // Create the events object if it's not already present + if (!chartInstance.events) chartInstance.events = {}; + + each(arrayOfEvents, function(eventName) { + chartInstance.events[eventName] = function() { + handler.apply(chartInstance, arguments); + }; + addEvent(chartInstance.chart.canvas, eventName, chartInstance.events[eventName]); + }); + }, + unbindEvents = helpers.unbindEvents = function(chartInstance, arrayOfEvents) { + each(arrayOfEvents, function(handler, eventName) { + removeEvent(chartInstance.chart.canvas, eventName, handler); + }); + }, + getMaximumWidth = helpers.getMaximumWidth = function(domNode) { + var container = domNode.parentNode, + padding = parseInt(getStyle(container, 'padding-left')) + parseInt(getStyle(container, 'padding-right')); + // TODO = check cross browser stuff with this. + return container.clientWidth - padding; + }, + getMaximumHeight = helpers.getMaximumHeight = function(domNode) { + var container = domNode.parentNode, + padding = parseInt(getStyle(container, 'padding-bottom')) + parseInt(getStyle(container, 'padding-top')); + // TODO = check cross browser stuff with this. + return container.clientHeight - padding; + }, + getStyle = helpers.getStyle = function(el, property) { + return el.currentStyle ? + el.currentStyle[property] : + document.defaultView.getComputedStyle(el, null).getPropertyValue(property); + }, + getMaximumSize = helpers.getMaximumSize = helpers.getMaximumWidth, // legacy support + retinaScale = helpers.retinaScale = function(chart) { + var ctx = chart.ctx, + width = chart.canvas.width, + height = chart.canvas.height; + + if (window.devicePixelRatio) { + ctx.canvas.style.width = width + "px"; + ctx.canvas.style.height = height + "px"; + ctx.canvas.height = height * window.devicePixelRatio; + ctx.canvas.width = width * window.devicePixelRatio; + ctx.scale(window.devicePixelRatio, window.devicePixelRatio); + } + }, + //-- Canvas methods + clear = helpers.clear = function(chart) { + chart.ctx.clearRect(0, 0, chart.width, chart.height); + }, + fontString = helpers.fontString = function(pixelSize, fontStyle, fontFamily) { + return fontStyle + " " + pixelSize + "px " + fontFamily; + }, + longestText = helpers.longestText = function(ctx, font, arrayOfStrings) { + ctx.font = font; + var longest = 0; + each(arrayOfStrings, function(string) { + var textWidth = ctx.measureText(string).width; + longest = (textWidth > longest) ? textWidth : longest; + }); + return longest; + }, + drawRoundedRectangle = helpers.drawRoundedRectangle = function(ctx, x, y, width, height, radius) { + ctx.beginPath(); + ctx.moveTo(x + radius, y); + ctx.lineTo(x + width - radius, y); + ctx.quadraticCurveTo(x + width, y, x + width, y + radius); + ctx.lineTo(x + width, y + height - radius); + ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height); + ctx.lineTo(x + radius, y + height); + ctx.quadraticCurveTo(x, y + height, x, y + height - radius); + ctx.lineTo(x, y + radius); + ctx.quadraticCurveTo(x, y, x + radius, y); + ctx.closePath(); + }, + color = helpers.color = function(color) { + if (!window.Color) { + console.log('Color.js not found!'); + return color; + } + return window.Color(color); + }, + isArray = helpers.isArray = function(obj) { + if (!Array.isArray) { + return Object.prototype.toString.call(arg) === '[object Array]'; + } + return Array.isArray(obj); + }; + + //Store a reference to each instance - allowing us to globally resize chart instances on window resize. + //Destroy method on the chart will remove the instance of the chart from this reference. + Chart.instances = {}; + + Chart.Type = function(config, instance) { + this.data = config.data; + this.options = config.options; + this.chart = instance; + this.id = uid(); + //Add the chart instance to the global namespace + Chart.instances[this.id] = this; + + // Initialize is always called when a chart type is created + // By default it is a no op, but it should be extended + if (this.options.responsive) { + this.resize(); + } + this.initialize.call(this); + }; + + //Core methods that'll be a part of every chart type + extend(Chart.Type.prototype, { + initialize: function() { + return this; + }, + clear: function() { + clear(this.chart); + return this; + }, + stop: function() { + // Stops any current animation loop occuring + Chart.animationService.cancelAnimation(this); + return this; + }, + resize: function() { + this.stop(); + var canvas = this.chart.canvas, + newWidth = getMaximumWidth(this.chart.canvas), + newHeight = this.options.maintainAspectRatio ? newWidth / this.chart.aspectRatio : getMaximumHeight(this.chart.canvas); + + canvas.width = this.chart.width = newWidth; + canvas.height = this.chart.height = newHeight; + + retinaScale(this.chart); + + return this; + }, + redraw: noop, + render: function(duration) { + + if (this.options.animation.duration !== 0 || duration) { + var animation = new Chart.Animation(); + animation.numSteps = (duration || this.options.animation.duration) / 16.66; //60 fps + animation.easing = this.options.animation.easing; + + // render function + animation.render = function(chartInstance, animationObject) { + var easingFunction = helpers.easingEffects[animationObject.easing]; + var stepDecimal = animationObject.currentStep / animationObject.numSteps; + var easeDecimal = easingFunction(stepDecimal); + + chartInstance.draw(easeDecimal, stepDecimal, animationObject.currentStep); + }; + + // user events + animation.onAnimationProgress = this.options.onAnimationProgress; + animation.onAnimationComplete = this.options.onAnimationComplete; + + Chart.animationService.addAnimation(this, animation, duration); + } else { + this.draw(); + this.options.onAnimationComplete.call(this); + } + return this; + }, + eachElement: function(callback) { + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + helpers.each(dataset.metaData, callback, this, dataset.metaData, datasetIndex); + }, this); + }, + eachValue: function(callback) { + helpers.each(this.data.datasets, function(dataset, datasetIndex) { + helpers.each(dataset.data, callback, this, datasetIndex); + }, this); + }, + eachDataset: function(callback) { + helpers.each(this.data.datasets, callback, this); + }, + getElementsAtEvent: function(e) { + var elementsArray = [], + eventPosition = helpers.getRelativePosition(e), + datasetIterator = function(dataset) { + elementsArray.push(dataset.metaData[elementIndex]); + }, + elementIndex; + + for (var datasetIndex = 0; datasetIndex < this.data.datasets.length; datasetIndex++) { + for (elementIndex = 0; elementIndex < this.data.datasets[datasetIndex].metaData.length; elementIndex++) { + if (this.data.datasets[datasetIndex].metaData[elementIndex].inGroupRange(eventPosition.x, eventPosition.y)) { + helpers.each(this.data.datasets, datasetIterator); + } + } + } + + return elementsArray.length ? elementsArray : []; + }, + // Get the single element that was clicked on + // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was drawn + getElementAtEvent: function(e) { + var element = []; + var eventPosition = helpers.getRelativePosition(e); + + for (var datasetIndex = 0; datasetIndex < this.data.datasets.length; ++datasetIndex) { + for (var elementIndex = 0; elementIndex < this.data.datasets[datasetIndex].metaData.length; ++elementIndex) { + if (this.data.datasets[datasetIndex].metaData[elementIndex].inRange(eventPosition.x, eventPosition.y)) { + element.push(this.data.datasets[datasetIndex].metaData[elementIndex]); + return element; + } + } + } + + return []; + }, + generateLegend: function() { + return template(this.options.legendTemplate, this); + }, + destroy: function() { + this.clear(); + unbindEvents(this, this.events); + var canvas = this.chart.canvas; + + // Reset canvas height/width attributes starts a fresh with the canvas context + canvas.width = this.chart.width; + canvas.height = this.chart.height; + + // < IE9 doesn't support removeProperty + if (canvas.style.removeProperty) { + canvas.style.removeProperty('width'); + canvas.style.removeProperty('height'); + } else { + canvas.style.removeAttribute('width'); + canvas.style.removeAttribute('height'); + } + + delete Chart.instances[this.id]; + }, + toBase64Image: function() { + return this.chart.canvas.toDataURL.apply(this.chart.canvas, arguments); + } + }); + + Chart.Type.extend = function(extensions) { + + var parent = this; + + var ChartType = function() { + return parent.apply(this, arguments); + }; + + //Copy the prototype object of the this class + ChartType.prototype = clone(parent.prototype); + //Now overwrite some of the properties in the base class with the new extensions + extend(ChartType.prototype, extensions); + + ChartType.extend = Chart.Type.extend; + + if (extensions.name || parent.prototype.name) { + + var chartName = extensions.name || parent.prototype.name; + //Assign any potential default values of the new chart type + + //If none are defined, we'll use a clone of the chart type this is being extended from. + //I.e. if we extend a line chart, we'll use the defaults from the line chart if our new chart + //doesn't define some defaults of their own. + + var baseDefaults = (Chart.defaults[parent.prototype.name]) ? clone(Chart.defaults[parent.prototype.name]) : {}; + + Chart.defaults[chartName] = helpers.configMerge(baseDefaults, extensions.defaults); + + Chart.types[chartName] = ChartType; + + //Register this new chart type in the Chart prototype + Chart.prototype[chartName] = function(config) { + config.options = helpers.configMerge(Chart.defaults.global, Chart.defaults[chartName], config.options || {}); + return new ChartType(config, this); + }; + } else { + warn("Name not provided for this chart, so it hasn't been registered"); + } + return parent; + }; + + Chart.Element = function(configuration) { + extend(this, configuration); + this.initialize.apply(this, arguments); + }; + extend(Chart.Element.prototype, { + initialize: function() {}, + pivot: function() { + if (!this._view) { + this._view = clone(this._model); + } + this._start = clone(this._view); + return this; + }, + transition: function(ease) { + if (!this._view) { + this._view = clone(this._model); + } + if (!this._start) { + this.pivot(); + } + + each(this._model, function(value, key) { + + if (key[0] === '_' || !this._model.hasOwnProperty(key)) { + // Only non-underscored properties + } + + // Init if doesn't exist + else if (!this._view[key]) { + if (typeof value === 'number') { + this._view[key] = value * ease; + } else { + this._view[key] = value || null; + } + } + + // No unnecessary computations + else if (this._model[key] === this._view[key]) { + // It's the same! Woohoo! + } + + // Color transitions if possible + else if (typeof value === 'string') { + try { + var color = helpers.color(this._start[key]).mix(helpers.color(this._model[key]), ease); + this._view[key] = color.rgbString(); + } catch (err) { + this._view[key] = value; + } + } + // Number transitions + else if (typeof value === 'number') { + var startVal = this._start[key] !== undefined ? this._start[key] : 0; + this._view[key] = ((this._model[key] - startVal) * ease) + startVal; + } + // Everything else + else { + this._view[key] = value; + } + + }, this); + + if (ease === 1) { + delete this._start; + } + return this; + }, + tooltipPosition: function() { + return { + x: this._model.x, + y: this._model.y + }; + }, + hasValue: function() { + return isNumber(this._model.x) && isNumber(this._model.y); + } + }); + + Chart.Element.extend = inherits; + + + // Attach global event to resize each chart instance when the browser resizes + helpers.addEvent(window, "resize", (function() { + // Basic debounce of resize function so it doesn't hurt performance when resizing browser. + var timeout; + return function() { + clearTimeout(timeout); + timeout = setTimeout(function() { + each(Chart.instances, function(instance) { + // If the responsive flag is set in the chart instance config + // Cascade the resize event down to the chart. + if (instance.options.responsive) { + instance.resize(); + instance.update(); + } + }); + }, 50); + }; + })()); + + + if (amd) { + define(function() { + return Chart; + }); + } else if (typeof module === 'object' && module.exports) { + module.exports = Chart; + } + + root.Chart = Chart; + + Chart.noConflict = function() { + root.Chart = previous; + return Chart; + }; }).call(this); diff --git a/src/core/core.scale.js b/src/core/core.scale.js index dc9a9a702..ce5c82460 100644 --- a/src/core/core.scale.js +++ b/src/core/core.scale.js @@ -1,302 +1,302 @@ (function() { - "use strict"; + "use strict"; - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; - // The scale service is used to resize charts along with all of their axes. We make this as - // a service where scales are registered with their respective charts so that changing the - // scales does not require - Chart.scaleService = { - // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then - // use the new chart options to grab the correct scale - constructors: {}, - // Use a registration function so that we can move to an ES6 map when we no longer need to support - // old browsers - registerScaleType: function(type, scaleConstructor) { - this.constructors[type] = scaleConstructor; - }, - getScaleConstructor: function(type) { - return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; - }, + // The scale service is used to resize charts along with all of their axes. We make this as + // a service where scales are registered with their respective charts so that changing the + // scales does not require + Chart.scaleService = { + // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then + // use the new chart options to grab the correct scale + constructors: {}, + // Use a registration function so that we can move to an ES6 map when we no longer need to support + // old browsers + registerScaleType: function(type, scaleConstructor) { + this.constructors[type] = scaleConstructor; + }, + getScaleConstructor: function(type) { + return this.constructors.hasOwnProperty(type) ? this.constructors[type] : undefined; + }, - // The interesting function - fitScalesForChart: function(chartInstance, width, height) { - var xPadding = width > 30 ? 5 : 2; - var yPadding = height > 30 ? 5 : 2; + // The interesting function + fitScalesForChart: function(chartInstance, width, height) { + var xPadding = width > 30 ? 5 : 2; + var yPadding = height > 30 ? 5 : 2; - if (chartInstance) { - var leftScales = helpers.where(chartInstance.scales, function(scaleInstance) { - return scaleInstance.options.position == "left"; - }); - var rightScales = helpers.where(chartInstance.scales, function(scaleInstance) { - return scaleInstance.options.position == "right"; - }); - var topScales = helpers.where(chartInstance.scales, function(scaleInstance) { - return scaleInstance.options.position == "top"; - }); - var bottomScales = helpers.where(chartInstance.scales, function(scaleInstance) { - return scaleInstance.options.position == "bottom"; - }); + if (chartInstance) { + var leftScales = helpers.where(chartInstance.scales, function(scaleInstance) { + return scaleInstance.options.position == "left"; + }); + var rightScales = helpers.where(chartInstance.scales, function(scaleInstance) { + return scaleInstance.options.position == "right"; + }); + var topScales = helpers.where(chartInstance.scales, function(scaleInstance) { + return scaleInstance.options.position == "top"; + }); + var bottomScales = helpers.where(chartInstance.scales, function(scaleInstance) { + return scaleInstance.options.position == "bottom"; + }); - var visibleLeftScales = helpers.where(chartInstance.scales, function(scaleInstance) { - return scaleInstance.options.position == "left"; - }); - var visibleRightScales = helpers.where(chartInstance.scales, function(scaleInstance) { - return scaleInstance.options.position == "right"; - }); - var visibleTopScales = helpers.where(chartInstance.scales, function(scaleInstance) { - return scaleInstance.options.position == "top"; - }); - var visibleBottomScales = helpers.where(chartInstance.scales, function(scaleInstance) { - return scaleInstance.options.position == "bottom"; - }); + var visibleLeftScales = helpers.where(chartInstance.scales, function(scaleInstance) { + return scaleInstance.options.position == "left"; + }); + var visibleRightScales = helpers.where(chartInstance.scales, function(scaleInstance) { + return scaleInstance.options.position == "right"; + }); + var visibleTopScales = helpers.where(chartInstance.scales, function(scaleInstance) { + return scaleInstance.options.position == "top"; + }); + var visibleBottomScales = helpers.where(chartInstance.scales, function(scaleInstance) { + return scaleInstance.options.position == "bottom"; + }); - // // Adjust the padding to take into account displaying labels - // if (topScales.length === 0 || bottomScales.length === 0) { - // var maxFontHeight = 0; + // // Adjust the padding to take into account displaying labels + // if (topScales.length === 0 || bottomScales.length === 0) { + // var maxFontHeight = 0; - // var maxFontHeightFunction = function(scaleInstance) { - // if (scaleInstance.options.labels.show) { - // // Only consider font sizes for axes that actually show labels - // maxFontHeight = Math.max(maxFontHeight, scaleInstance.options.labels.fontSize); - // } - // }; + // var maxFontHeightFunction = function(scaleInstance) { + // if (scaleInstance.options.labels.show) { + // // Only consider font sizes for axes that actually show labels + // maxFontHeight = Math.max(maxFontHeight, scaleInstance.options.labels.fontSize); + // } + // }; - // helpers.each(leftScales, maxFontHeightFunction); - // helpers.each(rightScales, maxFontHeightFunction); + // 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 (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; - // } - // } + // 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 | | - // | | | | - // |------------------------------------------------------| + // 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 + // 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% + // Step 1 + var chartWidth = width / 2; // min 50% + var chartHeight = height / 2; // min 50% - chartWidth -= (2 * xPadding); - chartHeight -= (2 * yPadding); + chartWidth -= (2 * xPadding); + chartHeight -= (2 * yPadding); - // Step 2 - var verticalScaleWidth = (width - chartWidth) / (leftScales.length + rightScales.length); + // Step 2 + var verticalScaleWidth = (width - chartWidth) / (leftScales.length + rightScales.length); - // Step 3 - var horizontalScaleHeight = (height - chartHeight) / (topScales.length + bottomScales.length); + // Step 3 + var horizontalScaleHeight = (height - chartHeight) / (topScales.length + bottomScales.length); - // Step 4; - var minimumScaleSizes = []; + // Step 4; + var minimumScaleSizes = []; - var verticalScaleMinSizeFunction = function(scaleInstance) { - var minSize = scaleInstance.fit(verticalScaleWidth, chartHeight); - minimumScaleSizes.push({ - horizontal: false, - minSize: minSize, - scale: scaleInstance, - }); - }; + 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, - }); - }; + 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); + // vertical scales + helpers.each(leftScales, verticalScaleMinSizeFunction); + helpers.each(rightScales, verticalScaleMinSizeFunction); - // horizontal scales - helpers.each(topScales, horizontalScaleMinSizeFunction); - helpers.each(bottomScales, horizontalScaleMinSizeFunction); + // horizontal scales + helpers.each(topScales, horizontalScaleMinSizeFunction); + helpers.each(bottomScales, horizontalScaleMinSizeFunction); - // Step 5 - var maxChartHeight = height - (2 * yPadding); - var maxChartWidth = width - (2 * xPadding); + // 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; - } - }); + 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. + // At this point, maxChartHeight and maxChartWidth are the size the chart area could + // be if the axes are drawn at their minimum sizes. - // Step 6 - var verticalScaleFitFunction = function(scaleInstance) { - var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { - return wrapper.scale === scaleInstance; - }); + // Step 6 + var verticalScaleFitFunction = function(scaleInstance) { + var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { + return wrapper.scale === scaleInstance; + }); - if (wrapper) { - scaleInstance.fit(wrapper.minSize.width, maxChartHeight); - } - }; + if (wrapper) { + scaleInstance.fit(wrapper.minSize.width, maxChartHeight); + } + }; - var horizontalScaleFitFunction = function(scaleInstance) { - var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { - return wrapper.scale === scaleInstance; - }); + var horizontalScaleFitFunction = function(scaleInstance) { + var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { + return wrapper.scale === scaleInstance; + }); - var scaleMargin = { - left: totalLeftWidth, - right: totalRightWidth, - top: 0, - bottom: 0, - }; + var scaleMargin = { + left: totalLeftWidth, + right: totalRightWidth, + top: 0, + bottom: 0, + }; - if (wrapper) { - scaleInstance.fit(maxChartWidth, wrapper.minSize.height, scaleMargin); - } - }; + if (wrapper) { + scaleInstance.fit(maxChartWidth, wrapper.minSize.height, scaleMargin); + } + }; - var totalLeftWidth = xPadding; - var totalRightWidth = xPadding; - var totalTopHeight = yPadding; - var totalBottomHeight = yPadding; + var totalLeftWidth = xPadding; + var totalRightWidth = xPadding; + var totalTopHeight = yPadding; + var totalBottomHeight = yPadding; - helpers.each(leftScales, verticalScaleFitFunction); - helpers.each(rightScales, verticalScaleFitFunction); + helpers.each(leftScales, verticalScaleFitFunction); + helpers.each(rightScales, verticalScaleFitFunction); - // Figure out how much margin is on the left and right of the horizontal axes - helpers.each(leftScales, function(scaleInstance) { - totalLeftWidth += scaleInstance.width; - }); + // Figure out how much margin is on the left and right of the horizontal axes + helpers.each(leftScales, function(scaleInstance) { + totalLeftWidth += scaleInstance.width; + }); - helpers.each(rightScales, function(scaleInstance) { - totalRightWidth += scaleInstance.width; - }); + helpers.each(rightScales, function(scaleInstance) { + totalRightWidth += scaleInstance.width; + }); - helpers.each(topScales, horizontalScaleFitFunction); - helpers.each(bottomScales, horizontalScaleFitFunction); + helpers.each(topScales, horizontalScaleFitFunction); + helpers.each(bottomScales, horizontalScaleFitFunction); - helpers.each(topScales, function(scaleInstance) { - totalTopHeight += scaleInstance.height; - }); - helpers.each(bottomScales, function(scaleInstance) { - totalBottomHeight += scaleInstance.height; - }); + helpers.each(topScales, function(scaleInstance) { + totalTopHeight += scaleInstance.height; + }); + helpers.each(bottomScales, function(scaleInstance) { + totalBottomHeight += scaleInstance.height; + }); - // Let the left scale know the final margin - helpers.each(leftScales, function(scaleInstance) { - var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { - return wrapper.scale === scaleInstance; - }); + // Let the left scale know the final margin + helpers.each(leftScales, function(scaleInstance) { + var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { + return wrapper.scale === scaleInstance; + }); - var scaleMargin = { - left: 0, - right: 0, - top: totalTopHeight, - bottom: totalBottomHeight - }; + var scaleMargin = { + left: 0, + right: 0, + top: totalTopHeight, + bottom: totalBottomHeight + }; - if (wrapper) { - scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin); - } - }); + if (wrapper) { + scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin); + } + }); - helpers.each(rightScales, function(scaleInstance) { - var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { - return wrapper.scale === scaleInstance; - }); + helpers.each(rightScales, function(scaleInstance) { + var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) { + return wrapper.scale === scaleInstance; + }); - var scaleMargin = { - left: 0, - right: 0, - top: totalTopHeight, - bottom: totalBottomHeight - }; + var scaleMargin = { + left: 0, + right: 0, + top: totalTopHeight, + bottom: totalBottomHeight + }; - if (wrapper) { - scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin); - } - }); + if (wrapper) { + scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin); + } + }); - // Step 7 - // Position the scales - var left = xPadding; - var top = yPadding; - var right = 0; - var bottom = 0; + // Step 7 + // Position the scales + var left = xPadding; + var top = yPadding; + var right = 0; + var bottom = 0; - var verticalScalePlacer = function(scaleInstance) { - scaleInstance.left = left; - scaleInstance.right = left + scaleInstance.width; - scaleInstance.top = totalTopHeight; - scaleInstance.bottom = totalTopHeight + maxChartHeight; + 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; - }; + // 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; + 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; - }; + // Move to next point + top = scaleInstance.bottom; + }; - helpers.each(leftScales, verticalScalePlacer); - helpers.each(topScales, horizontalScalePlacer); + helpers.each(leftScales, verticalScalePlacer); + helpers.each(topScales, horizontalScalePlacer); - // Account for chart width and height - left += maxChartWidth; - top += maxChartHeight; + // Account for chart width and height + left += maxChartWidth; + top += maxChartHeight; - helpers.each(rightScales, verticalScalePlacer); - helpers.each(bottomScales, horizontalScalePlacer); + helpers.each(rightScales, verticalScalePlacer); + helpers.each(bottomScales, horizontalScalePlacer); - // Step 8 - chartInstance.chartArea = { - left: totalLeftWidth, - top: totalTopHeight, - right: totalLeftWidth + maxChartWidth, - bottom: totalTopHeight + maxChartHeight, - }; - } - } - }; + // Step 8 + chartInstance.chartArea = { + left: totalLeftWidth, + top: totalTopHeight, + right: totalLeftWidth + maxChartWidth, + bottom: totalTopHeight + maxChartHeight, + }; + } + } + }; }).call(this); diff --git a/src/core/core.tooltip.js b/src/core/core.tooltip.js index 72abb14f0..308a0c069 100644 --- a/src/core/core.tooltip.js +++ b/src/core/core.tooltip.js @@ -11,334 +11,334 @@ (function() { - "use strict"; + "use strict"; - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; - Chart.defaults.global.tooltips = { - enabled: true, - custom: null, - backgroundColor: "rgba(0,0,0,0.8)", - fontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", - fontSize: 10, - fontStyle: "normal", - fontColor: "#fff", - titleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", - titleFontSize: 12, - titleFontStyle: "bold", - titleFontColor: "#fff", - yPadding: 6, - xPadding: 6, - caretSize: 8, - cornerRadius: 6, - xOffset: 10, - template: [ - '<% if(label){ %>', - '<%=label %>: ', - '<% } %>', - '<%=value %>', - ].join(''), - multiTemplate: [ - '<%if (datasetLabel){ %>', - '<%=datasetLabel %>: ', - '<% } %>', - '<%=value %>' - ].join(''), - multiKeyBackground: '#fff', - }; + Chart.defaults.global.tooltips = { + enabled: true, + custom: null, + backgroundColor: "rgba(0,0,0,0.8)", + fontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + fontSize: 10, + fontStyle: "normal", + fontColor: "#fff", + titleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + titleFontSize: 12, + titleFontStyle: "bold", + titleFontColor: "#fff", + yPadding: 6, + xPadding: 6, + caretSize: 8, + cornerRadius: 6, + xOffset: 10, + template: [ + '<% if(label){ %>', + '<%=label %>: ', + '<% } %>', + '<%=value %>', + ].join(''), + multiTemplate: [ + '<%if (datasetLabel){ %>', + '<%=datasetLabel %>: ', + '<% } %>', + '<%=value %>' + ].join(''), + multiKeyBackground: '#fff', + }; - Chart.Tooltip = Chart.Element.extend({ - initialize: function() { - var options = this._options; - helpers.extend(this, { - _model: { - // Positioning - xPadding: options.tooltips.xPadding, - yPadding: options.tooltips.yPadding, - xOffset: options.tooltips.xOffset, + Chart.Tooltip = Chart.Element.extend({ + initialize: function() { + var options = this._options; + helpers.extend(this, { + _model: { + // Positioning + xPadding: options.tooltips.xPadding, + yPadding: options.tooltips.yPadding, + xOffset: options.tooltips.xOffset, - // Labels - textColor: options.tooltips.fontColor, - _fontFamily: options.tooltips.fontFamily, - _fontStyle: options.tooltips.fontStyle, - fontSize: options.tooltips.fontSize, + // 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, + // 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() { + // 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; + var ctx = this._chart.ctx; - switch (this._options.hover.mode) { - case 'single': - helpers.extend(this._model, { - text: helpers.template(this._options.tooltips.template, { - // These variables are available in the template function. Add others here - element: this._active[0], - value: this._data.datasets[this._active[0]._datasetIndex].data[this._active[0]._index], - label: this._data.labels ? this._data.labels[this._active[0]._index] : '', - }), - }); + switch (this._options.hover.mode) { + case 'single': + helpers.extend(this._model, { + text: helpers.template(this._options.tooltips.template, { + // These variables are available in the template function. Add others here + element: this._active[0], + value: this._data.datasets[this._active[0]._datasetIndex].data[this._active[0]._index], + label: this._data.labels ? this._data.labels[this._active[0]._index] : '', + }), + }); - var tooltipPosition = this._active[0].tooltipPosition(); - helpers.extend(this._model, { - x: Math.round(tooltipPosition.x), - y: Math.round(tooltipPosition.y), - caretPadding: tooltipPosition.padding - }); + var tooltipPosition = this._active[0].tooltipPosition(); + helpers.extend(this._model, { + x: Math.round(tooltipPosition.x), + y: Math.round(tooltipPosition.y), + caretPadding: tooltipPosition.padding + }); - break; + break; - case 'label': + case 'label': - // Tooltip Content + // Tooltip Content - var dataArray, - dataIndex; + var dataArray, + dataIndex; - var labels = [], - colors = []; + var labels = [], + colors = []; - for (var i = this._data.datasets.length - 1; i >= 0; i--) { - dataArray = this._data.datasets[i].metaData; - dataIndex = helpers.indexOf(dataArray, this._active[0]); - if (dataIndex !== -1) { - break; - } - } + for (var i = this._data.datasets.length - 1; i >= 0; i--) { + dataArray = this._data.datasets[i].metaData; + dataIndex = helpers.indexOf(dataArray, this._active[0]); + if (dataIndex !== -1) { + break; + } + } - var medianPosition = (function(index) { - // Get all the points at that particular index - var elements = [], - dataCollection, - xPositions = [], - yPositions = [], - xMax, - yMax, - xMin, - yMin; - helpers.each(this._data.datasets, function(dataset) { - dataCollection = dataset.metaData; - if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()) { - elements.push(dataCollection[dataIndex]); - } - }, this); + var medianPosition = (function(index) { + // Get all the points at that particular index + var elements = [], + dataCollection, + xPositions = [], + yPositions = [], + xMax, + yMax, + xMin, + yMin; + helpers.each(this._data.datasets, function(dataset) { + dataCollection = dataset.metaData; + if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()) { + elements.push(dataCollection[dataIndex]); + } + }, this); - // Reverse labels if stacked - helpers.each(this._options.stacked ? elements.reverse() : elements, function(element) { - xPositions.push(element._view.x); - yPositions.push(element._view.y); + // Reverse labels if stacked + helpers.each(this._options.stacked ? elements.reverse() : elements, function(element) { + xPositions.push(element._view.x); + yPositions.push(element._view.y); - //Include any colour information about the element - labels.push(helpers.template(this._options.tooltips.multiTemplate, { - // These variables are available in the template function. Add others here - element: element, - datasetLabel: this._data.datasets[element._datasetIndex].label, - value: this._data.datasets[element._datasetIndex].data[element._index], - })); - colors.push({ - fill: element._view.backgroundColor, - stroke: element._view.borderColor - }); + //Include any colour information about the element + labels.push(helpers.template(this._options.tooltips.multiTemplate, { + // These variables are available in the template function. Add others here + element: element, + datasetLabel: this._data.datasets[element._datasetIndex].label, + value: this._data.datasets[element._datasetIndex].data[element._index], + })); + colors.push({ + fill: element._view.backgroundColor, + stroke: element._view.borderColor + }); - }, this); + }, this); - yMin = helpers.min(yPositions); - yMax = helpers.max(yPositions); + yMin = helpers.min(yPositions); + yMax = helpers.max(yPositions); - xMin = helpers.min(xPositions); - xMax = helpers.max(xPositions); + xMin = helpers.min(xPositions); + xMax = helpers.max(xPositions); - return { - x: (xMin > this._chart.width / 2) ? xMin : xMax, - y: (yMin + yMax) / 2, - }; - }).call(this, dataIndex); + return { + x: (xMin > this._chart.width / 2) ? xMin : xMax, + y: (yMin + yMax) / 2, + }; + }).call(this, dataIndex); - // Apply for now - helpers.extend(this._model, { - x: medianPosition.x, - y: medianPosition.y, - labels: labels, - title: this._data.labels && this._data.labels.length ? this._data.labels[this._active[0]._index] : '', - legendColors: colors, - legendBackgroundColor: this._options.tooltips.multiKeyBackground, - }); + // Apply for now + helpers.extend(this._model, { + x: medianPosition.x, + y: medianPosition.y, + labels: labels, + title: this._data.labels && this._data.labels.length ? this._data.labels[this._active[0]._index] : '', + legendColors: colors, + legendBackgroundColor: this._options.tooltips.multiKeyBackground, + }); - // Calculate Appearance Tweaks + // Calculate Appearance Tweaks - this._model.height = (labels.length * this._model.fontSize) + ((labels.length - 1) * (this._model.fontSize / 2)) + (this._model.yPadding * 2) + this._model.titleFontSize * 1.5; + this._model.height = (labels.length * this._model.fontSize) + ((labels.length - 1) * (this._model.fontSize / 2)) + (this._model.yPadding * 2) + this._model.titleFontSize * 1.5; - var titleWidth = ctx.measureText(this.title).width, - //Label has a legend square as well so account for this. - labelWidth = helpers.longestText(ctx, this.font, labels) + this._model.fontSize + 3, - longestTextWidth = helpers.max([labelWidth, titleWidth]); + var titleWidth = ctx.measureText(this.title).width, + //Label has a legend square as well so account for this. + labelWidth = helpers.longestText(ctx, this.font, labels) + this._model.fontSize + 3, + longestTextWidth = helpers.max([labelWidth, titleWidth]); - this._model.width = longestTextWidth + (this._model.xPadding * 2); + this._model.width = longestTextWidth + (this._model.xPadding * 2); - var halfHeight = this._model.height / 2; + var halfHeight = this._model.height / 2; - //Check to ensure the height will fit on the canvas - if (this._model.y - halfHeight < 0) { - this._model.y = halfHeight; - } else if (this._model.y + halfHeight > this._chart.height) { - this._model.y = this._chart.height - halfHeight; - } + //Check to ensure the height will fit on the canvas + if (this._model.y - halfHeight < 0) { + this._model.y = halfHeight; + } else if (this._model.y + halfHeight > this._chart.height) { + this._model.y = this._chart.height - halfHeight; + } - //Decide whether to align left or right based on position on canvas - if (this._model.x > this._chart.width / 2) { - this._model.x -= this._model.xOffset + this._model.width; - } else { - this._model.x += this._model.xOffset; - } - break; - } + //Decide whether to align left or right based on position on canvas + if (this._model.x > this._chart.width / 2) { + this._model.x -= this._model.xOffset + this._model.width; + } else { + this._model.x += this._model.xOffset; + } + break; + } - return this; - }, - draw: function() { + return this; + }, + draw: function() { - var ctx = this._chart.ctx; - var vm = this._view; + var ctx = this._chart.ctx; + var vm = this._view; - switch (this._options.hover.mode) { - case 'single': + switch (this._options.hover.mode) { + case 'single': - ctx.font = helpers.fontString(vm.fontSize, vm._fontStyle, vm._fontFamily); + ctx.font = helpers.fontString(vm.fontSize, vm._fontStyle, vm._fontFamily); - vm.xAlign = "center"; - vm.yAlign = "above"; + vm.xAlign = "center"; + vm.yAlign = "above"; - //Distance between the actual element.y position and the start of the tooltip caret - var caretPadding = vm.caretPadding || 2; + //Distance between the actual element.y position and the start of the tooltip caret + var caretPadding = vm.caretPadding || 2; - var tooltipWidth = ctx.measureText(vm.text).width + 2 * vm.xPadding, - tooltipRectHeight = vm.fontSize + 2 * vm.yPadding, - tooltipHeight = tooltipRectHeight + vm.caretHeight + caretPadding; + var tooltipWidth = ctx.measureText(vm.text).width + 2 * vm.xPadding, + tooltipRectHeight = vm.fontSize + 2 * vm.yPadding, + tooltipHeight = tooltipRectHeight + vm.caretHeight + caretPadding; - if (vm.x + tooltipWidth / 2 > this._chart.width) { - vm.xAlign = "left"; - } else if (vm.x - tooltipWidth / 2 < 0) { - vm.xAlign = "right"; - } + if (vm.x + tooltipWidth / 2 > this._chart.width) { + vm.xAlign = "left"; + } else if (vm.x - tooltipWidth / 2 < 0) { + vm.xAlign = "right"; + } - if (vm.y - tooltipHeight < 0) { - vm.yAlign = "below"; - } + if (vm.y - tooltipHeight < 0) { + vm.yAlign = "below"; + } - var tooltipX = vm.x - tooltipWidth / 2, - tooltipY = vm.y - tooltipHeight; + var tooltipX = vm.x - tooltipWidth / 2, + tooltipY = vm.y - tooltipHeight; - ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString(); + ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString(); - // Custom Tooltips - if (this._custom) { - this._custom(this._view); - } else { - switch (vm.yAlign) { - case "above": - //Draw a caret above the x/y - ctx.beginPath(); - ctx.moveTo(vm.x, vm.y - caretPadding); - ctx.lineTo(vm.x + vm.caretHeight, vm.y - (caretPadding + vm.caretHeight)); - ctx.lineTo(vm.x - vm.caretHeight, vm.y - (caretPadding + vm.caretHeight)); - ctx.closePath(); - ctx.fill(); - break; - case "below": - tooltipY = vm.y + caretPadding + vm.caretHeight; - //Draw a caret below the x/y - ctx.beginPath(); - ctx.moveTo(vm.x, vm.y + caretPadding); - ctx.lineTo(vm.x + vm.caretHeight, vm.y + caretPadding + vm.caretHeight); - ctx.lineTo(vm.x - vm.caretHeight, vm.y + caretPadding + vm.caretHeight); - ctx.closePath(); - ctx.fill(); - break; - } + // Custom Tooltips + if (this._custom) { + this._custom(this._view); + } else { + switch (vm.yAlign) { + case "above": + //Draw a caret above the x/y + ctx.beginPath(); + ctx.moveTo(vm.x, vm.y - caretPadding); + ctx.lineTo(vm.x + vm.caretHeight, vm.y - (caretPadding + vm.caretHeight)); + ctx.lineTo(vm.x - vm.caretHeight, vm.y - (caretPadding + vm.caretHeight)); + ctx.closePath(); + ctx.fill(); + break; + case "below": + tooltipY = vm.y + caretPadding + vm.caretHeight; + //Draw a caret below the x/y + ctx.beginPath(); + ctx.moveTo(vm.x, vm.y + caretPadding); + ctx.lineTo(vm.x + vm.caretHeight, vm.y + caretPadding + vm.caretHeight); + ctx.lineTo(vm.x - vm.caretHeight, vm.y + caretPadding + vm.caretHeight); + ctx.closePath(); + ctx.fill(); + break; + } - switch (vm.xAlign) { - case "left": - tooltipX = vm.x - tooltipWidth + (vm.cornerRadius + vm.caretHeight); - break; - case "right": - tooltipX = vm.x - (vm.cornerRadius + vm.caretHeight); - break; - } + switch (vm.xAlign) { + case "left": + tooltipX = vm.x - tooltipWidth + (vm.cornerRadius + vm.caretHeight); + break; + case "right": + tooltipX = vm.x - (vm.cornerRadius + vm.caretHeight); + break; + } - helpers.drawRoundedRectangle(ctx, tooltipX, tooltipY, tooltipWidth, tooltipRectHeight, vm.cornerRadius); + helpers.drawRoundedRectangle(ctx, tooltipX, tooltipY, tooltipWidth, tooltipRectHeight, vm.cornerRadius); - ctx.fill(); + ctx.fill(); - ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString(); - ctx.textAlign = "center"; - ctx.textBaseline = "middle"; - ctx.fillText(vm.text, tooltipX + tooltipWidth / 2, tooltipY + tooltipRectHeight / 2); + ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString(); + ctx.textAlign = "center"; + ctx.textBaseline = "middle"; + ctx.fillText(vm.text, tooltipX + tooltipWidth / 2, tooltipY + tooltipRectHeight / 2); - } - break; - case 'label': + } + break; + case 'label': - helpers.drawRoundedRectangle(ctx, vm.x, vm.y - vm.height / 2, vm.width, vm.height, vm.cornerRadius); - ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString(); - ctx.fill(); - ctx.closePath(); + helpers.drawRoundedRectangle(ctx, vm.x, vm.y - vm.height / 2, vm.width, vm.height, vm.cornerRadius); + ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString(); + ctx.fill(); + ctx.closePath(); - ctx.textAlign = "left"; - ctx.textBaseline = "middle"; - ctx.fillStyle = helpers.color(vm.titleTextColor).alpha(vm.opacity).rgbString(); - ctx.font = helpers.fontString(vm.fontSize, vm._titleFontStyle, vm._titleFontFamily); - ctx.fillText(vm.title, vm.x + vm.xPadding, this.getLineHeight(0)); + ctx.textAlign = "left"; + ctx.textBaseline = "middle"; + ctx.fillStyle = helpers.color(vm.titleTextColor).alpha(vm.opacity).rgbString(); + ctx.font = helpers.fontString(vm.fontSize, vm._titleFontStyle, vm._titleFontFamily); + ctx.fillText(vm.title, vm.x + vm.xPadding, this.getLineHeight(0)); - ctx.font = helpers.fontString(vm.fontSize, vm._fontStyle, vm._fontFamily); - helpers.each(vm.labels, function(label, index) { - ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString(); - ctx.fillText(label, vm.x + vm.xPadding + vm.fontSize + 3, this.getLineHeight(index + 1)); + ctx.font = helpers.fontString(vm.fontSize, vm._fontStyle, vm._fontFamily); + helpers.each(vm.labels, function(label, index) { + ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString(); + ctx.fillText(label, vm.x + vm.xPadding + vm.fontSize + 3, this.getLineHeight(index + 1)); - //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas) - //ctx.clearRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize/2, vm.fontSize, vm.fontSize); - //Instead we'll make a white filled block to put the legendColour palette over. + //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas) + //ctx.clearRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize/2, vm.fontSize, vm.fontSize); + //Instead we'll make a white filled block to put the legendColour palette over. - ctx.fillStyle = helpers.color(vm.legendColors[index].stroke).alpha(vm.opacity).rgbString(); - ctx.fillRect(vm.x + vm.xPadding - 1, this.getLineHeight(index + 1) - vm.fontSize / 2 - 1, vm.fontSize + 2, vm.fontSize + 2); + ctx.fillStyle = helpers.color(vm.legendColors[index].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); + ctx.fillStyle = helpers.color(vm.legendColors[index].fill).alpha(vm.opacity).rgbString(); + ctx.fillRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize / 2, vm.fontSize, vm.fontSize); - }, this); - break; - } - }, - getLineHeight: function(index) { - var baseLineHeight = this._view.y - (this._view.height / 2) + this._view.yPadding, - afterTitleIndex = index - 1; + }, this); + break; + } + }, + getLineHeight: function(index) { + var baseLineHeight = this._view.y - (this._view.height / 2) + this._view.yPadding, + afterTitleIndex = index - 1; - //If the index is zero, we're getting the title - if (index === 0) { - return baseLineHeight + this._view.titleFontSize / 2; - } else { - return baseLineHeight + ((this._view.fontSize * 1.5 * afterTitleIndex) + this._view.fontSize / 2) + this._view.titleFontSize * 1.5; - } + //If the index is zero, we're getting the title + if (index === 0) { + return baseLineHeight + this._view.titleFontSize / 2; + } else { + return baseLineHeight + ((this._view.fontSize * 1.5 * afterTitleIndex) + this._view.fontSize / 2) + this._view.titleFontSize * 1.5; + } - }, - }); + }, + }); }).call(this); diff --git a/src/elements/element.arc.js b/src/elements/element.arc.js index 0d3bddb68..a726abfd2 100644 --- a/src/elements/element.arc.js +++ b/src/elements/element.arc.js @@ -11,83 +11,83 @@ (function() { - "use strict"; + "use strict"; - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; - Chart.defaults.global.elements.arc = { - backgroundColor: Chart.defaults.global.defaultColor, - borderColor: "#fff", - borderWidth: 2 - }; + Chart.defaults.global.elements.arc = { + backgroundColor: Chart.defaults.global.defaultColor, + borderColor: "#fff", + borderWidth: 2 + }; - Chart.Arc = Chart.Element.extend({ - inGroupRange: function(mouseX) { - var vm = this._view; + Chart.Arc = Chart.Element.extend({ + inGroupRange: function(mouseX) { + var vm = this._view; - if (vm) { - return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); - } else { - return false; - } - }, - inRange: function(chartX, chartY) { + if (vm) { + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2)); + } else { + return false; + } + }, + inRange: function(chartX, chartY) { - var vm = this._view; + var vm = this._view; - var pointRelativePosition = helpers.getAngleFromPoint(vm, { - x: chartX, - y: chartY - }); + var pointRelativePosition = helpers.getAngleFromPoint(vm, { + x: chartX, + y: chartY + }); - // Put into the range of (-PI/2, 3PI/2] - var startAngle = vm.startAngle < (-0.5 * Math.PI) ? vm.startAngle + (2.0 * Math.PI) : vm.startAngle > (1.5 * Math.PI) ? vm.startAngle - (2.0 * Math.PI) : vm.startAngle; - var endAngle = vm.endAngle < (-0.5 * Math.PI) ? vm.endAngle + (2.0 * Math.PI) : vm.endAngle > (1.5 * Math.PI) ? vm.endAngle - (2.0 * Math.PI) : vm.endAngle + // Put into the range of (-PI/2, 3PI/2] + var startAngle = vm.startAngle < (-0.5 * Math.PI) ? vm.startAngle + (2.0 * Math.PI) : vm.startAngle > (1.5 * Math.PI) ? vm.startAngle - (2.0 * Math.PI) : vm.startAngle; + var endAngle = vm.endAngle < (-0.5 * Math.PI) ? vm.endAngle + (2.0 * Math.PI) : vm.endAngle > (1.5 * Math.PI) ? vm.endAngle - (2.0 * Math.PI) : vm.endAngle - //Check if within the range of the open/close angle - var betweenAngles = (pointRelativePosition.angle >= startAngle && pointRelativePosition.angle <= endAngle), - withinRadius = (pointRelativePosition.distance >= vm.innerRadius && pointRelativePosition.distance <= vm.outerRadius); + //Check if within the range of the open/close angle + var betweenAngles = (pointRelativePosition.angle >= startAngle && pointRelativePosition.angle <= endAngle), + withinRadius = (pointRelativePosition.distance >= vm.innerRadius && pointRelativePosition.distance <= vm.outerRadius); - return (betweenAngles && withinRadius); - //Ensure within the outside of the arc centre, but inside arc outer - }, - tooltipPosition: function() { - var vm = this._view; + return (betweenAngles && withinRadius); + //Ensure within the outside of the arc centre, but inside arc outer + }, + tooltipPosition: function() { + var vm = this._view; - var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2), - rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; - return { - x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), - y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) - }; - }, - draw: function() { + var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2), + rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius; + return { + x: vm.x + (Math.cos(centreAngle) * rangeFromCentre), + y: vm.y + (Math.sin(centreAngle) * rangeFromCentre) + }; + }, + draw: function() { - var ctx = this._chart.ctx; - var vm = this._view; + var ctx = this._chart.ctx; + var vm = this._view; - ctx.beginPath(); + ctx.beginPath(); - ctx.arc(vm.x, vm.y, vm.outerRadius, vm.startAngle, vm.endAngle); + ctx.arc(vm.x, vm.y, vm.outerRadius, vm.startAngle, vm.endAngle); - ctx.arc(vm.x, vm.y, vm.innerRadius, vm.endAngle, vm.startAngle, true); + ctx.arc(vm.x, vm.y, vm.innerRadius, vm.endAngle, vm.startAngle, true); - ctx.closePath(); - ctx.strokeStyle = vm.borderColor; - ctx.lineWidth = vm.borderWidth; + ctx.closePath(); + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = vm.borderWidth; - ctx.fillStyle = vm.backgroundColor; + ctx.fillStyle = vm.backgroundColor; - ctx.fill(); - ctx.lineJoin = 'bevel'; + ctx.fill(); + ctx.lineJoin = 'bevel'; - if (vm.borderWidth) { - ctx.stroke(); - } - } - }); + if (vm.borderWidth) { + ctx.stroke(); + } + } + }); }).call(this); diff --git a/src/elements/element.line.js b/src/elements/element.line.js index eaa245c76..edefce6d0 100644 --- a/src/elements/element.line.js +++ b/src/elements/element.line.js @@ -11,180 +11,180 @@ (function() { - "use strict"; + "use strict"; - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; - Chart.defaults.global.elements.line = { - tension: 0.4, - backgroundColor: Chart.defaults.global.defaultColor, - borderWidth: 3, - borderColor: Chart.defaults.global.defaultColor, - fill: true, // do we fill in the area between the line and its base axis - skipNull: true, - drawNull: false, - }; + Chart.defaults.global.elements.line = { + tension: 0.4, + backgroundColor: Chart.defaults.global.defaultColor, + borderWidth: 3, + borderColor: Chart.defaults.global.defaultColor, + fill: true, // do we fill in the area between the line and its base axis + skipNull: true, + drawNull: false, + }; - Chart.Line = Chart.Element.extend({ - draw: function() { + Chart.Line = Chart.Element.extend({ + draw: function() { - var vm = this._view; - var ctx = this._chart.ctx; - var first = this._children[0]; - var last = this._children[this._children.length - 1]; + var vm = this._view; + var ctx = this._chart.ctx; + var first = this._children[0]; + var last = this._children[this._children.length - 1]; - // Draw the background first (so the border is always on top) - helpers.each(this._children, function(point, index) { - var previous = this.previousPoint(point, this._children, index); - var next = this.nextPoint(point, this._children, index); + // Draw the background first (so the border is always on top) + helpers.each(this._children, function(point, index) { + var previous = this.previousPoint(point, this._children, index); + var next = this.nextPoint(point, this._children, index); - // First point only - if (index === 0) { - ctx.moveTo(point._view.x, point._view.y); - return; - } + // First point only + if (index === 0) { + ctx.moveTo(point._view.x, point._view.y); + return; + } - // Start Skip and drag along scale baseline - if (point._view.skip && vm.skipNull && !this._loop) { - ctx.lineTo(previous._view.x, point._view.y); - ctx.moveTo(next._view.x, point._view.y); - } - // End Skip Stright line from the base line - else if (previous._view.skip && vm.skipNull && !this._loop) { - ctx.moveTo(point._view.x, previous._view.y); - ctx.lineTo(point._view.x, point._view.y); - } + // Start Skip and drag along scale baseline + if (point._view.skip && vm.skipNull && !this._loop) { + ctx.lineTo(previous._view.x, point._view.y); + ctx.moveTo(next._view.x, point._view.y); + } + // End Skip Stright line from the base line + else if (previous._view.skip && vm.skipNull && !this._loop) { + ctx.moveTo(point._view.x, previous._view.y); + ctx.lineTo(point._view.x, point._view.y); + } - if (previous._view.skip && vm.skipNull) { - ctx.moveTo(point._view.x, point._view.y); - } - // Normal Bezier Curve - else { - if (vm.tension > 0) { - ctx.bezierCurveTo( - previous._view.controlPointNextX, - previous._view.controlPointNextY, - point._view.controlPointPreviousX, - point._view.controlPointPreviousY, - point._view.x, - point._view.y - ); - } else { - ctx.lineTo(point._view.x, point._view.y); - } - } - }, this); + if (previous._view.skip && vm.skipNull) { + ctx.moveTo(point._view.x, point._view.y); + } + // Normal Bezier Curve + else { + if (vm.tension > 0) { + ctx.bezierCurveTo( + previous._view.controlPointNextX, + previous._view.controlPointNextY, + point._view.controlPointPreviousX, + point._view.controlPointPreviousY, + point._view.x, + point._view.y + ); + } else { + ctx.lineTo(point._view.x, point._view.y); + } + } + }, this); - // For radial scales, loop back around to the first point - if (this._loop) { - if (vm.tension > 0 && !first._view.skip) { + // For radial scales, loop back around to the first point + if (this._loop) { + if (vm.tension > 0 && !first._view.skip) { - ctx.bezierCurveTo( - last._view.controlPointNextX, - last._view.controlPointNextY, - first._view.controlPointPreviousX, - first._view.controlPointPreviousY, - first._view.x, - first._view.y - ); - } else { - ctx.lineTo(first._view.x, first._view.y); - } - } + ctx.bezierCurveTo( + last._view.controlPointNextX, + last._view.controlPointNextY, + first._view.controlPointPreviousX, + first._view.controlPointPreviousY, + first._view.x, + first._view.y + ); + } else { + ctx.lineTo(first._view.x, first._view.y); + } + } - // If we had points and want to fill this line, do so. - if (this._children.length > 0 && vm.fill) { - //Round off the line by going to the base of the chart, back to the start, then fill. - ctx.lineTo(this._children[this._children.length - 1]._view.x, vm.scaleZero); - ctx.lineTo(this._children[0]._view.x, vm.scaleZero); - ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; - ctx.closePath(); - ctx.fill(); - } + // If we had points and want to fill this line, do so. + if (this._children.length > 0 && vm.fill) { + //Round off the line by going to the base of the chart, back to the start, then fill. + ctx.lineTo(this._children[this._children.length - 1]._view.x, vm.scaleZero); + ctx.lineTo(this._children[0]._view.x, vm.scaleZero); + ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; + ctx.closePath(); + ctx.fill(); + } - // Now draw the line between all the points with any borders - ctx.lineWidth = vm.borderWidth || Chart.defaults.global.defaultColor; - ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; - ctx.beginPath(); + // Now draw the line between all the points with any borders + ctx.lineWidth = vm.borderWidth || Chart.defaults.global.defaultColor; + ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; + ctx.beginPath(); - helpers.each(this._children, function(point, index) { - var previous = this.previousPoint(point, this._children, index); - var next = this.nextPoint(point, this._children, index); + helpers.each(this._children, function(point, index) { + var previous = this.previousPoint(point, this._children, index); + var next = this.nextPoint(point, this._children, index); - // First point only - if (index === 0) { - ctx.moveTo(point._view.x, point._view.y); - return; - } + // First point only + if (index === 0) { + ctx.moveTo(point._view.x, point._view.y); + return; + } - // Start Skip and drag along scale baseline - if (point._view.skip && vm.skipNull && !this._loop) { - ctx.moveTo(previous._view.x, point._view.y); - ctx.moveTo(next._view.x, point._view.y); - return; - } - // End Skip Stright line from the base line - if (previous._view.skip && vm.skipNull && !this._loop) { - ctx.moveTo(point._view.x, previous._view.y); - ctx.moveTo(point._view.x, point._view.y); - return; - } + // Start Skip and drag along scale baseline + if (point._view.skip && vm.skipNull && !this._loop) { + ctx.moveTo(previous._view.x, point._view.y); + ctx.moveTo(next._view.x, point._view.y); + return; + } + // End Skip Stright line from the base line + if (previous._view.skip && vm.skipNull && !this._loop) { + ctx.moveTo(point._view.x, previous._view.y); + ctx.moveTo(point._view.x, point._view.y); + return; + } - if (previous._view.skip && vm.skipNull) { - ctx.moveTo(point._view.x, point._view.y); - return; - } - // Normal Bezier Curve - if (vm.tension > 0) { - ctx.bezierCurveTo( - previous._view.controlPointNextX, - previous._view.controlPointNextY, - point._view.controlPointPreviousX, - point._view.controlPointPreviousY, - point._view.x, - point._view.y - ); - } else { - ctx.lineTo(point._view.x, point._view.y); - } - }, this); + if (previous._view.skip && vm.skipNull) { + ctx.moveTo(point._view.x, point._view.y); + return; + } + // Normal Bezier Curve + if (vm.tension > 0) { + ctx.bezierCurveTo( + previous._view.controlPointNextX, + previous._view.controlPointNextY, + point._view.controlPointPreviousX, + point._view.controlPointPreviousY, + point._view.x, + point._view.y + ); + } else { + ctx.lineTo(point._view.x, point._view.y); + } + }, this); - if (this._loop && !first._view.skip) { - if (vm.tension > 0) { + if (this._loop && !first._view.skip) { + if (vm.tension > 0) { - ctx.bezierCurveTo( - last._view.controlPointNextX, - last._view.controlPointNextY, - first._view.controlPointPreviousX, - first._view.controlPointPreviousY, - first._view.x, - first._view.y - ); - } else { - ctx.lineTo(first._view.x, first._view.y); - } - } + ctx.bezierCurveTo( + last._view.controlPointNextX, + last._view.controlPointNextY, + first._view.controlPointPreviousX, + first._view.controlPointPreviousY, + first._view.x, + first._view.y + ); + } else { + ctx.lineTo(first._view.x, first._view.y); + } + } - ctx.stroke(); + ctx.stroke(); - }, - nextPoint: function(point, collection, index) { - if (this.loop) { - return collection[index + 1] || collection[0]; - } - return collection[index + 1] || collection[collection.length - 1]; - }, - previousPoint: function(point, collection, index) { - if (this.loop) { - return collection[index - 1] || collection[collection.length - 1]; - } - return collection[index - 1] || collection[0]; - }, - }); + }, + nextPoint: function(point, collection, index) { + if (this.loop) { + return collection[index + 1] || collection[0]; + } + return collection[index + 1] || collection[collection.length - 1]; + }, + previousPoint: function(point, collection, index) { + if (this.loop) { + return collection[index - 1] || collection[collection.length - 1]; + } + return collection[index - 1] || collection[0]; + }, + }); }).call(this); diff --git a/src/elements/element.point.js b/src/elements/element.point.js index f402d7292..13e272de6 100644 --- a/src/elements/element.point.js +++ b/src/elements/element.point.js @@ -11,74 +11,74 @@ (function() { - "use strict"; + "use strict"; - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; - Chart.defaults.global.elements.point = { - radius: 3, - backgroundColor: Chart.defaults.global.defaultColor, - borderWidth: 1, - borderColor: Chart.defaults.global.defaultColor, - // Hover - hitRadius: 1, - hoverRadius: 4, - hoverBorderWidth: 1, - }; + Chart.defaults.global.elements.point = { + radius: 3, + backgroundColor: Chart.defaults.global.defaultColor, + borderWidth: 1, + borderColor: Chart.defaults.global.defaultColor, + // Hover + hitRadius: 1, + hoverRadius: 4, + hoverBorderWidth: 1, + }; - Chart.Point = Chart.Element.extend({ - inRange: function(mouseX, mouseY) { - var vm = this._view; - var hoverRange = vm.hitRadius + vm.radius; - return ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(hoverRange, 2)); - }, - inGroupRange: function(mouseX) { - var vm = this._view; + Chart.Point = Chart.Element.extend({ + inRange: function(mouseX, mouseY) { + var vm = this._view; + var hoverRange = vm.hitRadius + vm.radius; + return ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(hoverRange, 2)); + }, + inGroupRange: function(mouseX) { + var vm = this._view; - if (vm) { - return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)); - } else { - return false; - } - }, - tooltipPosition: function() { - var vm = this._view; - return { - x: vm.x, - y: vm.y, - padding: vm.radius + vm.borderWidth - }; - }, - draw: function() { + if (vm) { + return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2)); + } else { + return false; + } + }, + tooltipPosition: function() { + var vm = this._view; + return { + x: vm.x, + y: vm.y, + padding: vm.radius + vm.borderWidth + }; + }, + draw: function() { - var vm = this._view; - var ctx = this._chart.ctx; + var vm = this._view; + var ctx = this._chart.ctx; - if (vm.skip) { - return; - } + if (vm.skip) { + return; + } - if (vm.radius > 0 || vm.borderWidth > 0) { + if (vm.radius > 0 || vm.borderWidth > 0) { - ctx.beginPath(); + ctx.beginPath(); - ctx.arc(vm.x, vm.y, vm.radius || Chart.defaults.global.elements.point.radius, 0, Math.PI * 2); - ctx.closePath(); + ctx.arc(vm.x, vm.y, vm.radius || Chart.defaults.global.elements.point.radius, 0, Math.PI * 2); + ctx.closePath(); - ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; - ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.point.borderWidth; + ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor; + ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.point.borderWidth; - ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; + ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor; - ctx.fill(); - ctx.stroke(); - } - } - }); + ctx.fill(); + ctx.stroke(); + } + } + }); }).call(this); diff --git a/src/elements/element.rectangle.js b/src/elements/element.rectangle.js index fc4a58c0f..7c35df01d 100644 --- a/src/elements/element.rectangle.js +++ b/src/elements/element.rectangle.js @@ -11,85 +11,85 @@ (function() { - "use strict"; + "use strict"; - var root = this, - Chart = root.Chart, - helpers = Chart.helpers; + var root = this, + Chart = root.Chart, + helpers = Chart.helpers; - Chart.defaults.global.elements.rectangle = { - backgroundColor: Chart.defaults.global.defaultColor, - borderWidth: 0, - borderColor: Chart.defaults.global.defaultColor, - }; + Chart.defaults.global.elements.rectangle = { + backgroundColor: Chart.defaults.global.defaultColor, + borderWidth: 0, + borderColor: Chart.defaults.global.defaultColor, + }; - Chart.Rectangle = Chart.Element.extend({ - draw: function() { + Chart.Rectangle = Chart.Element.extend({ + draw: function() { - var ctx = this._chart.ctx; - var vm = this._view; + var ctx = this._chart.ctx; + var vm = this._view; - var halfWidth = vm.width / 2, - leftX = vm.x - halfWidth, - rightX = vm.x + halfWidth, - top = vm.base - (vm.base - vm.y), - halfStroke = vm.borderWidth / 2; + var halfWidth = vm.width / 2, + leftX = vm.x - halfWidth, + rightX = vm.x + halfWidth, + top = vm.base - (vm.base - vm.y), + halfStroke = vm.borderWidth / 2; - // Canvas doesn't allow us to stroke inside the width so we can - // adjust the sizes to fit if we're setting a stroke on the line - if (vm.borderWidth) { - leftX += halfStroke; - rightX -= halfStroke; - top += halfStroke; - } + // Canvas doesn't allow us to stroke inside the width so we can + // adjust the sizes to fit if we're setting a stroke on the line + if (vm.borderWidth) { + leftX += halfStroke; + rightX -= halfStroke; + top += halfStroke; + } - ctx.beginPath(); + ctx.beginPath(); - ctx.fillStyle = vm.backgroundColor; - ctx.strokeStyle = vm.borderColor; - ctx.lineWidth = vm.borderWidth; + ctx.fillStyle = vm.backgroundColor; + ctx.strokeStyle = vm.borderColor; + ctx.lineWidth = vm.borderWidth; - // It'd be nice to keep this class totally generic to any rectangle - // and simply specify which border to miss out. - ctx.moveTo(leftX, vm.base); - ctx.lineTo(leftX, top); - ctx.lineTo(rightX, top); - ctx.lineTo(rightX, vm.base); - ctx.fill(); - if (vm.borderWidth) { - ctx.stroke(); - } - }, - height: function() { - var vm = this._view; - return vm.base - vm.y; - }, - inRange: function(mouseX, mouseY) { - var vm = this._view; - if (vm.y < vm.base) { - return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base); - } else { - return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y); - } - }, - inGroupRange: function(mouseX) { - var vm = this._view; - return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2); - }, - tooltipPosition: function() { - var vm = this._view; - if (vm.y < vm.base) { - return { - x: vm.x, - y: vm.y - }; - } else { - return { - x: vm.x, - y: vm.base - }; - } - }, - }); + // It'd be nice to keep this class totally generic to any rectangle + // and simply specify which border to miss out. + ctx.moveTo(leftX, vm.base); + ctx.lineTo(leftX, top); + ctx.lineTo(rightX, top); + ctx.lineTo(rightX, vm.base); + ctx.fill(); + if (vm.borderWidth) { + ctx.stroke(); + } + }, + height: function() { + var vm = this._view; + return vm.base - vm.y; + }, + inRange: function(mouseX, mouseY) { + var vm = this._view; + if (vm.y < vm.base) { + return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.y && mouseY <= vm.base); + } else { + return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2) && (mouseY >= vm.base && mouseY <= vm.y); + } + }, + inGroupRange: function(mouseX) { + var vm = this._view; + return (mouseX >= vm.x - vm.width / 2 && mouseX <= vm.x + vm.width / 2); + }, + tooltipPosition: function() { + var vm = this._view; + if (vm.y < vm.base) { + return { + x: vm.x, + y: vm.y + }; + } else { + return { + x: vm.x, + y: vm.base + }; + } + }, + }); }).call(this);