]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Make `Chart.plugins` importable (#5114)
authorSimon Brunel <simonbrunel@users.noreply.github.com>
Mon, 8 Jan 2018 10:48:59 +0000 (11:48 +0100)
committerGitHub <noreply@github.com>
Mon, 8 Jan 2018 10:48:59 +0000 (11:48 +0100)
Explicitly deprecate (since 2.1.5) `Chart.Legend` and `Chart.Title`.

src/chart.js
src/core/core.controller.js
src/core/core.plugin.js [deleted file]
src/core/core.plugins.js [new file with mode: 0644]
src/plugins/index.js [new file with mode: 0644]
src/plugins/plugin.filler.js
src/plugins/plugin.legend.js
src/plugins/plugin.title.js
test/specs/global.deprecations.tests.js
test/specs/plugin.legend.tests.js
test/specs/plugin.title.tests.js

index b46e66b9e6fb3f48631904cada2ea149899d086e..70d70a669952640b47b6c1007c14b527828cfe66 100644 (file)
@@ -14,9 +14,9 @@ Chart.elements = require('./elements/index');
 Chart.Interaction = require('./core/core.interaction');
 Chart.layout = require('./core/core.layout');
 Chart.platform = require('./platforms/platform');
+Chart.plugins = require('./core/core.plugins');
 Chart.Ticks = require('./core/core.ticks');
 
-require('./core/core.plugin')(Chart);
 require('./core/core.animation')(Chart);
 require('./core/core.controller')(Chart);
 require('./core/core.datasetController')(Chart);
@@ -50,15 +50,12 @@ require('./charts/Chart.Radar')(Chart);
 require('./charts/Chart.Scatter')(Chart);
 
 // Loading built-it plugins
-var plugins = [];
-
-plugins.push(
-       require('./plugins/plugin.filler')(Chart),
-       require('./plugins/plugin.legend')(Chart),
-       require('./plugins/plugin.title')(Chart)
-);
-
-Chart.plugins.register(plugins);
+var plugins = require('./plugins');
+for (var k in plugins) {
+       if (plugins.hasOwnProperty(k)) {
+               Chart.plugins.register(plugins[k]);
+       }
+}
 
 Chart.platform.initialize();
 
@@ -69,6 +66,43 @@ if (typeof window !== 'undefined') {
 
 // DEPRECATIONS
 
+/**
+ * Provided for backward compatibility, not available anymore
+ * @namespace Chart.Legend
+ * @deprecated since version 2.1.5
+ * @todo remove at version 3
+ * @private
+ */
+Chart.Legend = plugins.legend._element;
+
+/**
+ * Provided for backward compatibility, not available anymore
+ * @namespace Chart.Title
+ * @deprecated since version 2.1.5
+ * @todo remove at version 3
+ * @private
+ */
+Chart.Title = plugins.title._element;
+
+/**
+ * Provided for backward compatibility, use Chart.plugins instead
+ * @namespace Chart.pluginService
+ * @deprecated since version 2.1.5
+ * @todo remove at version 3
+ * @private
+ */
+Chart.pluginService = Chart.plugins;
+
+/**
+ * Provided for backward compatibility, inheriting from Chart.PlugingBase has no
+ * effect, instead simply create/register plugins via plain JavaScript objects.
+ * @interface Chart.PluginBase
+ * @deprecated since version 2.5.0
+ * @todo remove at version 3
+ * @private
+ */
+Chart.PluginBase = Chart.Element.extend({});
+
 /**
  * Provided for backward compatibility, use Chart.helpers.canvas instead.
  * @namespace Chart.canvasHelpers
index 2d143f6e0093c60f234466bb16db1b593ca08876..157bc50a42362661339e0203f6aeb476fdc81ae2 100644 (file)
@@ -5,9 +5,9 @@ var helpers = require('../helpers/index');
 var Interaction = require('./core.interaction');
 var layout = require('./core.layout');
 var platform = require('../platforms/platform');
+var plugins = require('./core.plugins');
 
 module.exports = function(Chart) {
-       var plugins = Chart.plugins;
 
        // Create a dictionary of chart types, to allow for extension of existing types
        Chart.types = {};
diff --git a/src/core/core.plugin.js b/src/core/core.plugin.js
deleted file mode 100644 (file)
index 0b7423a..0000000
+++ /dev/null
@@ -1,395 +0,0 @@
-'use strict';
-
-var defaults = require('./core.defaults');
-var Element = require('./core.element');
-var helpers = require('../helpers/index');
-
-defaults._set('global', {
-       plugins: {}
-});
-
-module.exports = function(Chart) {
-
-       /**
-        * The plugin service singleton
-        * @namespace Chart.plugins
-        * @since 2.1.0
-        */
-       Chart.plugins = {
-               /**
-                * Globally registered plugins.
-                * @private
-                */
-               _plugins: [],
-
-               /**
-                * This identifier is used to invalidate the descriptors cache attached to each chart
-                * when a global plugin is registered or unregistered. In this case, the cache ID is
-                * incremented and descriptors are regenerated during following API calls.
-                * @private
-                */
-               _cacheId: 0,
-
-               /**
-                * Registers the given plugin(s) if not already registered.
-                * @param {Array|Object} plugins plugin instance(s).
-                */
-               register: function(plugins) {
-                       var p = this._plugins;
-                       ([]).concat(plugins).forEach(function(plugin) {
-                               if (p.indexOf(plugin) === -1) {
-                                       p.push(plugin);
-                               }
-                       });
-
-                       this._cacheId++;
-               },
-
-               /**
-                * Unregisters the given plugin(s) only if registered.
-                * @param {Array|Object} plugins plugin instance(s).
-                */
-               unregister: function(plugins) {
-                       var p = this._plugins;
-                       ([]).concat(plugins).forEach(function(plugin) {
-                               var idx = p.indexOf(plugin);
-                               if (idx !== -1) {
-                                       p.splice(idx, 1);
-                               }
-                       });
-
-                       this._cacheId++;
-               },
-
-               /**
-                * Remove all registered plugins.
-                * @since 2.1.5
-                */
-               clear: function() {
-                       this._plugins = [];
-                       this._cacheId++;
-               },
-
-               /**
-                * Returns the number of registered plugins?
-                * @returns {Number}
-                * @since 2.1.5
-                */
-               count: function() {
-                       return this._plugins.length;
-               },
-
-               /**
-                * Returns all registered plugin instances.
-                * @returns {Array} array of plugin objects.
-                * @since 2.1.5
-                */
-               getAll: function() {
-                       return this._plugins;
-               },
-
-               /**
-                * Calls enabled plugins for `chart` on the specified hook and with the given args.
-                * This method immediately returns as soon as a plugin explicitly returns false. The
-                * returned value can be used, for instance, to interrupt the current action.
-                * @param {Object} chart - The chart instance for which plugins should be called.
-                * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate').
-                * @param {Array} [args] - Extra arguments to apply to the hook call.
-                * @returns {Boolean} false if any of the plugins return false, else returns true.
-                */
-               notify: function(chart, hook, args) {
-                       var descriptors = this.descriptors(chart);
-                       var ilen = descriptors.length;
-                       var i, descriptor, plugin, params, method;
-
-                       for (i = 0; i < ilen; ++i) {
-                               descriptor = descriptors[i];
-                               plugin = descriptor.plugin;
-                               method = plugin[hook];
-                               if (typeof method === 'function') {
-                                       params = [chart].concat(args || []);
-                                       params.push(descriptor.options);
-                                       if (method.apply(plugin, params) === false) {
-                                               return false;
-                                       }
-                               }
-                       }
-
-                       return true;
-               },
-
-               /**
-                * Returns descriptors of enabled plugins for the given chart.
-                * @returns {Array} [{ plugin, options }]
-                * @private
-                */
-               descriptors: function(chart) {
-                       var cache = chart._plugins || (chart._plugins = {});
-                       if (cache.id === this._cacheId) {
-                               return cache.descriptors;
-                       }
-
-                       var plugins = [];
-                       var descriptors = [];
-                       var config = (chart && chart.config) || {};
-                       var options = (config.options && config.options.plugins) || {};
-
-                       this._plugins.concat(config.plugins || []).forEach(function(plugin) {
-                               var idx = plugins.indexOf(plugin);
-                               if (idx !== -1) {
-                                       return;
-                               }
-
-                               var id = plugin.id;
-                               var opts = options[id];
-                               if (opts === false) {
-                                       return;
-                               }
-
-                               if (opts === true) {
-                                       opts = helpers.clone(defaults.global.plugins[id]);
-                               }
-
-                               plugins.push(plugin);
-                               descriptors.push({
-                                       plugin: plugin,
-                                       options: opts || {}
-                               });
-                       });
-
-                       cache.descriptors = descriptors;
-                       cache.id = this._cacheId;
-                       return descriptors;
-               }
-       };
-
-       /**
-        * Plugin extension hooks.
-        * @interface IPlugin
-        * @since 2.1.0
-        */
-       /**
-        * @method IPlugin#beforeInit
-        * @desc Called before initializing `chart`.
-        * @param {Chart.Controller} chart - The chart instance.
-        * @param {Object} options - The plugin options.
-        */
-       /**
-        * @method IPlugin#afterInit
-        * @desc Called after `chart` has been initialized and before the first update.
-        * @param {Chart.Controller} chart - The chart instance.
-        * @param {Object} options - The plugin options.
-        */
-       /**
-        * @method IPlugin#beforeUpdate
-        * @desc Called before updating `chart`. If any plugin returns `false`, the update
-        * is cancelled (and thus subsequent render(s)) until another `update` is triggered.
-        * @param {Chart.Controller} chart - The chart instance.
-        * @param {Object} options - The plugin options.
-        * @returns {Boolean} `false` to cancel the chart update.
-        */
-       /**
-        * @method IPlugin#afterUpdate
-        * @desc Called after `chart` has been updated and before rendering. Note that this
-        * hook will not be called if the chart update has been previously cancelled.
-        * @param {Chart.Controller} chart - The chart instance.
-        * @param {Object} options - The plugin options.
-        */
-       /**
-        * @method IPlugin#beforeDatasetsUpdate
-        * @desc Called before updating the `chart` datasets. If any plugin returns `false`,
-        * the datasets update is cancelled until another `update` is triggered.
-        * @param {Chart.Controller} chart - The chart instance.
-        * @param {Object} options - The plugin options.
-        * @returns {Boolean} false to cancel the datasets update.
-        * @since version 2.1.5
-        */
-       /**
-        * @method IPlugin#afterDatasetsUpdate
-        * @desc Called after the `chart` datasets have been updated. Note that this hook
-        * will not be called if the datasets update has been previously cancelled.
-        * @param {Chart.Controller} chart - The chart instance.
-        * @param {Object} options - The plugin options.
-        * @since version 2.1.5
-        */
-       /**
-        * @method IPlugin#beforeDatasetUpdate
-        * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin
-        * returns `false`, the datasets update is cancelled until another `update` is triggered.
-        * @param {Chart} chart - The chart instance.
-        * @param {Object} args - The call arguments.
-        * @param {Number} args.index - The dataset index.
-        * @param {Object} args.meta - The dataset metadata.
-        * @param {Object} options - The plugin options.
-        * @returns {Boolean} `false` to cancel the chart datasets drawing.
-        */
-       /**
-        * @method IPlugin#afterDatasetUpdate
-        * @desc Called after the `chart` datasets at the given `args.index` has been updated. Note
-        * that this hook will not be called if the datasets update has been previously cancelled.
-        * @param {Chart} chart - The chart instance.
-        * @param {Object} args - The call arguments.
-        * @param {Number} args.index - The dataset index.
-        * @param {Object} args.meta - The dataset metadata.
-        * @param {Object} options - The plugin options.
-        */
-       /**
-        * @method IPlugin#beforeLayout
-        * @desc Called before laying out `chart`. If any plugin returns `false`,
-        * the layout update is cancelled until another `update` is triggered.
-        * @param {Chart.Controller} chart - The chart instance.
-        * @param {Object} options - The plugin options.
-        * @returns {Boolean} `false` to cancel the chart layout.
-        */
-       /**
-        * @method IPlugin#afterLayout
-        * @desc Called after the `chart` has been layed out. Note that this hook will not
-        * be called if the layout update has been previously cancelled.
-        * @param {Chart.Controller} chart - The chart instance.
-        * @param {Object} options - The plugin options.
-        */
-       /**
-        * @method IPlugin#beforeRender
-        * @desc Called before rendering `chart`. If any plugin returns `false`,
-        * the rendering is cancelled until another `render` is triggered.
-        * @param {Chart.Controller} chart - The chart instance.
-        * @param {Object} options - The plugin options.
-        * @returns {Boolean} `false` to cancel the chart rendering.
-        */
-       /**
-        * @method IPlugin#afterRender
-        * @desc Called after the `chart` has been fully rendered (and animation completed). Note
-        * that this hook will not be called if the rendering has been previously cancelled.
-        * @param {Chart.Controller} chart - The chart instance.
-        * @param {Object} options - The plugin options.
-        */
-       /**
-        * @method IPlugin#beforeDraw
-        * @desc Called before drawing `chart` at every animation frame specified by the given
-        * easing value. If any plugin returns `false`, the frame drawing is cancelled until
-        * another `render` is triggered.
-        * @param {Chart.Controller} chart - The chart instance.
-        * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
-        * @param {Object} options - The plugin options.
-        * @returns {Boolean} `false` to cancel the chart drawing.
-        */
-       /**
-        * @method IPlugin#afterDraw
-        * @desc Called after the `chart` has been drawn for the specific easing value. Note
-        * that this hook will not be called if the drawing has been previously cancelled.
-        * @param {Chart.Controller} chart - The chart instance.
-        * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
-        * @param {Object} options - The plugin options.
-        */
-       /**
-        * @method IPlugin#beforeDatasetsDraw
-        * @desc Called before drawing the `chart` datasets. If any plugin returns `false`,
-        * the datasets drawing is cancelled until another `render` is triggered.
-        * @param {Chart.Controller} chart - The chart instance.
-        * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
-        * @param {Object} options - The plugin options.
-        * @returns {Boolean} `false` to cancel the chart datasets drawing.
-        */
-       /**
-        * @method IPlugin#afterDatasetsDraw
-        * @desc Called after the `chart` datasets have been drawn. Note that this hook
-        * will not be called if the datasets drawing has been previously cancelled.
-        * @param {Chart.Controller} chart - The chart instance.
-        * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
-        * @param {Object} options - The plugin options.
-        */
-       /**
-        * @method IPlugin#beforeDatasetDraw
-        * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets
-        * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing
-        * is cancelled until another `render` is triggered.
-        * @param {Chart} chart - The chart instance.
-        * @param {Object} args - The call arguments.
-        * @param {Number} args.index - The dataset index.
-        * @param {Object} args.meta - The dataset metadata.
-        * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
-        * @param {Object} options - The plugin options.
-        * @returns {Boolean} `false` to cancel the chart datasets drawing.
-        */
-       /**
-        * @method IPlugin#afterDatasetDraw
-        * @desc Called after the `chart` datasets at the given `args.index` have been drawn
-        * (datasets are drawn in the reverse order). Note that this hook will not be called
-        * if the datasets drawing has been previously cancelled.
-        * @param {Chart} chart - The chart instance.
-        * @param {Object} args - The call arguments.
-        * @param {Number} args.index - The dataset index.
-        * @param {Object} args.meta - The dataset metadata.
-        * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
-        * @param {Object} options - The plugin options.
-        */
-       /**
-        * @method IPlugin#beforeTooltipDraw
-        * @desc Called before drawing the `tooltip`. If any plugin returns `false`,
-        * the tooltip drawing is cancelled until another `render` is triggered.
-        * @param {Chart} chart - The chart instance.
-        * @param {Object} args - The call arguments.
-        * @param {Object} args.tooltip - The tooltip.
-        * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
-        * @param {Object} options - The plugin options.
-        * @returns {Boolean} `false` to cancel the chart tooltip drawing.
-        */
-       /**
-        * @method IPlugin#afterTooltipDraw
-        * @desc Called after drawing the `tooltip`. Note that this hook will not
-        * be called if the tooltip drawing has been previously cancelled.
-        * @param {Chart} chart - The chart instance.
-        * @param {Object} args - The call arguments.
-        * @param {Object} args.tooltip - The tooltip.
-        * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
-        * @param {Object} options - The plugin options.
-        */
-       /**
-        * @method IPlugin#beforeEvent
-        * @desc Called before processing the specified `event`. If any plugin returns `false`,
-        * the event will be discarded.
-        * @param {Chart.Controller} chart - The chart instance.
-        * @param {IEvent} event - The event object.
-        * @param {Object} options - The plugin options.
-        */
-       /**
-        * @method IPlugin#afterEvent
-        * @desc Called after the `event` has been consumed. Note that this hook
-        * will not be called if the `event` has been previously discarded.
-        * @param {Chart.Controller} chart - The chart instance.
-        * @param {IEvent} event - The event object.
-        * @param {Object} options - The plugin options.
-        */
-       /**
-        * @method IPlugin#resize
-        * @desc Called after the chart as been resized.
-        * @param {Chart.Controller} chart - The chart instance.
-        * @param {Number} size - The new canvas display size (eq. canvas.style width & height).
-        * @param {Object} options - The plugin options.
-        */
-       /**
-        * @method IPlugin#destroy
-        * @desc Called after the chart as been destroyed.
-        * @param {Chart.Controller} chart - The chart instance.
-        * @param {Object} options - The plugin options.
-        */
-
-       /**
-        * Provided for backward compatibility, use Chart.plugins instead
-        * @namespace Chart.pluginService
-        * @deprecated since version 2.1.5
-        * @todo remove at version 3
-        * @private
-        */
-       Chart.pluginService = Chart.plugins;
-
-       /**
-        * Provided for backward compatibility, inheriting from Chart.PlugingBase has no
-        * effect, instead simply create/register plugins via plain JavaScript objects.
-        * @interface Chart.PluginBase
-        * @deprecated since version 2.5.0
-        * @todo remove at version 3
-        * @private
-        */
-       Chart.PluginBase = Element.extend({});
-};
diff --git a/src/core/core.plugins.js b/src/core/core.plugins.js
new file mode 100644 (file)
index 0000000..f5e8d10
--- /dev/null
@@ -0,0 +1,372 @@
+'use strict';
+
+var defaults = require('./core.defaults');
+var helpers = require('../helpers/index');
+
+defaults._set('global', {
+       plugins: {}
+});
+
+/**
+ * The plugin service singleton
+ * @namespace Chart.plugins
+ * @since 2.1.0
+ */
+module.exports = {
+       /**
+        * Globally registered plugins.
+        * @private
+        */
+       _plugins: [],
+
+       /**
+        * This identifier is used to invalidate the descriptors cache attached to each chart
+        * when a global plugin is registered or unregistered. In this case, the cache ID is
+        * incremented and descriptors are regenerated during following API calls.
+        * @private
+        */
+       _cacheId: 0,
+
+       /**
+        * Registers the given plugin(s) if not already registered.
+        * @param {Array|Object} plugins plugin instance(s).
+        */
+       register: function(plugins) {
+               var p = this._plugins;
+               ([]).concat(plugins).forEach(function(plugin) {
+                       if (p.indexOf(plugin) === -1) {
+                               p.push(plugin);
+                       }
+               });
+
+               this._cacheId++;
+       },
+
+       /**
+        * Unregisters the given plugin(s) only if registered.
+        * @param {Array|Object} plugins plugin instance(s).
+        */
+       unregister: function(plugins) {
+               var p = this._plugins;
+               ([]).concat(plugins).forEach(function(plugin) {
+                       var idx = p.indexOf(plugin);
+                       if (idx !== -1) {
+                               p.splice(idx, 1);
+                       }
+               });
+
+               this._cacheId++;
+       },
+
+       /**
+        * Remove all registered plugins.
+        * @since 2.1.5
+        */
+       clear: function() {
+               this._plugins = [];
+               this._cacheId++;
+       },
+
+       /**
+        * Returns the number of registered plugins?
+        * @returns {Number}
+        * @since 2.1.5
+        */
+       count: function() {
+               return this._plugins.length;
+       },
+
+       /**
+        * Returns all registered plugin instances.
+        * @returns {Array} array of plugin objects.
+        * @since 2.1.5
+        */
+       getAll: function() {
+               return this._plugins;
+       },
+
+       /**
+        * Calls enabled plugins for `chart` on the specified hook and with the given args.
+        * This method immediately returns as soon as a plugin explicitly returns false. The
+        * returned value can be used, for instance, to interrupt the current action.
+        * @param {Object} chart - The chart instance for which plugins should be called.
+        * @param {String} hook - The name of the plugin method to call (e.g. 'beforeUpdate').
+        * @param {Array} [args] - Extra arguments to apply to the hook call.
+        * @returns {Boolean} false if any of the plugins return false, else returns true.
+        */
+       notify: function(chart, hook, args) {
+               var descriptors = this.descriptors(chart);
+               var ilen = descriptors.length;
+               var i, descriptor, plugin, params, method;
+
+               for (i = 0; i < ilen; ++i) {
+                       descriptor = descriptors[i];
+                       plugin = descriptor.plugin;
+                       method = plugin[hook];
+                       if (typeof method === 'function') {
+                               params = [chart].concat(args || []);
+                               params.push(descriptor.options);
+                               if (method.apply(plugin, params) === false) {
+                                       return false;
+                               }
+                       }
+               }
+
+               return true;
+       },
+
+       /**
+        * Returns descriptors of enabled plugins for the given chart.
+        * @returns {Array} [{ plugin, options }]
+        * @private
+        */
+       descriptors: function(chart) {
+               var cache = chart._plugins || (chart._plugins = {});
+               if (cache.id === this._cacheId) {
+                       return cache.descriptors;
+               }
+
+               var plugins = [];
+               var descriptors = [];
+               var config = (chart && chart.config) || {};
+               var options = (config.options && config.options.plugins) || {};
+
+               this._plugins.concat(config.plugins || []).forEach(function(plugin) {
+                       var idx = plugins.indexOf(plugin);
+                       if (idx !== -1) {
+                               return;
+                       }
+
+                       var id = plugin.id;
+                       var opts = options[id];
+                       if (opts === false) {
+                               return;
+                       }
+
+                       if (opts === true) {
+                               opts = helpers.clone(defaults.global.plugins[id]);
+                       }
+
+                       plugins.push(plugin);
+                       descriptors.push({
+                               plugin: plugin,
+                               options: opts || {}
+                       });
+               });
+
+               cache.descriptors = descriptors;
+               cache.id = this._cacheId;
+               return descriptors;
+       }
+};
+
+/**
+ * Plugin extension hooks.
+ * @interface IPlugin
+ * @since 2.1.0
+ */
+/**
+ * @method IPlugin#beforeInit
+ * @desc Called before initializing `chart`.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ */
+/**
+ * @method IPlugin#afterInit
+ * @desc Called after `chart` has been initialized and before the first update.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ */
+/**
+ * @method IPlugin#beforeUpdate
+ * @desc Called before updating `chart`. If any plugin returns `false`, the update
+ * is cancelled (and thus subsequent render(s)) until another `update` is triggered.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ * @returns {Boolean} `false` to cancel the chart update.
+ */
+/**
+ * @method IPlugin#afterUpdate
+ * @desc Called after `chart` has been updated and before rendering. Note that this
+ * hook will not be called if the chart update has been previously cancelled.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ */
+/**
+ * @method IPlugin#beforeDatasetsUpdate
+ * @desc Called before updating the `chart` datasets. If any plugin returns `false`,
+ * the datasets update is cancelled until another `update` is triggered.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ * @returns {Boolean} false to cancel the datasets update.
+ * @since version 2.1.5
+*/
+/**
+ * @method IPlugin#afterDatasetsUpdate
+ * @desc Called after the `chart` datasets have been updated. Note that this hook
+ * will not be called if the datasets update has been previously cancelled.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ * @since version 2.1.5
+ */
+/**
+ * @method IPlugin#beforeDatasetUpdate
+ * @desc Called before updating the `chart` dataset at the given `args.index`. If any plugin
+ * returns `false`, the datasets update is cancelled until another `update` is triggered.
+ * @param {Chart} chart - The chart instance.
+ * @param {Object} args - The call arguments.
+ * @param {Number} args.index - The dataset index.
+ * @param {Object} args.meta - The dataset metadata.
+ * @param {Object} options - The plugin options.
+ * @returns {Boolean} `false` to cancel the chart datasets drawing.
+ */
+/**
+ * @method IPlugin#afterDatasetUpdate
+ * @desc Called after the `chart` datasets at the given `args.index` has been updated. Note
+ * that this hook will not be called if the datasets update has been previously cancelled.
+ * @param {Chart} chart - The chart instance.
+ * @param {Object} args - The call arguments.
+ * @param {Number} args.index - The dataset index.
+ * @param {Object} args.meta - The dataset metadata.
+ * @param {Object} options - The plugin options.
+ */
+/**
+ * @method IPlugin#beforeLayout
+ * @desc Called before laying out `chart`. If any plugin returns `false`,
+ * the layout update is cancelled until another `update` is triggered.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ * @returns {Boolean} `false` to cancel the chart layout.
+ */
+/**
+ * @method IPlugin#afterLayout
+ * @desc Called after the `chart` has been layed out. Note that this hook will not
+ * be called if the layout update has been previously cancelled.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ */
+/**
+ * @method IPlugin#beforeRender
+ * @desc Called before rendering `chart`. If any plugin returns `false`,
+ * the rendering is cancelled until another `render` is triggered.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ * @returns {Boolean} `false` to cancel the chart rendering.
+ */
+/**
+ * @method IPlugin#afterRender
+ * @desc Called after the `chart` has been fully rendered (and animation completed). Note
+ * that this hook will not be called if the rendering has been previously cancelled.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ */
+/**
+ * @method IPlugin#beforeDraw
+ * @desc Called before drawing `chart` at every animation frame specified by the given
+ * easing value. If any plugin returns `false`, the frame drawing is cancelled until
+ * another `render` is triggered.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
+ * @param {Object} options - The plugin options.
+ * @returns {Boolean} `false` to cancel the chart drawing.
+ */
+/**
+ * @method IPlugin#afterDraw
+ * @desc Called after the `chart` has been drawn for the specific easing value. Note
+ * that this hook will not be called if the drawing has been previously cancelled.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
+ * @param {Object} options - The plugin options.
+ */
+/**
+ * @method IPlugin#beforeDatasetsDraw
+ * @desc Called before drawing the `chart` datasets. If any plugin returns `false`,
+ * the datasets drawing is cancelled until another `render` is triggered.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
+ * @param {Object} options - The plugin options.
+ * @returns {Boolean} `false` to cancel the chart datasets drawing.
+ */
+/**
+ * @method IPlugin#afterDatasetsDraw
+ * @desc Called after the `chart` datasets have been drawn. Note that this hook
+ * will not be called if the datasets drawing has been previously cancelled.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Number} easingValue - The current animation value, between 0.0 and 1.0.
+ * @param {Object} options - The plugin options.
+ */
+/**
+ * @method IPlugin#beforeDatasetDraw
+ * @desc Called before drawing the `chart` dataset at the given `args.index` (datasets
+ * are drawn in the reverse order). If any plugin returns `false`, the datasets drawing
+ * is cancelled until another `render` is triggered.
+ * @param {Chart} chart - The chart instance.
+ * @param {Object} args - The call arguments.
+ * @param {Number} args.index - The dataset index.
+ * @param {Object} args.meta - The dataset metadata.
+ * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
+ * @param {Object} options - The plugin options.
+ * @returns {Boolean} `false` to cancel the chart datasets drawing.
+ */
+/**
+ * @method IPlugin#afterDatasetDraw
+ * @desc Called after the `chart` datasets at the given `args.index` have been drawn
+ * (datasets are drawn in the reverse order). Note that this hook will not be called
+ * if the datasets drawing has been previously cancelled.
+ * @param {Chart} chart - The chart instance.
+ * @param {Object} args - The call arguments.
+ * @param {Number} args.index - The dataset index.
+ * @param {Object} args.meta - The dataset metadata.
+ * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
+ * @param {Object} options - The plugin options.
+ */
+/**
+ * @method IPlugin#beforeTooltipDraw
+ * @desc Called before drawing the `tooltip`. If any plugin returns `false`,
+ * the tooltip drawing is cancelled until another `render` is triggered.
+ * @param {Chart} chart - The chart instance.
+ * @param {Object} args - The call arguments.
+ * @param {Object} args.tooltip - The tooltip.
+ * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
+ * @param {Object} options - The plugin options.
+ * @returns {Boolean} `false` to cancel the chart tooltip drawing.
+ */
+/**
+ * @method IPlugin#afterTooltipDraw
+ * @desc Called after drawing the `tooltip`. Note that this hook will not
+ * be called if the tooltip drawing has been previously cancelled.
+ * @param {Chart} chart - The chart instance.
+ * @param {Object} args - The call arguments.
+ * @param {Object} args.tooltip - The tooltip.
+ * @param {Number} args.easingValue - The current animation value, between 0.0 and 1.0.
+ * @param {Object} options - The plugin options.
+ */
+/**
+ * @method IPlugin#beforeEvent
+ * @desc Called before processing the specified `event`. If any plugin returns `false`,
+ * the event will be discarded.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {IEvent} event - The event object.
+ * @param {Object} options - The plugin options.
+ */
+/**
+ * @method IPlugin#afterEvent
+ * @desc Called after the `event` has been consumed. Note that this hook
+ * will not be called if the `event` has been previously discarded.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {IEvent} event - The event object.
+ * @param {Object} options - The plugin options.
+ */
+/**
+ * @method IPlugin#resize
+ * @desc Called after the chart as been resized.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Number} size - The new canvas display size (eq. canvas.style width & height).
+ * @param {Object} options - The plugin options.
+ */
+/**
+ * @method IPlugin#destroy
+ * @desc Called after the chart as been destroyed.
+ * @param {Chart.Controller} chart - The chart instance.
+ * @param {Object} options - The plugin options.
+ */
diff --git a/src/plugins/index.js b/src/plugins/index.js
new file mode 100644 (file)
index 0000000..1cd9815
--- /dev/null
@@ -0,0 +1,6 @@
+'use strict';
+
+module.exports = {};
+module.exports.filler = require('./plugin.filler');
+module.exports.legend = require('./plugin.legend');
+module.exports.title = require('./plugin.title');
index cf022654284f6772e498735974804c4e2468ac47..eb8dad4c3b0cb2224a1167b79217c8ec1a0db217 100644 (file)
@@ -18,304 +18,301 @@ defaults._set('global', {
        }
 });
 
-module.exports = function() {
-
-       var mappers = {
-               dataset: function(source) {
-                       var index = source.fill;
-                       var chart = source.chart;
-                       var meta = chart.getDatasetMeta(index);
-                       var visible = meta && chart.isDatasetVisible(index);
-                       var points = (visible && meta.dataset._children) || [];
-                       var length = points.length || 0;
-
-                       return !length ? null : function(point, i) {
-                               return (i < length && points[i]._view) || null;
+var mappers = {
+       dataset: function(source) {
+               var index = source.fill;
+               var chart = source.chart;
+               var meta = chart.getDatasetMeta(index);
+               var visible = meta && chart.isDatasetVisible(index);
+               var points = (visible && meta.dataset._children) || [];
+               var length = points.length || 0;
+
+               return !length ? null : function(point, i) {
+                       return (i < length && points[i]._view) || null;
+               };
+       },
+
+       boundary: function(source) {
+               var boundary = source.boundary;
+               var x = boundary ? boundary.x : null;
+               var y = boundary ? boundary.y : null;
+
+               return function(point) {
+                       return {
+                               x: x === null ? point.x : x,
+                               y: y === null ? point.y : y,
                        };
-               },
+               };
+       }
+};
 
-               boundary: function(source) {
-                       var boundary = source.boundary;
-                       var x = boundary ? boundary.x : null;
-                       var y = boundary ? boundary.y : null;
+// @todo if (fill[0] === '#')
+function decodeFill(el, index, count) {
+       var model = el._model || {};
+       var fill = model.fill;
+       var target;
 
-                       return function(point) {
-                               return {
-                                       x: x === null ? point.x : x,
-                                       y: y === null ? point.y : y,
-                               };
-                       };
-               }
-       };
+       if (fill === undefined) {
+               fill = !!model.backgroundColor;
+       }
+
+       if (fill === false || fill === null) {
+               return false;
+       }
 
-       // @todo if (fill[0] === '#')
-       function decodeFill(el, index, count) {
-               var model = el._model || {};
-               var fill = model.fill;
-               var target;
+       if (fill === true) {
+               return 'origin';
+       }
 
-               if (fill === undefined) {
-                       fill = !!model.backgroundColor;
+       target = parseFloat(fill, 10);
+       if (isFinite(target) && Math.floor(target) === target) {
+               if (fill[0] === '-' || fill[0] === '+') {
+                       target = index + target;
                }
 
-               if (fill === false || fill === null) {
+               if (target === index || target < 0 || target >= count) {
                        return false;
                }
 
-               if (fill === true) {
-                       return 'origin';
-               }
+               return target;
+       }
 
-               target = parseFloat(fill, 10);
-               if (isFinite(target) && Math.floor(target) === target) {
-                       if (fill[0] === '-' || fill[0] === '+') {
-                               target = index + target;
-                       }
+       switch (fill) {
+       // compatibility
+       case 'bottom':
+               return 'start';
+       case 'top':
+               return 'end';
+       case 'zero':
+               return 'origin';
+       // supported boundaries
+       case 'origin':
+       case 'start':
+       case 'end':
+               return fill;
+       // invalid fill values
+       default:
+               return false;
+       }
+}
 
-                       if (target === index || target < 0 || target >= count) {
-                               return false;
-                       }
+function computeBoundary(source) {
+       var model = source.el._model || {};
+       var scale = source.el._scale || {};
+       var fill = source.fill;
+       var target = null;
+       var horizontal;
 
-                       return target;
-               }
-
-               switch (fill) {
-               // compatibility
-               case 'bottom':
-                       return 'start';
-               case 'top':
-                       return 'end';
-               case 'zero':
-                       return 'origin';
-               // supported boundaries
-               case 'origin':
-               case 'start':
-               case 'end':
-                       return fill;
-               // invalid fill values
-               default:
-                       return false;
-               }
+       if (isFinite(fill)) {
+               return null;
        }
 
-       function computeBoundary(source) {
-               var model = source.el._model || {};
-               var scale = source.el._scale || {};
-               var fill = source.fill;
-               var target = null;
-               var horizontal;
+       // Backward compatibility: until v3, we still need to support boundary values set on
+       // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and
+       // controllers might still use it (e.g. the Smith chart).
+
+       if (fill === 'start') {
+               target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom;
+       } else if (fill === 'end') {
+               target = model.scaleTop === undefined ? scale.top : model.scaleTop;
+       } else if (model.scaleZero !== undefined) {
+               target = model.scaleZero;
+       } else if (scale.getBasePosition) {
+               target = scale.getBasePosition();
+       } else if (scale.getBasePixel) {
+               target = scale.getBasePixel();
+       }
 
-               if (isFinite(fill)) {
-                       return null;
+       if (target !== undefined && target !== null) {
+               if (target.x !== undefined && target.y !== undefined) {
+                       return target;
                }
 
-               // Backward compatibility: until v3, we still need to support boundary values set on
-               // the model (scaleTop, scaleBottom and scaleZero) because some external plugins and
-               // controllers might still use it (e.g. the Smith chart).
-
-               if (fill === 'start') {
-                       target = model.scaleBottom === undefined ? scale.bottom : model.scaleBottom;
-               } else if (fill === 'end') {
-                       target = model.scaleTop === undefined ? scale.top : model.scaleTop;
-               } else if (model.scaleZero !== undefined) {
-                       target = model.scaleZero;
-               } else if (scale.getBasePosition) {
-                       target = scale.getBasePosition();
-               } else if (scale.getBasePixel) {
-                       target = scale.getBasePixel();
+               if (typeof target === 'number' && isFinite(target)) {
+                       horizontal = scale.isHorizontal();
+                       return {
+                               x: horizontal ? target : null,
+                               y: horizontal ? null : target
+                       };
                }
+       }
 
-               if (target !== undefined && target !== null) {
-                       if (target.x !== undefined && target.y !== undefined) {
-                               return target;
-                       }
+       return null;
+}
 
-                       if (typeof target === 'number' && isFinite(target)) {
-                               horizontal = scale.isHorizontal();
-                               return {
-                                       x: horizontal ? target : null,
-                                       y: horizontal ? null : target
-                               };
-                       }
-               }
+function resolveTarget(sources, index, propagate) {
+       var source = sources[index];
+       var fill = source.fill;
+       var visited = [index];
+       var target;
 
-               return null;
+       if (!propagate) {
+               return fill;
        }
 
-       function resolveTarget(sources, index, propagate) {
-               var source = sources[index];
-               var fill = source.fill;
-               var visited = [index];
-               var target;
-
-               if (!propagate) {
+       while (fill !== false && visited.indexOf(fill) === -1) {
+               if (!isFinite(fill)) {
                        return fill;
                }
 
-               while (fill !== false && visited.indexOf(fill) === -1) {
-                       if (!isFinite(fill)) {
-                               return fill;
-                       }
-
-                       target = sources[fill];
-                       if (!target) {
-                               return false;
-                       }
-
-                       if (target.visible) {
-                               return fill;
-                       }
+               target = sources[fill];
+               if (!target) {
+                       return false;
+               }
 
-                       visited.push(fill);
-                       fill = target.fill;
+               if (target.visible) {
+                       return fill;
                }
 
-               return false;
+               visited.push(fill);
+               fill = target.fill;
        }
 
-       function createMapper(source) {
-               var fill = source.fill;
-               var type = 'dataset';
-
-               if (fill === false) {
-                       return null;
-               }
+       return false;
+}
 
-               if (!isFinite(fill)) {
-                       type = 'boundary';
-               }
+function createMapper(source) {
+       var fill = source.fill;
+       var type = 'dataset';
 
-               return mappers[type](source);
+       if (fill === false) {
+               return null;
        }
 
-       function isDrawable(point) {
-               return point && !point.skip;
+       if (!isFinite(fill)) {
+               type = 'boundary';
        }
 
-       function drawArea(ctx, curve0, curve1, len0, len1) {
-               var i;
+       return mappers[type](source);
+}
 
-               if (!len0 || !len1) {
-                       return;
-               }
+function isDrawable(point) {
+       return point && !point.skip;
+}
 
-               // building first area curve (normal)
-               ctx.moveTo(curve0[0].x, curve0[0].y);
-               for (i = 1; i < len0; ++i) {
-                       helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]);
-               }
+function drawArea(ctx, curve0, curve1, len0, len1) {
+       var i;
 
-               // joining the two area curves
-               ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
+       if (!len0 || !len1) {
+               return;
+       }
 
-               // building opposite area curve (reverse)
-               for (i = len1 - 1; i > 0; --i) {
-                       helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true);
-               }
+       // building first area curve (normal)
+       ctx.moveTo(curve0[0].x, curve0[0].y);
+       for (i = 1; i < len0; ++i) {
+               helpers.canvas.lineTo(ctx, curve0[i - 1], curve0[i]);
        }
 
-       function doFill(ctx, points, mapper, view, color, loop) {
-               var count = points.length;
-               var span = view.spanGaps;
-               var curve0 = [];
-               var curve1 = [];
-               var len0 = 0;
-               var len1 = 0;
-               var i, ilen, index, p0, p1, d0, d1;
-
-               ctx.beginPath();
-
-               for (i = 0, ilen = (count + !!loop); i < ilen; ++i) {
-                       index = i % count;
-                       p0 = points[index]._view;
-                       p1 = mapper(p0, index, view);
-                       d0 = isDrawable(p0);
-                       d1 = isDrawable(p1);
-
-                       if (d0 && d1) {
-                               len0 = curve0.push(p0);
-                               len1 = curve1.push(p1);
-                       } else if (len0 && len1) {
-                               if (!span) {
-                                       drawArea(ctx, curve0, curve1, len0, len1);
-                                       len0 = len1 = 0;
-                                       curve0 = [];
-                                       curve1 = [];
-                               } else {
-                                       if (d0) {
-                                               curve0.push(p0);
-                                       }
-                                       if (d1) {
-                                               curve1.push(p1);
-                                       }
+       // joining the two area curves
+       ctx.lineTo(curve1[len1 - 1].x, curve1[len1 - 1].y);
+
+       // building opposite area curve (reverse)
+       for (i = len1 - 1; i > 0; --i) {
+               helpers.canvas.lineTo(ctx, curve1[i], curve1[i - 1], true);
+       }
+}
+
+function doFill(ctx, points, mapper, view, color, loop) {
+       var count = points.length;
+       var span = view.spanGaps;
+       var curve0 = [];
+       var curve1 = [];
+       var len0 = 0;
+       var len1 = 0;
+       var i, ilen, index, p0, p1, d0, d1;
+
+       ctx.beginPath();
+
+       for (i = 0, ilen = (count + !!loop); i < ilen; ++i) {
+               index = i % count;
+               p0 = points[index]._view;
+               p1 = mapper(p0, index, view);
+               d0 = isDrawable(p0);
+               d1 = isDrawable(p1);
+
+               if (d0 && d1) {
+                       len0 = curve0.push(p0);
+                       len1 = curve1.push(p1);
+               } else if (len0 && len1) {
+                       if (!span) {
+                               drawArea(ctx, curve0, curve1, len0, len1);
+                               len0 = len1 = 0;
+                               curve0 = [];
+                               curve1 = [];
+                       } else {
+                               if (d0) {
+                                       curve0.push(p0);
+                               }
+                               if (d1) {
+                                       curve1.push(p1);
                                }
                        }
                }
-
-               drawArea(ctx, curve0, curve1, len0, len1);
-
-               ctx.closePath();
-               ctx.fillStyle = color;
-               ctx.fill();
        }
 
-       return {
-               id: 'filler',
-
-               afterDatasetsUpdate: function(chart, options) {
-                       var count = (chart.data.datasets || []).length;
-                       var propagate = options.propagate;
-                       var sources = [];
-                       var meta, i, el, source;
-
-                       for (i = 0; i < count; ++i) {
-                               meta = chart.getDatasetMeta(i);
-                               el = meta.dataset;
-                               source = null;
-
-                               if (el && el._model && el instanceof elements.Line) {
-                                       source = {
-                                               visible: chart.isDatasetVisible(i),
-                                               fill: decodeFill(el, i, count),
-                                               chart: chart,
-                                               el: el
-                                       };
-                               }
-
-                               meta.$filler = source;
-                               sources.push(source);
+       drawArea(ctx, curve0, curve1, len0, len1);
+
+       ctx.closePath();
+       ctx.fillStyle = color;
+       ctx.fill();
+}
+
+module.exports = {
+       id: 'filler',
+
+       afterDatasetsUpdate: function(chart, options) {
+               var count = (chart.data.datasets || []).length;
+               var propagate = options.propagate;
+               var sources = [];
+               var meta, i, el, source;
+
+               for (i = 0; i < count; ++i) {
+                       meta = chart.getDatasetMeta(i);
+                       el = meta.dataset;
+                       source = null;
+
+                       if (el && el._model && el instanceof elements.Line) {
+                               source = {
+                                       visible: chart.isDatasetVisible(i),
+                                       fill: decodeFill(el, i, count),
+                                       chart: chart,
+                                       el: el
+                               };
                        }
 
-                       for (i = 0; i < count; ++i) {
-                               source = sources[i];
-                               if (!source) {
-                                       continue;
-                               }
+                       meta.$filler = source;
+                       sources.push(source);
+               }
 
-                               source.fill = resolveTarget(sources, i, propagate);
-                               source.boundary = computeBoundary(source);
-                               source.mapper = createMapper(source);
+               for (i = 0; i < count; ++i) {
+                       source = sources[i];
+                       if (!source) {
+                               continue;
                        }
-               },
 
-               beforeDatasetDraw: function(chart, args) {
-                       var meta = args.meta.$filler;
-                       if (!meta) {
-                               return;
-                       }
+                       source.fill = resolveTarget(sources, i, propagate);
+                       source.boundary = computeBoundary(source);
+                       source.mapper = createMapper(source);
+               }
+       },
 
-                       var ctx = chart.ctx;
-                       var el = meta.el;
-                       var view = el._view;
-                       var points = el._children || [];
-                       var mapper = meta.mapper;
-                       var color = view.backgroundColor || defaults.global.defaultColor;
-
-                       if (mapper && color && points.length) {
-                               helpers.canvas.clipArea(ctx, chart.chartArea);
-                               doFill(ctx, points, mapper, view, color, el._loop);
-                               helpers.canvas.unclipArea(ctx);
-                       }
+       beforeDatasetDraw: function(chart, args) {
+               var meta = args.meta.$filler;
+               if (!meta) {
+                       return;
+               }
+
+               var ctx = chart.ctx;
+               var el = meta.el;
+               var view = el._view;
+               var points = el._children || [];
+               var mapper = meta.mapper;
+               var color = view.backgroundColor || defaults.global.defaultColor;
+
+               if (mapper && color && points.length) {
+                       helpers.canvas.clipArea(ctx, chart.chartArea);
+                       doFill(ctx, points, mapper, view, color, el._loop);
+                       helpers.canvas.unclipArea(ctx);
                }
-       };
+       }
 };
index 7e0e429d0ceac5b7978b592ac1d4066049afab5f..3715ea3d5f782b06e398f072b6cb37c26fb594b4 100644 (file)
@@ -5,6 +5,8 @@ var Element = require('../core/core.element');
 var helpers = require('../helpers/index');
 var layout = require('../core/core.layout');
 
+var noop = helpers.noop;
+
 defaults._set('global', {
        legend: {
                display: true,
@@ -80,488 +82,495 @@ defaults._set('global', {
        }
 });
 
-module.exports = function(Chart) {
-
-       var noop = helpers.noop;
-
-       /**
-        * Helper function to get the box width based on the usePointStyle option
-        * @param labelopts {Object} the label options on the legend
-        * @param fontSize {Number} the label font size
-        * @return {Number} width of the color box area
-        */
-       function getBoxWidth(labelOpts, fontSize) {
-               return labelOpts.usePointStyle ?
-                       fontSize * Math.SQRT2 :
-                       labelOpts.boxWidth;
-       }
-
-       Chart.Legend = Element.extend({
-
-               initialize: function(config) {
-                       helpers.extend(this, config);
-
-                       // Contains hit boxes for each dataset (in dataset order)
-                       this.legendHitBoxes = [];
-
-                       // Are we in doughnut mode which has a different data type
-                       this.doughnutMode = false;
-               },
-
-               // These methods are ordered by lifecycle. Utilities then follow.
-               // Any function defined here is inherited by all legend types.
-               // Any function can be extended by the legend type
-
-               beforeUpdate: noop,
-               update: function(maxWidth, maxHeight, margins) {
-                       var me = this;
-
-                       // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
-                       me.beforeUpdate();
-
-                       // Absorb the master measurements
-                       me.maxWidth = maxWidth;
-                       me.maxHeight = maxHeight;
-                       me.margins = margins;
-
-                       // Dimensions
-                       me.beforeSetDimensions();
-                       me.setDimensions();
-                       me.afterSetDimensions();
-                       // Labels
-                       me.beforeBuildLabels();
-                       me.buildLabels();
-                       me.afterBuildLabels();
-
-                       // Fit
-                       me.beforeFit();
-                       me.fit();
-                       me.afterFit();
-                       //
-                       me.afterUpdate();
-
-                       return me.minSize;
-               },
-               afterUpdate: noop,
+/**
+ * Helper function to get the box width based on the usePointStyle option
+ * @param labelopts {Object} the label options on the legend
+ * @param fontSize {Number} the label font size
+ * @return {Number} width of the color box area
+ */
+function getBoxWidth(labelOpts, fontSize) {
+       return labelOpts.usePointStyle ?
+               fontSize * Math.SQRT2 :
+               labelOpts.boxWidth;
+}
+
+/**
+ * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required!
+ */
+var Legend = Element.extend({
+
+       initialize: function(config) {
+               helpers.extend(this, config);
+
+               // Contains hit boxes for each dataset (in dataset order)
+               this.legendHitBoxes = [];
+
+               // Are we in doughnut mode which has a different data type
+               this.doughnutMode = false;
+       },
 
+       // These methods are ordered by lifecycle. Utilities then follow.
+       // Any function defined here is inherited by all legend types.
+       // Any function can be extended by the legend type
+
+       beforeUpdate: noop,
+       update: function(maxWidth, maxHeight, margins) {
+               var me = this;
+
+               // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
+               me.beforeUpdate();
+
+               // Absorb the master measurements
+               me.maxWidth = maxWidth;
+               me.maxHeight = maxHeight;
+               me.margins = margins;
+
+               // Dimensions
+               me.beforeSetDimensions();
+               me.setDimensions();
+               me.afterSetDimensions();
+               // Labels
+               me.beforeBuildLabels();
+               me.buildLabels();
+               me.afterBuildLabels();
+
+               // Fit
+               me.beforeFit();
+               me.fit();
+               me.afterFit();
                //
+               me.afterUpdate();
 
-               beforeSetDimensions: noop,
-               setDimensions: function() {
-                       var me = this;
-                       // Set the unconstrained dimension before label rotation
-                       if (me.isHorizontal()) {
-                               // Reset position before calculating rotation
-                               me.width = me.maxWidth;
-                               me.left = 0;
-                               me.right = me.width;
-                       } else {
-                               me.height = me.maxHeight;
+               return me.minSize;
+       },
+       afterUpdate: noop,
+
+       //
+
+       beforeSetDimensions: noop,
+       setDimensions: function() {
+               var me = this;
+               // Set the unconstrained dimension before label rotation
+               if (me.isHorizontal()) {
+                       // Reset position before calculating rotation
+                       me.width = me.maxWidth;
+                       me.left = 0;
+                       me.right = me.width;
+               } else {
+                       me.height = me.maxHeight;
+
+                       // Reset position before calculating rotation
+                       me.top = 0;
+                       me.bottom = me.height;
+               }
 
-                               // Reset position before calculating rotation
-                               me.top = 0;
-                               me.bottom = me.height;
-                       }
+               // Reset padding
+               me.paddingLeft = 0;
+               me.paddingTop = 0;
+               me.paddingRight = 0;
+               me.paddingBottom = 0;
+
+               // Reset minSize
+               me.minSize = {
+                       width: 0,
+                       height: 0
+               };
+       },
+       afterSetDimensions: noop,
 
-                       // Reset padding
-                       me.paddingLeft = 0;
-                       me.paddingTop = 0;
-                       me.paddingRight = 0;
-                       me.paddingBottom = 0;
+       //
 
-                       // Reset minSize
-                       me.minSize = {
-                               width: 0,
-                               height: 0
-                       };
-               },
-               afterSetDimensions: noop,
+       beforeBuildLabels: noop,
+       buildLabels: function() {
+               var me = this;
+               var labelOpts = me.options.labels || {};
+               var legendItems = helpers.callback(labelOpts.generateLabels, [me.chart], me) || [];
 
-               //
+               if (labelOpts.filter) {
+                       legendItems = legendItems.filter(function(item) {
+                               return labelOpts.filter(item, me.chart.data);
+                       });
+               }
 
-               beforeBuildLabels: noop,
-               buildLabels: function() {
-                       var me = this;
-                       var labelOpts = me.options.labels || {};
-                       var legendItems = helpers.callback(labelOpts.generateLabels, [me.chart], me) || [];
+               if (me.options.reverse) {
+                       legendItems.reverse();
+               }
 
-                       if (labelOpts.filter) {
-                               legendItems = legendItems.filter(function(item) {
-                                       return labelOpts.filter(item, me.chart.data);
-                               });
-                       }
+               me.legendItems = legendItems;
+       },
+       afterBuildLabels: noop,
+
+       //
+
+       beforeFit: noop,
+       fit: function() {
+               var me = this;
+               var opts = me.options;
+               var labelOpts = opts.labels;
+               var display = opts.display;
+
+               var ctx = me.ctx;
+
+               var globalDefault = defaults.global;
+               var valueOrDefault = helpers.valueOrDefault;
+               var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
+               var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
+               var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
+               var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
+
+               // Reset hit boxes
+               var hitboxes = me.legendHitBoxes = [];
+
+               var minSize = me.minSize;
+               var isHorizontal = me.isHorizontal();
+
+               if (isHorizontal) {
+                       minSize.width = me.maxWidth; // fill all the width
+                       minSize.height = display ? 10 : 0;
+               } else {
+                       minSize.width = display ? 10 : 0;
+                       minSize.height = me.maxHeight; // fill all the height
+               }
 
-                       if (me.options.reverse) {
-                               legendItems.reverse();
-                       }
+               // Increase sizes here
+               if (display) {
+                       ctx.font = labelFont;
 
-                       me.legendItems = legendItems;
-               },
-               afterBuildLabels: noop,
+                       if (isHorizontal) {
+                               // Labels
 
-               //
+                               // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
+                               var lineWidths = me.lineWidths = [0];
+                               var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0;
 
-               beforeFit: noop,
-               fit: function() {
-                       var me = this;
-                       var opts = me.options;
-                       var labelOpts = opts.labels;
-                       var display = opts.display;
+                               ctx.textAlign = 'left';
+                               ctx.textBaseline = 'top';
 
-                       var ctx = me.ctx;
+                               helpers.each(me.legendItems, function(legendItem, i) {
+                                       var boxWidth = getBoxWidth(labelOpts, fontSize);
+                                       var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
 
-                       var globalDefault = defaults.global;
-                       var valueOrDefault = helpers.valueOrDefault;
-                       var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
-                       var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
-                       var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
-                       var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
+                                       if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {
+                                               totalHeight += fontSize + (labelOpts.padding);
+                                               lineWidths[lineWidths.length] = me.left;
+                                       }
 
-                       // Reset hit boxes
-                       var hitboxes = me.legendHitBoxes = [];
+                                       // Store the hitbox width and height here. Final position will be updated in `draw`
+                                       hitboxes[i] = {
+                                               left: 0,
+                                               top: 0,
+                                               width: width,
+                                               height: fontSize
+                                       };
 
-                       var minSize = me.minSize;
-                       var isHorizontal = me.isHorizontal();
+                                       lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
+                               });
 
-                       if (isHorizontal) {
-                               minSize.width = me.maxWidth; // fill all the width
-                               minSize.height = display ? 10 : 0;
-                       } else {
-                               minSize.width = display ? 10 : 0;
-                               minSize.height = me.maxHeight; // fill all the height
-                       }
+                               minSize.height += totalHeight;
 
-                       // Increase sizes here
-                       if (display) {
-                               ctx.font = labelFont;
+                       } else {
+                               var vPadding = labelOpts.padding;
+                               var columnWidths = me.columnWidths = [];
+                               var totalWidth = labelOpts.padding;
+                               var currentColWidth = 0;
+                               var currentColHeight = 0;
+                               var itemHeight = fontSize + vPadding;
 
-                               if (isHorizontal) {
-                                       // Labels
+                               helpers.each(me.legendItems, function(legendItem, i) {
+                                       var boxWidth = getBoxWidth(labelOpts, fontSize);
+                                       var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
 
-                                       // Width of each line of legend boxes. Labels wrap onto multiple lines when there are too many to fit on one
-                                       var lineWidths = me.lineWidths = [0];
-                                       var totalHeight = me.legendItems.length ? fontSize + (labelOpts.padding) : 0;
+                                       // If too tall, go to new column
+                                       if (currentColHeight + itemHeight > minSize.height) {
+                                               totalWidth += currentColWidth + labelOpts.padding;
+                                               columnWidths.push(currentColWidth); // previous column width
 
-                                       ctx.textAlign = 'left';
-                                       ctx.textBaseline = 'top';
+                                               currentColWidth = 0;
+                                               currentColHeight = 0;
+                                       }
 
-                                       helpers.each(me.legendItems, function(legendItem, i) {
-                                               var boxWidth = getBoxWidth(labelOpts, fontSize);
-                                               var width = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
+                                       // Get max width
+                                       currentColWidth = Math.max(currentColWidth, itemWidth);
+                                       currentColHeight += itemHeight;
 
-                                               if (lineWidths[lineWidths.length - 1] + width + labelOpts.padding >= me.width) {
-                                                       totalHeight += fontSize + (labelOpts.padding);
-                                                       lineWidths[lineWidths.length] = me.left;
-                                               }
+                                       // Store the hitbox width and height here. Final position will be updated in `draw`
+                                       hitboxes[i] = {
+                                               left: 0,
+                                               top: 0,
+                                               width: itemWidth,
+                                               height: fontSize
+                                       };
+                               });
 
-                                               // Store the hitbox width and height here. Final position will be updated in `draw`
-                                               hitboxes[i] = {
-                                                       left: 0,
-                                                       top: 0,
-                                                       width: width,
-                                                       height: fontSize
-                                               };
+                               totalWidth += currentColWidth;
+                               columnWidths.push(currentColWidth);
+                               minSize.width += totalWidth;
+                       }
+               }
 
-                                               lineWidths[lineWidths.length - 1] += width + labelOpts.padding;
-                                       });
+               me.width = minSize.width;
+               me.height = minSize.height;
+       },
+       afterFit: noop,
 
-                                       minSize.height += totalHeight;
+       // Shared Methods
+       isHorizontal: function() {
+               return this.options.position === 'top' || this.options.position === 'bottom';
+       },
 
-                               } else {
-                                       var vPadding = labelOpts.padding;
-                                       var columnWidths = me.columnWidths = [];
-                                       var totalWidth = labelOpts.padding;
-                                       var currentColWidth = 0;
-                                       var currentColHeight = 0;
-                                       var itemHeight = fontSize + vPadding;
-
-                                       helpers.each(me.legendItems, function(legendItem, i) {
-                                               var boxWidth = getBoxWidth(labelOpts, fontSize);
-                                               var itemWidth = boxWidth + (fontSize / 2) + ctx.measureText(legendItem.text).width;
-
-                                               // If too tall, go to new column
-                                               if (currentColHeight + itemHeight > minSize.height) {
-                                                       totalWidth += currentColWidth + labelOpts.padding;
-                                                       columnWidths.push(currentColWidth); // previous column width
-
-                                                       currentColWidth = 0;
-                                                       currentColHeight = 0;
-                                               }
-
-                                               // Get max width
-                                               currentColWidth = Math.max(currentColWidth, itemWidth);
-                                               currentColHeight += itemHeight;
-
-                                               // Store the hitbox width and height here. Final position will be updated in `draw`
-                                               hitboxes[i] = {
-                                                       left: 0,
-                                                       top: 0,
-                                                       width: itemWidth,
-                                                       height: fontSize
-                                               };
-                                       });
-
-                                       totalWidth += currentColWidth;
-                                       columnWidths.push(currentColWidth);
-                                       minSize.width += totalWidth;
+       // Actually draw the legend on the canvas
+       draw: function() {
+               var me = this;
+               var opts = me.options;
+               var labelOpts = opts.labels;
+               var globalDefault = defaults.global;
+               var lineDefault = globalDefault.elements.line;
+               var legendWidth = me.width;
+               var lineWidths = me.lineWidths;
+
+               if (opts.display) {
+                       var ctx = me.ctx;
+                       var valueOrDefault = helpers.valueOrDefault;
+                       var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor);
+                       var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
+                       var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
+                       var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
+                       var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
+                       var cursor;
+
+                       // Canvas setup
+                       ctx.textAlign = 'left';
+                       ctx.textBaseline = 'middle';
+                       ctx.lineWidth = 0.5;
+                       ctx.strokeStyle = fontColor; // for strikethrough effect
+                       ctx.fillStyle = fontColor; // render in correct colour
+                       ctx.font = labelFont;
+
+                       var boxWidth = getBoxWidth(labelOpts, fontSize);
+                       var hitboxes = me.legendHitBoxes;
+
+                       // current position
+                       var drawLegendBox = function(x, y, legendItem) {
+                               if (isNaN(boxWidth) || boxWidth <= 0) {
+                                       return;
                                }
-                       }
-
-                       me.width = minSize.width;
-                       me.height = minSize.height;
-               },
-               afterFit: noop,
 
-               // Shared Methods
-               isHorizontal: function() {
-                       return this.options.position === 'top' || this.options.position === 'bottom';
-               },
+                               // Set the ctx for the box
+                               ctx.save();
 
-               // Actually draw the legend on the canvas
-               draw: function() {
-                       var me = this;
-                       var opts = me.options;
-                       var labelOpts = opts.labels;
-                       var globalDefault = defaults.global;
-                       var lineDefault = globalDefault.elements.line;
-                       var legendWidth = me.width;
-                       var lineWidths = me.lineWidths;
-
-                       if (opts.display) {
-                               var ctx = me.ctx;
-                               var valueOrDefault = helpers.valueOrDefault;
-                               var fontColor = valueOrDefault(labelOpts.fontColor, globalDefault.defaultFontColor);
-                               var fontSize = valueOrDefault(labelOpts.fontSize, globalDefault.defaultFontSize);
-                               var fontStyle = valueOrDefault(labelOpts.fontStyle, globalDefault.defaultFontStyle);
-                               var fontFamily = valueOrDefault(labelOpts.fontFamily, globalDefault.defaultFontFamily);
-                               var labelFont = helpers.fontString(fontSize, fontStyle, fontFamily);
-                               var cursor;
-
-                               // Canvas setup
-                               ctx.textAlign = 'left';
-                               ctx.textBaseline = 'middle';
-                               ctx.lineWidth = 0.5;
-                               ctx.strokeStyle = fontColor; // for strikethrough effect
-                               ctx.fillStyle = fontColor; // render in correct colour
-                               ctx.font = labelFont;
-
-                               var boxWidth = getBoxWidth(labelOpts, fontSize);
-                               var hitboxes = me.legendHitBoxes;
-
-                               // current position
-                               var drawLegendBox = function(x, y, legendItem) {
-                                       if (isNaN(boxWidth) || boxWidth <= 0) {
-                                               return;
-                                       }
+                               ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor);
+                               ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);
+                               ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);
+                               ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);
+                               ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth);
+                               ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor);
+                               var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0);
 
-                                       // Set the ctx for the box
-                                       ctx.save();
+                               if (ctx.setLineDash) {
+                                       // IE 9 and 10 do not support line dash
+                                       ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash));
+                               }
 
-                                       ctx.fillStyle = valueOrDefault(legendItem.fillStyle, globalDefault.defaultColor);
-                                       ctx.lineCap = valueOrDefault(legendItem.lineCap, lineDefault.borderCapStyle);
-                                       ctx.lineDashOffset = valueOrDefault(legendItem.lineDashOffset, lineDefault.borderDashOffset);
-                                       ctx.lineJoin = valueOrDefault(legendItem.lineJoin, lineDefault.borderJoinStyle);
-                                       ctx.lineWidth = valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth);
-                                       ctx.strokeStyle = valueOrDefault(legendItem.strokeStyle, globalDefault.defaultColor);
-                                       var isLineWidthZero = (valueOrDefault(legendItem.lineWidth, lineDefault.borderWidth) === 0);
+                               if (opts.labels && opts.labels.usePointStyle) {
+                                       // Recalculate x and y for drawPoint() because its expecting
+                                       // x and y to be center of figure (instead of top left)
+                                       var radius = fontSize * Math.SQRT2 / 2;
+                                       var offSet = radius / Math.SQRT2;
+                                       var centerX = x + offSet;
+                                       var centerY = y + offSet;
 
-                                       if (ctx.setLineDash) {
-                                               // IE 9 and 10 do not support line dash
-                                               ctx.setLineDash(valueOrDefault(legendItem.lineDash, lineDefault.borderDash));
+                                       // Draw pointStyle as legend symbol
+                                       helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
+                               } else {
+                                       // Draw box as legend symbol
+                                       if (!isLineWidthZero) {
+                                               ctx.strokeRect(x, y, boxWidth, fontSize);
                                        }
+                                       ctx.fillRect(x, y, boxWidth, fontSize);
+                               }
 
-                                       if (opts.labels && opts.labels.usePointStyle) {
-                                               // Recalculate x and y for drawPoint() because its expecting
-                                               // x and y to be center of figure (instead of top left)
-                                               var radius = fontSize * Math.SQRT2 / 2;
-                                               var offSet = radius / Math.SQRT2;
-                                               var centerX = x + offSet;
-                                               var centerY = y + offSet;
-
-                                               // Draw pointStyle as legend symbol
-                                               helpers.canvas.drawPoint(ctx, legendItem.pointStyle, radius, centerX, centerY);
-                                       } else {
-                                               // Draw box as legend symbol
-                                               if (!isLineWidthZero) {
-                                                       ctx.strokeRect(x, y, boxWidth, fontSize);
-                                               }
-                                               ctx.fillRect(x, y, boxWidth, fontSize);
-                                       }
+                               ctx.restore();
+                       };
+                       var fillText = function(x, y, legendItem, textWidth) {
+                               var halfFontSize = fontSize / 2;
+                               var xLeft = boxWidth + halfFontSize + x;
+                               var yMiddle = y + halfFontSize;
+
+                               ctx.fillText(legendItem.text, xLeft, yMiddle);
+
+                               if (legendItem.hidden) {
+                                       // Strikethrough the text if hidden
+                                       ctx.beginPath();
+                                       ctx.lineWidth = 2;
+                                       ctx.moveTo(xLeft, yMiddle);
+                                       ctx.lineTo(xLeft + textWidth, yMiddle);
+                                       ctx.stroke();
+                               }
+                       };
 
-                                       ctx.restore();
+                       // Horizontal
+                       var isHorizontal = me.isHorizontal();
+                       if (isHorizontal) {
+                               cursor = {
+                                       x: me.left + ((legendWidth - lineWidths[0]) / 2),
+                                       y: me.top + labelOpts.padding,
+                                       line: 0
                                };
-                               var fillText = function(x, y, legendItem, textWidth) {
-                                       var halfFontSize = fontSize / 2;
-                                       var xLeft = boxWidth + halfFontSize + x;
-                                       var yMiddle = y + halfFontSize;
-
-                                       ctx.fillText(legendItem.text, xLeft, yMiddle);
-
-                                       if (legendItem.hidden) {
-                                               // Strikethrough the text if hidden
-                                               ctx.beginPath();
-                                               ctx.lineWidth = 2;
-                                               ctx.moveTo(xLeft, yMiddle);
-                                               ctx.lineTo(xLeft + textWidth, yMiddle);
-                                               ctx.stroke();
-                                       }
+                       } else {
+                               cursor = {
+                                       x: me.left + labelOpts.padding,
+                                       y: me.top + labelOpts.padding,
+                                       line: 0
                                };
+                       }
 
-                               // Horizontal
-                               var isHorizontal = me.isHorizontal();
-                               if (isHorizontal) {
-                                       cursor = {
-                                               x: me.left + ((legendWidth - lineWidths[0]) / 2),
-                                               y: me.top + labelOpts.padding,
-                                               line: 0
-                                       };
-                               } else {
-                                       cursor = {
-                                               x: me.left + labelOpts.padding,
-                                               y: me.top + labelOpts.padding,
-                                               line: 0
-                                       };
-                               }
+                       var itemHeight = fontSize + labelOpts.padding;
+                       helpers.each(me.legendItems, function(legendItem, i) {
+                               var textWidth = ctx.measureText(legendItem.text).width;
+                               var width = boxWidth + (fontSize / 2) + textWidth;
+                               var x = cursor.x;
+                               var y = cursor.y;
 
-                               var itemHeight = fontSize + labelOpts.padding;
-                               helpers.each(me.legendItems, function(legendItem, i) {
-                                       var textWidth = ctx.measureText(legendItem.text).width;
-                                       var width = boxWidth + (fontSize / 2) + textWidth;
-                                       var x = cursor.x;
-                                       var y = cursor.y;
-
-                                       if (isHorizontal) {
-                                               if (x + width >= legendWidth) {
-                                                       y = cursor.y += itemHeight;
-                                                       cursor.line++;
-                                                       x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2);
-                                               }
-                                       } else if (y + itemHeight > me.bottom) {
-                                               x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
-                                               y = cursor.y = me.top + labelOpts.padding;
+                               if (isHorizontal) {
+                                       if (x + width >= legendWidth) {
+                                               y = cursor.y += itemHeight;
                                                cursor.line++;
+                                               x = cursor.x = me.left + ((legendWidth - lineWidths[cursor.line]) / 2);
                                        }
+                               } else if (y + itemHeight > me.bottom) {
+                                       x = cursor.x = x + me.columnWidths[cursor.line] + labelOpts.padding;
+                                       y = cursor.y = me.top + labelOpts.padding;
+                                       cursor.line++;
+                               }
 
-                                       drawLegendBox(x, y, legendItem);
+                               drawLegendBox(x, y, legendItem);
 
-                                       hitboxes[i].left = x;
-                                       hitboxes[i].top = y;
+                               hitboxes[i].left = x;
+                               hitboxes[i].top = y;
 
-                                       // Fill the actual label
-                                       fillText(x, y, legendItem, textWidth);
+                               // Fill the actual label
+                               fillText(x, y, legendItem, textWidth);
 
-                                       if (isHorizontal) {
-                                               cursor.x += width + (labelOpts.padding);
-                                       } else {
-                                               cursor.y += itemHeight;
-                                       }
+                               if (isHorizontal) {
+                                       cursor.x += width + (labelOpts.padding);
+                               } else {
+                                       cursor.y += itemHeight;
+                               }
 
-                               });
-                       }
-               },
+                       });
+               }
+       },
 
-               /**
-                * Handle an event
-                * @private
-                * @param {IEvent} event - The event to handle
-                * @return {Boolean} true if a change occured
-                */
-               handleEvent: function(e) {
-                       var me = this;
-                       var opts = me.options;
-                       var type = e.type === 'mouseup' ? 'click' : e.type;
-                       var changed = false;
-
-                       if (type === 'mousemove') {
-                               if (!opts.onHover) {
-                                       return;
-                               }
-                       } else if (type === 'click') {
-                               if (!opts.onClick) {
-                                       return;
-                               }
-                       } else {
+       /**
+        * Handle an event
+        * @private
+        * @param {IEvent} event - The event to handle
+        * @return {Boolean} true if a change occured
+        */
+       handleEvent: function(e) {
+               var me = this;
+               var opts = me.options;
+               var type = e.type === 'mouseup' ? 'click' : e.type;
+               var changed = false;
+
+               if (type === 'mousemove') {
+                       if (!opts.onHover) {
+                               return;
+                       }
+               } else if (type === 'click') {
+                       if (!opts.onClick) {
                                return;
                        }
+               } else {
+                       return;
+               }
 
-                       // Chart event already has relative position in it
-                       var x = e.x;
-                       var y = e.y;
-
-                       if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
-                               // See if we are touching one of the dataset boxes
-                               var lh = me.legendHitBoxes;
-                               for (var i = 0; i < lh.length; ++i) {
-                                       var hitBox = lh[i];
-
-                                       if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
-                                               // Touching an element
-                                               if (type === 'click') {
-                                                       // use e.native for backwards compatibility
-                                                       opts.onClick.call(me, e.native, me.legendItems[i]);
-                                                       changed = true;
-                                                       break;
-                                               } else if (type === 'mousemove') {
-                                                       // use e.native for backwards compatibility
-                                                       opts.onHover.call(me, e.native, me.legendItems[i]);
-                                                       changed = true;
-                                                       break;
-                                               }
+               // Chart event already has relative position in it
+               var x = e.x;
+               var y = e.y;
+
+               if (x >= me.left && x <= me.right && y >= me.top && y <= me.bottom) {
+                       // See if we are touching one of the dataset boxes
+                       var lh = me.legendHitBoxes;
+                       for (var i = 0; i < lh.length; ++i) {
+                               var hitBox = lh[i];
+
+                               if (x >= hitBox.left && x <= hitBox.left + hitBox.width && y >= hitBox.top && y <= hitBox.top + hitBox.height) {
+                                       // Touching an element
+                                       if (type === 'click') {
+                                               // use e.native for backwards compatibility
+                                               opts.onClick.call(me, e.native, me.legendItems[i]);
+                                               changed = true;
+                                               break;
+                                       } else if (type === 'mousemove') {
+                                               // use e.native for backwards compatibility
+                                               opts.onHover.call(me, e.native, me.legendItems[i]);
+                                               changed = true;
+                                               break;
                                        }
                                }
                        }
-
-                       return changed;
                }
-       });
 
-       function createNewLegendAndAttach(chart, legendOpts) {
-               var legend = new Chart.Legend({
-                       ctx: chart.ctx,
-                       options: legendOpts,
-                       chart: chart
-               });
-
-               layout.configure(chart, legend, legendOpts);
-               layout.addBox(chart, legend);
-               chart.legend = legend;
+               return changed;
        }
+});
 
-       return {
-               id: 'legend',
+function createNewLegendAndAttach(chart, legendOpts) {
+       var legend = new Legend({
+               ctx: chart.ctx,
+               options: legendOpts,
+               chart: chart
+       });
 
-               beforeInit: function(chart) {
-                       var legendOpts = chart.options.legend;
+       layout.configure(chart, legend, legendOpts);
+       layout.addBox(chart, legend);
+       chart.legend = legend;
+}
 
-                       if (legendOpts) {
-                               createNewLegendAndAttach(chart, legendOpts);
-                       }
-               },
+module.exports = {
+       id: 'legend',
 
-               beforeUpdate: function(chart) {
-                       var legendOpts = chart.options.legend;
-                       var legend = chart.legend;
+       /**
+        * Backward compatibility: since 2.1.5, the legend is registered as a plugin, making
+        * Chart.Legend obsolete. To avoid a breaking change, we export the Legend as part of
+        * the plugin, which one will be re-exposed in the chart.js file.
+        * https://github.com/chartjs/Chart.js/pull/2640
+        * @private
+        */
+       _element: Legend,
 
-                       if (legendOpts) {
-                               helpers.mergeIf(legendOpts, defaults.global.legend);
+       beforeInit: function(chart) {
+               var legendOpts = chart.options.legend;
 
-                               if (legend) {
-                                       layout.configure(chart, legend, legendOpts);
-                                       legend.options = legendOpts;
-                               } else {
-                                       createNewLegendAndAttach(chart, legendOpts);
-                               }
-                       } else if (legend) {
-                               layout.removeBox(chart, legend);
-                               delete chart.legend;
-                       }
-               },
+               if (legendOpts) {
+                       createNewLegendAndAttach(chart, legendOpts);
+               }
+       },
+
+       beforeUpdate: function(chart) {
+               var legendOpts = chart.options.legend;
+               var legend = chart.legend;
+
+               if (legendOpts) {
+                       helpers.mergeIf(legendOpts, defaults.global.legend);
 
-               afterEvent: function(chart, e) {
-                       var legend = chart.legend;
                        if (legend) {
-                               legend.handleEvent(e);
+                               layout.configure(chart, legend, legendOpts);
+                               legend.options = legendOpts;
+                       } else {
+                               createNewLegendAndAttach(chart, legendOpts);
                        }
+               } else if (legend) {
+                       layout.removeBox(chart, legend);
+                       delete chart.legend;
                }
-       };
+       },
+
+       afterEvent: function(chart, e) {
+               var legend = chart.legend;
+               if (legend) {
+                       legend.handleEvent(e);
+               }
+       }
 };
index 8eac103f542f3805009cc886a5c7b3fbf2447a85..0a233f9bca4443f79218d37b39d5c1b48af6c73f 100644 (file)
@@ -5,6 +5,8 @@ var Element = require('../core/core.element');
 var helpers = require('../helpers/index');
 var layout = require('../core/core.layout');
 
+var noop = helpers.noop;
+
 defaults._set('global', {
        title: {
                display: false,
@@ -18,226 +20,233 @@ defaults._set('global', {
        }
 });
 
-module.exports = function(Chart) {
-
-       var noop = helpers.noop;
-
-       Chart.Title = Element.extend({
-               initialize: function(config) {
-                       var me = this;
-                       helpers.extend(me, config);
-
-                       // Contains hit boxes for each dataset (in dataset order)
-                       me.legendHitBoxes = [];
-               },
-
-               // These methods are ordered by lifecycle. Utilities then follow.
-
-               beforeUpdate: noop,
-               update: function(maxWidth, maxHeight, margins) {
-                       var me = this;
-
-                       // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
-                       me.beforeUpdate();
-
-                       // Absorb the master measurements
-                       me.maxWidth = maxWidth;
-                       me.maxHeight = maxHeight;
-                       me.margins = margins;
-
-                       // Dimensions
-                       me.beforeSetDimensions();
-                       me.setDimensions();
-                       me.afterSetDimensions();
-                       // Labels
-                       me.beforeBuildLabels();
-                       me.buildLabels();
-                       me.afterBuildLabels();
-
-                       // Fit
-                       me.beforeFit();
-                       me.fit();
-                       me.afterFit();
-                       //
-                       me.afterUpdate();
-
-                       return me.minSize;
-
-               },
-               afterUpdate: noop,
-
-               //
-
-               beforeSetDimensions: noop,
-               setDimensions: function() {
-                       var me = this;
-                       // Set the unconstrained dimension before label rotation
-                       if (me.isHorizontal()) {
-                               // Reset position before calculating rotation
-                               me.width = me.maxWidth;
-                               me.left = 0;
-                               me.right = me.width;
-                       } else {
-                               me.height = me.maxHeight;
-
-                               // Reset position before calculating rotation
-                               me.top = 0;
-                               me.bottom = me.height;
-                       }
-
-                       // Reset padding
-                       me.paddingLeft = 0;
-                       me.paddingTop = 0;
-                       me.paddingRight = 0;
-                       me.paddingBottom = 0;
-
-                       // Reset minSize
-                       me.minSize = {
-                               width: 0,
-                               height: 0
-                       };
-               },
-               afterSetDimensions: noop,
-
+/**
+ * IMPORTANT: this class is exposed publicly as Chart.Legend, backward compatibility required!
+ */
+var Title = Element.extend({
+       initialize: function(config) {
+               var me = this;
+               helpers.extend(me, config);
+
+               // Contains hit boxes for each dataset (in dataset order)
+               me.legendHitBoxes = [];
+       },
+
+       // These methods are ordered by lifecycle. Utilities then follow.
+
+       beforeUpdate: noop,
+       update: function(maxWidth, maxHeight, margins) {
+               var me = this;
+
+               // Update Lifecycle - Probably don't want to ever extend or overwrite this function ;)
+               me.beforeUpdate();
+
+               // Absorb the master measurements
+               me.maxWidth = maxWidth;
+               me.maxHeight = maxHeight;
+               me.margins = margins;
+
+               // Dimensions
+               me.beforeSetDimensions();
+               me.setDimensions();
+               me.afterSetDimensions();
+               // Labels
+               me.beforeBuildLabels();
+               me.buildLabels();
+               me.afterBuildLabels();
+
+               // Fit
+               me.beforeFit();
+               me.fit();
+               me.afterFit();
                //
+               me.afterUpdate();
+
+               return me.minSize;
+
+       },
+       afterUpdate: noop,
+
+       //
+
+       beforeSetDimensions: noop,
+       setDimensions: function() {
+               var me = this;
+               // Set the unconstrained dimension before label rotation
+               if (me.isHorizontal()) {
+                       // Reset position before calculating rotation
+                       me.width = me.maxWidth;
+                       me.left = 0;
+                       me.right = me.width;
+               } else {
+                       me.height = me.maxHeight;
+
+                       // Reset position before calculating rotation
+                       me.top = 0;
+                       me.bottom = me.height;
+               }
 
-               beforeBuildLabels: noop,
-               buildLabels: noop,
-               afterBuildLabels: noop,
-
-               //
+               // Reset padding
+               me.paddingLeft = 0;
+               me.paddingTop = 0;
+               me.paddingRight = 0;
+               me.paddingBottom = 0;
+
+               // Reset minSize
+               me.minSize = {
+                       width: 0,
+                       height: 0
+               };
+       },
+       afterSetDimensions: noop,
+
+       //
+
+       beforeBuildLabels: noop,
+       buildLabels: noop,
+       afterBuildLabels: noop,
+
+       //
+
+       beforeFit: noop,
+       fit: function() {
+               var me = this;
+               var valueOrDefault = helpers.valueOrDefault;
+               var opts = me.options;
+               var display = opts.display;
+               var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize);
+               var minSize = me.minSize;
+               var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1;
+               var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
+               var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0;
+
+               if (me.isHorizontal()) {
+                       minSize.width = me.maxWidth; // fill all the width
+                       minSize.height = textSize;
+               } else {
+                       minSize.width = textSize;
+                       minSize.height = me.maxHeight; // fill all the height
+               }
 
-               beforeFit: noop,
-               fit: function() {
-                       var me = this;
-                       var valueOrDefault = helpers.valueOrDefault;
-                       var opts = me.options;
-                       var display = opts.display;
-                       var fontSize = valueOrDefault(opts.fontSize, defaults.global.defaultFontSize);
-                       var minSize = me.minSize;
-                       var lineCount = helpers.isArray(opts.text) ? opts.text.length : 1;
+               me.width = minSize.width;
+               me.height = minSize.height;
+
+       },
+       afterFit: noop,
+
+       // Shared Methods
+       isHorizontal: function() {
+               var pos = this.options.position;
+               return pos === 'top' || pos === 'bottom';
+       },
+
+       // Actually draw the title block on the canvas
+       draw: function() {
+               var me = this;
+               var ctx = me.ctx;
+               var valueOrDefault = helpers.valueOrDefault;
+               var opts = me.options;
+               var globalDefaults = defaults.global;
+
+               if (opts.display) {
+                       var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize);
+                       var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle);
+                       var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily);
+                       var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily);
                        var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
-                       var textSize = display ? (lineCount * lineHeight) + (opts.padding * 2) : 0;
-
+                       var offset = lineHeight / 2 + opts.padding;
+                       var rotation = 0;
+                       var top = me.top;
+                       var left = me.left;
+                       var bottom = me.bottom;
+                       var right = me.right;
+                       var maxWidth, titleX, titleY;
+
+                       ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour
+                       ctx.font = titleFont;
+
+                       // Horizontal
                        if (me.isHorizontal()) {
-                               minSize.width = me.maxWidth; // fill all the width
-                               minSize.height = textSize;
+                               titleX = left + ((right - left) / 2); // midpoint of the width
+                               titleY = top + offset;
+                               maxWidth = right - left;
                        } else {
-                               minSize.width = textSize;
-                               minSize.height = me.maxHeight; // fill all the height
+                               titleX = opts.position === 'left' ? left + offset : right - offset;
+                               titleY = top + ((bottom - top) / 2);
+                               maxWidth = bottom - top;
+                               rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
                        }
 
-                       me.width = minSize.width;
-                       me.height = minSize.height;
-
-               },
-               afterFit: noop,
-
-               // Shared Methods
-               isHorizontal: function() {
-                       var pos = this.options.position;
-                       return pos === 'top' || pos === 'bottom';
-               },
-
-               // Actually draw the title block on the canvas
-               draw: function() {
-                       var me = this;
-                       var ctx = me.ctx;
-                       var valueOrDefault = helpers.valueOrDefault;
-                       var opts = me.options;
-                       var globalDefaults = defaults.global;
-
-                       if (opts.display) {
-                               var fontSize = valueOrDefault(opts.fontSize, globalDefaults.defaultFontSize);
-                               var fontStyle = valueOrDefault(opts.fontStyle, globalDefaults.defaultFontStyle);
-                               var fontFamily = valueOrDefault(opts.fontFamily, globalDefaults.defaultFontFamily);
-                               var titleFont = helpers.fontString(fontSize, fontStyle, fontFamily);
-                               var lineHeight = helpers.options.toLineHeight(opts.lineHeight, fontSize);
-                               var offset = lineHeight / 2 + opts.padding;
-                               var rotation = 0;
-                               var top = me.top;
-                               var left = me.left;
-                               var bottom = me.bottom;
-                               var right = me.right;
-                               var maxWidth, titleX, titleY;
-
-                               ctx.fillStyle = valueOrDefault(opts.fontColor, globalDefaults.defaultFontColor); // render in correct colour
-                               ctx.font = titleFont;
-
-                               // Horizontal
-                               if (me.isHorizontal()) {
-                                       titleX = left + ((right - left) / 2); // midpoint of the width
-                                       titleY = top + offset;
-                                       maxWidth = right - left;
-                               } else {
-                                       titleX = opts.position === 'left' ? left + offset : right - offset;
-                                       titleY = top + ((bottom - top) / 2);
-                                       maxWidth = bottom - top;
-                                       rotation = Math.PI * (opts.position === 'left' ? -0.5 : 0.5);
+                       ctx.save();
+                       ctx.translate(titleX, titleY);
+                       ctx.rotate(rotation);
+                       ctx.textAlign = 'center';
+                       ctx.textBaseline = 'middle';
+
+                       var text = opts.text;
+                       if (helpers.isArray(text)) {
+                               var y = 0;
+                               for (var i = 0; i < text.length; ++i) {
+                                       ctx.fillText(text[i], 0, y, maxWidth);
+                                       y += lineHeight;
                                }
-
-                               ctx.save();
-                               ctx.translate(titleX, titleY);
-                               ctx.rotate(rotation);
-                               ctx.textAlign = 'center';
-                               ctx.textBaseline = 'middle';
-
-                               var text = opts.text;
-                               if (helpers.isArray(text)) {
-                                       var y = 0;
-                                       for (var i = 0; i < text.length; ++i) {
-                                               ctx.fillText(text[i], 0, y, maxWidth);
-                                               y += lineHeight;
-                                       }
-                               } else {
-                                       ctx.fillText(text, 0, 0, maxWidth);
-                               }
-
-                               ctx.restore();
+                       } else {
+                               ctx.fillText(text, 0, 0, maxWidth);
                        }
+
+                       ctx.restore();
                }
+       }
+});
+
+function createNewTitleBlockAndAttach(chart, titleOpts) {
+       var title = new Title({
+               ctx: chart.ctx,
+               options: titleOpts,
+               chart: chart
        });
 
-       function createNewTitleBlockAndAttach(chart, titleOpts) {
-               var title = new Chart.Title({
-                       ctx: chart.ctx,
-                       options: titleOpts,
-                       chart: chart
-               });
+       layout.configure(chart, title, titleOpts);
+       layout.addBox(chart, title);
+       chart.titleBlock = title;
+}
 
-               layout.configure(chart, title, titleOpts);
-               layout.addBox(chart, title);
-               chart.titleBlock = title;
-       }
+module.exports = {
+       id: 'title',
 
-       return {
-               id: 'title',
+       /**
+        * Backward compatibility: since 2.1.5, the title is registered as a plugin, making
+        * Chart.Title obsolete. To avoid a breaking change, we export the Title as part of
+        * the plugin, which one will be re-exposed in the chart.js file.
+        * https://github.com/chartjs/Chart.js/pull/2640
+        * @private
+        */
+       _element: Title,
 
-               beforeInit: function(chart) {
-                       var titleOpts = chart.options.title;
+       beforeInit: function(chart) {
+               var titleOpts = chart.options.title;
 
-                       if (titleOpts) {
-                               createNewTitleBlockAndAttach(chart, titleOpts);
-                       }
-               },
+               if (titleOpts) {
+                       createNewTitleBlockAndAttach(chart, titleOpts);
+               }
+       },
 
-               beforeUpdate: function(chart) {
-                       var titleOpts = chart.options.title;
-                       var titleBlock = chart.titleBlock;
+       beforeUpdate: function(chart) {
+               var titleOpts = chart.options.title;
+               var titleBlock = chart.titleBlock;
 
-                       if (titleOpts) {
-                               helpers.mergeIf(titleOpts, defaults.global.title);
+               if (titleOpts) {
+                       helpers.mergeIf(titleOpts, defaults.global.title);
 
-                               if (titleBlock) {
-                                       layout.configure(chart, titleBlock, titleOpts);
-                                       titleBlock.options = titleOpts;
-                               } else {
-                                       createNewTitleBlockAndAttach(chart, titleOpts);
-                               }
-                       } else if (titleBlock) {
-                               layout.removeBox(chart, titleBlock);
-                               delete chart.titleBlock;
+                       if (titleBlock) {
+                               layout.configure(chart, titleBlock, titleOpts);
+                               titleBlock.options = titleOpts;
+                       } else {
+                               createNewTitleBlockAndAttach(chart, titleOpts);
                        }
+               } else if (titleBlock) {
+                       layout.removeBox(chart, titleBlock);
+                       delete chart.titleBlock;
                }
-       };
+       }
 };
index fee37288df9a5c1663a28173c6958180a4e634e6..08d5e79dbab9a3ab649c8cdcdf29a3499e9fc56a 100644 (file)
@@ -381,5 +381,23 @@ describe('Deprecations', function() {
                                expect(Chart.pluginService).toBe(Chart.plugins);
                        });
                });
+
+               describe('Chart.Legend', function() {
+                       it('should be defined and an instance of Chart.Element', function() {
+                               var legend = new Chart.Legend({});
+                               expect(Chart.Legend).toBeDefined();
+                               expect(legend).not.toBe(undefined);
+                               expect(legend instanceof Chart.Element).toBeTruthy();
+                       });
+               });
+
+               describe('Chart.Title', function() {
+                       it('should be defined and an instance of Chart.Element', function() {
+                               var title = new Chart.Title({});
+                               expect(Chart.Title).toBeDefined();
+                               expect(title).not.toBe(undefined);
+                               expect(title instanceof Chart.Element).toBeTruthy();
+                       });
+               });
        });
 });
index b950c36016089c5018fa11ca55c025a00dd7f127..5b75069aaea6f4db0294b6a893336ceb074291ea 100644 (file)
@@ -1,10 +1,5 @@
 // Test the rectangle element
 describe('Legend block tests', function() {
-       it('Should be constructed', function() {
-               var legend = new Chart.Legend({});
-               expect(legend).not.toBe(undefined);
-       });
-
        it('should have the correct default config', function() {
                expect(Chart.defaults.global.legend).toEqual({
                        display: true,
index edeb32a8b47acf3cf26b6a7f0af12d8b80bddbd3..28786f05402a125ad7a4bb9f58a8843e47c726b0 100644 (file)
@@ -1,11 +1,6 @@
 // Test the rectangle element
 
 describe('Title block tests', function() {
-       it('Should be constructed', function() {
-               var title = new Chart.Title({});
-               expect(title).not.toBe(undefined);
-       });
-
        it('Should have the correct default config', function() {
                expect(Chart.defaults.global.title).toEqual({
                        display: false,