Relocate chart type and dataset type defaults (#8563)

* Relocate chart type and dataset type defaults

* Update types

* Separate overrides and descriptors

* Update derived sample, use merge for inherit

* Don't merge overrides

* Review update
This commit is contained in:
Jukka Kurkela 2021-03-06 17:34:52 +02:00 committed by GitHub
parent 279b6ae1da
commit 8d6e87881a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
36 changed files with 327 additions and 253 deletions

View File

@ -235,7 +235,7 @@ If true, the bars for a particular data point fall between the grid lines. The g
## Default Options
It is common to want to apply a configuration setting to all created bar charts. The global bar chart settings are stored in `Chart.defaults.controllers.bar`. Changing the global options only affects charts created after the change. Existing charts are not changed.
It is common to want to apply a configuration setting to all created bar charts. The global bar chart settings are stored in `Chart.overrides.bar`. Changing the global options only affects charts created after the change. Existing charts are not changed.
## barPercentage vs categoryPercentage

View File

@ -106,7 +106,7 @@ All these values, if `undefined`, fallback to the associated [`elements.point.*`
## Default Options
We can also change the default values for the Bubble chart type. Doing so will give all bubble charts created after this point the new defaults. The default configuration for the bubble chart can be accessed at `Chart.defaults.controllers.bubble`.
We can also change the default values for the Bubble chart type. Doing so will give all bubble charts created after this point the new defaults. The default configuration for the bubble chart can be accessed at `Chart.overrides.bubble`.
## Data Structure

View File

@ -172,7 +172,7 @@ These are the customisation options specific to Pie & Doughnut charts. These opt
## Default Options
We can also change these default values for each Doughnut type that is created, this object is available at `Chart.defaults.controllers.doughnut`. Pie charts also have a clone of these defaults available to change at `Chart.defaults.controllers.pie`, with the only difference being `cutout` being set to 0.
We can also change these default values for each Doughnut type that is created, this object is available at `Chart.overrides.doughnut`. Pie charts also have a clone of these defaults available to change at `Chart.overrides.pie`, with the only difference being `cutout` being set to 0.
## Data Structure

View File

@ -188,12 +188,12 @@ The line chart defines the following configuration options. These options are lo
## Default Options
It is common to want to apply a configuration setting to all created line charts. The global line chart settings are stored in `Chart.defaults.controllers.line`. Changing the global options only affects charts created after the change. Existing charts are not changed.
It is common to want to apply a configuration setting to all created line charts. The global line chart settings are stored in `Chart.overrides.line`. Changing the global options only affects charts created after the change. Existing charts are not changed.
For example, to configure all line charts with `spanGaps = true` you would do:
```javascript
Chart.defaults.controllers.line.spanGaps = true;
Chart.overrides.line.spanGaps = true;
```
## Data Structure

View File

@ -120,12 +120,12 @@ The polar area chart uses the [radialLinear](../axes/radial/linear.mdx) scale. A
## Default Options
We can also change these default values for each PolarArea type that is created, this object is available at `Chart.defaults.controllers.polarArea`. Changing the global options only affects charts created after the change. Existing charts are not changed.
We can also change these default values for each PolarArea type that is created, this object is available at `Chart.overrides.polarArea`. Changing the global options only affects charts created after the change. Existing charts are not changed.
For example, to configure all new polar area charts with `animateScale = false` you would do:
```javascript
Chart.defaults.controllers.polarArea.animation.animateScale = false;
Chart.overrides.polarArea.animation.animateScale = false;
```
## Data Structure

View File

@ -193,7 +193,7 @@ options = {
## Default Options
It is common to want to apply a configuration setting to all created radar charts. The global radar chart settings are stored in `Chart.defaults.controllers.radar`. Changing the global options only affects charts created after the change. Existing charts are not changed.
It is common to want to apply a configuration setting to all created radar charts. The global radar chart settings are stored in `Chart.overrides.radar`. Changing the global options only affects charts created after the change. Existing charts are not changed.
## Data Structure

View File

@ -86,7 +86,7 @@ function example() {
y: {
from: 0
}
}
}
},
hide: {
animations: {
@ -124,9 +124,8 @@ Animation configuration consists of 3 keys.
These keys can be configured in following paths:
* `` - chart options
* `controllers[type]` - controller type options
* `controllers[type].datasets` - dataset type options
* `datasets[type]` - dataset type options
* `overrides[type]` - chart type options
These paths are valid under `defaults` for global confuguration and `options` for instance configuration.

View File

@ -42,7 +42,7 @@ The following example would set the `showLine` option to 'false' for all line da
```javascript
// Do not show lines for all datasets by default
Chart.defaults.controllers.line.showLine = false;
Chart.defaults.datasets.line.showLine = false;
// This chart would show a line only for the third dataset
var chart = new Chart(ctx, {

View File

@ -9,7 +9,7 @@ Options are resolved from top to bottom, using a context dependent route.
### Chart level options
* options
* defaults.controllers[`config.type`]
* overrides[`config.type`]
* defaults
### Dataset level options
@ -18,18 +18,18 @@ Options are resolved from top to bottom, using a context dependent route.
* dataset
* options.datasets[`dataset.type`]
* options.controllers[`dataset.type`].datasets
* options
* overrides[`config.type`].datasets[`dataset.type`]
* defaults.datasets[`dataset.type`]
* defaults.controllers[`dataset.type`].datasets
* defaults
### Dataset animation options
* dataset.animation
* options.controllers[`dataset.type`].datasets.animation
* options.datasets[`dataset.type`].animation
* options.animation
* defaults.controllers[`dataset.type`].datasets.animation
* overrides[`config.type`].datasets[`dataset.type`].animation
* defaults.datasets[`dataset.type`].animation
* defaults.animation
### Dataset element level options
@ -38,31 +38,30 @@ Each scope is looked up with `elementType` prefix in the option name first, then
* dataset
* options.datasets[`dataset.type`]
* options.controllers[`dataset.type`].datasets
* options.controllers[`dataset.type`].elements[`elementType`]
* options.datasets[`dataset.type`].elements[`elementType`]
* options.elements[`elementType`]
* options
* overrides[`config.type`].datasets[`dataset.type`]
* overrides[`config.type`].datasets[`dataset.type`].elements[`elementType`]
* defaults.datasets[`dataset.type`]
* defaults.controllers[`dataset.type`].datasets
* defaults.controllers[`dataset.type`].elements[`elementType`]
* defaults.datasets[`dataset.type`].elements[`elementType`]
* defaults.elements[`elementType`]
* defaults
### Scale options
* options.scales
* defaults.controllers[`config.type`].scales
* defaults.controllers[`dataset.type`].scales
* overrides[`config.type`].scales
* defaults.scales
* defaults.scale
### Plugin options
A plugin can provide `additionalOptionScopes` array of paths to additionally look for its options in. For root scope, use empty string: `''`. Most core plugins also take options from root scope.
* options.plugins[`plugin.id`]
* options.controllers[`config.type`].plugins[`plugin.id`]
* (options.[`...plugin.additionalOptionScopes`])
* defaults.controllers[`config.type`].plugins[`plugin.id`]
* overrides[`config.type`].plugins[`plugin.id`]
* defaults.plugins[`plugin.id`]
* (defaults.[`...plugin.additionalOptionScopes`])

View File

@ -111,7 +111,7 @@ A number of changes were made to the configuration options passed to the `Chart`
#### Defaults
* `global` namespace was removed from `defaults`. So `Chart.defaults.global` is now `Chart.defaults`
* Dataset controller defaults were relocate to `controllers`. For example `Chart.defaults.line` is now `Chart.defaults.controllers.line`
* Dataset controller defaults were relocate to `overrides`. For example `Chart.defaults.line` is now `Chart.overrides.line`
* `default` prefix was removed from defaults. For example `Chart.defaults.global.defaultColor` is now `Chart.defaults.color`
* `defaultColor` was split to `color`, `borderColor` and `backgroundColor`
* `defaultFontColor` was renamed to `color`

View File

@ -24,7 +24,7 @@
// Call bubble controller method to draw all the points
super.draw(arguments);
// Now we can do some custom drawing for this dataset. Here we'll draw a red box around the first point in each dataset
// Now we can do some custom drawing for this dataset. Here we'll draw a box around the first point in each dataset, using `boxStrokeStyle` dataset option for color
var meta = this.getMeta();
var pt0 = meta.data[0];
@ -33,14 +33,19 @@
var ctx = this.chart.ctx;
ctx.save();
ctx.strokeStyle = 'red';
ctx.strokeStyle = this.options.boxStrokeStyle;
ctx.lineWidth = 1;
ctx.strokeRect(x - radius, y - radius, 2 * radius, 2 * radius);
ctx.restore();
}
}
Custom.id = 'derivedBubble';
Custom.defaults = Chart.controllers.bubble.defaults;
Custom.defaults = {
// Custom defaults. Bubble defaults are inherited.
boxStrokeStyle: 'red'
};
// Overrides are only inherited, but not merged if defined
// Custom.overrides = Chart.overrides.bubble;
// Stores the controller so that the chart initialization routine can look it up
Chart.register(Custom);

View File

@ -48,7 +48,7 @@
labels: {
generateLabels: function(chart) {
// Get the default label list
var original = Chart.defaults.controllers.pie.plugins.legend.labels.generateLabels;
var original = Chart.overrides.pie.plugins.legend.labels.generateLabels;
var labels = original.call(this, chart);
// Build an array of colors used in the datasets of the chart

View File

@ -511,23 +511,27 @@ BarController.defaults = {
datasetElementType: false,
dataElementType: 'bar',
categoryPercentage: 0.8,
barPercentage: 0.9,
animations: {
numbers: {
type: 'number',
properties: ['x', 'y', 'base', 'width', 'height']
}
}
};
/**
* @type {any}
*/
BarController.overrides = {
interaction: {
mode: 'index'
},
hover: {},
datasets: {
categoryPercentage: 0.8,
barPercentage: 0.9,
animations: {
numbers: {
type: 'number',
properties: ['x', 'y', 'base', 'width', 'height']
}
}
},
scales: {
_index_: {
type: 'category',

View File

@ -133,12 +133,19 @@ BubbleController.id = 'bubble';
BubbleController.defaults = {
datasetElementType: false,
dataElementType: 'point',
animations: {
numbers: {
type: 'number',
properties: ['x', 'y', 'borderWidth', 'radius']
}
},
}
};
/**
* @type {any}
*/
BubbleController.overrides = {
scales: {
x: {
type: 'linear'

View File

@ -328,23 +328,26 @@ DoughnutController.defaults = {
properties: ['circumference', 'endAngle', 'innerRadius', 'outerRadius', 'startAngle', 'x', 'y', 'offset', 'borderWidth']
},
},
aspectRatio: 1,
// The percentage of the chart that we cut out of the middle.
cutout: '50%',
datasets: {
// The percentage of the chart that we cut out of the middle.
cutout: '50%',
// The rotation of the chart, where the first data arc begins.
rotation: 0,
// The rotation of the chart, where the first data arc begins.
rotation: 0,
// The total circumference of the chart.
circumference: 360,
// The total circumference of the chart.
circumference: 360,
// The outr radius of the chart
radius: '100%'
},
// The outr radius of the chart
radius: '100%',
indexAxis: 'r',
};
/**
* @type {any}
*/
DoughnutController.overrides = {
aspectRatio: 1,
// Need to override these to give a nice default
plugins: {

View File

@ -111,11 +111,14 @@ LineController.defaults = {
datasetElementType: 'line',
dataElementType: 'point',
datasets: {
showLine: true,
spanGaps: false,
},
showLine: true,
spanGaps: false,
};
/**
* @type {any}
*/
LineController.overrides = {
interaction: {
mode: 'index'
},

View File

@ -11,17 +11,15 @@ PieController.id = 'pie';
* @type {any}
*/
PieController.defaults = {
datasets: {
// The percentage of the chart that we cut out of the middle.
cutout: 0,
// The percentage of the chart that we cut out of the middle.
cutout: 0,
// The rotation of the chart, where the first data arc begins.
rotation: 0,
// The rotation of the chart, where the first data arc begins.
rotation: 0,
// The total circumference of the chart.
circumference: 360,
// The total circumference of the chart.
circumference: 360,
// The outr radius of the chart
radius: '100%'
}
// The outr radius of the chart
radius: '100%'
};

View File

@ -131,25 +131,16 @@ PolarAreaController.defaults = {
properties: ['x', 'y', 'startAngle', 'endAngle', 'innerRadius', 'outerRadius']
},
},
aspectRatio: 1,
indexAxis: 'r',
scales: {
r: {
type: 'radialLinear',
angleLines: {
display: false
},
beginAtZero: true,
gridLines: {
circular: true
},
pointLabels: {
display: false
}
}
},
startAngle: 0,
};
/**
* @type {any}
*/
PolarAreaController.overrides = {
aspectRatio: 1,
plugins: {
legend: {
labels: {
@ -193,6 +184,21 @@ PolarAreaController.defaults = {
}
}
}
}
},
scales: {
r: {
type: 'radialLinear',
angleLines: {
display: false
},
beginAtZero: true,
gridLines: {
circular: true
},
pointLabels: {
display: false
}
}
}
};

View File

@ -80,16 +80,21 @@ RadarController.id = 'radar';
RadarController.defaults = {
datasetElementType: 'line',
dataElementType: 'point',
aspectRatio: 1,
datasets: {
showLine: true,
},
indexAxis: 'r',
showLine: true,
elements: {
line: {
fill: 'start'
}
},
indexAxis: 'r',
};
/**
* @type {any}
*/
RadarController.overrides = {
aspectRatio: 1,
scales: {
r: {
type: 'radialLinear',

View File

@ -10,19 +10,14 @@ ScatterController.id = 'scatter';
* @type {any}
*/
ScatterController.defaults = {
scales: {
x: {
type: 'linear'
},
y: {
type: 'linear'
}
},
showLine: false,
fill: false
};
datasets: {
showLine: false,
fill: false
},
/**
* @type {any}
*/
ScatterController.overrides = {
interaction: {
mode: 'point'
@ -39,5 +34,14 @@ ScatterController.defaults = {
}
}
}
},
scales: {
x: {
type: 'linear'
},
y: {
type: 'linear'
}
}
};

View File

@ -1,13 +1,11 @@
import defaults from './core.defaults';
import defaults, {overrides, descriptors} from './core.defaults';
import {mergeIf, resolveObjectKey, isArray, isFunction, valueOrDefault, isObject} from '../helpers/helpers.core';
import {_attachContext, _createResolver, _descriptors} from '../helpers/helpers.config';
export function getIndexAxis(type, options) {
const typeDefaults = defaults.controllers[type] || {};
const datasetDefaults = typeDefaults.datasets || {};
const datasetOptions = options.datasets || {};
const typeOptions = datasetOptions[type] || {};
return typeOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x';
const datasetDefaults = defaults.datasets[type] || {};
const datasetOptions = (options.datasets || {})[type] || {};
return datasetOptions.indexAxis || options.indexAxis || datasetDefaults.indexAxis || 'x';
}
function getAxisFromDefaultScaleID(id, indexAxis) {
@ -41,7 +39,7 @@ export function determineAxis(id, scaleOptions) {
}
function mergeScaleConfig(config, options) {
const chartDefaults = defaults.controllers[config.type] || {scales: {}};
const chartDefaults = overrides[config.type] || {scales: {}};
const configScales = options.scales || {};
const chartIndexAxis = getIndexAxis(config.type, options);
const firstIDs = Object.create(null);
@ -61,7 +59,7 @@ function mergeScaleConfig(config, options) {
config.data.datasets.forEach(dataset => {
const type = dataset.type || config.type;
const indexAxis = dataset.indexAxis || getIndexAxis(type, options);
const datasetDefaults = defaults.controllers[type] || {};
const datasetDefaults = overrides[type] || {};
const defaultScaleOptions = datasetDefaults.scales || {};
Object.keys(defaultScaleOptions).forEach(defaultID => {
const axis = getAxisFromDefaultScaleID(defaultID, indexAxis);
@ -175,8 +173,6 @@ export default class Config {
return cachedKeys(datasetType,
() => [
`datasets.${datasetType}`,
`controllers.${datasetType}`,
`controllers.${datasetType}.datasets`,
''
]);
}
@ -192,12 +188,9 @@ export default class Config {
return cachedKeys(`${datasetType}.transition.${transition}`,
() => [
`datasets.${datasetType}.transitions.${transition}`,
`controllers.${datasetType}.transitions.${transition}`,
`controllers.${datasetType}.datasets.transitions.${transition}`,
`transitions.${transition}`,
// The following are used for looking up the `animations` and `animation` keys
`datasets.${datasetType}`,
`controllers.${datasetType}`,
`controllers.${datasetType}.datasets`,
''
]);
}
@ -213,9 +206,8 @@ export default class Config {
datasetElementScopeKeys(datasetType, elementType) {
return cachedKeys(`${datasetType}-${elementType}`,
() => [
`datasets.${datasetType}.elements.${elementType}`,
`datasets.${datasetType}`,
`controllers.${datasetType}.datasets`,
`controllers.${datasetType}.elements.${elementType}`,
`elements.${elementType}`,
''
]);
@ -231,7 +223,6 @@ export default class Config {
const type = this.type;
return cachedKeys(`${type}-plugin-${id}`,
() => [
`controllers.${type}.plugins.${id}`,
`plugins.${id}`,
...plugin.additionalOptionScopes || [],
]);
@ -244,10 +235,11 @@ export default class Config {
* @param {boolean} [resetCache] - reset the cache for this mainScope
*/
getOptionScopes(mainScope, scopeKeys, resetCache) {
let cache = this._scopeCache.get(mainScope);
const {_scopeCache, options, type} = this;
let cache = _scopeCache.get(mainScope);
if (!cache || resetCache) {
cache = new Map();
this._scopeCache.set(mainScope, cache);
_scopeCache.set(mainScope, cache);
}
const cached = cache.get(scopeKeys);
if (cached) {
@ -260,9 +252,10 @@ export default class Config {
scopes.add(mainScope);
scopeKeys.forEach(key => addIfFound(scopes, mainScope, key));
}
scopeKeys.forEach(key => addIfFound(scopes, this.options, key));
scopeKeys.forEach(key => addIfFound(scopes, options, key));
scopeKeys.forEach(key => addIfFound(scopes, overrides[type] || {}, key));
scopeKeys.forEach(key => addIfFound(scopes, defaults, key));
scopeKeys.forEach(key => addIfFound(scopes, defaults.descriptors, key));
scopeKeys.forEach(key => addIfFound(scopes, descriptors, key));
const array = [...scopes];
if (keysCached.has(scopeKeys)) {
@ -276,14 +269,15 @@ export default class Config {
* @return {object[]}
*/
chartOptionScopes() {
const controllerDefaults = defaults.controllers[this.type] || {};
const {options, type} = this;
return [
this.options,
controllerDefaults,
controllerDefaults.datasets || {},
{type: this.type},
options,
overrides[type] || {},
defaults.datasets[type] || {}, // https://github.com/chartjs/Chart.js/issues/8531
{type},
defaults,
defaults.descriptors
descriptors
];
}

View File

@ -1,5 +1,5 @@
import animator from './core.animator';
import defaults from './core.defaults';
import defaults, {overrides} from './core.defaults';
import Interaction from './core.interaction';
import layouts from './core.layouts';
import {BasicPlatform, DomPlatform} from '../platform';
@ -410,11 +410,11 @@ class Chart {
meta.controller.updateIndex(i);
meta.controller.linkScales();
} else {
const controllerDefaults = defaults.controllers[type];
const ControllerClass = registry.getController(type);
const {datasetElementType, dataElementType} = defaults.datasets[type];
Object.assign(ControllerClass.prototype, {
dataElementType: registry.getElement(controllerDefaults.dataElementType),
datasetElementType: controllerDefaults.datasetElementType && registry.getElement(controllerDefaults.datasetElementType)
dataElementType: registry.getElement(dataElementType),
datasetElementType: datasetElementType && registry.getElement(datasetElementType)
});
meta.controller = new ControllerClass(me, i);
newControllers.push(meta.controller);
@ -1117,6 +1117,10 @@ Object.defineProperties(Chart, {
enumerable,
value: instances
},
overrides: {
enumerable,
value: overrides
},
registry: {
enumerable,
value: registry

View File

@ -1,7 +1,8 @@
import {getHoverColor} from '../helpers/helpers.color';
import {isObject, merge, valueOrDefault} from '../helpers/helpers.core';
const privateSymbol = Symbol('private');
export const overrides = Object.create(null);
export const descriptors = Object.create(null);
/**
* @param {object} node
@ -20,17 +21,24 @@ function getScope(node, key) {
return node;
}
function set(root, scope, values) {
if (typeof scope === 'string') {
return merge(getScope(root, scope), values);
}
return merge(getScope(root, ''), scope);
}
/**
* Please use the module's default export which provides a singleton instance
* Note: class is exported for typedoc
*/
export class Defaults {
constructor(descriptors) {
constructor(_descriptors) {
this.animation = undefined;
this.backgroundColor = 'rgba(0,0,0,0.1)';
this.borderColor = 'rgba(0,0,0,0.1)';
this.color = '#666';
this.controllers = {};
this.datasets = {};
this.devicePixelRatio = (context) => context.chart.platform.getDevicePixelRatio();
this.elements = {};
this.events = [
@ -68,12 +76,7 @@ export class Defaults {
this.scales = {};
this.showLine = true;
Object.defineProperty(this, privateSymbol, {
value: Object.create(null),
writable: false
});
this.describe(descriptors);
this.describe(_descriptors);
}
/**
@ -81,10 +84,7 @@ export class Defaults {
* @param {object} [values]
*/
set(scope, values) {
if (typeof scope === 'string') {
return merge(getScope(this, scope), values);
}
return merge(getScope(this, ''), scope);
return set(this, scope, values);
}
/**
@ -99,15 +99,11 @@ export class Defaults {
* @param {object} [values]
*/
describe(scope, values) {
const root = this[privateSymbol];
if (typeof scope === 'string') {
return merge(getScope(root, scope), values);
}
return merge(getScope(root, ''), scope);
return set(descriptors, scope, values);
}
get descriptors() {
return this[privateSymbol];
override(scope, values) {
return set(overrides, scope, values);
}
/**

View File

@ -10,7 +10,7 @@ import {each, callback as call, _capitalize} from '../helpers/helpers.core';
*/
export class Registry {
constructor() {
this.controllers = new TypedRegistry(DatasetController, 'controllers');
this.controllers = new TypedRegistry(DatasetController, 'datasets', true);
this.elements = new TypedRegistry(Element, 'elements');
this.plugins = new TypedRegistry(Object, 'plugins');
this.scales = new TypedRegistry(Scale, 'scales');

View File

@ -1,13 +1,15 @@
import defaults from './core.defaults';
import {merge} from '../helpers';
import defaults, {overrides} from './core.defaults';
/**
* @typedef {{id: string, defaults: any, defaultRoutes: any}} IChartComponent
* @typedef {{id: string, defaults: any, overrides?: any, defaultRoutes: any}} IChartComponent
*/
export default class TypedRegistry {
constructor(type, scope) {
constructor(type, scope, override) {
this.type = type;
this.scope = scope;
this.override = override;
this.items = Object.create(null);
}
@ -20,18 +22,18 @@ export default class TypedRegistry {
* @returns {string} The scope where items defaults were registered to.
*/
register(item) {
const me = this;
const proto = Object.getPrototypeOf(item);
let parentScope;
if (isIChartComponent(proto)) {
// Make sure the parent is registered and note the scope where its defaults are.
parentScope = this.register(proto);
parentScope = me.register(proto);
}
const items = this.items;
const items = me.items;
const id = item.id;
const baseScope = this.scope;
const scope = baseScope ? baseScope + '.' + id : id;
const scope = me.scope + '.' + id;
if (!id) {
throw new Error('class does not have id: ' + item);
@ -44,6 +46,9 @@ export default class TypedRegistry {
items[id] = item;
registerDefaults(item, scope, parentScope);
if (me.override) {
defaults.override(item.id, item.overrides);
}
return scope;
}
@ -70,18 +75,20 @@ export default class TypedRegistry {
if (scope && id in defaults[scope]) {
delete defaults[scope][id];
if (this.override) {
delete overrides[id];
}
}
}
}
function registerDefaults(item, scope, parentScope) {
// Inherit the parent's defaults and keep existing defaults
const itemDefaults = Object.assign(
Object.create(null),
parentScope && defaults.get(parentScope),
item.defaults,
defaults.get(scope)
);
const itemDefaults = merge(Object.create(null), [
parentScope ? defaults.get(parentScope) : {},
defaults.get(scope),
item.defaults
]);
defaults.set(scope, itemDefaults);

View File

@ -1306,7 +1306,7 @@ describe('Chart.controllers.bar', function() {
var chart = window.acquireChart(this.config);
var meta = chart.getDatasetMeta(0);
var xScale = chart.scales[meta.xAxisID];
var options = Chart.defaults.controllers.bar.datasets;
var options = Chart.defaults.datasets.bar;
var categoryPercentage = options.categoryPercentage;
var barPercentage = options.barPercentage;
@ -1482,7 +1482,7 @@ describe('Chart.controllers.bar', function() {
expected = barThickness;
} else {
var scale = chart.scales.x;
var options = Chart.defaults.controllers.bar.datasets;
var options = Chart.defaults.datasets.bar;
var categoryPercentage = options.categoryPercentage;
var barPercentage = options.barPercentage;
var tickInterval = scale.getPixelForTick(1) - scale.getPixelForTick(0);

View File

@ -511,18 +511,16 @@ describe('Chart.controllers.line', function() {
describe('dataset global defaults', function() {
beforeEach(function() {
this._defaults = Chart.helpers.clone(Chart.defaults.controllers.line.datasets);
this._defaults = Chart.helpers.clone(Chart.defaults.datasets.line);
});
afterEach(function() {
Chart.defaults.controllers.line.datasets = this._defaults;
Chart.defaults.datasets.line = this._defaults;
delete this._defaults;
});
it('should utilize the dataset global default options', function() {
Chart.defaults.controllers.line.datasets = Chart.defaults.controllers.line.datasets || {};
Chart.helpers.merge(Chart.defaults.controllers.line.datasets, {
Chart.helpers.merge(Chart.defaults.datasets.line, {
spanGaps: true,
tension: 0.231,
backgroundColor: '#add',
@ -563,9 +561,7 @@ describe('Chart.controllers.line', function() {
});
it('should be overriden by user-supplied values', function() {
Chart.defaults.controllers.line.datasets = Chart.defaults.controllers.line.datasets || {};
Chart.helpers.merge(Chart.defaults.controllers.line.datasets, {
Chart.helpers.merge(Chart.defaults.datasets.line, {
spanGaps: true,
tension: 0.231
});

View File

@ -1,5 +1,7 @@
describe('Chart', function() {
const overrides = Chart.overrides;
// https://github.com/chartjs/Chart.js/issues/2481
// See global.deprecations.tests.js for backward compatibility
it('should be defined and prototype of chart instances', function() {
@ -92,11 +94,10 @@ describe('Chart', function() {
it('should initialize config with default interaction options', function() {
var callback = function() {};
var defaults = Chart.defaults;
var defaultMode = defaults.controllers.line.interaction.mode;
var defaultMode = overrides.line.interaction.mode;
defaults.hover.onHover = callback;
defaults.controllers.line.spanGaps = true;
defaults.controllers.line.interaction.mode = 'test';
overrides.line.interaction.mode = 'test';
var chart = acquireChart({
type: 'line'
@ -104,14 +105,11 @@ describe('Chart', function() {
var options = chart.options;
expect(options.font.size).toBe(defaults.font.size);
expect(options.showLine).toBe(defaults.controllers.line.datasets.showLine);
expect(options.spanGaps).toBe(true);
expect(options.hover.onHover).toBe(callback);
expect(options.hover.mode).toBe('test');
defaults.hover.onHover = null;
defaults.controllers.line.spanGaps = false;
defaults.controllers.line.interaction.mode = defaultMode;
overrides.line.interaction.mode = defaultMode;
});
it('should initialize config with default hover options', function() {
@ -119,8 +117,7 @@ describe('Chart', function() {
var defaults = Chart.defaults;
defaults.hover.onHover = callback;
defaults.controllers.line.spanGaps = true;
defaults.controllers.line.hover.mode = 'test';
overrides.line.hover.mode = 'test';
var chart = acquireChart({
type: 'line'
@ -128,23 +125,21 @@ describe('Chart', function() {
var options = chart.options;
expect(options.font.size).toBe(defaults.font.size);
expect(options.showLine).toBe(defaults.controllers.line.datasets.showLine);
expect(options.spanGaps).toBe(true);
expect(options.hover.onHover).toBe(callback);
expect(options.hover.mode).toBe('test');
defaults.hover.onHover = null;
defaults.controllers.line.spanGaps = false;
delete defaults.controllers.line.hover.mode;
delete overrides.line.hover.mode;
});
it('should override default options', function() {
var callback = function() {};
var defaults = Chart.defaults;
var defaultSpanGaps = defaults.datasets.line.spanGaps;
defaults.hover.onHover = callback;
defaults.controllers.line.hover.mode = 'x-axis';
defaults.controllers.line.spanGaps = true;
overrides.line.hover.mode = 'x-axis';
defaults.datasets.line.spanGaps = true;
var chart = acquireChart({
type: 'line',
@ -167,12 +162,12 @@ describe('Chart', function() {
expect(options.plugins.title.position).toBe('bottom');
defaults.hover.onHover = null;
delete defaults.controllers.line.hover.mode;
defaults.controllers.line.spanGaps = false;
delete overrides.line.hover.mode;
defaults.datasets.line.spanGaps = defaultSpanGaps;
});
it('should initialize config with default dataset options', function() {
var defaults = Chart.defaults.controllers.pie.datasets;
var defaults = Chart.defaults.datasets.pie;
var chart = acquireChart({
type: 'pie'
@ -431,7 +426,7 @@ describe('Chart', function() {
expect(chart.scales.x.options._jasmineCheck).toBeDefined();
expect(chart.scales.y.options._jasmineCheck).toBeDefined();
expect(Chart.defaults.controllers.line._jasmineCheck).not.toBeDefined();
expect(Chart.overrides.line._jasmineCheck).not.toBeDefined();
expect(Chart.defaults._jasmineCheck).not.toBeDefined();
expect(Chart.defaults.scales.linear._jasmineCheck).not.toBeDefined();
expect(Chart.defaults.scales.category._jasmineCheck).not.toBeDefined();

View File

@ -5,17 +5,22 @@ describe('Chart.registry', function() {
CustomController.defaults = {
foo: 'bar'
};
CustomController.overrides = {
bar: 'foo'
};
Chart.register(CustomController);
expect(Chart.registry.getController('custom')).toEqual(CustomController);
expect(Chart.defaults.controllers.custom).toEqual(CustomController.defaults);
expect(Chart.defaults.datasets.custom).toEqual(CustomController.defaults);
expect(Chart.overrides.custom).toEqual(CustomController.overrides);
Chart.unregister(CustomController);
expect(function() {
Chart.registry.getController('custom');
}).toThrow(new Error('"custom" is not a registered controller.'));
expect(Chart.defaults.controllers.custom).not.toBeDefined();
expect(Chart.overrides.custom).not.toBeDefined();
expect(Chart.defaults.datasets.custom).not.toBeDefined();
});
it('should handle an ES6 scale extension', function() {
@ -34,7 +39,7 @@ describe('Chart.registry', function() {
expect(function() {
Chart.registry.getScale('es6Scale');
}).toThrow(new Error('"es6Scale" is not a registered scale.'));
expect(Chart.defaults.controllers.custom).not.toBeDefined();
expect(Chart.defaults.scales.es6Scale).not.toBeDefined();
});
it('should handle an ES6 element extension', function() {
@ -104,14 +109,14 @@ describe('Chart.registry', function() {
Chart.registry.addControllers(customExtension);
expect(Chart.registry.getController('custom')).toEqual(customExtension);
expect(Chart.defaults.controllers.custom).toEqual(customExtension.defaults);
expect(Chart.defaults.datasets.custom).toEqual(customExtension.defaults);
Chart.registry.removeControllers(customExtension);
expect(function() {
Chart.registry.getController('custom');
}).toThrow(new Error('"custom" is not a registered controller.'));
expect(Chart.defaults.controllers.custom).not.toBeDefined();
expect(Chart.defaults.datasets.custom).not.toBeDefined();
});
it('as scale', function() {
@ -199,17 +204,21 @@ describe('Chart.registry', function() {
});
it('should preserve existing defaults', function() {
Chart.defaults.controllers.test = {test1: true};
Chart.defaults.datasets.test = {test1: true, test3: false};
Chart.overrides.test = {testA: true, testC: false};
class testController extends Chart.DatasetController {}
testController.id = 'test';
testController.defaults = {test1: false, test2: true};
testController.overrides = {testA: false, testB: true};
Chart.register(testController);
expect(Chart.defaults.controllers.test).toEqual({test1: true, test2: true});
expect(Chart.defaults.datasets.test).toEqual({test1: false, test2: true, test3: false});
expect(Chart.overrides.test).toEqual({testA: false, testB: true, testC: false});
Chart.unregister(testController);
expect(Chart.defaults.controllers.test).not.toBeDefined();
expect(Chart.defaults.datasets.test).not.toBeDefined();
expect(Chart.overrides.test).not.toBeDefined();
});
describe('should handle multiple items', function() {

View File

@ -1,7 +1,6 @@
describe('Default Configs', function() {
describe('Bubble Chart', function() {
it('should return correct tooltip strings', function() {
var config = Chart.defaults.controllers.bubble;
var chart = window.acquireChart({
type: 'bubble',
data: {
@ -14,7 +13,6 @@ describe('Default Configs', function() {
}]
}]
},
options: config
});
// fake out the tooltip hover and force the tooltip to update
@ -33,7 +31,6 @@ describe('Default Configs', function() {
describe('Doughnut Chart', function() {
it('should return correct tooltip strings', function() {
var config = Chart.defaults.controllers.doughnut;
var chart = window.acquireChart({
type: 'doughnut',
data: {
@ -42,7 +39,6 @@ describe('Default Configs', function() {
data: [10, 20, 30],
}]
},
options: config
});
// fake out the tooltip hover and force the tooltip to update
@ -59,7 +55,6 @@ describe('Default Configs', function() {
});
it('should return correct tooltip string for a multiline label', function() {
var config = Chart.defaults.controllers.doughnut;
var chart = window.acquireChart({
type: 'doughnut',
data: {
@ -68,7 +63,6 @@ describe('Default Configs', function() {
data: [10, 20, 30],
}]
},
options: config
});
// fake out the tooltip hover and force the tooltip to update
@ -89,7 +83,6 @@ describe('Default Configs', function() {
});
it('should return correct legend label objects', function() {
var config = Chart.defaults.controllers.doughnut;
var chart = window.acquireChart({
type: 'doughnut',
data: {
@ -101,7 +94,6 @@ describe('Default Configs', function() {
borderColor: '#000'
}]
},
options: config
});
var expected = [{
@ -130,7 +122,7 @@ describe('Default Configs', function() {
});
it('should hide the correct arc when a legend item is clicked', function() {
var config = Chart.defaults.controllers.doughnut;
var config = Chart.overrides.doughnut;
var chart = window.acquireChart({
type: 'doughnut',
data: {
@ -142,7 +134,6 @@ describe('Default Configs', function() {
borderColor: '#000'
}]
},
options: config
});
spyOn(chart, 'update').and.callThrough();
@ -159,7 +150,6 @@ describe('Default Configs', function() {
describe('Polar Area Chart', function() {
it('should return correct tooltip strings', function() {
var config = Chart.defaults.controllers.polarArea;
var chart = window.acquireChart({
type: 'polarArea',
data: {
@ -168,7 +158,6 @@ describe('Default Configs', function() {
data: [10, 20, 30],
}]
},
options: config
});
// fake out the tooltip hover and force the tooltip to update
@ -185,7 +174,6 @@ describe('Default Configs', function() {
});
it('should return correct legend label objects', function() {
var config = Chart.defaults.controllers.polarArea;
var chart = window.acquireChart({
type: 'polarArea',
data: {
@ -197,7 +185,6 @@ describe('Default Configs', function() {
borderColor: '#000'
}]
},
options: config
});
var expected = [{
@ -226,7 +213,7 @@ describe('Default Configs', function() {
});
it('should hide the correct arc when a legend item is clicked', function() {
var config = Chart.defaults.controllers.polarArea;
var config = Chart.overrides.polarArea;
var chart = window.acquireChart({
type: 'polarArea',
data: {
@ -238,7 +225,6 @@ describe('Default Configs', function() {
borderColor: '#000'
}]
},
options: config
});
spyOn(chart, 'update').and.callThrough();

View File

@ -119,8 +119,8 @@ describe('Platform.dom', function() {
});
it('should use default "chart" aspect ratio for render and display sizes', function() {
var ratio = Chart.defaults.controllers.doughnut.aspectRatio;
Chart.defaults.controllers.doughnut.aspectRatio = 1;
var ratio = Chart.overrides.doughnut.aspectRatio;
Chart.overrides.doughnut.aspectRatio = 1;
var chart = acquireChart({
type: 'doughnut',
@ -133,7 +133,7 @@ describe('Platform.dom', function() {
}
});
Chart.defaults.controllers.doughnut.aspectRatio = ratio;
Chart.overrides.doughnut.aspectRatio = ratio;
expect(chart).toBeChartOfSize({
dw: 425, dh: 425,

58
types/index.esm.d.ts vendored
View File

@ -93,7 +93,8 @@ export interface ControllerDatasetOptions extends ParsingOptions {
export interface BarControllerDatasetOptions
extends ControllerDatasetOptions,
ScriptableAndArrayOptions<BarOptions, ScriptableContext<'bar'>>,
ScriptableAndArrayOptions<CommonHoverOptions, ScriptableContext<'bar'>> {
ScriptableAndArrayOptions<CommonHoverOptions, ScriptableContext<'bar'>>,
AnimationOptions<'bar'> {
/**
* The ID of the x axis to plot this dataset on.
*/
@ -182,7 +183,8 @@ export interface LineControllerDatasetOptions
ScriptableAndArrayOptions<PointPrefixedOptions, ScriptableContext<'line'>>,
ScriptableAndArrayOptions<PointPrefixedHoverOptions, ScriptableContext<'line'>>,
ScriptableOptions<LineOptions, ScriptableContext<'line'>>,
ScriptableOptions<LineHoverOptions, ScriptableContext<'line'>> {
ScriptableOptions<LineHoverOptions, ScriptableContext<'line'>>,
AnimationOptions<'line'> {
/**
* The ID of the x axis to plot this dataset on.
*/
@ -238,7 +240,8 @@ export const ScatterController: ChartComponent & {
export interface DoughnutControllerDatasetOptions
extends ControllerDatasetOptions,
ScriptableAndArrayOptions<ArcOptions, ScriptableContext<'doughnut'>>,
ScriptableAndArrayOptions<ArcHoverOptions, ScriptableContext<'doughnut'>> {
ScriptableAndArrayOptions<ArcHoverOptions, ScriptableContext<'doughnut'>>,
AnimationOptions<'doughnut'> {
/**
* Sweep to allow arcs to cover.
@ -364,8 +367,9 @@ export interface RadarControllerDatasetOptions
ScriptableOptions<PointPrefixedOptions, ScriptableContext<'radar'>>,
ScriptableOptions<PointPrefixedHoverOptions, ScriptableContext<'radar'>>,
ScriptableOptions<LineOptions, ScriptableContext<'radar'>>,
ScriptableOptions<LineHoverOptions, ScriptableContext<'radar'>> {
/**
ScriptableOptions<LineHoverOptions, ScriptableContext<'radar'>>,
AnimationOptions<'radar'> {
/**
* The ID of the x axis to plot this dataset on.
*/
xAxisID: string;
@ -496,6 +500,7 @@ export declare class Chart<
notifyPlugins(hook: string, args?: AnyObject): boolean | void;
static readonly defaults: Defaults;
static readonly overrides: Overrides;
static readonly version: string;
static readonly instances: { [key: string]: Chart };
static readonly registry: Registry;
@ -608,16 +613,6 @@ export interface DatasetControllerChartComponent extends ChartComponent {
}
export interface Defaults extends CoreChartOptions<ChartType>, ElementChartOptions, PluginChartOptions<ChartType> {
controllers: {
[key in ChartType]: DeepPartial<
CoreChartOptions<key> &
ElementChartOptions &
PluginChartOptions<key> &
DatasetChartOptions<key>[key] &
ScaleChartOptions<key> &
ChartTypeRegistry[key]['chartOptions']
>;
};
scale: ScaleOptionsByType;
scales: {
@ -648,6 +643,17 @@ export interface Defaults extends CoreChartOptions<ChartType>, ElementChartOptio
route(scope: string, name: string, targetScope: string, targetName: string): void;
}
export type Overrides = {
[key in ChartType]: DeepPartial<
CoreChartOptions<key> &
ElementChartOptions &
PluginChartOptions<key> &
DatasetChartOptions<ChartType> &
ScaleChartOptions<key> &
ChartTypeRegistry[key]['chartOptions']
>;
}
export const defaults: Defaults;
export interface InteractionOptions {
axis?: string;
@ -1349,7 +1355,9 @@ export interface HoverInteractionOptions extends CoreInteractionOptions {
export interface CoreChartOptions<TType extends ChartType> extends ParsingOptions, AnimationOptions<TType> {
datasets: AnimationOptions<TType>;
datasets: {
[key in ChartType]: ChartTypeRegistry[key]['datasetOptions']
}
/**
* The base axis of the chart. 'x' for vertical charts and 'y' for horizontal charts.
@ -1474,30 +1482,24 @@ export type AnimationSpec<TType extends ChartType> = {
* The number of milliseconds an animation takes.
* @default 1000
*/
duration: Scriptable<number, ScriptableContext<TType>>;
duration?: Scriptable<number, ScriptableContext<TType>>;
/**
* Easing function to use
* @default 'easeOutQuart'
*/
easing: Scriptable<EasingFunction, ScriptableContext<TType>>;
/**
* Running animation count + FPS display in upper left corner of the chart.
* @default false
*/
debug: Scriptable<boolean, ScriptableContext<TType>>;
easing?: Scriptable<EasingFunction, ScriptableContext<TType>>;
/**
* Delay before starting the animations.
* @default 0
*/
delay: Scriptable<number, ScriptableContext<TType>>;
delay?: Scriptable<number, ScriptableContext<TType>>;
/**
* If set to true, the animations loop endlessly.
* @default false
*/
loop: Scriptable<boolean, ScriptableContext<TType>>;
loop?: Scriptable<boolean, ScriptableContext<TType>>;
}
export type AnimationsSpec<TType extends ChartType> = {
@ -1536,11 +1538,11 @@ export type AnimationOptions<TType extends ChartType> = {
/**
* Callback called on each step of an animation.
*/
onProgress: (this: Chart, event: AnimationEvent) => void;
onProgress?: (this: Chart, event: AnimationEvent) => void;
/**
* Callback called when all animations are completed.
*/
onComplete: (this: Chart, event: AnimationEvent) => void;
onComplete?: (this: Chart, event: AnimationEvent) => void;
};
animations: AnimationsSpec<TType>;
transitions: TransitionsSpec<TType>;

9
types/tests/defaults.ts Normal file
View File

@ -0,0 +1,9 @@
import { Chart } from '../index.esm';
Chart.defaults.scales.time.time.minUnit = 'day';
Chart.defaults.plugins.title.display = false;
Chart.defaults.datasets.bar.backgroundColor = 'red';
Chart.defaults.animation = { duration: 500 };

33
types/tests/options.ts Normal file
View File

@ -0,0 +1,33 @@
import { Chart } from '../index.esm';
export const chart = new Chart('test', {
type: 'bar',
data: {
labels: ['a'],
datasets: [{
data: [1],
}, {
type: 'line',
data: [{ x: 1, y: 1 }]
}]
},
options: {
animation: {
duration: 500
},
backgroundColor: 'red',
datasets: {
line: {
animation: {
duration: 600
},
backgroundColor: 'blue',
}
},
elements: {
point: {
backgroundColor: 'red'
}
}
}
});

10
types/tests/overrides.ts Normal file
View File

@ -0,0 +1,10 @@
import { Chart } from '../index.esm';
Chart.overrides.bar.scales.x.type = 'time';
Chart.overrides.bar.plugins.title.display = false;
Chart.overrides.line.datasets.bar.backgroundColor = 'red';
Chart.overrides.line.animation = false;
Chart.overrides.line.datasets.bar.animation = { duration: 100 };

View File

@ -1,6 +1,6 @@
import { Chart } from '../../../index.esm';
Chart.defaults.controllers.bubble.plugins.tooltip.callbacks.label = (item) => {
Chart.overrides.bubble.plugins.tooltip.callbacks.label = (item) => {
const { x, y, _custom: r } = item.parsed;
return `${item.label}: (${x}, ${y}, ${r})`;
};