var valueOrDefault = helpers.valueOrDefault;
-module.exports = function(Chart) {
-
- // Create a dictionary of chart types, to allow for extension of existing types
- Chart.types = {};
+defaults._set('global', {
+ elements: {},
+ events: [
+ 'mousemove',
+ 'mouseout',
+ 'click',
+ 'touchstart',
+ 'touchmove'
+ ],
+ hover: {
+ onHover: null,
+ mode: 'nearest',
+ intersect: true,
+ animationDuration: 400
+ },
+ onClick: null,
+ maintainAspectRatio: true,
+ responsive: true,
+ responsiveAnimationDuration: 0
+});
+
+function initConfig(config) {
+ config = config || {};
+
+ // Do NOT use configMerge() for the data object because this method merges arrays
+ // and so would change references to labels and datasets, preventing data updates.
+ var data = config.data = config.data || {};
+ data.datasets = data.datasets || [];
+ data.labels = data.labels || [];
+
+ config.options = helpers.configMerge(
+ defaults.global,
+ defaults[config.type],
+ config.options || {});
+
+ return config;
+}
+
+function updateConfig(chart) {
+ var newOptions = chart.options;
+
+ helpers.each(chart.scales, function(scale) {
+ layouts.removeBox(chart, scale);
+ });
- // 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 = {};
+ newOptions = helpers.configMerge(
+ defaults.global,
+ defaults[chart.config.type],
+ newOptions);
- /**
- * Initializes the given config with global and chart default values.
- */
- function initConfig(config) {
- config = config || {};
+ chart.options = chart.config.options = newOptions;
+ chart.ensureScalesHaveIDs();
+ chart.buildOrUpdateScales();
- // Do NOT use configMerge() for the data object because this method merges arrays
- // and so would change references to labels and datasets, preventing data updates.
- var data = config.data = config.data || {};
- data.datasets = data.datasets || [];
- data.labels = data.labels || [];
+ // Tooltip
+ chart.tooltip._options = newOptions.tooltips;
+ chart.tooltip.initialize();
+}
- config.options = helpers.configMerge(
- defaults.global,
- defaults[config.type],
- config.options || {});
+function positionIsHorizontal(position) {
+ return position === 'top' || position === 'bottom';
+}
- return config;
- }
+var Chart = function(item, config) {
+ this.construct(item, config);
+ return this;
+};
+helpers.extend(Chart.prototype, /** @lends Chart */ {
/**
- * Updates the config of the chart
- * @param chart {Chart} chart to update the options for
+ * @private
*/
- function updateConfig(chart) {
- var newOptions = chart.options;
-
- helpers.each(chart.scales, function(scale) {
- layouts.removeBox(chart, scale);
- });
-
- newOptions = helpers.configMerge(
- Chart.defaults.global,
- Chart.defaults[chart.config.type],
- newOptions);
-
- chart.options = chart.config.options = newOptions;
- chart.ensureScalesHaveIDs();
- chart.buildOrUpdateScales();
- // Tooltip
- chart.tooltip._options = newOptions.tooltips;
- chart.tooltip.initialize();
- }
-
- function positionIsHorizontal(position) {
- return position === 'top' || position === 'bottom';
- }
+ construct: function(item, config) {
+ var me = this;
+
+ config = initConfig(config);
+
+ var context = platform.acquireContext(item, config);
+ var canvas = context && context.canvas;
+ var height = canvas && canvas.height;
+ var width = canvas && canvas.width;
+
+ me.id = helpers.uid();
+ me.ctx = context;
+ me.canvas = canvas;
+ me.config = config;
+ me.width = width;
+ me.height = height;
+ me.aspectRatio = height ? width / height : null;
+ me.options = config.options;
+ me._bufferedRender = false;
- helpers.extend(Chart.prototype, /** @lends Chart */ {
/**
+ * Provided for backward compatibility, Chart and Chart.Controller have been merged,
+ * the "instance" still need to be defined since it might be called from plugins.
+ * @prop Chart#chart
+ * @deprecated since version 2.6.0
+ * @todo remove at version 3
* @private
*/
- construct: function(item, config) {
- var me = this;
-
- config = initConfig(config);
-
- var context = platform.acquireContext(item, config);
- var canvas = context && context.canvas;
- var height = canvas && canvas.height;
- var width = canvas && canvas.width;
-
- me.id = helpers.uid();
- me.ctx = context;
- me.canvas = canvas;
- me.config = config;
- me.width = width;
- me.height = height;
- me.aspectRatio = height ? width / height : null;
- me.options = config.options;
- me._bufferedRender = false;
-
- /**
- * Provided for backward compatibility, Chart and Chart.Controller have been merged,
- * the "instance" still need to be defined since it might be called from plugins.
- * @prop Chart#chart
- * @deprecated since version 2.6.0
- * @todo remove at version 3
- * @private
- */
- me.chart = me;
- me.controller = me; // chart.chart.controller #inception
-
- // Add the chart instance to the global namespace
- Chart.instances[me.id] = me;
-
- // Define alias to the config data: `chart.data === chart.config.data`
- Object.defineProperty(me, 'data', {
- get: function() {
- return me.config.data;
- },
- set: function(value) {
- me.config.data = value;
- }
- });
+ me.chart = me;
+ me.controller = me; // chart.chart.controller #inception
- if (!context || !canvas) {
- // The given item is not a compatible context2d element, let's return before finalizing
- // the chart initialization but after setting basic chart / controller properties that
- // can help to figure out that the chart is not valid (e.g chart.canvas !== null);
- // https://github.com/chartjs/Chart.js/issues/2807
- console.error("Failed to create chart: can't acquire context from the given item");
- return;
+ // Add the chart instance to the global namespace
+ Chart.instances[me.id] = me;
+
+ // Define alias to the config data: `chart.data === chart.config.data`
+ Object.defineProperty(me, 'data', {
+ get: function() {
+ return me.config.data;
+ },
+ set: function(value) {
+ me.config.data = value;
}
+ });
- me.initialize();
- me.update();
- },
+ if (!context || !canvas) {
+ // The given item is not a compatible context2d element, let's return before finalizing
+ // the chart initialization but after setting basic chart / controller properties that
+ // can help to figure out that the chart is not valid (e.g chart.canvas !== null);
+ // https://github.com/chartjs/Chart.js/issues/2807
+ console.error("Failed to create chart: can't acquire context from the given item");
+ return;
+ }
- /**
- * @private
- */
- initialize: function() {
- var me = this;
+ me.initialize();
+ me.update();
+ },
- // Before init plugin notification
- plugins.notify(me, 'beforeInit');
+ /**
+ * @private
+ */
+ initialize: function() {
+ var me = this;
- helpers.retinaScale(me, me.options.devicePixelRatio);
+ // Before init plugin notification
+ plugins.notify(me, 'beforeInit');
- me.bindEvents();
+ helpers.retinaScale(me, me.options.devicePixelRatio);
- if (me.options.responsive) {
- // Initial resize before chart draws (must be silent to preserve initial animations).
- me.resize(true);
- }
+ me.bindEvents();
- // Make sure scales have IDs and are built before we build any controllers.
- me.ensureScalesHaveIDs();
- me.buildOrUpdateScales();
- me.initToolTip();
+ if (me.options.responsive) {
+ // Initial resize before chart draws (must be silent to preserve initial animations).
+ me.resize(true);
+ }
- // After init plugin notification
- plugins.notify(me, 'afterInit');
+ // Make sure scales have IDs and are built before we build any controllers.
+ me.ensureScalesHaveIDs();
+ me.buildOrUpdateScales();
+ me.initToolTip();
- return me;
- },
+ // After init plugin notification
+ plugins.notify(me, 'afterInit');
- clear: function() {
- helpers.canvas.clear(this);
- return this;
- },
+ return me;
+ },
- stop: function() {
- // Stops any current animation loop occurring
- animations.cancelAnimation(this);
- return this;
- },
+ clear: function() {
+ helpers.canvas.clear(this);
+ return this;
+ },
- resize: function(silent) {
- var me = this;
- var options = me.options;
- var canvas = me.canvas;
- var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null;
+ stop: function() {
+ // Stops any current animation loop occurring
+ animations.cancelAnimation(this);
+ return this;
+ },
- // the canvas render width and height will be casted to integers so make sure that
- // the canvas display style uses the same integer values to avoid blurring effect.
+ resize: function(silent) {
+ var me = this;
+ var options = me.options;
+ var canvas = me.canvas;
+ var aspectRatio = (options.maintainAspectRatio && me.aspectRatio) || null;
- // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collapsed
- var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas)));
- var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas)));
+ // the canvas render width and height will be casted to integers so make sure that
+ // the canvas display style uses the same integer values to avoid blurring effect.
- if (me.width === newWidth && me.height === newHeight) {
- return;
- }
+ // Set to 0 instead of canvas.size because the size defaults to 300x150 if the element is collapsed
+ var newWidth = Math.max(0, Math.floor(helpers.getMaximumWidth(canvas)));
+ var newHeight = Math.max(0, Math.floor(aspectRatio ? newWidth / aspectRatio : helpers.getMaximumHeight(canvas)));
- canvas.width = me.width = newWidth;
- canvas.height = me.height = newHeight;
- canvas.style.width = newWidth + 'px';
- canvas.style.height = newHeight + 'px';
+ if (me.width === newWidth && me.height === newHeight) {
+ return;
+ }
- helpers.retinaScale(me, options.devicePixelRatio);
+ canvas.width = me.width = newWidth;
+ canvas.height = me.height = newHeight;
+ canvas.style.width = newWidth + 'px';
+ canvas.style.height = newHeight + 'px';
- if (!silent) {
- // Notify any plugins about the resize
- var newSize = {width: newWidth, height: newHeight};
- plugins.notify(me, 'resize', [newSize]);
+ helpers.retinaScale(me, options.devicePixelRatio);
- // Notify of resize
- if (me.options.onResize) {
- me.options.onResize(me, newSize);
- }
+ if (!silent) {
+ // Notify any plugins about the resize
+ var newSize = {width: newWidth, height: newHeight};
+ plugins.notify(me, 'resize', [newSize]);
- me.stop();
- me.update({
- duration: me.options.responsiveAnimationDuration
- });
+ // Notify of resize
+ if (me.options.onResize) {
+ me.options.onResize(me, newSize);
}
- },
-
- ensureScalesHaveIDs: function() {
- var options = this.options;
- var scalesOptions = options.scales || {};
- var scaleOptions = options.scale;
- helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) {
- xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index);
+ me.stop();
+ me.update({
+ duration: me.options.responsiveAnimationDuration
});
+ }
+ },
- helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) {
- yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index);
- });
+ ensureScalesHaveIDs: function() {
+ var options = this.options;
+ var scalesOptions = options.scales || {};
+ var scaleOptions = options.scale;
- if (scaleOptions) {
- scaleOptions.id = scaleOptions.id || 'scale';
- }
- },
+ helpers.each(scalesOptions.xAxes, function(xAxisOptions, index) {
+ xAxisOptions.id = xAxisOptions.id || ('x-axis-' + index);
+ });
- /**
- * Builds a map of scale ID to scale object for future lookup.
- */
- buildOrUpdateScales: function() {
- var me = this;
- var options = me.options;
- var scales = me.scales || {};
- var items = [];
- var updated = Object.keys(scales).reduce(function(obj, id) {
- obj[id] = false;
- return obj;
- }, {});
-
- if (options.scales) {
- items = items.concat(
- (options.scales.xAxes || []).map(function(xAxisOptions) {
- return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'};
- }),
- (options.scales.yAxes || []).map(function(yAxisOptions) {
- return {options: yAxisOptions, dtype: 'linear', dposition: 'left'};
- })
- );
- }
+ helpers.each(scalesOptions.yAxes, function(yAxisOptions, index) {
+ yAxisOptions.id = yAxisOptions.id || ('y-axis-' + index);
+ });
- if (options.scale) {
- items.push({
- options: options.scale,
- dtype: 'radialLinear',
- isDefault: true,
- dposition: 'chartArea'
- });
- }
+ if (scaleOptions) {
+ scaleOptions.id = scaleOptions.id || 'scale';
+ }
+ },
- helpers.each(items, function(item) {
- var scaleOptions = item.options;
- var id = scaleOptions.id;
- var scaleType = valueOrDefault(scaleOptions.type, item.dtype);
+ /**
+ * Builds a map of scale ID to scale object for future lookup.
+ */
+ buildOrUpdateScales: function() {
+ var me = this;
+ var options = me.options;
+ var scales = me.scales || {};
+ var items = [];
+ var updated = Object.keys(scales).reduce(function(obj, id) {
+ obj[id] = false;
+ return obj;
+ }, {});
+
+ if (options.scales) {
+ items = items.concat(
+ (options.scales.xAxes || []).map(function(xAxisOptions) {
+ return {options: xAxisOptions, dtype: 'category', dposition: 'bottom'};
+ }),
+ (options.scales.yAxes || []).map(function(yAxisOptions) {
+ return {options: yAxisOptions, dtype: 'linear', dposition: 'left'};
+ })
+ );
+ }
- if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) {
- scaleOptions.position = item.dposition;
- }
+ if (options.scale) {
+ items.push({
+ options: options.scale,
+ dtype: 'radialLinear',
+ isDefault: true,
+ dposition: 'chartArea'
+ });
+ }
- updated[id] = true;
- var scale = null;
- if (id in scales && scales[id].type === scaleType) {
- scale = scales[id];
- scale.options = scaleOptions;
- scale.ctx = me.ctx;
- scale.chart = me;
- } else {
- var scaleClass = scaleService.getScaleConstructor(scaleType);
- if (!scaleClass) {
- return;
- }
- scale = new scaleClass({
- id: id,
- type: scaleType,
- options: scaleOptions,
- ctx: me.ctx,
- chart: me
- });
- scales[scale.id] = scale;
- }
+ helpers.each(items, function(item) {
+ var scaleOptions = item.options;
+ var id = scaleOptions.id;
+ var scaleType = valueOrDefault(scaleOptions.type, item.dtype);
- scale.mergeTicksOptions();
+ if (positionIsHorizontal(scaleOptions.position) !== positionIsHorizontal(item.dposition)) {
+ scaleOptions.position = item.dposition;
+ }
- // TODO(SB): I think we should be able to remove this custom case (options.scale)
- // and consider it as a regular scale part of the "scales"" map only! This would
- // make the logic easier and remove some useless? custom code.
- if (item.isDefault) {
- me.scale = scale;
- }
- });
- // clear up discarded scales
- helpers.each(updated, function(hasUpdated, id) {
- if (!hasUpdated) {
- delete scales[id];
+ updated[id] = true;
+ var scale = null;
+ if (id in scales && scales[id].type === scaleType) {
+ scale = scales[id];
+ scale.options = scaleOptions;
+ scale.ctx = me.ctx;
+ scale.chart = me;
+ } else {
+ var scaleClass = scaleService.getScaleConstructor(scaleType);
+ if (!scaleClass) {
+ return;
}
- });
+ scale = new scaleClass({
+ id: id,
+ type: scaleType,
+ options: scaleOptions,
+ ctx: me.ctx,
+ chart: me
+ });
+ scales[scale.id] = scale;
+ }
- me.scales = scales;
+ scale.mergeTicksOptions();
- scaleService.addScalesToLayout(this);
- },
+ // TODO(SB): I think we should be able to remove this custom case (options.scale)
+ // and consider it as a regular scale part of the "scales"" map only! This would
+ // make the logic easier and remove some useless? custom code.
+ if (item.isDefault) {
+ me.scale = scale;
+ }
+ });
+ // clear up discarded scales
+ helpers.each(updated, function(hasUpdated, id) {
+ if (!hasUpdated) {
+ delete scales[id];
+ }
+ });
- buildOrUpdateControllers: function() {
- var me = this;
- var types = [];
- var newControllers = [];
+ me.scales = scales;
- helpers.each(me.data.datasets, function(dataset, datasetIndex) {
- var meta = me.getDatasetMeta(datasetIndex);
- var type = dataset.type || me.config.type;
+ scaleService.addScalesToLayout(this);
+ },
- if (meta.type && meta.type !== type) {
- me.destroyDatasetMeta(datasetIndex);
- meta = me.getDatasetMeta(datasetIndex);
- }
- meta.type = type;
+ buildOrUpdateControllers: function() {
+ var me = this;
+ var newControllers = [];
- types.push(meta.type);
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
+ var meta = me.getDatasetMeta(datasetIndex);
+ var type = dataset.type || me.config.type;
- if (meta.controller) {
- meta.controller.updateIndex(datasetIndex);
- meta.controller.linkScales();
- } else {
- var ControllerClass = controllers[meta.type];
- if (ControllerClass === undefined) {
- throw new Error('"' + meta.type + '" is not a chart type.');
- }
+ if (meta.type && meta.type !== type) {
+ me.destroyDatasetMeta(datasetIndex);
+ meta = me.getDatasetMeta(datasetIndex);
+ }
+ meta.type = type;
- meta.controller = new ControllerClass(me, datasetIndex);
- newControllers.push(meta.controller);
+ if (meta.controller) {
+ meta.controller.updateIndex(datasetIndex);
+ meta.controller.linkScales();
+ } else {
+ var ControllerClass = controllers[meta.type];
+ if (ControllerClass === undefined) {
+ throw new Error('"' + meta.type + '" is not a chart type.');
}
- }, me);
- return newControllers;
- },
+ meta.controller = new ControllerClass(me, datasetIndex);
+ newControllers.push(meta.controller);
+ }
+ }, me);
- /**
- * Reset the elements of all datasets
- * @private
- */
- resetElements: function() {
- var me = this;
- helpers.each(me.data.datasets, function(dataset, datasetIndex) {
- me.getDatasetMeta(datasetIndex).controller.reset();
- }, me);
- },
+ return newControllers;
+ },
- /**
- * Resets the chart back to it's state before the initial animation
- */
- reset: function() {
- this.resetElements();
- this.tooltip.initialize();
- },
-
- update: function(config) {
- var me = this;
-
- if (!config || typeof config !== 'object') {
- // backwards compatibility
- config = {
- duration: config,
- lazy: arguments[1]
- };
- }
+ /**
+ * Reset the elements of all datasets
+ * @private
+ */
+ resetElements: function() {
+ var me = this;
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
+ me.getDatasetMeta(datasetIndex).controller.reset();
+ }, me);
+ },
- updateConfig(me);
+ /**
+ * Resets the chart back to it's state before the initial animation
+ */
+ reset: function() {
+ this.resetElements();
+ this.tooltip.initialize();
+ },
+
+ update: function(config) {
+ var me = this;
+
+ if (!config || typeof config !== 'object') {
+ // backwards compatibility
+ config = {
+ duration: config,
+ lazy: arguments[1]
+ };
+ }
- // plugins options references might have change, let's invalidate the cache
- // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
- plugins._invalidate(me);
+ updateConfig(me);
- if (plugins.notify(me, 'beforeUpdate') === false) {
- return;
- }
+ // plugins options references might have change, let's invalidate the cache
+ // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
+ plugins._invalidate(me);
- // In case the entire data object changed
- me.tooltip._data = me.data;
+ if (plugins.notify(me, 'beforeUpdate') === false) {
+ return;
+ }
- // Make sure dataset controllers are updated and new controllers are reset
- var newControllers = me.buildOrUpdateControllers();
+ // In case the entire data object changed
+ me.tooltip._data = me.data;
- // Make sure all dataset controllers have correct meta data counts
- helpers.each(me.data.datasets, function(dataset, datasetIndex) {
- me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
- }, me);
+ // Make sure dataset controllers are updated and new controllers are reset
+ var newControllers = me.buildOrUpdateControllers();
- me.updateLayout();
+ // Make sure all dataset controllers have correct meta data counts
+ helpers.each(me.data.datasets, function(dataset, datasetIndex) {
+ me.getDatasetMeta(datasetIndex).controller.buildOrUpdateElements();
+ }, me);
- // Can only reset the new controllers after the scales have been updated
- if (me.options.animation && me.options.animation.duration) {
- helpers.each(newControllers, function(controller) {
- controller.reset();
- });
- }
+ me.updateLayout();
- me.updateDatasets();
+ // Can only reset the new controllers after the scales have been updated
+ if (me.options.animation && me.options.animation.duration) {
+ helpers.each(newControllers, function(controller) {
+ controller.reset();
+ });
+ }
- // Need to reset tooltip in case it is displayed with elements that are removed
- // after update.
- me.tooltip.initialize();
+ me.updateDatasets();
- // Last active contains items that were previously in the tooltip.
- // When we reset the tooltip, we need to clear it
- me.lastActive = [];
+ // Need to reset tooltip in case it is displayed with elements that are removed
+ // after update.
+ me.tooltip.initialize();
- // Do this before render so that any plugins that need final scale updates can use it
- plugins.notify(me, 'afterUpdate');
+ // Last active contains items that were previously in the tooltip.
+ // When we reset the tooltip, we need to clear it
+ me.lastActive = [];
- if (me._bufferedRender) {
- me._bufferedRequest = {
- duration: config.duration,
- easing: config.easing,
- lazy: config.lazy
- };
- } else {
- me.render(config);
- }
- },
+ // Do this before render so that any plugins that need final scale updates can use it
+ plugins.notify(me, 'afterUpdate');
- /**
- * Updates the chart layout unless a plugin returns `false` to the `beforeLayout`
- * hook, in which case, plugins will not be called on `afterLayout`.
- * @private
- */
- updateLayout: function() {
- var me = this;
+ if (me._bufferedRender) {
+ me._bufferedRequest = {
+ duration: config.duration,
+ easing: config.easing,
+ lazy: config.lazy
+ };
+ } else {
+ me.render(config);
+ }
+ },
- if (plugins.notify(me, 'beforeLayout') === false) {
- return;
- }
+ /**
+ * Updates the chart layout unless a plugin returns `false` to the `beforeLayout`
+ * hook, in which case, plugins will not be called on `afterLayout`.
+ * @private
+ */
+ updateLayout: function() {
+ var me = this;
- layouts.update(this, this.width, this.height);
+ if (plugins.notify(me, 'beforeLayout') === false) {
+ return;
+ }
- /**
- * Provided for backward compatibility, use `afterLayout` instead.
- * @method IPlugin#afterScaleUpdate
- * @deprecated since version 2.5.0
- * @todo remove at version 3
- * @private
- */
- plugins.notify(me, 'afterScaleUpdate');
- plugins.notify(me, 'afterLayout');
- },
+ layouts.update(this, this.width, this.height);
/**
- * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate`
- * hook, in which case, plugins will not be called on `afterDatasetsUpdate`.
+ * Provided for backward compatibility, use `afterLayout` instead.
+ * @method IPlugin#afterScaleUpdate
+ * @deprecated since version 2.5.0
+ * @todo remove at version 3
* @private
*/
- updateDatasets: function() {
- var me = this;
+ plugins.notify(me, 'afterScaleUpdate');
+ plugins.notify(me, 'afterLayout');
+ },
- if (plugins.notify(me, 'beforeDatasetsUpdate') === false) {
- return;
- }
+ /**
+ * Updates all datasets unless a plugin returns `false` to the `beforeDatasetsUpdate`
+ * hook, in which case, plugins will not be called on `afterDatasetsUpdate`.
+ * @private
+ */
+ updateDatasets: function() {
+ var me = this;
- for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
- me.updateDataset(i);
- }
+ if (plugins.notify(me, 'beforeDatasetsUpdate') === false) {
+ return;
+ }
- plugins.notify(me, 'afterDatasetsUpdate');
- },
+ for (var i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
+ me.updateDataset(i);
+ }
- /**
- * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate`
- * hook, in which case, plugins will not be called on `afterDatasetUpdate`.
- * @private
- */
- updateDataset: function(index) {
- var me = this;
- var meta = me.getDatasetMeta(index);
- var args = {
- meta: meta,
- index: index
- };
+ plugins.notify(me, 'afterDatasetsUpdate');
+ },
- if (plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) {
- return;
- }
+ /**
+ * Updates dataset at index unless a plugin returns `false` to the `beforeDatasetUpdate`
+ * hook, in which case, plugins will not be called on `afterDatasetUpdate`.
+ * @private
+ */
+ updateDataset: function(index) {
+ var me = this;
+ var meta = me.getDatasetMeta(index);
+ var args = {
+ meta: meta,
+ index: index
+ };
+
+ if (plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) {
+ return;
+ }
- meta.controller.update();
+ meta.controller.update();
- plugins.notify(me, 'afterDatasetUpdate', [args]);
- },
+ plugins.notify(me, 'afterDatasetUpdate', [args]);
+ },
- render: function(config) {
- var me = this;
+ render: function(config) {
+ var me = this;
- if (!config || typeof config !== 'object') {
- // backwards compatibility
- config = {
- duration: config,
- lazy: arguments[1]
- };
- }
+ if (!config || typeof config !== 'object') {
+ // backwards compatibility
+ config = {
+ duration: config,
+ lazy: arguments[1]
+ };
+ }
- var animationOptions = me.options.animation;
- var duration = valueOrDefault(config.duration, animationOptions && animationOptions.duration);
- var lazy = config.lazy;
+ var animationOptions = me.options.animation;
+ var duration = valueOrDefault(config.duration, animationOptions && animationOptions.duration);
+ var lazy = config.lazy;
- if (plugins.notify(me, 'beforeRender') === false) {
- return;
- }
+ if (plugins.notify(me, 'beforeRender') === false) {
+ return;
+ }
- var onComplete = function(animation) {
- plugins.notify(me, 'afterRender');
- helpers.callback(animationOptions && animationOptions.onComplete, [animation], me);
- };
+ var onComplete = function(animation) {
+ plugins.notify(me, 'afterRender');
+ helpers.callback(animationOptions && animationOptions.onComplete, [animation], me);
+ };
- if (animationOptions && duration) {
- var animation = new Animation({
- numSteps: duration / 16.66, // 60 fps
- easing: config.easing || animationOptions.easing,
+ if (animationOptions && duration) {
+ var animation = new Animation({
+ numSteps: duration / 16.66, // 60 fps
+ easing: config.easing || animationOptions.easing,
- render: function(chart, animationObject) {
- var easingFunction = helpers.easing.effects[animationObject.easing];
- var currentStep = animationObject.currentStep;
- var stepDecimal = currentStep / animationObject.numSteps;
+ render: function(chart, animationObject) {
+ var easingFunction = helpers.easing.effects[animationObject.easing];
+ var currentStep = animationObject.currentStep;
+ var stepDecimal = currentStep / animationObject.numSteps;
- chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep);
- },
+ chart.draw(easingFunction(stepDecimal), stepDecimal, currentStep);
+ },
- onAnimationProgress: animationOptions.onProgress,
- onAnimationComplete: onComplete
- });
+ onAnimationProgress: animationOptions.onProgress,
+ onAnimationComplete: onComplete
+ });
- animations.addAnimation(me, animation, duration, lazy);
- } else {
- me.draw();
+ animations.addAnimation(me, animation, duration, lazy);
+ } else {
+ me.draw();
- // See https://github.com/chartjs/Chart.js/issues/3781
- onComplete(new Animation({numSteps: 0, chart: me}));
- }
+ // See https://github.com/chartjs/Chart.js/issues/3781
+ onComplete(new Animation({numSteps: 0, chart: me}));
+ }
- return me;
- },
+ return me;
+ },
- draw: function(easingValue) {
- var me = this;
+ draw: function(easingValue) {
+ var me = this;
- me.clear();
+ me.clear();
- if (helpers.isNullOrUndef(easingValue)) {
- easingValue = 1;
- }
+ if (helpers.isNullOrUndef(easingValue)) {
+ easingValue = 1;
+ }
- me.transition(easingValue);
+ me.transition(easingValue);
- if (me.width <= 0 || me.height <= 0) {
- return;
- }
+ if (me.width <= 0 || me.height <= 0) {
+ return;
+ }
- if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) {
- return;
- }
+ if (plugins.notify(me, 'beforeDraw', [easingValue]) === false) {
+ return;
+ }
- // Draw all the scales
- helpers.each(me.boxes, function(box) {
- box.draw(me.chartArea);
- }, me);
+ // Draw all the scales
+ helpers.each(me.boxes, function(box) {
+ box.draw(me.chartArea);
+ }, me);
- if (me.scale) {
- me.scale.draw();
- }
+ if (me.scale) {
+ me.scale.draw();
+ }
- me.drawDatasets(easingValue);
- me._drawTooltip(easingValue);
+ me.drawDatasets(easingValue);
+ me._drawTooltip(easingValue);
- plugins.notify(me, 'afterDraw', [easingValue]);
- },
+ plugins.notify(me, 'afterDraw', [easingValue]);
+ },
- /**
- * @private
- */
- transition: function(easingValue) {
- var me = this;
+ /**
+ * @private
+ */
+ transition: function(easingValue) {
+ var me = this;
- for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) {
- if (me.isDatasetVisible(i)) {
- me.getDatasetMeta(i).controller.transition(easingValue);
- }
+ for (var i = 0, ilen = (me.data.datasets || []).length; i < ilen; ++i) {
+ if (me.isDatasetVisible(i)) {
+ me.getDatasetMeta(i).controller.transition(easingValue);
}
+ }
- me.tooltip.transition(easingValue);
- },
+ me.tooltip.transition(easingValue);
+ },
- /**
- * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw`
- * hook, in which case, plugins will not be called on `afterDatasetsDraw`.
- * @private
- */
- drawDatasets: function(easingValue) {
- var me = this;
+ /**
+ * Draws all datasets unless a plugin returns `false` to the `beforeDatasetsDraw`
+ * hook, in which case, plugins will not be called on `afterDatasetsDraw`.
+ * @private
+ */
+ drawDatasets: function(easingValue) {
+ var me = this;
- if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) {
- return;
- }
+ if (plugins.notify(me, 'beforeDatasetsDraw', [easingValue]) === false) {
+ return;
+ }
- // Draw datasets reversed to support proper line stacking
- for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) {
- if (me.isDatasetVisible(i)) {
- me.drawDataset(i, easingValue);
- }
+ // Draw datasets reversed to support proper line stacking
+ for (var i = (me.data.datasets || []).length - 1; i >= 0; --i) {
+ if (me.isDatasetVisible(i)) {
+ me.drawDataset(i, easingValue);
}
+ }
- plugins.notify(me, 'afterDatasetsDraw', [easingValue]);
- },
+ plugins.notify(me, 'afterDatasetsDraw', [easingValue]);
+ },
- /**
- * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw`
- * hook, in which case, plugins will not be called on `afterDatasetDraw`.
- * @private
- */
- drawDataset: function(index, easingValue) {
- var me = this;
- var meta = me.getDatasetMeta(index);
- var args = {
- meta: meta,
- index: index,
- easingValue: easingValue
- };
+ /**
+ * Draws dataset at index unless a plugin returns `false` to the `beforeDatasetDraw`
+ * hook, in which case, plugins will not be called on `afterDatasetDraw`.
+ * @private
+ */
+ drawDataset: function(index, easingValue) {
+ var me = this;
+ var meta = me.getDatasetMeta(index);
+ var args = {
+ meta: meta,
+ index: index,
+ easingValue: easingValue
+ };
+
+ if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
+ return;
+ }
- if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
- return;
- }
+ meta.controller.draw(easingValue);
- meta.controller.draw(easingValue);
+ plugins.notify(me, 'afterDatasetDraw', [args]);
+ },
- plugins.notify(me, 'afterDatasetDraw', [args]);
- },
+ /**
+ * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw`
+ * hook, in which case, plugins will not be called on `afterTooltipDraw`.
+ * @private
+ */
+ _drawTooltip: function(easingValue) {
+ var me = this;
+ var tooltip = me.tooltip;
+ var args = {
+ tooltip: tooltip,
+ easingValue: easingValue
+ };
+
+ if (plugins.notify(me, 'beforeTooltipDraw', [args]) === false) {
+ return;
+ }
- /**
- * Draws tooltip unless a plugin returns `false` to the `beforeTooltipDraw`
- * hook, in which case, plugins will not be called on `afterTooltipDraw`.
- * @private
- */
- _drawTooltip: function(easingValue) {
- var me = this;
- var tooltip = me.tooltip;
- var args = {
- tooltip: tooltip,
- easingValue: easingValue
- };
+ tooltip.draw();
- if (plugins.notify(me, 'beforeTooltipDraw', [args]) === false) {
- return;
- }
+ plugins.notify(me, 'afterTooltipDraw', [args]);
+ },
- tooltip.draw();
+ // Get the single element that was clicked on
+ // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
+ getElementAtEvent: function(e) {
+ return Interaction.modes.single(this, e);
+ },
- plugins.notify(me, 'afterTooltipDraw', [args]);
- },
+ getElementsAtEvent: function(e) {
+ return Interaction.modes.label(this, e, {intersect: true});
+ },
- // Get the single element that was clicked on
- // @return : An object containing the dataset index and element index of the matching element. Also contains the rectangle that was draw
- getElementAtEvent: function(e) {
- return Interaction.modes.single(this, e);
- },
+ getElementsAtXAxis: function(e) {
+ return Interaction.modes['x-axis'](this, e, {intersect: true});
+ },
- getElementsAtEvent: function(e) {
- return Interaction.modes.label(this, e, {intersect: true});
- },
+ getElementsAtEventForMode: function(e, mode, options) {
+ var method = Interaction.modes[mode];
+ if (typeof method === 'function') {
+ return method(this, e, options);
+ }
- getElementsAtXAxis: function(e) {
- return Interaction.modes['x-axis'](this, e, {intersect: true});
- },
+ return [];
+ },
- getElementsAtEventForMode: function(e, mode, options) {
- var method = Interaction.modes[mode];
- if (typeof method === 'function') {
- return method(this, e, options);
- }
+ getDatasetAtEvent: function(e) {
+ return Interaction.modes.dataset(this, e, {intersect: true});
+ },
- return [];
- },
+ getDatasetMeta: function(datasetIndex) {
+ var me = this;
+ var dataset = me.data.datasets[datasetIndex];
+ if (!dataset._meta) {
+ dataset._meta = {};
+ }
- getDatasetAtEvent: function(e) {
- return Interaction.modes.dataset(this, e, {intersect: true});
- },
+ var meta = dataset._meta[me.id];
+ if (!meta) {
+ meta = dataset._meta[me.id] = {
+ type: null,
+ data: [],
+ dataset: null,
+ controller: null,
+ hidden: null, // See isDatasetVisible() comment
+ xAxisID: null,
+ yAxisID: null
+ };
+ }
- getDatasetMeta: function(datasetIndex) {
- var me = this;
- var dataset = me.data.datasets[datasetIndex];
- if (!dataset._meta) {
- dataset._meta = {};
- }
+ return meta;
+ },
- var meta = dataset._meta[me.id];
- if (!meta) {
- meta = dataset._meta[me.id] = {
- type: null,
- data: [],
- dataset: null,
- controller: null,
- hidden: null, // See isDatasetVisible() comment
- xAxisID: null,
- yAxisID: null
- };
+ getVisibleDatasetCount: function() {
+ var count = 0;
+ for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
+ if (this.isDatasetVisible(i)) {
+ count++;
}
+ }
+ return count;
+ },
- return meta;
- },
-
- getVisibleDatasetCount: function() {
- var count = 0;
- for (var i = 0, ilen = this.data.datasets.length; i < ilen; ++i) {
- if (this.isDatasetVisible(i)) {
- count++;
- }
- }
- return count;
- },
+ isDatasetVisible: function(datasetIndex) {
+ var meta = this.getDatasetMeta(datasetIndex);
- isDatasetVisible: function(datasetIndex) {
- var meta = this.getDatasetMeta(datasetIndex);
+ // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
+ // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
+ return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden;
+ },
- // meta.hidden is a per chart dataset hidden flag override with 3 states: if true or false,
- // the dataset.hidden value is ignored, else if null, the dataset hidden state is returned.
- return typeof meta.hidden === 'boolean' ? !meta.hidden : !this.data.datasets[datasetIndex].hidden;
- },
+ generateLegend: function() {
+ return this.options.legendCallback(this);
+ },
- generateLegend: function() {
- return this.options.legendCallback(this);
- },
+ /**
+ * @private
+ */
+ destroyDatasetMeta: function(datasetIndex) {
+ var id = this.id;
+ var dataset = this.data.datasets[datasetIndex];
+ var meta = dataset._meta && dataset._meta[id];
+
+ if (meta) {
+ meta.controller.destroy();
+ delete dataset._meta[id];
+ }
+ },
- /**
- * @private
- */
- destroyDatasetMeta: function(datasetIndex) {
- var id = this.id;
- var dataset = this.data.datasets[datasetIndex];
- var meta = dataset._meta && dataset._meta[id];
-
- if (meta) {
- meta.controller.destroy();
- delete dataset._meta[id];
- }
- },
+ destroy: function() {
+ var me = this;
+ var canvas = me.canvas;
+ var i, ilen;
- destroy: function() {
- var me = this;
- var canvas = me.canvas;
- var i, ilen;
+ me.stop();
- me.stop();
+ // dataset controllers need to cleanup associated data
+ for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
+ me.destroyDatasetMeta(i);
+ }
- // dataset controllers need to cleanup associated data
- for (i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
- me.destroyDatasetMeta(i);
- }
+ if (canvas) {
+ me.unbindEvents();
+ helpers.canvas.clear(me);
+ platform.releaseContext(me.ctx);
+ me.canvas = null;
+ me.ctx = null;
+ }
- if (canvas) {
- me.unbindEvents();
- helpers.canvas.clear(me);
- platform.releaseContext(me.ctx);
- me.canvas = null;
- me.ctx = null;
- }
+ plugins.notify(me, 'destroy');
- plugins.notify(me, 'destroy');
+ delete Chart.instances[me.id];
+ },
- delete Chart.instances[me.id];
- },
+ toBase64Image: function() {
+ return this.canvas.toDataURL.apply(this.canvas, arguments);
+ },
- toBase64Image: function() {
- return this.canvas.toDataURL.apply(this.canvas, arguments);
- },
+ initToolTip: function() {
+ var me = this;
+ me.tooltip = new Tooltip({
+ _chart: me,
+ _chartInstance: me, // deprecated, backward compatibility
+ _data: me.data,
+ _options: me.options.tooltips
+ }, me);
+ },
- initToolTip: function() {
- var me = this;
- me.tooltip = new Tooltip({
- _chart: me,
- _chartInstance: me, // deprecated, backward compatibility
- _data: me.data,
- _options: me.options.tooltips
- }, me);
- },
+ /**
+ * @private
+ */
+ bindEvents: function() {
+ var me = this;
+ var listeners = me._listeners = {};
+ var listener = function() {
+ me.eventHandler.apply(me, arguments);
+ };
+
+ helpers.each(me.options.events, function(type) {
+ platform.addEventListener(me, type, listener);
+ listeners[type] = listener;
+ });
- /**
- * @private
- */
- bindEvents: function() {
- var me = this;
- var listeners = me._listeners = {};
- var listener = function() {
- me.eventHandler.apply(me, arguments);
+ // Elements used to detect size change should not be injected for non responsive charts.
+ // See https://github.com/chartjs/Chart.js/issues/2210
+ if (me.options.responsive) {
+ listener = function() {
+ me.resize();
};
- helpers.each(me.options.events, function(type) {
- platform.addEventListener(me, type, listener);
- listeners[type] = listener;
- });
-
- // Elements used to detect size change should not be injected for non responsive charts.
- // See https://github.com/chartjs/Chart.js/issues/2210
- if (me.options.responsive) {
- listener = function() {
- me.resize();
- };
-
- platform.addEventListener(me, 'resize', listener);
- listeners.resize = listener;
- }
- },
+ platform.addEventListener(me, 'resize', listener);
+ listeners.resize = listener;
+ }
+ },
- /**
- * @private
- */
- unbindEvents: function() {
- var me = this;
- var listeners = me._listeners;
- if (!listeners) {
- return;
- }
+ /**
+ * @private
+ */
+ unbindEvents: function() {
+ var me = this;
+ var listeners = me._listeners;
+ if (!listeners) {
+ return;
+ }
- delete me._listeners;
- helpers.each(listeners, function(listener, type) {
- platform.removeEventListener(me, type, listener);
- });
- },
+ delete me._listeners;
+ helpers.each(listeners, function(listener, type) {
+ platform.removeEventListener(me, type, listener);
+ });
+ },
- updateHoverStyle: function(elements, mode, enabled) {
- var method = enabled ? 'setHoverStyle' : 'removeHoverStyle';
- var element, i, ilen;
+ updateHoverStyle: function(elements, mode, enabled) {
+ var method = enabled ? 'setHoverStyle' : 'removeHoverStyle';
+ var element, i, ilen;
- for (i = 0, ilen = elements.length; i < ilen; ++i) {
- element = elements[i];
- if (element) {
- this.getDatasetMeta(element._datasetIndex).controller[method](element);
- }
+ for (i = 0, ilen = elements.length; i < ilen; ++i) {
+ element = elements[i];
+ if (element) {
+ this.getDatasetMeta(element._datasetIndex).controller[method](element);
}
- },
-
- /**
- * @private
- */
- eventHandler: function(e) {
- var me = this;
- var tooltip = me.tooltip;
+ }
+ },
- if (plugins.notify(me, 'beforeEvent', [e]) === false) {
- return;
- }
+ /**
+ * @private
+ */
+ eventHandler: function(e) {
+ var me = this;
+ var tooltip = me.tooltip;
- // Buffer any update calls so that renders do not occur
- me._bufferedRender = true;
- me._bufferedRequest = null;
-
- var changed = me.handleEvent(e);
- // for smooth tooltip animations issue #4989
- // the tooltip should be the source of change
- // Animation check workaround:
- // tooltip._start will be null when tooltip isn't animating
- if (tooltip) {
- changed = tooltip._start
- ? tooltip.handleEvent(e)
- : changed | tooltip.handleEvent(e);
- }
+ if (plugins.notify(me, 'beforeEvent', [e]) === false) {
+ return;
+ }
- plugins.notify(me, 'afterEvent', [e]);
-
- var bufferedRequest = me._bufferedRequest;
- if (bufferedRequest) {
- // If we have an update that was triggered, we need to do a normal render
- me.render(bufferedRequest);
- } else if (changed && !me.animating) {
- // If entering, leaving, or changing elements, animate the change via pivot
- me.stop();
-
- // We only need to render at this point. Updating will cause scales to be
- // recomputed generating flicker & using more memory than necessary.
- me.render({
- duration: me.options.hover.animationDuration,
- lazy: true
- });
- }
+ // Buffer any update calls so that renders do not occur
+ me._bufferedRender = true;
+ me._bufferedRequest = null;
+
+ var changed = me.handleEvent(e);
+ // for smooth tooltip animations issue #4989
+ // the tooltip should be the source of change
+ // Animation check workaround:
+ // tooltip._start will be null when tooltip isn't animating
+ if (tooltip) {
+ changed = tooltip._start
+ ? tooltip.handleEvent(e)
+ : changed | tooltip.handleEvent(e);
+ }
- me._bufferedRender = false;
- me._bufferedRequest = null;
+ plugins.notify(me, 'afterEvent', [e]);
- return me;
- },
+ var bufferedRequest = me._bufferedRequest;
+ if (bufferedRequest) {
+ // If we have an update that was triggered, we need to do a normal render
+ me.render(bufferedRequest);
+ } else if (changed && !me.animating) {
+ // If entering, leaving, or changing elements, animate the change via pivot
+ me.stop();
- /**
- * Handle an event
- * @private
- * @param {IEvent} event the event to handle
- * @return {Boolean} true if the chart needs to re-render
- */
- handleEvent: function(e) {
- var me = this;
- var options = me.options || {};
- var hoverOptions = options.hover;
- var changed = false;
+ // We only need to render at this point. Updating will cause scales to be
+ // recomputed generating flicker & using more memory than necessary.
+ me.render({
+ duration: me.options.hover.animationDuration,
+ lazy: true
+ });
+ }
- me.lastActive = me.lastActive || [];
+ me._bufferedRender = false;
+ me._bufferedRequest = null;
- // Find Active Elements for hover and tooltips
- if (e.type === 'mouseout') {
- me.active = [];
- } else {
- me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
- }
+ return me;
+ },
- // Invoke onHover hook
- // Need to call with native event here to not break backwards compatibility
- helpers.callback(options.onHover || options.hover.onHover, [e.native, me.active], me);
+ /**
+ * Handle an event
+ * @private
+ * @param {IEvent} event the event to handle
+ * @return {Boolean} true if the chart needs to re-render
+ */
+ handleEvent: function(e) {
+ var me = this;
+ var options = me.options || {};
+ var hoverOptions = options.hover;
+ var changed = false;
+
+ me.lastActive = me.lastActive || [];
+
+ // Find Active Elements for hover and tooltips
+ if (e.type === 'mouseout') {
+ me.active = [];
+ } else {
+ me.active = me.getElementsAtEventForMode(e, hoverOptions.mode, hoverOptions);
+ }
- if (e.type === 'mouseup' || e.type === 'click') {
- if (options.onClick) {
- // Use e.native here for backwards compatibility
- options.onClick.call(me, e.native, me.active);
- }
- }
+ // Invoke onHover hook
+ // Need to call with native event here to not break backwards compatibility
+ helpers.callback(options.onHover || options.hover.onHover, [e.native, me.active], me);
- // Remove styling for last active (even if it may still be active)
- if (me.lastActive.length) {
- me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
+ if (e.type === 'mouseup' || e.type === 'click') {
+ if (options.onClick) {
+ // Use e.native here for backwards compatibility
+ options.onClick.call(me, e.native, me.active);
}
+ }
- // Built in hover styling
- if (me.active.length && hoverOptions.mode) {
- me.updateHoverStyle(me.active, hoverOptions.mode, true);
- }
+ // Remove styling for last active (even if it may still be active)
+ if (me.lastActive.length) {
+ me.updateHoverStyle(me.lastActive, hoverOptions.mode, false);
+ }
- changed = !helpers.arrayEquals(me.active, me.lastActive);
+ // Built in hover styling
+ if (me.active.length && hoverOptions.mode) {
+ me.updateHoverStyle(me.active, hoverOptions.mode, true);
+ }
- // Remember Last Actives
- me.lastActive = me.active;
+ changed = !helpers.arrayEquals(me.active, me.lastActive);
- return changed;
- }
- });
+ // Remember Last Actives
+ me.lastActive = me.active;
- /**
- * Provided for backward compatibility, use Chart instead.
- * @class Chart.Controller
- * @deprecated since version 2.6.0
- * @todo remove at version 3
- * @private
- */
- Chart.Controller = Chart;
-};
+ return changed;
+ }
+});
+
+/**
+ * NOTE(SB) We actually don't use this container anymore but we need to keep it
+ * for backward compatibility. Though, it can still be useful for plugins that
+ * would need to work on multiple charts?!
+ */
+Chart.instances = {};
+
+module.exports = Chart;
+
+// DEPRECATIONS
+
+/**
+ * Provided for backward compatibility, use Chart instead.
+ * @class Chart.Controller
+ * @deprecated since version 2.6
+ * @todo remove at version 3
+ * @private
+ */
+Chart.Controller = Chart;
+
+/**
+ * Provided for backward compatibility, not available anymore.
+ * @namespace Chart
+ * @deprecated since version 2.8
+ * @todo remove at version 3
+ * @private
+ */
+Chart.types = {};