(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);
(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);
(function() {
- "use strict";
-
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
-
- Chart.defaults.global.animation = {
- duration: 1000,
- easing: "easeOutQuart",
- onProgress: function() {},
- onComplete: function() {},
- };
-
- Chart.Animation = Chart.Element.extend({
- currentStep: null, // the current animation step
- numSteps: 60, // default number of steps
- easing: "", // the easing to use for this animation
- render: null, // render function used by the animation service
-
- onAnimationProgress: null, // user specified callback to fire on each step of the animation
- onAnimationComplete: null, // user specified callback to fire when the animation finishes
- });
-
- Chart.animationService = {
- frameDuration: 17,
- animations: [],
- dropFrames: 0,
- addAnimation: function(chartInstance, animationObject, duration) {
-
- if (!duration) {
- chartInstance.animating = true;
- }
-
- for (var index = 0; index < this.animations.length; ++index) {
- if (this.animations[index].chartInstance === chartInstance) {
- // replacing an in progress animation
- this.animations[index].animationObject = animationObject;
- return;
- }
- }
-
- this.animations.push({
- chartInstance: chartInstance,
- animationObject: animationObject
- });
-
- // If there are no animations queued, manually kickstart a digest, for lack of a better word
- if (this.animations.length == 1) {
- helpers.requestAnimFrame.call(window, this.digestWrapper);
- }
- },
- // Cancel the animation for a given chart instance
- cancelAnimation: function(chartInstance) {
- var index = helpers.findNextWhere(this.animations, function(animationWrapper) {
- return animationWrapper.chartInstance === chartInstance;
- });
-
- if (index) {
- this.animations.splice(index, 1);
- chartInstance.animating = false;
- }
- },
- // calls startDigest with the proper context
- digestWrapper: function() {
- Chart.animationService.startDigest.call(Chart.animationService);
- },
- startDigest: function() {
-
- var startTime = Date.now();
- var framesToDrop = 0;
-
- if (this.dropFrames > 1) {
- framesToDrop = Math.floor(this.dropFrames);
- this.dropFrames -= framesToDrop;
- }
-
- for (var i = 0; i < this.animations.length; i++) {
-
- if (this.animations[i].animationObject.currentStep === null) {
- this.animations[i].animationObject.currentStep = 0;
- }
-
- this.animations[i].animationObject.currentStep += 1 + framesToDrop;
- if (this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps) {
- this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps;
- }
-
- this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject);
-
- if (this.animations[i].animationObject.currentStep == this.animations[i].animationObject.numSteps) {
- // executed the last frame. Remove the animation.
- this.animations[i].chartInstance.animating = false;
- this.animations.splice(i, 1);
- // Keep the index in place to offset the splice
- i--;
- }
- }
-
- var endTime = Date.now();
- var delay = endTime - startTime - this.frameDuration;
- var frameDelay = delay / this.frameDuration;
-
- if (frameDelay > 1) {
- this.dropFrames += frameDelay;
- }
-
- // Do we have more stuff to animate?
- if (this.animations.length > 0) {
- helpers.requestAnimFrame.call(window, this.digestWrapper);
- }
- }
- };
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ helpers = Chart.helpers;
+
+ Chart.defaults.global.animation = {
+ duration: 1000,
+ easing: "easeOutQuart",
+ onProgress: function() {},
+ onComplete: function() {},
+ };
+
+ Chart.Animation = Chart.Element.extend({
+ currentStep: null, // the current animation step
+ numSteps: 60, // default number of steps
+ easing: "", // the easing to use for this animation
+ render: null, // render function used by the animation service
+
+ onAnimationProgress: null, // user specified callback to fire on each step of the animation
+ onAnimationComplete: null, // user specified callback to fire when the animation finishes
+ });
+
+ Chart.animationService = {
+ frameDuration: 17,
+ animations: [],
+ dropFrames: 0,
+ addAnimation: function(chartInstance, animationObject, duration) {
+
+ if (!duration) {
+ chartInstance.animating = true;
+ }
+
+ for (var index = 0; index < this.animations.length; ++index) {
+ if (this.animations[index].chartInstance === chartInstance) {
+ // replacing an in progress animation
+ this.animations[index].animationObject = animationObject;
+ return;
+ }
+ }
+
+ this.animations.push({
+ chartInstance: chartInstance,
+ animationObject: animationObject
+ });
+
+ // If there are no animations queued, manually kickstart a digest, for lack of a better word
+ if (this.animations.length == 1) {
+ helpers.requestAnimFrame.call(window, this.digestWrapper);
+ }
+ },
+ // Cancel the animation for a given chart instance
+ cancelAnimation: function(chartInstance) {
+ var index = helpers.findNextWhere(this.animations, function(animationWrapper) {
+ return animationWrapper.chartInstance === chartInstance;
+ });
+
+ if (index) {
+ this.animations.splice(index, 1);
+ chartInstance.animating = false;
+ }
+ },
+ // calls startDigest with the proper context
+ digestWrapper: function() {
+ Chart.animationService.startDigest.call(Chart.animationService);
+ },
+ startDigest: function() {
+
+ var startTime = Date.now();
+ var framesToDrop = 0;
+
+ if (this.dropFrames > 1) {
+ framesToDrop = Math.floor(this.dropFrames);
+ this.dropFrames -= framesToDrop;
+ }
+
+ for (var i = 0; i < this.animations.length; i++) {
+
+ if (this.animations[i].animationObject.currentStep === null) {
+ this.animations[i].animationObject.currentStep = 0;
+ }
+
+ this.animations[i].animationObject.currentStep += 1 + framesToDrop;
+ if (this.animations[i].animationObject.currentStep > this.animations[i].animationObject.numSteps) {
+ this.animations[i].animationObject.currentStep = this.animations[i].animationObject.numSteps;
+ }
+
+ this.animations[i].animationObject.render(this.animations[i].chartInstance, this.animations[i].animationObject);
+
+ if (this.animations[i].animationObject.currentStep == this.animations[i].animationObject.numSteps) {
+ // executed the last frame. Remove the animation.
+ this.animations[i].chartInstance.animating = false;
+ this.animations.splice(i, 1);
+ // Keep the index in place to offset the splice
+ i--;
+ }
+ }
+
+ var endTime = Date.now();
+ var delay = endTime - startTime - this.frameDuration;
+ var frameDelay = delay / this.frameDuration;
+
+ if (frameDelay > 1) {
+ this.dropFrames += frameDelay;
+ }
+
+ // Do we have more stuff to animate?
+ if (this.animations.length > 0) {
+ helpers.requestAnimFrame.call(window, this.digestWrapper);
+ }
+ }
+ };
}).call(this);
(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);
(function() {
- "use strict";
-
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
-
- // The scale service is used to resize charts along with all of their axes. We make this as
- // a service where scales are registered with their respective charts so that changing the
- // scales does not require
- Chart.scaleService = {
- // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
- // use the new chart options to grab the correct scale
- constructors: {},
- // Use a registration function so that we can move to an ES6 map when we no longer need to support
- // old browsers
- 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;
-
- if (chartInstance) {
- var leftScales = helpers.where(chartInstance.scales, function(scaleInstance) {
- return scaleInstance.options.position == "left";
- });
- var rightScales = helpers.where(chartInstance.scales, function(scaleInstance) {
- return scaleInstance.options.position == "right";
- });
- var topScales = helpers.where(chartInstance.scales, function(scaleInstance) {
- return scaleInstance.options.position == "top";
- });
- var bottomScales = helpers.where(chartInstance.scales, function(scaleInstance) {
- return scaleInstance.options.position == "bottom";
- });
-
- var visibleLeftScales = helpers.where(chartInstance.scales, function(scaleInstance) {
- return scaleInstance.options.position == "left";
- });
- var visibleRightScales = helpers.where(chartInstance.scales, function(scaleInstance) {
- return scaleInstance.options.position == "right";
- });
- var visibleTopScales = helpers.where(chartInstance.scales, function(scaleInstance) {
- return scaleInstance.options.position == "top";
- });
- var visibleBottomScales = helpers.where(chartInstance.scales, function(scaleInstance) {
- return scaleInstance.options.position == "bottom";
- });
-
- // // Adjust the padding to take into account displaying labels
- // if (topScales.length === 0 || bottomScales.length === 0) {
- // var maxFontHeight = 0;
-
- // var maxFontHeightFunction = function(scaleInstance) {
- // if (scaleInstance.options.labels.show) {
- // // Only consider font sizes for axes that actually show labels
- // maxFontHeight = Math.max(maxFontHeight, scaleInstance.options.labels.fontSize);
- // }
- // };
-
- // helpers.each(leftScales, maxFontHeightFunction);
- // helpers.each(rightScales, maxFontHeightFunction);
-
- // if (topScales.length === 0) {
- // // Add padding so that we can handle drawing the top nicely
- // yPadding += 0.75 * maxFontHeight; // 0.75 since padding added on both sides
- // }
-
- // if (bottomScales.length === 0) {
- // // Add padding so that we can handle drawing the bottom nicely
- // yPadding += 1.5 * maxFontHeight;
- // }
- // }
-
- // Essentially we now have any number of scales on each of the 4 sides.
- // Our canvas looks like the following.
- // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
- // B1 is the bottom axis
- // |------------------------------------------------------|
- // | | T1 | |
- // |----|-----|-------------------------------------|-----|
- // | | | | |
- // | L1 | L2 | Chart area | R1 |
- // | | | | |
- // | | | | |
- // |----|-----|-------------------------------------|-----|
- // | | B1 | |
- // | | | |
- // |------------------------------------------------------|
-
- // What we do to find the best sizing, we do the following
- // 1. Determine the minimum size of the chart area.
- // 2. Split the remaining width equally between each vertical axis
- // 3. Split the remaining height equally between each horizontal axis
- // 4. Give each scale the maximum size it can be. The scale will return it's minimum size
- // 5. Adjust the sizes of each axis based on it's minimum reported size.
- // 6. Refit each axis
- // 7. Position each axis in the final location
- // 8. Tell the chart the final location of the chart area
-
- // Step 1
- var chartWidth = width / 2; // min 50%
- var chartHeight = height / 2; // min 50%
-
- chartWidth -= (2 * xPadding);
- chartHeight -= (2 * yPadding);
-
-
- // Step 2
- var verticalScaleWidth = (width - chartWidth) / (leftScales.length + rightScales.length);
-
- // Step 3
- var horizontalScaleHeight = (height - chartHeight) / (topScales.length + bottomScales.length);
-
- // Step 4;
- var minimumScaleSizes = [];
-
- var verticalScaleMinSizeFunction = function(scaleInstance) {
- var minSize = scaleInstance.fit(verticalScaleWidth, chartHeight);
- minimumScaleSizes.push({
- horizontal: false,
- minSize: minSize,
- scale: scaleInstance,
- });
- };
-
- var horizontalScaleMinSizeFunction = function(scaleInstance) {
- var minSize = scaleInstance.fit(chartWidth, horizontalScaleHeight);
- minimumScaleSizes.push({
- horizontal: true,
- minSize: minSize,
- scale: scaleInstance,
- });
- };
-
- // vertical scales
- helpers.each(leftScales, verticalScaleMinSizeFunction);
- helpers.each(rightScales, verticalScaleMinSizeFunction);
-
- // horizontal scales
- helpers.each(topScales, horizontalScaleMinSizeFunction);
- helpers.each(bottomScales, horizontalScaleMinSizeFunction);
-
- // Step 5
- var maxChartHeight = height - (2 * yPadding);
- var maxChartWidth = width - (2 * xPadding);
-
- helpers.each(minimumScaleSizes, function(wrapper) {
- if (wrapper.horizontal) {
- maxChartHeight -= wrapper.minSize.height;
- } else {
- maxChartWidth -= wrapper.minSize.width;
- }
- });
-
- // At this point, maxChartHeight and maxChartWidth are the size the chart area could
- // be if the axes are drawn at their minimum sizes.
-
- // Step 6
- var verticalScaleFitFunction = function(scaleInstance) {
- var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
- return wrapper.scale === scaleInstance;
- });
-
- if (wrapper) {
- scaleInstance.fit(wrapper.minSize.width, maxChartHeight);
- }
- };
-
- var horizontalScaleFitFunction = function(scaleInstance) {
- var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
- return wrapper.scale === scaleInstance;
- });
-
- var scaleMargin = {
- left: totalLeftWidth,
- right: totalRightWidth,
- top: 0,
- bottom: 0,
- };
-
- if (wrapper) {
- scaleInstance.fit(maxChartWidth, wrapper.minSize.height, scaleMargin);
- }
- };
-
- var totalLeftWidth = xPadding;
- var totalRightWidth = xPadding;
- var totalTopHeight = yPadding;
- var totalBottomHeight = yPadding;
-
- helpers.each(leftScales, verticalScaleFitFunction);
- helpers.each(rightScales, verticalScaleFitFunction);
-
- // Figure out how much margin is on the left and right of the horizontal axes
- helpers.each(leftScales, function(scaleInstance) {
- totalLeftWidth += scaleInstance.width;
- });
-
- helpers.each(rightScales, function(scaleInstance) {
- totalRightWidth += scaleInstance.width;
- });
-
- helpers.each(topScales, horizontalScaleFitFunction);
- helpers.each(bottomScales, horizontalScaleFitFunction);
-
- helpers.each(topScales, function(scaleInstance) {
- totalTopHeight += scaleInstance.height;
- });
- helpers.each(bottomScales, function(scaleInstance) {
- totalBottomHeight += scaleInstance.height;
- });
-
- // Let the left scale know the final margin
- helpers.each(leftScales, function(scaleInstance) {
- var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
- return wrapper.scale === scaleInstance;
- });
-
- var scaleMargin = {
- left: 0,
- right: 0,
- top: totalTopHeight,
- bottom: totalBottomHeight
- };
-
- if (wrapper) {
- scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin);
- }
- });
-
- helpers.each(rightScales, function(scaleInstance) {
- var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
- return wrapper.scale === scaleInstance;
- });
-
- var scaleMargin = {
- left: 0,
- right: 0,
- top: totalTopHeight,
- bottom: totalBottomHeight
- };
-
- if (wrapper) {
- scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin);
- }
- });
-
- // Step 7
- // Position the scales
- var left = xPadding;
- var top = yPadding;
- var right = 0;
- var bottom = 0;
-
- var verticalScalePlacer = function(scaleInstance) {
- scaleInstance.left = left;
- scaleInstance.right = left + scaleInstance.width;
- scaleInstance.top = totalTopHeight;
- scaleInstance.bottom = totalTopHeight + maxChartHeight;
-
- // Move to next point
- left = scaleInstance.right;
- };
-
- var horizontalScalePlacer = function(scaleInstance) {
- scaleInstance.left = totalLeftWidth;
- scaleInstance.right = totalLeftWidth + maxChartWidth;
- scaleInstance.top = top;
- scaleInstance.bottom = top + scaleInstance.height;
-
- // Move to next point
- top = scaleInstance.bottom;
- };
-
- helpers.each(leftScales, verticalScalePlacer);
- helpers.each(topScales, horizontalScalePlacer);
-
- // Account for chart width and height
- left += maxChartWidth;
- top += maxChartHeight;
-
- helpers.each(rightScales, verticalScalePlacer);
- helpers.each(bottomScales, horizontalScalePlacer);
-
- // Step 8
- chartInstance.chartArea = {
- left: totalLeftWidth,
- top: totalTopHeight,
- right: totalLeftWidth + maxChartWidth,
- bottom: totalTopHeight + maxChartHeight,
- };
- }
- }
- };
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ helpers = Chart.helpers;
+
+ // The scale service is used to resize charts along with all of their axes. We make this as
+ // a service where scales are registered with their respective charts so that changing the
+ // scales does not require
+ Chart.scaleService = {
+ // Scale registration object. Extensions can register new scale types (such as log or DB scales) and then
+ // use the new chart options to grab the correct scale
+ constructors: {},
+ // Use a registration function so that we can move to an ES6 map when we no longer need to support
+ // old browsers
+ 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;
+
+ if (chartInstance) {
+ var leftScales = helpers.where(chartInstance.scales, function(scaleInstance) {
+ return scaleInstance.options.position == "left";
+ });
+ var rightScales = helpers.where(chartInstance.scales, function(scaleInstance) {
+ return scaleInstance.options.position == "right";
+ });
+ var topScales = helpers.where(chartInstance.scales, function(scaleInstance) {
+ return scaleInstance.options.position == "top";
+ });
+ var bottomScales = helpers.where(chartInstance.scales, function(scaleInstance) {
+ return scaleInstance.options.position == "bottom";
+ });
+
+ var visibleLeftScales = helpers.where(chartInstance.scales, function(scaleInstance) {
+ return scaleInstance.options.position == "left";
+ });
+ var visibleRightScales = helpers.where(chartInstance.scales, function(scaleInstance) {
+ return scaleInstance.options.position == "right";
+ });
+ var visibleTopScales = helpers.where(chartInstance.scales, function(scaleInstance) {
+ return scaleInstance.options.position == "top";
+ });
+ var visibleBottomScales = helpers.where(chartInstance.scales, function(scaleInstance) {
+ return scaleInstance.options.position == "bottom";
+ });
+
+ // // Adjust the padding to take into account displaying labels
+ // if (topScales.length === 0 || bottomScales.length === 0) {
+ // var maxFontHeight = 0;
+
+ // var maxFontHeightFunction = function(scaleInstance) {
+ // if (scaleInstance.options.labels.show) {
+ // // Only consider font sizes for axes that actually show labels
+ // maxFontHeight = Math.max(maxFontHeight, scaleInstance.options.labels.fontSize);
+ // }
+ // };
+
+ // helpers.each(leftScales, maxFontHeightFunction);
+ // helpers.each(rightScales, maxFontHeightFunction);
+
+ // if (topScales.length === 0) {
+ // // Add padding so that we can handle drawing the top nicely
+ // yPadding += 0.75 * maxFontHeight; // 0.75 since padding added on both sides
+ // }
+
+ // if (bottomScales.length === 0) {
+ // // Add padding so that we can handle drawing the bottom nicely
+ // yPadding += 1.5 * maxFontHeight;
+ // }
+ // }
+
+ // Essentially we now have any number of scales on each of the 4 sides.
+ // Our canvas looks like the following.
+ // The areas L1 and L2 are the left axes. R1 is the right axis, T1 is the top axis and
+ // B1 is the bottom axis
+ // |------------------------------------------------------|
+ // | | T1 | |
+ // |----|-----|-------------------------------------|-----|
+ // | | | | |
+ // | L1 | L2 | Chart area | R1 |
+ // | | | | |
+ // | | | | |
+ // |----|-----|-------------------------------------|-----|
+ // | | B1 | |
+ // | | | |
+ // |------------------------------------------------------|
+
+ // What we do to find the best sizing, we do the following
+ // 1. Determine the minimum size of the chart area.
+ // 2. Split the remaining width equally between each vertical axis
+ // 3. Split the remaining height equally between each horizontal axis
+ // 4. Give each scale the maximum size it can be. The scale will return it's minimum size
+ // 5. Adjust the sizes of each axis based on it's minimum reported size.
+ // 6. Refit each axis
+ // 7. Position each axis in the final location
+ // 8. Tell the chart the final location of the chart area
+
+ // Step 1
+ var chartWidth = width / 2; // min 50%
+ var chartHeight = height / 2; // min 50%
+
+ chartWidth -= (2 * xPadding);
+ chartHeight -= (2 * yPadding);
+
+
+ // Step 2
+ var verticalScaleWidth = (width - chartWidth) / (leftScales.length + rightScales.length);
+
+ // Step 3
+ var horizontalScaleHeight = (height - chartHeight) / (topScales.length + bottomScales.length);
+
+ // Step 4;
+ var minimumScaleSizes = [];
+
+ var verticalScaleMinSizeFunction = function(scaleInstance) {
+ var minSize = scaleInstance.fit(verticalScaleWidth, chartHeight);
+ minimumScaleSizes.push({
+ horizontal: false,
+ minSize: minSize,
+ scale: scaleInstance,
+ });
+ };
+
+ var horizontalScaleMinSizeFunction = function(scaleInstance) {
+ var minSize = scaleInstance.fit(chartWidth, horizontalScaleHeight);
+ minimumScaleSizes.push({
+ horizontal: true,
+ minSize: minSize,
+ scale: scaleInstance,
+ });
+ };
+
+ // vertical scales
+ helpers.each(leftScales, verticalScaleMinSizeFunction);
+ helpers.each(rightScales, verticalScaleMinSizeFunction);
+
+ // horizontal scales
+ helpers.each(topScales, horizontalScaleMinSizeFunction);
+ helpers.each(bottomScales, horizontalScaleMinSizeFunction);
+
+ // Step 5
+ var maxChartHeight = height - (2 * yPadding);
+ var maxChartWidth = width - (2 * xPadding);
+
+ helpers.each(minimumScaleSizes, function(wrapper) {
+ if (wrapper.horizontal) {
+ maxChartHeight -= wrapper.minSize.height;
+ } else {
+ maxChartWidth -= wrapper.minSize.width;
+ }
+ });
+
+ // At this point, maxChartHeight and maxChartWidth are the size the chart area could
+ // be if the axes are drawn at their minimum sizes.
+
+ // Step 6
+ var verticalScaleFitFunction = function(scaleInstance) {
+ var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
+ return wrapper.scale === scaleInstance;
+ });
+
+ if (wrapper) {
+ scaleInstance.fit(wrapper.minSize.width, maxChartHeight);
+ }
+ };
+
+ var horizontalScaleFitFunction = function(scaleInstance) {
+ var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
+ return wrapper.scale === scaleInstance;
+ });
+
+ var scaleMargin = {
+ left: totalLeftWidth,
+ right: totalRightWidth,
+ top: 0,
+ bottom: 0,
+ };
+
+ if (wrapper) {
+ scaleInstance.fit(maxChartWidth, wrapper.minSize.height, scaleMargin);
+ }
+ };
+
+ var totalLeftWidth = xPadding;
+ var totalRightWidth = xPadding;
+ var totalTopHeight = yPadding;
+ var totalBottomHeight = yPadding;
+
+ helpers.each(leftScales, verticalScaleFitFunction);
+ helpers.each(rightScales, verticalScaleFitFunction);
+
+ // Figure out how much margin is on the left and right of the horizontal axes
+ helpers.each(leftScales, function(scaleInstance) {
+ totalLeftWidth += scaleInstance.width;
+ });
+
+ helpers.each(rightScales, function(scaleInstance) {
+ totalRightWidth += scaleInstance.width;
+ });
+
+ helpers.each(topScales, horizontalScaleFitFunction);
+ helpers.each(bottomScales, horizontalScaleFitFunction);
+
+ helpers.each(topScales, function(scaleInstance) {
+ totalTopHeight += scaleInstance.height;
+ });
+ helpers.each(bottomScales, function(scaleInstance) {
+ totalBottomHeight += scaleInstance.height;
+ });
+
+ // Let the left scale know the final margin
+ helpers.each(leftScales, function(scaleInstance) {
+ var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
+ return wrapper.scale === scaleInstance;
+ });
+
+ var scaleMargin = {
+ left: 0,
+ right: 0,
+ top: totalTopHeight,
+ bottom: totalBottomHeight
+ };
+
+ if (wrapper) {
+ scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin);
+ }
+ });
+
+ helpers.each(rightScales, function(scaleInstance) {
+ var wrapper = helpers.findNextWhere(minimumScaleSizes, function(wrapper) {
+ return wrapper.scale === scaleInstance;
+ });
+
+ var scaleMargin = {
+ left: 0,
+ right: 0,
+ top: totalTopHeight,
+ bottom: totalBottomHeight
+ };
+
+ if (wrapper) {
+ scaleInstance.fit(wrapper.minSize.width, maxChartHeight, scaleMargin);
+ }
+ });
+
+ // Step 7
+ // Position the scales
+ var left = xPadding;
+ var top = yPadding;
+ var right = 0;
+ var bottom = 0;
+
+ var verticalScalePlacer = function(scaleInstance) {
+ scaleInstance.left = left;
+ scaleInstance.right = left + scaleInstance.width;
+ scaleInstance.top = totalTopHeight;
+ scaleInstance.bottom = totalTopHeight + maxChartHeight;
+
+ // Move to next point
+ left = scaleInstance.right;
+ };
+
+ var horizontalScalePlacer = function(scaleInstance) {
+ scaleInstance.left = totalLeftWidth;
+ scaleInstance.right = totalLeftWidth + maxChartWidth;
+ scaleInstance.top = top;
+ scaleInstance.bottom = top + scaleInstance.height;
+
+ // Move to next point
+ top = scaleInstance.bottom;
+ };
+
+ helpers.each(leftScales, verticalScalePlacer);
+ helpers.each(topScales, horizontalScalePlacer);
+
+ // Account for chart width and height
+ left += maxChartWidth;
+ top += maxChartHeight;
+
+ helpers.each(rightScales, verticalScalePlacer);
+ helpers.each(bottomScales, horizontalScalePlacer);
+
+ // Step 8
+ chartInstance.chartArea = {
+ left: totalLeftWidth,
+ top: totalTopHeight,
+ right: totalLeftWidth + maxChartWidth,
+ bottom: totalTopHeight + maxChartHeight,
+ };
+ }
+ }
+ };
}).call(this);
(function() {
- "use strict";
-
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
-
- Chart.defaults.global.tooltips = {
- enabled: true,
- custom: null,
- backgroundColor: "rgba(0,0,0,0.8)",
- fontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
- fontSize: 10,
- fontStyle: "normal",
- fontColor: "#fff",
- titleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
- titleFontSize: 12,
- titleFontStyle: "bold",
- titleFontColor: "#fff",
- yPadding: 6,
- xPadding: 6,
- caretSize: 8,
- cornerRadius: 6,
- xOffset: 10,
- template: [
- '<% if(label){ %>',
- '<%=label %>: ',
- '<% } %>',
- '<%=value %>',
- ].join(''),
- multiTemplate: [
- '<%if (datasetLabel){ %>',
- '<%=datasetLabel %>: ',
- '<% } %>',
- '<%=value %>'
- ].join(''),
- multiKeyBackground: '#fff',
- };
-
- Chart.Tooltip = Chart.Element.extend({
- initialize: function() {
- var options = this._options;
- helpers.extend(this, {
- _model: {
- // Positioning
- xPadding: options.tooltips.xPadding,
- yPadding: options.tooltips.yPadding,
- xOffset: options.tooltips.xOffset,
-
- // Labels
- textColor: options.tooltips.fontColor,
- _fontFamily: options.tooltips.fontFamily,
- _fontStyle: options.tooltips.fontStyle,
- fontSize: options.tooltips.fontSize,
-
- // Title
- titleTextColor: options.tooltips.titleFontColor,
- _titleFontFamily: options.tooltips.titleFontFamily,
- _titleFontStyle: options.tooltips.titleFontStyle,
- titleFontSize: options.tooltips.titleFontSize,
-
- // Appearance
- caretHeight: options.tooltips.caretSize,
- cornerRadius: options.tooltips.cornerRadius,
- backgroundColor: options.tooltips.backgroundColor,
- opacity: 0,
- legendColorBackground: options.tooltips.multiKeyBackground,
- },
- });
- },
- update: function() {
-
- var ctx = this._chart.ctx;
-
- switch (this._options.hover.mode) {
- case 'single':
- helpers.extend(this._model, {
- text: helpers.template(this._options.tooltips.template, {
- // These variables are available in the template function. Add others here
- element: this._active[0],
- value: this._data.datasets[this._active[0]._datasetIndex].data[this._active[0]._index],
- label: this._data.labels ? this._data.labels[this._active[0]._index] : '',
- }),
- });
-
- var tooltipPosition = this._active[0].tooltipPosition();
- helpers.extend(this._model, {
- x: Math.round(tooltipPosition.x),
- y: Math.round(tooltipPosition.y),
- caretPadding: tooltipPosition.padding
- });
-
- break;
-
- case 'label':
-
- // Tooltip Content
-
- var dataArray,
- dataIndex;
-
- var labels = [],
- colors = [];
-
- for (var i = this._data.datasets.length - 1; i >= 0; i--) {
- dataArray = this._data.datasets[i].metaData;
- dataIndex = helpers.indexOf(dataArray, this._active[0]);
- if (dataIndex !== -1) {
- break;
- }
- }
-
- var medianPosition = (function(index) {
- // Get all the points at that particular index
- var elements = [],
- dataCollection,
- xPositions = [],
- yPositions = [],
- xMax,
- yMax,
- xMin,
- yMin;
- helpers.each(this._data.datasets, function(dataset) {
- dataCollection = dataset.metaData;
- if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()) {
- elements.push(dataCollection[dataIndex]);
- }
- }, this);
-
- // Reverse labels if stacked
- helpers.each(this._options.stacked ? elements.reverse() : elements, function(element) {
- xPositions.push(element._view.x);
- yPositions.push(element._view.y);
-
- //Include any colour information about the element
- labels.push(helpers.template(this._options.tooltips.multiTemplate, {
- // These variables are available in the template function. Add others here
- element: element,
- datasetLabel: this._data.datasets[element._datasetIndex].label,
- value: this._data.datasets[element._datasetIndex].data[element._index],
- }));
- colors.push({
- fill: element._view.backgroundColor,
- stroke: element._view.borderColor
- });
-
- }, this);
-
- yMin = helpers.min(yPositions);
- yMax = helpers.max(yPositions);
-
- xMin = helpers.min(xPositions);
- xMax = helpers.max(xPositions);
-
- return {
- x: (xMin > this._chart.width / 2) ? xMin : xMax,
- y: (yMin + yMax) / 2,
- };
- }).call(this, dataIndex);
-
- // Apply for now
- helpers.extend(this._model, {
- x: medianPosition.x,
- y: medianPosition.y,
- labels: labels,
- title: this._data.labels && this._data.labels.length ? this._data.labels[this._active[0]._index] : '',
- legendColors: colors,
- legendBackgroundColor: this._options.tooltips.multiKeyBackground,
- });
-
-
- // Calculate Appearance Tweaks
-
- this._model.height = (labels.length * this._model.fontSize) + ((labels.length - 1) * (this._model.fontSize / 2)) + (this._model.yPadding * 2) + this._model.titleFontSize * 1.5;
-
- var titleWidth = ctx.measureText(this.title).width,
- //Label has a legend square as well so account for this.
- labelWidth = helpers.longestText(ctx, this.font, labels) + this._model.fontSize + 3,
- longestTextWidth = helpers.max([labelWidth, titleWidth]);
-
- this._model.width = longestTextWidth + (this._model.xPadding * 2);
-
-
- var halfHeight = this._model.height / 2;
-
- //Check to ensure the height will fit on the canvas
- if (this._model.y - halfHeight < 0) {
- this._model.y = halfHeight;
- } else if (this._model.y + halfHeight > this._chart.height) {
- this._model.y = this._chart.height - halfHeight;
- }
-
- //Decide whether to align left or right based on position on canvas
- if (this._model.x > this._chart.width / 2) {
- this._model.x -= this._model.xOffset + this._model.width;
- } else {
- this._model.x += this._model.xOffset;
- }
- break;
- }
-
- return this;
- },
- draw: function() {
-
- var ctx = this._chart.ctx;
- var vm = this._view;
-
- switch (this._options.hover.mode) {
- case 'single':
-
- ctx.font = helpers.fontString(vm.fontSize, vm._fontStyle, vm._fontFamily);
-
- vm.xAlign = "center";
- vm.yAlign = "above";
-
- //Distance between the actual element.y position and the start of the tooltip caret
- var caretPadding = vm.caretPadding || 2;
-
- var tooltipWidth = ctx.measureText(vm.text).width + 2 * vm.xPadding,
- tooltipRectHeight = vm.fontSize + 2 * vm.yPadding,
- tooltipHeight = tooltipRectHeight + vm.caretHeight + caretPadding;
-
- if (vm.x + tooltipWidth / 2 > this._chart.width) {
- vm.xAlign = "left";
- } else if (vm.x - tooltipWidth / 2 < 0) {
- vm.xAlign = "right";
- }
-
- if (vm.y - tooltipHeight < 0) {
- vm.yAlign = "below";
- }
-
- var tooltipX = vm.x - tooltipWidth / 2,
- tooltipY = vm.y - tooltipHeight;
-
- ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString();
-
- // Custom Tooltips
- if (this._custom) {
- this._custom(this._view);
- } else {
- switch (vm.yAlign) {
- case "above":
- //Draw a caret above the x/y
- ctx.beginPath();
- ctx.moveTo(vm.x, vm.y - caretPadding);
- ctx.lineTo(vm.x + vm.caretHeight, vm.y - (caretPadding + vm.caretHeight));
- ctx.lineTo(vm.x - vm.caretHeight, vm.y - (caretPadding + vm.caretHeight));
- ctx.closePath();
- ctx.fill();
- break;
- case "below":
- tooltipY = vm.y + caretPadding + vm.caretHeight;
- //Draw a caret below the x/y
- ctx.beginPath();
- ctx.moveTo(vm.x, vm.y + caretPadding);
- ctx.lineTo(vm.x + vm.caretHeight, vm.y + caretPadding + vm.caretHeight);
- ctx.lineTo(vm.x - vm.caretHeight, vm.y + caretPadding + vm.caretHeight);
- ctx.closePath();
- ctx.fill();
- break;
- }
-
- switch (vm.xAlign) {
- case "left":
- tooltipX = vm.x - tooltipWidth + (vm.cornerRadius + vm.caretHeight);
- break;
- case "right":
- tooltipX = vm.x - (vm.cornerRadius + vm.caretHeight);
- break;
- }
-
- helpers.drawRoundedRectangle(ctx, tooltipX, tooltipY, tooltipWidth, tooltipRectHeight, vm.cornerRadius);
-
- ctx.fill();
-
- ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString();
- ctx.textAlign = "center";
- ctx.textBaseline = "middle";
- ctx.fillText(vm.text, tooltipX + tooltipWidth / 2, tooltipY + tooltipRectHeight / 2);
-
- }
- break;
- case 'label':
-
- helpers.drawRoundedRectangle(ctx, vm.x, vm.y - vm.height / 2, vm.width, vm.height, vm.cornerRadius);
- ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString();
- ctx.fill();
- ctx.closePath();
-
- ctx.textAlign = "left";
- ctx.textBaseline = "middle";
- ctx.fillStyle = helpers.color(vm.titleTextColor).alpha(vm.opacity).rgbString();
- ctx.font = helpers.fontString(vm.fontSize, vm._titleFontStyle, vm._titleFontFamily);
- ctx.fillText(vm.title, vm.x + vm.xPadding, this.getLineHeight(0));
-
- ctx.font = helpers.fontString(vm.fontSize, vm._fontStyle, vm._fontFamily);
- helpers.each(vm.labels, function(label, index) {
- ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString();
- ctx.fillText(label, vm.x + vm.xPadding + vm.fontSize + 3, this.getLineHeight(index + 1));
-
- //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
- //ctx.clearRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize/2, vm.fontSize, vm.fontSize);
- //Instead we'll make a white filled block to put the legendColour palette over.
-
- ctx.fillStyle = helpers.color(vm.legendColors[index].stroke).alpha(vm.opacity).rgbString();
- ctx.fillRect(vm.x + vm.xPadding - 1, this.getLineHeight(index + 1) - vm.fontSize / 2 - 1, vm.fontSize + 2, vm.fontSize + 2);
-
- ctx.fillStyle = helpers.color(vm.legendColors[index].fill).alpha(vm.opacity).rgbString();
- ctx.fillRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize / 2, vm.fontSize, vm.fontSize);
-
-
- }, this);
- break;
- }
- },
- getLineHeight: function(index) {
- var baseLineHeight = this._view.y - (this._view.height / 2) + this._view.yPadding,
- afterTitleIndex = index - 1;
-
- //If the index is zero, we're getting the title
- if (index === 0) {
- return baseLineHeight + this._view.titleFontSize / 2;
- } else {
- return baseLineHeight + ((this._view.fontSize * 1.5 * afterTitleIndex) + this._view.fontSize / 2) + this._view.titleFontSize * 1.5;
- }
-
- },
- });
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ helpers = Chart.helpers;
+
+ Chart.defaults.global.tooltips = {
+ enabled: true,
+ custom: null,
+ backgroundColor: "rgba(0,0,0,0.8)",
+ fontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+ fontSize: 10,
+ fontStyle: "normal",
+ fontColor: "#fff",
+ titleFontFamily: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif",
+ titleFontSize: 12,
+ titleFontStyle: "bold",
+ titleFontColor: "#fff",
+ yPadding: 6,
+ xPadding: 6,
+ caretSize: 8,
+ cornerRadius: 6,
+ xOffset: 10,
+ template: [
+ '<% if(label){ %>',
+ '<%=label %>: ',
+ '<% } %>',
+ '<%=value %>',
+ ].join(''),
+ multiTemplate: [
+ '<%if (datasetLabel){ %>',
+ '<%=datasetLabel %>: ',
+ '<% } %>',
+ '<%=value %>'
+ ].join(''),
+ multiKeyBackground: '#fff',
+ };
+
+ Chart.Tooltip = Chart.Element.extend({
+ initialize: function() {
+ var options = this._options;
+ helpers.extend(this, {
+ _model: {
+ // Positioning
+ xPadding: options.tooltips.xPadding,
+ yPadding: options.tooltips.yPadding,
+ xOffset: options.tooltips.xOffset,
+
+ // Labels
+ textColor: options.tooltips.fontColor,
+ _fontFamily: options.tooltips.fontFamily,
+ _fontStyle: options.tooltips.fontStyle,
+ fontSize: options.tooltips.fontSize,
+
+ // Title
+ titleTextColor: options.tooltips.titleFontColor,
+ _titleFontFamily: options.tooltips.titleFontFamily,
+ _titleFontStyle: options.tooltips.titleFontStyle,
+ titleFontSize: options.tooltips.titleFontSize,
+
+ // Appearance
+ caretHeight: options.tooltips.caretSize,
+ cornerRadius: options.tooltips.cornerRadius,
+ backgroundColor: options.tooltips.backgroundColor,
+ opacity: 0,
+ legendColorBackground: options.tooltips.multiKeyBackground,
+ },
+ });
+ },
+ update: function() {
+
+ var ctx = this._chart.ctx;
+
+ switch (this._options.hover.mode) {
+ case 'single':
+ helpers.extend(this._model, {
+ text: helpers.template(this._options.tooltips.template, {
+ // These variables are available in the template function. Add others here
+ element: this._active[0],
+ value: this._data.datasets[this._active[0]._datasetIndex].data[this._active[0]._index],
+ label: this._data.labels ? this._data.labels[this._active[0]._index] : '',
+ }),
+ });
+
+ var tooltipPosition = this._active[0].tooltipPosition();
+ helpers.extend(this._model, {
+ x: Math.round(tooltipPosition.x),
+ y: Math.round(tooltipPosition.y),
+ caretPadding: tooltipPosition.padding
+ });
+
+ break;
+
+ case 'label':
+
+ // Tooltip Content
+
+ var dataArray,
+ dataIndex;
+
+ var labels = [],
+ colors = [];
+
+ for (var i = this._data.datasets.length - 1; i >= 0; i--) {
+ dataArray = this._data.datasets[i].metaData;
+ dataIndex = helpers.indexOf(dataArray, this._active[0]);
+ if (dataIndex !== -1) {
+ break;
+ }
+ }
+
+ var medianPosition = (function(index) {
+ // Get all the points at that particular index
+ var elements = [],
+ dataCollection,
+ xPositions = [],
+ yPositions = [],
+ xMax,
+ yMax,
+ xMin,
+ yMin;
+ helpers.each(this._data.datasets, function(dataset) {
+ dataCollection = dataset.metaData;
+ if (dataCollection[dataIndex] && dataCollection[dataIndex].hasValue()) {
+ elements.push(dataCollection[dataIndex]);
+ }
+ }, this);
+
+ // Reverse labels if stacked
+ helpers.each(this._options.stacked ? elements.reverse() : elements, function(element) {
+ xPositions.push(element._view.x);
+ yPositions.push(element._view.y);
+
+ //Include any colour information about the element
+ labels.push(helpers.template(this._options.tooltips.multiTemplate, {
+ // These variables are available in the template function. Add others here
+ element: element,
+ datasetLabel: this._data.datasets[element._datasetIndex].label,
+ value: this._data.datasets[element._datasetIndex].data[element._index],
+ }));
+ colors.push({
+ fill: element._view.backgroundColor,
+ stroke: element._view.borderColor
+ });
+
+ }, this);
+
+ yMin = helpers.min(yPositions);
+ yMax = helpers.max(yPositions);
+
+ xMin = helpers.min(xPositions);
+ xMax = helpers.max(xPositions);
+
+ return {
+ x: (xMin > this._chart.width / 2) ? xMin : xMax,
+ y: (yMin + yMax) / 2,
+ };
+ }).call(this, dataIndex);
+
+ // Apply for now
+ helpers.extend(this._model, {
+ x: medianPosition.x,
+ y: medianPosition.y,
+ labels: labels,
+ title: this._data.labels && this._data.labels.length ? this._data.labels[this._active[0]._index] : '',
+ legendColors: colors,
+ legendBackgroundColor: this._options.tooltips.multiKeyBackground,
+ });
+
+
+ // Calculate Appearance Tweaks
+
+ this._model.height = (labels.length * this._model.fontSize) + ((labels.length - 1) * (this._model.fontSize / 2)) + (this._model.yPadding * 2) + this._model.titleFontSize * 1.5;
+
+ var titleWidth = ctx.measureText(this.title).width,
+ //Label has a legend square as well so account for this.
+ labelWidth = helpers.longestText(ctx, this.font, labels) + this._model.fontSize + 3,
+ longestTextWidth = helpers.max([labelWidth, titleWidth]);
+
+ this._model.width = longestTextWidth + (this._model.xPadding * 2);
+
+
+ var halfHeight = this._model.height / 2;
+
+ //Check to ensure the height will fit on the canvas
+ if (this._model.y - halfHeight < 0) {
+ this._model.y = halfHeight;
+ } else if (this._model.y + halfHeight > this._chart.height) {
+ this._model.y = this._chart.height - halfHeight;
+ }
+
+ //Decide whether to align left or right based on position on canvas
+ if (this._model.x > this._chart.width / 2) {
+ this._model.x -= this._model.xOffset + this._model.width;
+ } else {
+ this._model.x += this._model.xOffset;
+ }
+ break;
+ }
+
+ return this;
+ },
+ draw: function() {
+
+ var ctx = this._chart.ctx;
+ var vm = this._view;
+
+ switch (this._options.hover.mode) {
+ case 'single':
+
+ ctx.font = helpers.fontString(vm.fontSize, vm._fontStyle, vm._fontFamily);
+
+ vm.xAlign = "center";
+ vm.yAlign = "above";
+
+ //Distance between the actual element.y position and the start of the tooltip caret
+ var caretPadding = vm.caretPadding || 2;
+
+ var tooltipWidth = ctx.measureText(vm.text).width + 2 * vm.xPadding,
+ tooltipRectHeight = vm.fontSize + 2 * vm.yPadding,
+ tooltipHeight = tooltipRectHeight + vm.caretHeight + caretPadding;
+
+ if (vm.x + tooltipWidth / 2 > this._chart.width) {
+ vm.xAlign = "left";
+ } else if (vm.x - tooltipWidth / 2 < 0) {
+ vm.xAlign = "right";
+ }
+
+ if (vm.y - tooltipHeight < 0) {
+ vm.yAlign = "below";
+ }
+
+ var tooltipX = vm.x - tooltipWidth / 2,
+ tooltipY = vm.y - tooltipHeight;
+
+ ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString();
+
+ // Custom Tooltips
+ if (this._custom) {
+ this._custom(this._view);
+ } else {
+ switch (vm.yAlign) {
+ case "above":
+ //Draw a caret above the x/y
+ ctx.beginPath();
+ ctx.moveTo(vm.x, vm.y - caretPadding);
+ ctx.lineTo(vm.x + vm.caretHeight, vm.y - (caretPadding + vm.caretHeight));
+ ctx.lineTo(vm.x - vm.caretHeight, vm.y - (caretPadding + vm.caretHeight));
+ ctx.closePath();
+ ctx.fill();
+ break;
+ case "below":
+ tooltipY = vm.y + caretPadding + vm.caretHeight;
+ //Draw a caret below the x/y
+ ctx.beginPath();
+ ctx.moveTo(vm.x, vm.y + caretPadding);
+ ctx.lineTo(vm.x + vm.caretHeight, vm.y + caretPadding + vm.caretHeight);
+ ctx.lineTo(vm.x - vm.caretHeight, vm.y + caretPadding + vm.caretHeight);
+ ctx.closePath();
+ ctx.fill();
+ break;
+ }
+
+ switch (vm.xAlign) {
+ case "left":
+ tooltipX = vm.x - tooltipWidth + (vm.cornerRadius + vm.caretHeight);
+ break;
+ case "right":
+ tooltipX = vm.x - (vm.cornerRadius + vm.caretHeight);
+ break;
+ }
+
+ helpers.drawRoundedRectangle(ctx, tooltipX, tooltipY, tooltipWidth, tooltipRectHeight, vm.cornerRadius);
+
+ ctx.fill();
+
+ ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString();
+ ctx.textAlign = "center";
+ ctx.textBaseline = "middle";
+ ctx.fillText(vm.text, tooltipX + tooltipWidth / 2, tooltipY + tooltipRectHeight / 2);
+
+ }
+ break;
+ case 'label':
+
+ helpers.drawRoundedRectangle(ctx, vm.x, vm.y - vm.height / 2, vm.width, vm.height, vm.cornerRadius);
+ ctx.fillStyle = helpers.color(vm.backgroundColor).alpha(vm.opacity).rgbString();
+ ctx.fill();
+ ctx.closePath();
+
+ ctx.textAlign = "left";
+ ctx.textBaseline = "middle";
+ ctx.fillStyle = helpers.color(vm.titleTextColor).alpha(vm.opacity).rgbString();
+ ctx.font = helpers.fontString(vm.fontSize, vm._titleFontStyle, vm._titleFontFamily);
+ ctx.fillText(vm.title, vm.x + vm.xPadding, this.getLineHeight(0));
+
+ ctx.font = helpers.fontString(vm.fontSize, vm._fontStyle, vm._fontFamily);
+ helpers.each(vm.labels, function(label, index) {
+ ctx.fillStyle = helpers.color(vm.textColor).alpha(vm.opacity).rgbString();
+ ctx.fillText(label, vm.x + vm.xPadding + vm.fontSize + 3, this.getLineHeight(index + 1));
+
+ //A bit gnarly, but clearing this rectangle breaks when using explorercanvas (clears whole canvas)
+ //ctx.clearRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize/2, vm.fontSize, vm.fontSize);
+ //Instead we'll make a white filled block to put the legendColour palette over.
+
+ ctx.fillStyle = helpers.color(vm.legendColors[index].stroke).alpha(vm.opacity).rgbString();
+ ctx.fillRect(vm.x + vm.xPadding - 1, this.getLineHeight(index + 1) - vm.fontSize / 2 - 1, vm.fontSize + 2, vm.fontSize + 2);
+
+ ctx.fillStyle = helpers.color(vm.legendColors[index].fill).alpha(vm.opacity).rgbString();
+ ctx.fillRect(vm.x + vm.xPadding, this.getLineHeight(index + 1) - vm.fontSize / 2, vm.fontSize, vm.fontSize);
+
+
+ }, this);
+ break;
+ }
+ },
+ getLineHeight: function(index) {
+ var baseLineHeight = this._view.y - (this._view.height / 2) + this._view.yPadding,
+ afterTitleIndex = index - 1;
+
+ //If the index is zero, we're getting the title
+ if (index === 0) {
+ return baseLineHeight + this._view.titleFontSize / 2;
+ } else {
+ return baseLineHeight + ((this._view.fontSize * 1.5 * afterTitleIndex) + this._view.fontSize / 2) + this._view.titleFontSize * 1.5;
+ }
+
+ },
+ });
}).call(this);
(function() {
- "use strict";
-
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
-
- Chart.defaults.global.elements.arc = {
- backgroundColor: Chart.defaults.global.defaultColor,
- borderColor: "#fff",
- borderWidth: 2
- };
-
- Chart.Arc = Chart.Element.extend({
- inGroupRange: function(mouseX) {
- var vm = this._view;
-
- if (vm) {
- return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
- } else {
- return false;
- }
- },
- inRange: function(chartX, chartY) {
-
- var vm = this._view;
-
- var pointRelativePosition = helpers.getAngleFromPoint(vm, {
- x: chartX,
- y: chartY
- });
-
- // Put into the range of (-PI/2, 3PI/2]
- var startAngle = vm.startAngle < (-0.5 * Math.PI) ? vm.startAngle + (2.0 * Math.PI) : vm.startAngle > (1.5 * Math.PI) ? vm.startAngle - (2.0 * Math.PI) : vm.startAngle;
- var endAngle = vm.endAngle < (-0.5 * Math.PI) ? vm.endAngle + (2.0 * Math.PI) : vm.endAngle > (1.5 * Math.PI) ? vm.endAngle - (2.0 * Math.PI) : vm.endAngle
-
- //Check if within the range of the open/close angle
- var betweenAngles = (pointRelativePosition.angle >= startAngle && pointRelativePosition.angle <= endAngle),
- withinRadius = (pointRelativePosition.distance >= vm.innerRadius && pointRelativePosition.distance <= vm.outerRadius);
-
- return (betweenAngles && withinRadius);
- //Ensure within the outside of the arc centre, but inside arc outer
- },
- tooltipPosition: function() {
- var vm = this._view;
-
- var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2),
- rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
- return {
- x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
- y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
- };
- },
- draw: function() {
-
- var ctx = this._chart.ctx;
- var vm = this._view;
-
- ctx.beginPath();
-
- ctx.arc(vm.x, vm.y, vm.outerRadius, vm.startAngle, vm.endAngle);
-
- ctx.arc(vm.x, vm.y, vm.innerRadius, vm.endAngle, vm.startAngle, true);
-
- ctx.closePath();
- ctx.strokeStyle = vm.borderColor;
- ctx.lineWidth = vm.borderWidth;
-
- ctx.fillStyle = vm.backgroundColor;
-
- ctx.fill();
- ctx.lineJoin = 'bevel';
-
- if (vm.borderWidth) {
- ctx.stroke();
- }
- }
- });
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ helpers = Chart.helpers;
+
+ Chart.defaults.global.elements.arc = {
+ backgroundColor: Chart.defaults.global.defaultColor,
+ borderColor: "#fff",
+ borderWidth: 2
+ };
+
+ Chart.Arc = Chart.Element.extend({
+ inGroupRange: function(mouseX) {
+ var vm = this._view;
+
+ if (vm) {
+ return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hoverRadius, 2));
+ } else {
+ return false;
+ }
+ },
+ inRange: function(chartX, chartY) {
+
+ var vm = this._view;
+
+ var pointRelativePosition = helpers.getAngleFromPoint(vm, {
+ x: chartX,
+ y: chartY
+ });
+
+ // Put into the range of (-PI/2, 3PI/2]
+ var startAngle = vm.startAngle < (-0.5 * Math.PI) ? vm.startAngle + (2.0 * Math.PI) : vm.startAngle > (1.5 * Math.PI) ? vm.startAngle - (2.0 * Math.PI) : vm.startAngle;
+ var endAngle = vm.endAngle < (-0.5 * Math.PI) ? vm.endAngle + (2.0 * Math.PI) : vm.endAngle > (1.5 * Math.PI) ? vm.endAngle - (2.0 * Math.PI) : vm.endAngle
+
+ //Check if within the range of the open/close angle
+ var betweenAngles = (pointRelativePosition.angle >= startAngle && pointRelativePosition.angle <= endAngle),
+ withinRadius = (pointRelativePosition.distance >= vm.innerRadius && pointRelativePosition.distance <= vm.outerRadius);
+
+ return (betweenAngles && withinRadius);
+ //Ensure within the outside of the arc centre, but inside arc outer
+ },
+ tooltipPosition: function() {
+ var vm = this._view;
+
+ var centreAngle = vm.startAngle + ((vm.endAngle - vm.startAngle) / 2),
+ rangeFromCentre = (vm.outerRadius - vm.innerRadius) / 2 + vm.innerRadius;
+ return {
+ x: vm.x + (Math.cos(centreAngle) * rangeFromCentre),
+ y: vm.y + (Math.sin(centreAngle) * rangeFromCentre)
+ };
+ },
+ draw: function() {
+
+ var ctx = this._chart.ctx;
+ var vm = this._view;
+
+ ctx.beginPath();
+
+ ctx.arc(vm.x, vm.y, vm.outerRadius, vm.startAngle, vm.endAngle);
+
+ ctx.arc(vm.x, vm.y, vm.innerRadius, vm.endAngle, vm.startAngle, true);
+
+ ctx.closePath();
+ ctx.strokeStyle = vm.borderColor;
+ ctx.lineWidth = vm.borderWidth;
+
+ ctx.fillStyle = vm.backgroundColor;
+
+ ctx.fill();
+ ctx.lineJoin = 'bevel';
+
+ if (vm.borderWidth) {
+ ctx.stroke();
+ }
+ }
+ });
}).call(this);
(function() {
- "use strict";
-
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
-
- Chart.defaults.global.elements.line = {
- tension: 0.4,
- backgroundColor: Chart.defaults.global.defaultColor,
- borderWidth: 3,
- borderColor: Chart.defaults.global.defaultColor,
- fill: true, // do we fill in the area between the line and its base axis
- skipNull: true,
- drawNull: false,
- };
-
-
- Chart.Line = Chart.Element.extend({
- draw: function() {
-
- 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);
-
- // 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);
- }
-
- 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) {
-
- 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();
- }
-
-
- // 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);
-
- // 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;
- }
-
- if (previous._view.skip && vm.skipNull) {
- ctx.moveTo(point._view.x, point._view.y);
- return;
- }
- // Normal Bezier Curve
- if (vm.tension > 0) {
- ctx.bezierCurveTo(
- previous._view.controlPointNextX,
- previous._view.controlPointNextY,
- point._view.controlPointPreviousX,
- point._view.controlPointPreviousY,
- point._view.x,
- point._view.y
- );
- } else {
- ctx.lineTo(point._view.x, point._view.y);
- }
- }, this);
-
- if (this._loop && !first._view.skip) {
- if (vm.tension > 0) {
-
- ctx.bezierCurveTo(
- last._view.controlPointNextX,
- last._view.controlPointNextY,
- first._view.controlPointPreviousX,
- first._view.controlPointPreviousY,
- first._view.x,
- first._view.y
- );
- } else {
- ctx.lineTo(first._view.x, first._view.y);
- }
- }
-
-
- ctx.stroke();
-
- },
- nextPoint: function(point, collection, index) {
- if (this.loop) {
- return collection[index + 1] || collection[0];
- }
- return collection[index + 1] || collection[collection.length - 1];
- },
- previousPoint: function(point, collection, index) {
- if (this.loop) {
- return collection[index - 1] || collection[collection.length - 1];
- }
- return collection[index - 1] || collection[0];
- },
- });
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ helpers = Chart.helpers;
+
+ Chart.defaults.global.elements.line = {
+ tension: 0.4,
+ backgroundColor: Chart.defaults.global.defaultColor,
+ borderWidth: 3,
+ borderColor: Chart.defaults.global.defaultColor,
+ fill: true, // do we fill in the area between the line and its base axis
+ skipNull: true,
+ drawNull: false,
+ };
+
+
+ Chart.Line = Chart.Element.extend({
+ draw: function() {
+
+ 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);
+
+ // 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);
+ }
+
+ 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) {
+
+ 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();
+ }
+
+
+ // 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);
+
+ // 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;
+ }
+
+ if (previous._view.skip && vm.skipNull) {
+ ctx.moveTo(point._view.x, point._view.y);
+ return;
+ }
+ // Normal Bezier Curve
+ if (vm.tension > 0) {
+ ctx.bezierCurveTo(
+ previous._view.controlPointNextX,
+ previous._view.controlPointNextY,
+ point._view.controlPointPreviousX,
+ point._view.controlPointPreviousY,
+ point._view.x,
+ point._view.y
+ );
+ } else {
+ ctx.lineTo(point._view.x, point._view.y);
+ }
+ }, this);
+
+ if (this._loop && !first._view.skip) {
+ if (vm.tension > 0) {
+
+ ctx.bezierCurveTo(
+ last._view.controlPointNextX,
+ last._view.controlPointNextY,
+ first._view.controlPointPreviousX,
+ first._view.controlPointPreviousY,
+ first._view.x,
+ first._view.y
+ );
+ } else {
+ ctx.lineTo(first._view.x, first._view.y);
+ }
+ }
+
+
+ ctx.stroke();
+
+ },
+ nextPoint: function(point, collection, index) {
+ if (this.loop) {
+ return collection[index + 1] || collection[0];
+ }
+ return collection[index + 1] || collection[collection.length - 1];
+ },
+ previousPoint: function(point, collection, index) {
+ if (this.loop) {
+ return collection[index - 1] || collection[collection.length - 1];
+ }
+ return collection[index - 1] || collection[0];
+ },
+ });
}).call(this);
(function() {
- "use strict";
-
- var root = this,
- Chart = root.Chart,
- helpers = Chart.helpers;
-
- Chart.defaults.global.elements.point = {
- radius: 3,
- backgroundColor: Chart.defaults.global.defaultColor,
- borderWidth: 1,
- borderColor: Chart.defaults.global.defaultColor,
- // Hover
- hitRadius: 1,
- hoverRadius: 4,
- hoverBorderWidth: 1,
- };
-
-
- Chart.Point = Chart.Element.extend({
- inRange: function(mouseX, mouseY) {
- var vm = this._view;
- var hoverRange = vm.hitRadius + vm.radius;
- return ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(hoverRange, 2));
- },
- inGroupRange: function(mouseX) {
- var vm = this._view;
-
- if (vm) {
- return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2));
- } else {
- return false;
- }
- },
- tooltipPosition: function() {
- var vm = this._view;
- return {
- x: vm.x,
- y: vm.y,
- padding: vm.radius + vm.borderWidth
- };
- },
- draw: function() {
-
- var vm = this._view;
- var ctx = this._chart.ctx;
-
-
- if (vm.skip) {
- return;
- }
-
- if (vm.radius > 0 || vm.borderWidth > 0) {
-
- ctx.beginPath();
-
- ctx.arc(vm.x, vm.y, vm.radius || Chart.defaults.global.elements.point.radius, 0, Math.PI * 2);
- ctx.closePath();
-
- ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor;
- ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.point.borderWidth;
-
- ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor;
-
- ctx.fill();
- ctx.stroke();
- }
- }
- });
+ "use strict";
+
+ var root = this,
+ Chart = root.Chart,
+ helpers = Chart.helpers;
+
+ Chart.defaults.global.elements.point = {
+ radius: 3,
+ backgroundColor: Chart.defaults.global.defaultColor,
+ borderWidth: 1,
+ borderColor: Chart.defaults.global.defaultColor,
+ // Hover
+ hitRadius: 1,
+ hoverRadius: 4,
+ hoverBorderWidth: 1,
+ };
+
+
+ Chart.Point = Chart.Element.extend({
+ inRange: function(mouseX, mouseY) {
+ var vm = this._view;
+ var hoverRange = vm.hitRadius + vm.radius;
+ return ((Math.pow(mouseX - vm.x, 2) + Math.pow(mouseY - vm.y, 2)) < Math.pow(hoverRange, 2));
+ },
+ inGroupRange: function(mouseX) {
+ var vm = this._view;
+
+ if (vm) {
+ return (Math.pow(mouseX - vm.x, 2) < Math.pow(vm.radius + vm.hitRadius, 2));
+ } else {
+ return false;
+ }
+ },
+ tooltipPosition: function() {
+ var vm = this._view;
+ return {
+ x: vm.x,
+ y: vm.y,
+ padding: vm.radius + vm.borderWidth
+ };
+ },
+ draw: function() {
+
+ var vm = this._view;
+ var ctx = this._chart.ctx;
+
+
+ if (vm.skip) {
+ return;
+ }
+
+ if (vm.radius > 0 || vm.borderWidth > 0) {
+
+ ctx.beginPath();
+
+ ctx.arc(vm.x, vm.y, vm.radius || Chart.defaults.global.elements.point.radius, 0, Math.PI * 2);
+ ctx.closePath();
+
+ ctx.strokeStyle = vm.borderColor || Chart.defaults.global.defaultColor;
+ ctx.lineWidth = vm.borderWidth || Chart.defaults.global.elements.point.borderWidth;
+
+ ctx.fillStyle = vm.backgroundColor || Chart.defaults.global.defaultColor;
+
+ ctx.fill();
+ ctx.stroke();
+ }
+ }
+ });
}).call(this);
(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);