]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
PluginService using registry (#7590)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Sat, 11 Jul 2020 22:08:45 +0000 (01:08 +0300)
committerGitHub <noreply@github.com>
Sat, 11 Jul 2020 22:08:45 +0000 (18:08 -0400)
PluginService using registry

24 files changed:
docs/docs/configuration/title.md
docs/docs/configuration/tooltip.md
docs/docs/developers/axes.md
docs/docs/developers/charts.md
docs/docs/getting-started/integration.md
docs/docs/getting-started/v3-migration.md
docs/docs/index.md
package.json
samples/tooltips/custom-pie.html
src/core/core.controller.js
src/core/core.datasetController.js
src/core/core.defaults.js
src/core/core.plugins.js
src/core/core.registry.js
src/helpers/helpers.collection.js
src/helpers/helpers.core.js
src/index.js
src/plugins/index.js
src/plugins/plugin.title.js
src/plugins/plugin.tooltip.js
test/specs/core.plugin.tests.js
test/specs/global.namespace.tests.js
test/specs/plugin.title.tests.js
test/specs/plugin.tooltip.tests.js

index efb75289d40df7863ae38ca0e1647f0c4d171b67..d1d5d6123a8f4a5cfae6fb48b2471b77e8ef89bc 100644 (file)
@@ -6,7 +6,7 @@ The chart title defines text to draw at the top of the chart.
 
 ## Title Configuration
 
-The title configuration is passed into the `options.title` namespace. The global options for the chart title is defined in `Chart.defaults.title`.
+The title configuration is passed into the `options.title` namespace. The global options for the chart title is defined in `Chart.defaults.plugins.title`.
 
 | Name | Type | Default | Description
 | ---- | ---- | ------- | -----------
index 85dc19a8add5de041b8a6e54089b0ddd309851c8..2cb65f11b74610d34627dab46df191af473923ca 100644 (file)
@@ -4,7 +4,7 @@ title: Tooltip
 
 ## Tooltip Configuration
 
-The tooltip configuration is passed into the `options.tooltips` namespace. The global options for the chart tooltips is defined in `Chart.defaults.tooltips`.
+The tooltip configuration is passed into the `options.tooltips` namespace. The global options for the chart tooltips is defined in `Chart.defaults.plugins.tooltip`.
 
 | Name | Type | Default | Description
 | ---- | ---- | ------- | -----------
@@ -63,7 +63,7 @@ Example:
  * @param eventPosition {Point} the position of the event in canvas coordinates
  * @returns {Point} the tooltip position
  */
-const tooltipPlugin = Chart.plugins.getAll().find(p => p.id === 'tooltip');
+const tooltipPlugin = Chart.registry.getPlugin('tooltip');
 tooltipPlugin.positioners.custom = function(elements, eventPosition) {
     /** @type {Tooltip} */
     var tooltip = this;
index e4cd873c108463dd7bcd4880ecd07f8e372e9e44..9af289ad38815b7ce36e70f555af4f54aa16dc83 100644 (file)
@@ -5,12 +5,27 @@ title: New Axes
 Axes in Chart.js can be individually extended. Axes should always derive from `Chart.Scale` but this is not a mandatory requirement.
 
 ```javascript
-class MyScale extends Chart.Scale{
+class MyScale extends Chart.Scale {
     /* extensions ... */
 }
 MyScale.id = 'myScale';
 MyScale.defaults = defaultConfigObject;
 
+// Or in classic style
+/*
+function MyScale() {
+  Chart.Scale.call(this, arguments);
+  // constructor stuff
+}
+
+MyScale.prototype.draw = function(ctx) {
+  Chart.Scale.prototype.draw.call(this, arguments);
+  // ...
+}
+MyScale.id = 'myScale';
+MyScale.defaults = defaultConfigObject;
+*/
+
 // MyScale is now derived from Chart.Scale
 ```
 
@@ -18,6 +33,11 @@ Once you have created your scale class, you need to register it with the global
 
 ```javascript
 Chart.register(MyScale);
+
+// If the scale is created in classical way, the prototype can not be used to detect what
+// you are trying to register - so you need to be explicit:
+
+// Chart.registry.addScales(MyScale);
 ```
 
 To use the new scale, simply pass in the string key to the config when creating a chart.
@@ -66,6 +86,7 @@ Scale instances are given the following properties during the fitting process.
 ```
 
 ## Scale Interface
+
 To work with Chart.js, custom scale types must implement the following interface.
 
 ```javascript
@@ -120,6 +141,7 @@ Optionally, the following methods may also be overwritten, but an implementation
 ```
 
 The Core.Scale base class also has some utility functions that you may find useful.
+
 ```javascript
 {
     // Returns true if the scale instance is horizontal
index 14c0cf1c8b2d827bd6727c7293f2cd65d06c35f0..dd98fd57aefa7154d7f3a5c4f77980b8c5d4c810 100644 (file)
@@ -111,3 +111,41 @@ new Chart(ctx, {
     options: options
 });
 ```
+
+Same example in classic style
+
+```javascript
+function Custom() {
+  Chart.controllers.bubble.call(this, arguments);
+  // constructor stuff
+}
+
+Custom.prototype.draw = function(ctx) {
+    Chart.controllers.bubble.prototype.draw.call(this, arguments);
+
+    var meta = this.getMeta();
+    var pt0 = meta.data[0];
+    var radius = pt0.radius;
+
+    var ctx = this.chart.chart.ctx;
+    ctx.save();
+    ctx.strokeStyle = 'red';
+    ctx.lineWidth = 1;
+    ctx.strokeRect(pt0.x - radius, pt0.y - radius, 2 * radius, 2 * radius);
+    ctx.restore();}
+}
+
+Custom.id = 'derivedBubble';
+Custom.defaults = Chart.defaults.bubble;
+
+// Prototype chain can not be used to detect we are trying to register a controller, so we need
+// to be explicit
+Chart.registry.addControllers(Custom);
+
+// Now we can create and use our new chart type
+new Chart(ctx, {
+    type: 'derivedBubble',
+    data: data,
+    options: options
+});
+```
index 687b004743324e9598669bedc151e6fc568af714..0cd9bf4560a180f6ad923b2910167f379b31fb02 100644 (file)
@@ -22,8 +22,13 @@ var myChart = new Chart(ctx, {...});
 
 ## Bundlers (Webpack, Rollup, etc.)
 
+Chart.js 3 is tree-shakeable, so it is necessary to import and register the controllers, elements, scales and plugins you are going to use.
+
 ```javascript
-import Chart from 'chart.js';
+import Chart, LineController, Line, Point, LinearScale, CategoryScale, Title, Tooltip, Filler, Legend from 'chart.js';
+
+Chart.register(LineController, Line, Point, LinearScale, CategoryScale, Title, Tooltip, Filler, Legend);
+
 var myChart = new Chart(ctx, {...});
 ```
 
index c264a1d625f9c657c6cb49d0d52d9a33801c828b..d6de6a1f2dc9a985781ad7ac5077805a384884f0 100644 (file)
@@ -12,6 +12,7 @@ Chart.js 3.0 introduces a number of breaking changes. Chart.js 2.0 was released
 * API documentation generated and verified by TypeDoc
 * No more CSS injection
 * Tons of bug fixes
+* Tree shaking
 
 ## End user migration
 
@@ -200,6 +201,32 @@ Some of the biggest things that have changed:
   * `Element._model` and `Element._view` are no longer used and properties are now set directly on the elements. You will have to use the method `getProps` to access these properties inside most methods such as `inXRange`/`inYRange` and `getCenterPoint`. Please take a look at [the Chart.js-provided elements](https://github.com/chartjs/Chart.js/tree/master/src/elements) for examples.
   * When building the elements in a controller, it's now suggested to call `updateElement` to provide the element properties. There are also methods such as `getSharedOptions` and `includeOptions` that have been added to skip redundant computation. Please take a look at [the Chart.js-provided controllers](https://github.com/chartjs/Chart.js/tree/master/src/controllers) for examples.
 * Scales introduced a new parsing API. This API takes user data and converts it into a more standard format. E.g. it allows users to provide numeric data as a `string` and converts it to a `number` where necessary. Previously this was done on the fly as charts were rendered. Now it's done up front with the ability to skip it for better performance if users provide data in the correct format. If you're using standard data format like `x`/`y` you may not need to do anything. If you're using a custom data format you will have to override some of the parse methods in `core.datasetController.js`. An example can be found in [chartjs-chart-financial](https://github.com/chartjs/chartjs-chart-financial), which uses an `{o, h, l, c}` data format.
+* Chart.js 3 is tree-shakeable. So when you use it as a module in a project, you need to import and register the controllers, elements, scales and plugins you want to use:
+
+```javascript
+import Chart, LineController, Line, Point, LinearScale, Title from `chart.js`
+
+Chart.register(LineController, Line, Point, LinearScale, Title);
+
+const chart = new Chart(ctx, {
+    type: 'line',
+    // data: ...
+    options: {
+        title: {
+            display: true,
+            text: 'Chart Title'
+        },
+        scales: {
+            x: {
+                type: 'linear'
+            },
+            y: {
+                type: 'linear'
+            }
+        }
+    }
+})
+```
 
 A few changes were made to controllers that are more straight-forward, but will affect all controllers:
 
@@ -231,12 +258,14 @@ The following properties and methods were removed:
 * `Chart.offsetX`
 * `Chart.offsetY`
 * `Chart.outerRadius` now lives on doughnut, pie, and polarArea controllers
+* `Chart.plugins` was replaced with `Chart.registry`. Plugin defaults are now in `Chart.defaults.plugins[id]`.
 * `Chart.PolarArea`. New charts are created via `new Chart` and providing the appropriate `type` parameter
 * `Chart.prototype.generateLegend`
 * `Chart.platform`. It only contained `disableCSSInjection`. CSS is never injected in v3.
 * `Chart.PluginBase`
 * `Chart.Radar`. New charts are created via `new Chart` and providing the appropriate `type` parameter
 * `Chart.radiusLength`
+* `Chart.scaleService` was replaced with `Chart.registry`. Scale defaults are now in `Chart.defaults.scales[type]`.
 * `Chart.Scatter`. New charts are created via `new Chart` and providing the appropriate `type` parameter
 * `Chart.types`
 * `Chart.Title` was moved to `Chart.plugins.title._element` and made private
@@ -405,7 +434,6 @@ The APIs listed in this section have changed in signature or behaviour from vers
 
 * `Scale.getLabelForIndex` was replaced by `scale.getLabelForValue`
 * `Scale.getPixelForValue` now has only one parameter. For the `TimeScale` that parameter must be millis since the epoch
-* `ScaleService.registerScaleType` was renamed to `ScaleService.registerScale` and now takes a scale constructors which is expected to have `id` and `defaults` properties.
 
 ##### Ticks
 
index 0731b1e1c76308c05754e2096e9642f16b450a0d..e43ea4a2d5fabf6c653039e37da644e4307521c4 100644 (file)
@@ -13,6 +13,7 @@ You can download the latest version of Chart.js from the [GitHub releases](https
 It's easy to get started with Chart.js. All that's required is the script included in your page along with a single `<canvas>` node to render the chart.
 
 In this example, we create a bar chart for a single dataset and render that in our page. You can see all the ways to use Chart.js in the [usage documentation](./getting-started/usage.md).
+
 ```html
 <canvas id="myChart" width="400" height="400"></canvas>
 <script>
index 546328c0832388009900aa5fdb50db783f870a9d..2fed3242c68bc9d804c74d488b1febebdad63749 100644 (file)
@@ -30,7 +30,7 @@
   "scripts": {
     "autobuild": "rollup -c -w",
     "build": "rollup -c",
-    "dev": "cross-env NODE_ENV=test karma start ---auto-watch --no-single-run --browsers chrome --grep",
+    "dev": "karma start ---auto-watch --no-single-run --browsers chrome --grep",
     "docs": "cd docs && npm install && npm run build && mkdir -p ../dist && cp -r build ../dist/docs",
     "lint-js": "eslint samples/**/*.html samples/**/*.js src/**/*.js test/**/*.js",
     "lint-tsc": "tsc",
index 96da5f56b28b11d94cfac8a1d0c60c848ab0e1f7..5803fb12c80bf5da0e1f6977ecb65661a6af5004 100644 (file)
@@ -43,7 +43,7 @@
        </div>
 
        <script>
-               Chart.defaults.tooltips.custom = function(tooltip) {
+               Chart.defaults.plugins.tooltip.custom = function(tooltip) {
                        // Tooltip Element
                        var tooltipEl = document.getElementById('chartjs-tooltip');
 
index 103b054c4ecc54038cee2f0ecfbd15cc814cf99b..b8704a44c522cc73591d0a5030e73456f5f9dd9e 100644 (file)
@@ -4,7 +4,7 @@ import defaults from './core.defaults';
 import Interaction from './core.interaction';
 import layouts from './core.layouts';
 import {BasicPlatform, DomPlatform} from '../platform';
-import plugins from './core.plugins';
+import PluginService from './core.plugins';
 import registry from './core.registry';
 import {getMaximumWidth, getMaximumHeight, retinaScale} from '../helpers/helpers.dom';
 import {mergeIf, merge, _merger, each, callback as callCallback, uid, valueOrDefault, _elementsEqual} from '../helpers/helpers.core';
@@ -111,12 +111,15 @@ function initConfig(config) {
 
        const scaleConfig = mergeScaleConfig(config, config.options);
 
-       config.options = mergeConfig(
+       const options = config.options = mergeConfig(
                defaults,
                defaults[config.type],
                config.options || {});
 
-       config.options.scales = scaleConfig;
+       options.scales = scaleConfig;
+
+       options.title = (options.title !== false) && merge({}, [defaults.plugins.title, options.title]);
+       options.tooltips = (options.tooltips !== false) && merge({}, [defaults.plugins.tooltip, options.tooltips]);
 
        return config;
 }
@@ -178,7 +181,7 @@ function onAnimationsComplete(ctx) {
        const chart = ctx.chart;
        const animationOptions = chart.options.animation;
 
-       plugins.notify(chart, 'afterRender');
+       chart._plugins.notify(chart, 'afterRender');
        callCallback(animationOptions && animationOptions.onComplete, [ctx], chart);
 }
 
@@ -265,7 +268,7 @@ class Chart {
                this._updating = false;
                this.scales = {};
                this.scale = undefined;
-               this.$plugins = undefined;
+               this._plugins = new PluginService();
                this.$proxies = {};
                this._hiddenIndices = {};
                this.attached = false;
@@ -308,7 +311,7 @@ class Chart {
                const me = this;
 
                // Before init plugin notification
-               plugins.notify(me, 'beforeInit');
+               me._plugins.notify(me, 'beforeInit');
 
                if (me.options.responsive) {
                        // Initial resize before chart draws (must be silent to preserve initial animations).
@@ -320,7 +323,7 @@ class Chart {
                me.bindEvents();
 
                // After init plugin notification
-               plugins.notify(me, 'afterInit');
+               me._plugins.notify(me, 'afterInit');
 
                return me;
        }
@@ -372,7 +375,7 @@ class Chart {
                retinaScale(me, newRatio);
 
                if (!silent) {
-                       plugins.notify(me, 'resize', [newSize]);
+                       me._plugins.notify(me, 'resize', [newSize]);
 
                        callCallback(options.onResize, [newSize], me);
 
@@ -572,7 +575,7 @@ class Chart {
        */
        reset() {
                this._resetElements();
-               plugins.notify(this, 'reset');
+               this._plugins.notify(this, 'reset');
        }
 
        update(mode) {
@@ -588,9 +591,9 @@ class Chart {
 
                // plugins options references might have change, let's invalidate the cache
                // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
-               plugins.invalidate(me);
+               me._plugins.invalidate();
 
-               if (plugins.notify(me, 'beforeUpdate') === false) {
+               if (me._plugins.notify(me, 'beforeUpdate') === false) {
                        return;
                }
 
@@ -614,7 +617,7 @@ class Chart {
                me._updateDatasets(mode);
 
                // Do this before render so that any plugins that need final scale updates can use it
-               plugins.notify(me, 'afterUpdate');
+               me._plugins.notify(me, 'afterUpdate');
 
                me._layers.sort(compare2Level('z', '_idx'));
 
@@ -636,7 +639,7 @@ class Chart {
        _updateLayout() {
                const me = this;
 
-               if (plugins.notify(me, 'beforeLayout') === false) {
+               if (me._plugins.notify(me, 'beforeLayout') === false) {
                        return;
                }
 
@@ -656,7 +659,7 @@ class Chart {
                        item._idx = index;
                });
 
-               plugins.notify(me, 'afterLayout');
+               me._plugins.notify(me, 'afterLayout');
        }
 
        /**
@@ -668,7 +671,7 @@ class Chart {
                const me = this;
                const isFunction = typeof mode === 'function';
 
-               if (plugins.notify(me, 'beforeDatasetsUpdate') === false) {
+               if (me._plugins.notify(me, 'beforeDatasetsUpdate') === false) {
                        return;
                }
 
@@ -676,7 +679,7 @@ class Chart {
                        me._updateDataset(i, isFunction ? mode({datasetIndex: i}) : mode);
                }
 
-               plugins.notify(me, 'afterDatasetsUpdate');
+               me._plugins.notify(me, 'afterDatasetsUpdate');
        }
 
        /**
@@ -689,23 +692,23 @@ class Chart {
                const meta = me.getDatasetMeta(index);
                const args = {meta, index, mode};
 
-               if (plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) {
+               if (me._plugins.notify(me, 'beforeDatasetUpdate', [args]) === false) {
                        return;
                }
 
                meta.controller._update(mode);
 
-               plugins.notify(me, 'afterDatasetUpdate', [args]);
+               me._plugins.notify(me, 'afterDatasetUpdate', [args]);
        }
 
        render() {
                const me = this;
                const animationOptions = me.options.animation;
-               if (plugins.notify(me, 'beforeRender') === false) {
+               if (me._plugins.notify(me, 'beforeRender') === false) {
                        return;
                }
                const onComplete = function() {
-                       plugins.notify(me, 'afterRender');
+                       me._plugins.notify(me, 'afterRender');
                        callCallback(animationOptions && animationOptions.onComplete, [], me);
                };
 
@@ -729,7 +732,7 @@ class Chart {
                        return;
                }
 
-               if (plugins.notify(me, 'beforeDraw') === false) {
+               if (me._plugins.notify(me, 'beforeDraw') === false) {
                        return;
                }
 
@@ -748,7 +751,7 @@ class Chart {
                        layers[i].draw(me.chartArea);
                }
 
-               plugins.notify(me, 'afterDraw');
+               me._plugins.notify(me, 'afterDraw');
        }
 
        /**
@@ -786,7 +789,7 @@ class Chart {
        _drawDatasets() {
                const me = this;
 
-               if (plugins.notify(me, 'beforeDatasetsDraw') === false) {
+               if (me._plugins.notify(me, 'beforeDatasetsDraw') === false) {
                        return;
                }
 
@@ -795,7 +798,7 @@ class Chart {
                        me._drawDataset(metasets[i]);
                }
 
-               plugins.notify(me, 'afterDatasetsDraw');
+               me._plugins.notify(me, 'afterDatasetsDraw');
        }
 
        /**
@@ -813,7 +816,7 @@ class Chart {
                        index: meta.index,
                };
 
-               if (plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
+               if (me._plugins.notify(me, 'beforeDatasetDraw', [args]) === false) {
                        return;
                }
 
@@ -828,7 +831,7 @@ class Chart {
 
                unclipArea(ctx);
 
-               plugins.notify(me, 'afterDatasetDraw', [args]);
+               me._plugins.notify(me, 'afterDatasetDraw', [args]);
        }
 
        /**
@@ -969,7 +972,7 @@ class Chart {
                        me.ctx = null;
                }
 
-               plugins.notify(me, 'destroy');
+               me._plugins.notify(me, 'destroy');
 
                delete Chart.instances[me.id];
        }
@@ -1096,13 +1099,13 @@ class Chart {
        _eventHandler(e, replay) {
                const me = this;
 
-               if (plugins.notify(me, 'beforeEvent', [e, replay]) === false) {
+               if (me._plugins.notify(me, 'beforeEvent', [e, replay]) === false) {
                        return;
                }
 
                me._handleEvent(e, replay);
 
-               plugins.notify(me, 'afterEvent', [e, replay]);
+               me._plugins.notify(me, 'afterEvent', [e, replay]);
 
                me.render();
 
index 57c431e6a3c95cd6a59f576709e69eb43ac81983..551d4e8ba6dd4321301cd8ee3729d7098c04c7f3 100644 (file)
@@ -1,5 +1,5 @@
 import Animations from './core.animations';
-import {isObject, merge, _merger, isArray, valueOrDefault, mergeIf, resolveObjectKey} from '../helpers/helpers.core';
+import {isObject, merge, _merger, isArray, valueOrDefault, mergeIf, resolveObjectKey, _capitalize} from '../helpers/helpers.core';
 import {listenArrayEvents, unlistenArrayEvents} from '../helpers/helpers.collection';
 import {resolve} from '../helpers/helpers.options';
 import {getHoverColor} from '../helpers/helpers.color';
@@ -152,7 +152,7 @@ function optionKeys(optionNames) {
 }
 
 function optionKey(key, active) {
-       return active ? 'hover' + key.charAt(0).toUpperCase() + key.slice(1) : key;
+       return active ? 'hover' + _capitalize(key) : key;
 }
 
 export default class DatasetController {
index 9cd435a762fe1df6bc6ceed4f25ea7fda89d45d9..85612b738cd253b7a68134ce8a2de758ddb59019 100644 (file)
@@ -51,7 +51,7 @@ export class Defaults {
                this.onClick = null;
                this.responsive = true;
                this.showLines = true;
-               this.plugins = undefined;
+               this.plugins = {};
                this.scale = undefined;
                this.legend = undefined;
                this.title = undefined;
index 3539a2347a939e5d4e1a8a62a140907c0052f2e2..4ceab715ec1f67d3c84249df31b47bf97caaeda4 100644 (file)
@@ -1,5 +1,6 @@
 import defaults from './core.defaults';
-import {clone} from '../helpers/helpers.core';
+import registry from './core.registry';
+import {mergeIf} from '../helpers/helpers.core';
 
 /**
  * @typedef { import("./core.controller").default } Chart
@@ -7,88 +8,7 @@ import {clone} from '../helpers/helpers.core';
  * @typedef { import("../plugins/plugin.tooltip").default } Tooltip
  */
 
-defaults.set('plugins', {});
-
-/**
- * The plugin service singleton
- * @namespace Chart.plugins
- * @since 2.1.0
- */
-export class PluginService {
-       constructor() {
-               /**
-                * Globally registered plugins.
-                * @private
-                */
-               this._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
-                */
-               this._cacheId = 0;
-       }
-
-       /**
-        * Registers the given plugin(s) if not already registered.
-        * @param {IPlugin[]|IPlugin} plugins plugin instance(s).
-        */
-       register(plugins) {
-               const p = this._plugins;
-               ([]).concat(plugins).forEach((plugin) => {
-                       if (p.indexOf(plugin) === -1) {
-                               p.push(plugin);
-                       }
-               });
-
-               this._cacheId++;
-       }
-
-       /**
-        * Unregisters the given plugin(s) only if registered.
-        * @param {IPlugin[]|IPlugin} plugins plugin instance(s).
-        */
-       unregister(plugins) {
-               const p = this._plugins;
-               ([]).concat(plugins).forEach((plugin) => {
-                       const idx = p.indexOf(plugin);
-                       if (idx !== -1) {
-                               p.splice(idx, 1);
-                       }
-               });
-
-               this._cacheId++;
-       }
-
-       /**
-        * Remove all registered plugins.
-        * @since 2.1.5
-        */
-       clear() {
-               this._plugins = [];
-               this._cacheId++;
-       }
-
-       /**
-        * Returns the number of registered plugins?
-        * @returns {number}
-        * @since 2.1.5
-        */
-       count() {
-               return this._plugins.length;
-       }
-
-       /**
-        * Returns all registered plugin instances.
-        * @returns {IPlugin[]} array of plugin objects.
-        * @since 2.1.5
-        */
-       getAll() {
-               return this._plugins;
-       }
-
+export default class PluginService {
        /**
         * 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
@@ -100,15 +20,13 @@ export class PluginService {
         */
        notify(chart, hook, args) {
                const descriptors = this._descriptors(chart);
-               const ilen = descriptors.length;
-               let i, descriptor, plugin, params, method;
 
-               for (i = 0; i < ilen; ++i) {
-                       descriptor = descriptors[i];
-                       plugin = descriptor.plugin;
-                       method = plugin[hook];
+               for (let i = 0; i < descriptors.length; ++i) {
+                       const descriptor = descriptors[i];
+                       const plugin = descriptor.plugin;
+                       const method = plugin[hook];
                        if (typeof method === 'function') {
-                               params = [chart].concat(args || []);
+                               const params = [chart].concat(args || []);
                                params.push(descriptor.options);
                                if (method.apply(plugin, params) === false) {
                                        return false;
@@ -119,64 +37,71 @@ export class PluginService {
                return true;
        }
 
+       invalidate() {
+               this._cache = undefined;
+       }
+
        /**
-        * Returns descriptors of enabled plugins for the given chart.
         * @param {Chart} chart
-        * @returns {object[]} [{ plugin, options }]
         * @private
         */
        _descriptors(chart) {
-               const cache = chart.$plugins || (chart.$plugins = {});
-               if (cache.id === this._cacheId) {
-                       return cache.descriptors;
+               if (this._cache) {
+                       return this._cache;
                }
 
-               const plugins = [];
-               const descriptors = [];
                const config = (chart && chart.config) || {};
                const options = (config.options && config.options.plugins) || {};
+               const plugins = allPlugins(config);
+               const descriptors = createDescriptors(plugins, options);
 
-               this._plugins.concat(config.plugins || []).forEach((plugin) => {
-                       const idx = plugins.indexOf(plugin);
-                       if (idx !== -1) {
-                               return;
-                       }
+               this._cache = descriptors;
 
-                       const id = plugin.id;
-                       let opts = options[id];
-                       if (opts === false) {
-                               return;
-                       }
+               return descriptors;
+       }
+}
 
-                       if (opts === true) {
-                               opts = clone(defaults.plugins[id]);
-                       }
+function allPlugins(config) {
+       const plugins = [];
+       const keys = Object.keys(registry.plugins.items);
+       for (let i = 0; i < keys.length; i++) {
+               plugins.push(registry.getPlugin(keys[i]));
+       }
 
-                       plugins.push(plugin);
-                       descriptors.push({
-                               plugin,
-                               options: opts || {}
-                       });
-               });
+       const local = config.plugins || [];
+       for (let i = 0; i < local.length; i++) {
+               const plugin = local[i];
 
-               cache.descriptors = descriptors;
-               cache.id = this._cacheId;
-               return descriptors;
+               if (plugins.indexOf(plugin) === -1) {
+                       plugins.push(plugin);
+               }
        }
 
-       /**
-        * Invalidates cache for the given chart: descriptors hold a reference on plugin option,
-        * but in some cases, this reference can be changed by the user when updating options.
-        * https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
-        * @param {Chart} chart
-        */
-       invalidate(chart) {
-               delete chart.$plugins;
-       }
+       return plugins;
 }
 
-// singleton instance
-export default new PluginService();
+function createDescriptors(plugins, options) {
+       const result = [];
+
+       for (let i = 0; i < plugins.length; i++) {
+               const plugin = plugins[i];
+               const id = plugin.id;
+
+               let opts = options[id];
+               if (opts === false) {
+                       continue;
+               }
+               if (opts === true) {
+                       opts = {};
+               }
+               result.push({
+                       plugin,
+                       options: mergeIf({}, [opts, defaults.plugins[id]])
+               });
+       }
+
+       return result;
+}
 
 /**
  * Plugin extension hooks.
index 9b685bca4e1a55dc3a83665a301430ab9a5a0adc..e71e50ba4a957b6bd918ac2d91c47fa569586b70 100644 (file)
@@ -2,7 +2,7 @@ import DatasetController from './core.datasetController';
 import Element from './core.element';
 import Scale from './core.scale';
 import TypedRegistry from './core.typedRegistry';
-import {each, callback as call} from '../helpers/helpers.core';
+import {each, callback as call, _capitalize} from '../helpers/helpers.core';
 
 /**
  * Please use the module's default export which provides a singleton instance
@@ -23,35 +23,39 @@ export class Registry {
         * @param  {...any} args
         */
        add(...args) {
-               this._registerEach(args);
+               this._each('register', args);
+       }
+
+       remove(...args) {
+               this._each('unregister', args);
        }
 
        /**
         * @param  {...typeof DatasetController} args
         */
        addControllers(...args) {
-               this._registerEach(args, this.controllers);
+               this._each('register', args, this.controllers);
        }
 
        /**
         * @param  {...typeof Element} args
         */
        addElements(...args) {
-               this._registerEach(args, this.elements);
+               this._each('register', args, this.elements);
        }
 
        /**
         * @param  {...any} args
         */
        addPlugins(...args) {
-               this._registerEach(args, this.plugins);
+               this._each('register', args, this.plugins);
        }
 
        /**
         * @param  {...typeof Scale} args
         */
        addScales(...args) {
-               this._registerEach(args, this.scales);
+               this._each('register', args, this.scales);
        }
 
        /**
@@ -89,12 +93,12 @@ export class Registry {
        /**
         * @private
         */
-       _registerEach(args, typedRegistry) {
+       _each(method, args, typedRegistry) {
                const me = this;
                [...args].forEach(arg => {
                        const reg = typedRegistry || me._getRegistryForType(arg);
-                       if (reg.isForType(arg)) {
-                               me._registerComponent(reg, arg);
+                       if (reg.isForType(arg) || (reg === me.plugins && arg.id)) {
+                               me._exec(method, reg, arg);
                        } else {
                                // Handle loopable args
                                // Use case:
@@ -108,7 +112,7 @@ export class Registry {
                                        //  Chart.register(treemap);
 
                                        const itemReg = typedRegistry || me._getRegistryForType(item);
-                                       me._registerComponent(itemReg, item);
+                                       me._exec(method, itemReg, item);
                                });
                        }
                });
@@ -117,10 +121,11 @@ export class Registry {
        /**
         * @private
         */
-       _registerComponent(registry, component) {
-               call(component.beforeRegister, [], component);
-               registry.register(component);
-               call(component.afterRegister, [], component);
+       _exec(method, registry, component) {
+               const camelMethod = _capitalize(method);
+               call(component['before' + camelMethod], [], component);
+               registry[method](component);
+               call(component['after' + camelMethod], [], component);
        }
 
        /**
index 2671810c2049ee9aa84abb78da17f568aea0753a..4cea116ce5f232f6e7441a29f00e1bd4c33efcf5 100644 (file)
@@ -1,3 +1,5 @@
+import {_capitalize} from './helpers.core';
+
 /**
  * Binary search
  * @param {array} table - the table search. must be sorted!
@@ -114,7 +116,7 @@ export function listenArrayEvents(array, listener) {
        });
 
        arrayEvents.forEach((key) => {
-               const method = '_onData' + key.charAt(0).toUpperCase() + key.slice(1);
+               const method = '_onData' + _capitalize(key);
                const base = array[key];
 
                Object.defineProperty(array, key, {
index 743e3f02d4ee1f4eef469af17d8be56ac4f26bdd..2aa8edc24904cbca1f785290b09f30d0e6fd1b33 100644 (file)
@@ -276,3 +276,10 @@ export function resolveObjectKey(obj, key) {
        }
        return obj;
 }
+
+/**
+ * @private
+ */
+export function _capitalize(str) {
+       return str.charAt(0).toUpperCase() + str.slice(1);
+}
index 3b2cd389cebdcd3a989ddf5b76a0af15053b7352..9a24a7c8b3768b434382d49719a2b9114c449745 100644 (file)
@@ -19,13 +19,23 @@ import Interaction from './core/core.interaction';
 import layouts from './core/core.layouts';
 import * as platforms from './platform/index';
 import * as plugins from './plugins';
-import pluginsCore from './core/core.plugins';
 import registry from './core/core.registry';
 import Scale from './core/core.scale';
 import * as scales from './scales';
 import Ticks from './core/core.ticks';
+import {each} from './helpers/helpers.core';
 
-Chart.register = (...items) => registry.add(...items);
+// @ts-ignore
+const invalidatePlugins = () => each(Chart.instances, (chart) => chart._plugins.invalidate());
+
+Chart.register = (...items) => {
+       registry.add(...items);
+       invalidatePlugins();
+};
+Chart.unregister = (...items) => {
+       registry.remove(...items);
+       invalidatePlugins();
+};
 
 // Register built-ins
 Chart.register(controllers, scales, elements, plugins);
@@ -43,17 +53,10 @@ Chart.elements = elements;
 Chart.Interaction = Interaction;
 Chart.layouts = layouts;
 Chart.platforms = platforms;
-Chart.plugins = pluginsCore;
 Chart.registry = registry;
 Chart.Scale = Scale;
 Chart.Ticks = Ticks;
 
-for (const k in plugins) {
-       if (Object.prototype.hasOwnProperty.call(plugins, k)) {
-               Chart.plugins.register(plugins[k]);
-       }
-}
-
 if (typeof window !== 'undefined') {
        // @ts-ignore
        window.Chart = Chart;
index d838a6f512ce8a55c3722c638fea15110e58a62e..65e8ec4a1900e0d6a3b77b292ba63a3230c0a1c5 100644 (file)
@@ -1,4 +1,4 @@
-export {default as filler} from './plugin.filler';
-export {default as legend} from './plugin.legend';
-export {default as title} from './plugin.title';
-export {default as tooltip} from './plugin.tooltip';
+export {default as Filler} from './plugin.filler';
+export {default as Legend} from './plugin.legend';
+export {default as Title} from './plugin.title';
+export {default as Tooltip} from './plugin.tooltip';
index 38c1078ad6475b23e1779a3624a34ef1a3ddc618..917e05a988965029775fab2ded9832eb20f1a6ad 100644 (file)
@@ -4,19 +4,6 @@ import layouts from '../core/core.layouts';
 import {isArray, mergeIf} from '../helpers/helpers.core';
 import {toPadding, toFont} from '../helpers/helpers.options';
 
-defaults.set('title', {
-       align: 'center',
-       display: false,
-       font: {
-               style: 'bold',
-       },
-       fullWidth: true,
-       padding: 10,
-       position: 'top',
-       text: '',
-       weight: 2000         // by default greater than legend (1000) to be above
-});
-
 export class Title extends Element {
        constructor(config) {
                super();
@@ -257,7 +244,7 @@ export default {
                const titleBlock = chart.titleBlock;
 
                if (titleOpts) {
-                       mergeIf(titleOpts, defaults.title);
+                       mergeIf(titleOpts, defaults.plugins.title);
 
                        if (titleBlock) {
                                layouts.configure(chart, titleBlock, titleOpts);
@@ -269,5 +256,18 @@ export default {
                        layouts.removeBox(chart, titleBlock);
                        delete chart.titleBlock;
                }
+       },
+
+       defaults: {
+               align: 'center',
+               display: false,
+               font: {
+                       style: 'bold',
+               },
+               fullWidth: true,
+               padding: 10,
+               position: 'top',
+               text: '',
+               weight: 2000         // by default greater than legend (1000) to be above
        }
 };
index ffd08be36b4ef1db70455084e29256d92e7ddcc5..c4363a1006adc5e56106bffd296cb62103de1eff 100644 (file)
@@ -1,8 +1,7 @@
 import Animations from '../core/core.animations';
 import defaults from '../core/core.defaults';
 import Element from '../core/core.element';
-import plugins from '../core/core.plugins';
-import {valueOrDefault, each, noop, isNullOrUndef, isArray, _elementsEqual} from '../helpers/helpers.core';
+import {valueOrDefault, each, noop, isNullOrUndef, isArray, _elementsEqual, merge} from '../helpers/helpers.core';
 import {getRtlAdapter, overrideTextDirection, restoreTextDirection} from '../helpers/helpers.rtl';
 import {distanceBetweenPoints} from '../helpers/helpers.math';
 import {toFont} from '../helpers/helpers.options';
@@ -11,114 +10,6 @@ import {toFont} from '../helpers/helpers.options';
  * @typedef { import("../platform/platform.base").IEvent } IEvent
  */
 
-defaults.set('tooltips', {
-       enabled: true,
-       custom: null,
-       mode: 'nearest',
-       position: 'average',
-       intersect: true,
-       backgroundColor: 'rgba(0,0,0,0.8)',
-       titleFont: {
-               style: 'bold',
-               color: '#fff',
-       },
-       titleSpacing: 2,
-       titleMarginBottom: 6,
-       titleAlign: 'left',
-       bodySpacing: 2,
-       bodyFont: {
-               color: '#fff',
-       },
-       bodyAlign: 'left',
-       footerSpacing: 2,
-       footerMarginTop: 6,
-       footerFont: {
-               color: '#fff',
-               style: 'bold',
-       },
-       footerAlign: 'left',
-       yPadding: 6,
-       xPadding: 6,
-       caretPadding: 2,
-       caretSize: 5,
-       cornerRadius: 6,
-       multiKeyBackground: '#fff',
-       displayColors: true,
-       borderColor: 'rgba(0,0,0,0)',
-       borderWidth: 0,
-       animation: {
-               duration: 400,
-               easing: 'easeOutQuart',
-               numbers: {
-                       type: 'number',
-                       properties: ['x', 'y', 'width', 'height', 'caretX', 'caretY'],
-               },
-               opacity: {
-                       easing: 'linear',
-                       duration: 200
-               }
-       },
-       callbacks: {
-               // Args are: (tooltipItems, data)
-               beforeTitle: noop,
-               title(tooltipItems, data) {
-                       let title = '';
-                       const labels = data.labels;
-                       const labelCount = labels ? labels.length : 0;
-
-                       if (tooltipItems.length > 0) {
-                               const item = tooltipItems[0];
-                               if (item.label) {
-                                       title = item.label;
-                               } else if (labelCount > 0 && item.index < labelCount) {
-                                       title = labels[item.index];
-                               }
-                       }
-
-                       return title;
-               },
-               afterTitle: noop,
-
-               // Args are: (tooltipItems, data)
-               beforeBody: noop,
-
-               // Args are: (tooltipItem, data)
-               beforeLabel: noop,
-               label(tooltipItem, data) {
-                       let label = data.datasets[tooltipItem.datasetIndex].label || '';
-
-                       if (label) {
-                               label += ': ';
-                       }
-                       const value = tooltipItem.value;
-                       if (!isNullOrUndef(value)) {
-                               label += value;
-                       }
-                       return label;
-               },
-               labelColor(tooltipItem, chart) {
-                       const meta = chart.getDatasetMeta(tooltipItem.datasetIndex);
-                       const options = meta.controller.getStyle(tooltipItem.index);
-                       return {
-                               borderColor: options.borderColor,
-                               backgroundColor: options.backgroundColor
-                       };
-               },
-               labelTextColor() {
-                       return this.options.bodyFont.color;
-               },
-               afterLabel: noop,
-
-               // Args are: (tooltipItems, data)
-               afterBody: noop,
-
-               // Args are: (tooltipItems, data)
-               beforeFooter: noop,
-               footer: noop,
-               afterFooter: noop
-       }
-});
-
 const positioners = {
        /**
         * Average mode places the tooltip at the average position of the elements shown
@@ -242,7 +133,7 @@ function createTooltipItem(chart, item) {
  */
 function resolveOptions(options) {
 
-       options = Object.assign({}, defaults.tooltips, options);
+       options = merge({}, [defaults.plugins.tooltip, options]);
 
        options.bodyFont = toFont(options.bodyFont);
        options.titleFont = toFont(options.titleFont);
@@ -1101,7 +992,7 @@ export default {
                        tooltip
                };
 
-               if (plugins.notify(chart, 'beforeTooltipDraw', [args]) === false) {
+               if (chart._plugins.notify(chart, 'beforeTooltipDraw', [args]) === false) {
                        return;
                }
 
@@ -1109,7 +1000,7 @@ export default {
                        tooltip.draw(chart.ctx);
                }
 
-               plugins.notify(chart, 'afterTooltipDraw', [args]);
+               chart._plugins.notify(chart, 'afterTooltipDraw', [args]);
        },
 
        afterEvent(chart, e, replay) {
@@ -1118,5 +1009,113 @@ export default {
                        const useFinalPosition = replay;
                        chart.tooltip.handleEvent(e, useFinalPosition);
                }
+       },
+
+       defaults: {
+               enabled: true,
+               custom: null,
+               mode: 'nearest',
+               position: 'average',
+               intersect: true,
+               backgroundColor: 'rgba(0,0,0,0.8)',
+               titleFont: {
+                       style: 'bold',
+                       color: '#fff',
+               },
+               titleSpacing: 2,
+               titleMarginBottom: 6,
+               titleAlign: 'left',
+               bodySpacing: 2,
+               bodyFont: {
+                       color: '#fff',
+               },
+               bodyAlign: 'left',
+               footerSpacing: 2,
+               footerMarginTop: 6,
+               footerFont: {
+                       color: '#fff',
+                       style: 'bold',
+               },
+               footerAlign: 'left',
+               yPadding: 6,
+               xPadding: 6,
+               caretPadding: 2,
+               caretSize: 5,
+               cornerRadius: 6,
+               multiKeyBackground: '#fff',
+               displayColors: true,
+               borderColor: 'rgba(0,0,0,0)',
+               borderWidth: 0,
+               animation: {
+                       duration: 400,
+                       easing: 'easeOutQuart',
+                       numbers: {
+                               type: 'number',
+                               properties: ['x', 'y', 'width', 'height', 'caretX', 'caretY'],
+                       },
+                       opacity: {
+                               easing: 'linear',
+                               duration: 200
+                       }
+               },
+               callbacks: {
+                       // Args are: (tooltipItems, data)
+                       beforeTitle: noop,
+                       title(tooltipItems, data) {
+                               let title = '';
+                               const labels = data.labels;
+                               const labelCount = labels ? labels.length : 0;
+
+                               if (tooltipItems.length > 0) {
+                                       const item = tooltipItems[0];
+                                       if (item.label) {
+                                               title = item.label;
+                                       } else if (labelCount > 0 && item.index < labelCount) {
+                                               title = labels[item.index];
+                                       }
+                               }
+
+                               return title;
+                       },
+                       afterTitle: noop,
+
+                       // Args are: (tooltipItems, data)
+                       beforeBody: noop,
+
+                       // Args are: (tooltipItem, data)
+                       beforeLabel: noop,
+                       label(tooltipItem, data) {
+                               let label = data.datasets[tooltipItem.datasetIndex].label || '';
+
+                               if (label) {
+                                       label += ': ';
+                               }
+                               const value = tooltipItem.value;
+                               if (!isNullOrUndef(value)) {
+                                       label += value;
+                               }
+                               return label;
+                       },
+                       labelColor(tooltipItem, chart) {
+                               const meta = chart.getDatasetMeta(tooltipItem.datasetIndex);
+                               const options = meta.controller.getStyle(tooltipItem.index);
+                               return {
+                                       borderColor: options.borderColor,
+                                       backgroundColor: options.backgroundColor
+                               };
+                       },
+                       labelTextColor() {
+                               return this.options.bodyFont.color;
+                       },
+                       afterLabel: noop,
+
+                       // Args are: (tooltipItems, data)
+                       afterBody: noop,
+
+                       // Args are: (tooltipItems, data)
+                       beforeFooter: noop,
+                       footer: noop,
+                       afterFooter: noop
+               }
        }
 };
index 2e193db080c43bad3d69ae0ff4e9ca0d66e4fc32..a2df9334e27688a519ad880d754a62160913646b 100644 (file)
@@ -1,67 +1,4 @@
 describe('Chart.plugins', function() {
-       beforeEach(function() {
-               this._plugins = Chart.plugins.getAll();
-               Chart.plugins.clear();
-       });
-
-       afterEach(function() {
-               Chart.plugins.clear();
-               Chart.plugins.register(this._plugins);
-               delete this._plugins;
-       });
-
-       describe('Chart.plugins.register', function() {
-               it('should register a plugin', function() {
-                       Chart.plugins.register({});
-                       expect(Chart.plugins.count()).toBe(1);
-                       Chart.plugins.register({});
-                       expect(Chart.plugins.count()).toBe(2);
-               });
-
-               it('should register an array of plugins', function() {
-                       Chart.plugins.register([{}, {}, {}]);
-                       expect(Chart.plugins.count()).toBe(3);
-               });
-
-               it('should succeed to register an already registered plugin', function() {
-                       var plugin = {};
-                       Chart.plugins.register(plugin);
-                       expect(Chart.plugins.count()).toBe(1);
-                       Chart.plugins.register(plugin);
-                       expect(Chart.plugins.count()).toBe(1);
-                       Chart.plugins.register([{}, plugin, plugin]);
-                       expect(Chart.plugins.count()).toBe(2);
-               });
-       });
-
-       describe('Chart.plugins.unregister', function() {
-               it('should unregister a plugin', function() {
-                       var plugin = {};
-                       Chart.plugins.register(plugin);
-                       expect(Chart.plugins.count()).toBe(1);
-                       Chart.plugins.unregister(plugin);
-                       expect(Chart.plugins.count()).toBe(0);
-               });
-
-               it('should unregister an array of plugins', function() {
-                       var plugins = [{}, {}, {}];
-                       Chart.plugins.register(plugins);
-                       expect(Chart.plugins.count()).toBe(3);
-                       Chart.plugins.unregister(plugins.slice(0, 2));
-                       expect(Chart.plugins.count()).toBe(1);
-               });
-
-               it('should succeed to unregister a plugin not registered', function() {
-                       var plugin = {};
-                       Chart.plugins.register(plugin);
-                       expect(Chart.plugins.count()).toBe(1);
-                       Chart.plugins.unregister({});
-                       expect(Chart.plugins.count()).toBe(1);
-                       Chart.plugins.unregister([{}, plugin]);
-                       expect(Chart.plugins.count()).toBe(0);
-               });
-       });
-
        describe('Chart.plugins.notify', function() {
                it('should call inline plugins with arguments', function() {
                        var plugin = {hook: function() {}};
@@ -71,7 +8,7 @@ describe('Chart.plugins', function() {
 
                        spyOn(plugin, 'hook');
 
-                       Chart.plugins.notify(chart, 'hook', 42);
+                       chart._plugins.notify(chart, 'hook', 42);
                        expect(plugin.hook.calls.count()).toBe(1);
                        expect(plugin.hook.calls.first().args[0]).toBe(chart);
                        expect(plugin.hook.calls.first().args[1]).toBe(42);
@@ -79,30 +16,32 @@ describe('Chart.plugins', function() {
                });
 
                it('should call global plugins with arguments', function() {
-                       var plugin = {hook: function() {}};
+                       var plugin = {id: 'a', hook: function() {}};
                        var chart = window.acquireChart({});
 
                        spyOn(plugin, 'hook');
 
-                       Chart.plugins.register(plugin);
-                       Chart.plugins.notify(chart, 'hook', 42);
+                       Chart.register(plugin);
+                       chart._plugins.notify(chart, 'hook', 42);
                        expect(plugin.hook.calls.count()).toBe(1);
                        expect(plugin.hook.calls.first().args[0]).toBe(chart);
                        expect(plugin.hook.calls.first().args[1]).toBe(42);
                        expect(plugin.hook.calls.first().args[2]).toEqual({});
+                       Chart.unregister(plugin);
                });
 
                it('should call plugin only once even if registered multiple times', function() {
-                       var plugin = {hook: function() {}};
+                       var plugin = {id: 'test', hook: function() {}};
                        var chart = window.acquireChart({
                                plugins: [plugin, plugin]
                        });
 
                        spyOn(plugin, 'hook');
 
-                       Chart.plugins.register([plugin, plugin]);
-                       Chart.plugins.notify(chart, 'hook');
+                       Chart.register([plugin, plugin]);
+                       chart._plugins.notify(chart, 'hook');
                        expect(plugin.hook.calls.count()).toBe(1);
+                       Chart.unregister(plugin);
                });
 
                it('should call plugins in the correct order (global first)', function() {
@@ -123,23 +62,28 @@ describe('Chart.plugins', function() {
                                }]
                        });
 
-                       Chart.plugins.register([{
+                       var plugins = [{
+                               id: 'a',
                                hook: function() {
                                        results.push(4);
                                }
                        }, {
+                               id: 'b',
                                hook: function() {
                                        results.push(5);
                                }
                        }, {
+                               id: 'c',
                                hook: function() {
                                        results.push(6);
                                }
-                       }]);
+                       }];
+                       Chart.register(plugins);
 
-                       var ret = Chart.plugins.notify(chart, 'hook');
+                       var ret = chart._plugins.notify(chart, 'hook');
                        expect(ret).toBeTruthy();
                        expect(results).toEqual([4, 5, 6, 1, 2, 3]);
+                       Chart.unregister(plugins);
                });
 
                it('should return TRUE if no plugin explicitly returns FALSE', function() {
@@ -170,7 +114,7 @@ describe('Chart.plugins', function() {
                                spyOn(plugin, 'hook').and.callThrough();
                        });
 
-                       var ret = Chart.plugins.notify(chart, 'hook');
+                       var ret = chart._plugins.notify(chart, 'hook');
                        expect(ret).toBeTruthy();
                        plugins.forEach(function(plugin) {
                                expect(plugin.hook).toHaveBeenCalled();
@@ -205,7 +149,7 @@ describe('Chart.plugins', function() {
                                spyOn(plugin, 'hook').and.callThrough();
                        });
 
-                       var ret = Chart.plugins.notify(chart, 'hook');
+                       var ret = chart._plugins.notify(chart, 'hook');
                        expect(ret).toBeFalsy();
                        expect(plugins[0].hook).toHaveBeenCalled();
                        expect(plugins[1].hook).toHaveBeenCalled();
@@ -218,6 +162,7 @@ describe('Chart.plugins', function() {
        describe('config.options.plugins', function() {
                it('should call plugins with options at last argument', function() {
                        var plugin = {id: 'foo', hook: function() {}};
+
                        var chart = window.acquireChart({
                                options: {
                                        plugins: {
@@ -228,15 +173,17 @@ describe('Chart.plugins', function() {
 
                        spyOn(plugin, 'hook');
 
-                       Chart.plugins.register(plugin);
-                       Chart.plugins.notify(chart, 'hook');
-                       Chart.plugins.notify(chart, 'hook', ['bla']);
-                       Chart.plugins.notify(chart, 'hook', ['bla', 42]);
+                       Chart.register(plugin);
+                       chart._plugins.notify(chart, 'hook');
+                       chart._plugins.notify(chart, 'hook', ['bla']);
+                       chart._plugins.notify(chart, 'hook', ['bla', 42]);
 
                        expect(plugin.hook.calls.count()).toBe(3);
                        expect(plugin.hook.calls.argsFor(0)[1]).toEqual({a: '123'});
                        expect(plugin.hook.calls.argsFor(1)[2]).toEqual({a: '123'});
                        expect(plugin.hook.calls.argsFor(2)[3]).toEqual({a: '123'});
+
+                       Chart.unregister(plugin);
                });
 
                it('should call plugins with options associated to their identifier', function() {
@@ -246,7 +193,7 @@ describe('Chart.plugins', function() {
                                c: {id: 'c', hook: function() {}}
                        };
 
-                       Chart.plugins.register(plugins.a);
+                       Chart.register(plugins.a);
 
                        var chart = window.acquireChart({
                                plugins: [plugins.b, plugins.c],
@@ -263,7 +210,7 @@ describe('Chart.plugins', function() {
                        spyOn(plugins.b, 'hook');
                        spyOn(plugins.c, 'hook');
 
-                       Chart.plugins.notify(chart, 'hook');
+                       chart._plugins.notify(chart, 'hook');
 
                        expect(plugins.a.hook).toHaveBeenCalled();
                        expect(plugins.b.hook).toHaveBeenCalled();
@@ -271,6 +218,8 @@ describe('Chart.plugins', function() {
                        expect(plugins.a.hook.calls.first().args[1]).toEqual({a: '123'});
                        expect(plugins.b.hook.calls.first().args[1]).toEqual({b: '456'});
                        expect(plugins.c.hook.calls.first().args[1]).toEqual({c: '789'});
+
+                       Chart.unregister(plugins.a);
                });
 
                it('should not called plugins when config.options.plugins.{id} is FALSE', function() {
@@ -280,7 +229,7 @@ describe('Chart.plugins', function() {
                                c: {id: 'c', hook: function() {}}
                        };
 
-                       Chart.plugins.register(plugins.a);
+                       Chart.register(plugins.a);
 
                        var chart = window.acquireChart({
                                plugins: [plugins.b, plugins.c],
@@ -296,18 +245,19 @@ describe('Chart.plugins', function() {
                        spyOn(plugins.b, 'hook');
                        spyOn(plugins.c, 'hook');
 
-                       Chart.plugins.notify(chart, 'hook');
+                       chart._plugins.notify(chart, 'hook');
 
                        expect(plugins.a.hook).not.toHaveBeenCalled();
                        expect(plugins.b.hook).not.toHaveBeenCalled();
                        expect(plugins.c.hook).toHaveBeenCalled();
+
+                       Chart.unregister(plugins.a);
                });
 
                it('should call plugins with default options when plugin options is TRUE', function() {
-                       var plugin = {id: 'a', hook: function() {}};
+                       var plugin = {id: 'a', hook: function() {}, defaults: {a: 42}};
 
-                       Chart.defaults.plugins.a = {a: 42};
-                       Chart.plugins.register(plugin);
+                       Chart.register(plugin);
 
                        var chart = window.acquireChart({
                                options: {
@@ -319,34 +269,33 @@ describe('Chart.plugins', function() {
 
                        spyOn(plugin, 'hook');
 
-                       Chart.plugins.notify(chart, 'hook');
+                       chart._plugins.notify(chart, 'hook');
 
                        expect(plugin.hook).toHaveBeenCalled();
                        expect(plugin.hook.calls.first().args[1]).toEqual({a: 42});
 
-                       delete Chart.defaults.plugins.a;
+                       Chart.unregister(plugin);
                });
 
 
                it('should call plugins with default options if plugin config options is undefined', function() {
-                       var plugin = {id: 'a', hook: function() {}};
+                       var plugin = {id: 'a', hook: function() {}, defaults: {a: 'foobar'}};
 
-                       Chart.defaults.plugins.a = {a: 'foobar'};
-                       Chart.plugins.register(plugin);
+                       Chart.register(plugin);
                        spyOn(plugin, 'hook');
 
                        var chart = window.acquireChart();
 
-                       Chart.plugins.notify(chart, 'hook');
+                       chart._plugins.notify(chart, 'hook');
 
                        expect(plugin.hook).toHaveBeenCalled();
                        expect(plugin.hook.calls.first().args[1]).toEqual({a: 'foobar'});
 
-                       delete Chart.defaults.plugins.a;
+                       Chart.unregister(plugin);
                });
 
                // https://github.com/chartjs/Chart.js/issues/5111#issuecomment-355934167
-               it('should invalidate cache when update plugin options', function() {
+               it('should update plugin options', function() {
                        var plugin = {id: 'a', hook: function() {}};
                        var chart = window.acquireChart({
                                plugins: [plugin],
@@ -361,7 +310,7 @@ describe('Chart.plugins', function() {
 
                        spyOn(plugin, 'hook');
 
-                       Chart.plugins.notify(chart, 'hook');
+                       chart._plugins.notify(chart, 'hook');
 
                        expect(plugin.hook).toHaveBeenCalled();
                        expect(plugin.hook.calls.first().args[1]).toEqual({foo: 'foo'});
@@ -370,7 +319,7 @@ describe('Chart.plugins', function() {
                        chart.update();
 
                        plugin.hook.calls.reset();
-                       Chart.plugins.notify(chart, 'hook');
+                       chart._plugins.notify(chart, 'hook');
 
                        expect(plugin.hook).toHaveBeenCalled();
                        expect(plugin.hook.calls.first().args[1]).toEqual({bar: 'bar'});
index 8e39d5574f4d3435a14543cbeae1b4a0f8c413fe..577c751e2ab4f3536051a670baeaf30347174bc0 100644 (file)
@@ -11,7 +11,6 @@ describe('Chart namespace', function() {
                        expect(Chart.Element instanceof Object).toBeTruthy();
                        expect(Chart.Interaction instanceof Object).toBeTruthy();
                        expect(Chart.layouts instanceof Object).toBeTruthy();
-                       expect(Chart.plugins instanceof Object).toBeTruthy();
 
                        expect(Chart.platforms.BasePlatform instanceof Function).toBeTruthy();
                        expect(Chart.platforms.BasicPlatform instanceof Function).toBeTruthy();
index b0c1c7ea8e84fa0e7a4079409d15992360e4ca24..31fa3bbbba43f92bbc3f74fa02495e0bc291a916 100644 (file)
@@ -1,10 +1,10 @@
 // Test the rectangle element
 
-var Title = Chart.plugins.getAll().find(p => p.id === 'title')._element;
+var Title = Chart.registry.getPlugin('title')._element;
 
 describe('Title block tests', function() {
        it('Should have the correct default config', function() {
-               expect(Chart.defaults.title).toEqual({
+               expect(Chart.defaults.plugins.title).toEqual({
                        align: 'center',
                        display: false,
                        position: 'top',
@@ -21,7 +21,7 @@ describe('Title block tests', function() {
        it('should update correctly', function() {
                var chart = {};
 
-               var options = Chart.helpers.clone(Chart.defaults.title);
+               var options = Chart.helpers.clone(Chart.defaults.plugins.title);
                options.text = 'My title';
 
                var title = new Title({
@@ -46,7 +46,7 @@ describe('Title block tests', function() {
        it('should update correctly when vertical', function() {
                var chart = {};
 
-               var options = Chart.helpers.clone(Chart.defaults.title);
+               var options = Chart.helpers.clone(Chart.defaults.plugins.title);
                options.text = 'My title';
                options.position = 'left';
 
@@ -72,7 +72,7 @@ describe('Title block tests', function() {
        it('should have the correct size when there are multiple lines of text', function() {
                var chart = {};
 
-               var options = Chart.helpers.clone(Chart.defaults.title);
+               var options = Chart.helpers.clone(Chart.defaults.plugins.title);
                options.text = ['line1', 'line2'];
                options.position = 'left';
                options.display = true;
@@ -93,7 +93,7 @@ describe('Title block tests', function() {
                var chart = {};
                var context = window.createMockContext();
 
-               var options = Chart.helpers.clone(Chart.defaults.title);
+               var options = Chart.helpers.clone(Chart.defaults.plugins.title);
                options.text = 'My title';
 
                var title = new Title({
@@ -145,7 +145,7 @@ describe('Title block tests', function() {
                var chart = {};
                var context = window.createMockContext();
 
-               var options = Chart.helpers.clone(Chart.defaults.title);
+               var options = Chart.helpers.clone(Chart.defaults.plugins.title);
                options.text = 'My title';
                options.position = 'left';
 
@@ -315,7 +315,7 @@ describe('Title block tests', function() {
                        chart.options.title = {};
                        chart.update();
                        expect(chart.titleBlock).not.toBe(undefined);
-                       expect(chart.titleBlock.options).toEqual(jasmine.objectContaining(Chart.defaults.title));
+                       expect(chart.titleBlock.options).toEqual(jasmine.objectContaining(Chart.defaults.plugins.title));
                });
        });
 });
index 9f4d3a94ab9d554d46bc3643e9f8622ee1c31bc9..75086ba8beb03ec3f75fec576b710b35ff57816c 100644 (file)
@@ -1,5 +1,5 @@
 // Test the rectangle element
-const tooltipPlugin = Chart.plugins.getAll().find(p => p.id === 'tooltip');
+const tooltipPlugin = Chart.registry.getPlugin('tooltip');
 const Tooltip = tooltipPlugin._element;
 
 describe('Plugin.Tooltip', function() {
@@ -22,11 +22,11 @@ describe('Plugin.Tooltip', function() {
                                value: '20'
                        };
 
-                       var label = Chart.defaults.tooltips.callbacks.label(tooltipItem, data);
+                       var label = Chart.defaults.plugins.tooltip.callbacks.label(tooltipItem, data);
                        expect(label).toBe('20');
 
                        data.datasets[0].label = 'My dataset';
-                       label = Chart.defaults.tooltips.callbacks.label(tooltipItem, data);
+                       label = Chart.defaults.plugins.tooltip.callbacks.label(tooltipItem, data);
                        expect(label).toBe('My dataset: 20');
                });
        });