mirror of
https://github.com/chartjs/Chart.js.git
synced 2025-12-08 20:36:08 +00:00
parent
69c8cc7e7b
commit
7024aad38f
@ -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
|
||||
| ---- | ---- | ------- | -----------
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
});
|
||||
```
|
||||
|
||||
@ -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, {...});
|
||||
```
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
|
||||
@ -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>
|
||||
|
||||
@ -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",
|
||||
|
||||
@ -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');
|
||||
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
if (opts === true) {
|
||||
opts = clone(defaults.plugins[id]);
|
||||
}
|
||||
|
||||
plugins.push(plugin);
|
||||
descriptors.push({
|
||||
plugin,
|
||||
options: opts || {}
|
||||
});
|
||||
});
|
||||
|
||||
cache.descriptors = descriptors;
|
||||
cache.id = this._cacheId;
|
||||
return descriptors;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
// singleton instance
|
||||
export default new PluginService();
|
||||
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]));
|
||||
}
|
||||
|
||||
const local = config.plugins || [];
|
||||
for (let i = 0; i < local.length; i++) {
|
||||
const plugin = local[i];
|
||||
|
||||
if (plugins.indexOf(plugin) === -1) {
|
||||
plugins.push(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
return plugins;
|
||||
}
|
||||
|
||||
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.
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -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, {
|
||||
|
||||
@ -276,3 +276,10 @@ export function resolveObjectKey(obj, key) {
|
||||
}
|
||||
return obj;
|
||||
}
|
||||
|
||||
/**
|
||||
* @private
|
||||
*/
|
||||
export function _capitalize(str) {
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
}
|
||||
|
||||
21
src/index.js
21
src/index.js
@ -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;
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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
|
||||
}
|
||||
};
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@ -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'});
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -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');
|
||||
});
|
||||
});
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user