Skip to content

Commit cfd9c98

Browse files
authored
Option resolution with proxies (#8374)
* Option resolution with proxies * Remove plugin fallback to root options/defaults * Update core plugins, reduntant font fallbacks * Add some notes
1 parent e1f254f commit cfd9c98

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+1221
-787
lines changed

docs/docs/configuration/index.md

Lines changed: 8 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -10,23 +10,23 @@ This concept was introduced in Chart.js 1.0 to keep configuration [DRY](https://
1010

1111
Chart.js merges the options object passed to the chart with the global configuration using chart type defaults and scales defaults appropriately. This way you can be as specific as you would like in your individual chart configuration, while still changing the defaults for all chart types where applicable. The global general options are defined in `Chart.defaults`. The defaults for each chart type are discussed in the documentation for that chart type.
1212

13-
The following example would set the hover mode to 'nearest' for all charts where this was not overridden by the chart type defaults or the options passed to the constructor on creation.
13+
The following example would set the interaction mode to 'nearest' for all charts where this was not overridden by the chart type defaults or the options passed to the constructor on creation.
1414

1515
```javascript
16-
Chart.defaults.hover.mode = 'nearest';
16+
Chart.defaults.interaction.mode = 'nearest';
1717

18-
// Hover mode is set to nearest because it was not overridden here
19-
var chartHoverModeNearest = new Chart(ctx, {
18+
// Interaction mode is set to nearest because it was not overridden here
19+
var chartInteractionModeNearest = new Chart(ctx, {
2020
type: 'line',
2121
data: data
2222
});
2323

24-
// This chart would have the hover mode that was passed in
25-
var chartDifferentHoverMode = new Chart(ctx, {
24+
// This chart would have the interaction mode that was passed in
25+
var chartDifferentInteractionMode = new Chart(ctx, {
2626
type: 'line',
2727
data: data,
2828
options: {
29-
hover: {
29+
interaction: {
3030
// Overrides the global setting
3131
mode: 'index'
3232
}
@@ -36,15 +36,7 @@ var chartDifferentHoverMode = new Chart(ctx, {
3636

3737
## Dataset Configuration
3838

39-
Options may be configured directly on the dataset. The dataset options can be changed at 3 different levels and are evaluated with the following priority:
40-
41-
- per dataset: dataset.*
42-
- per chart: options.datasets[type].*
43-
- or globally: Chart.defaults.controllers[type].datasets.*
44-
45-
where type corresponds to the dataset type.
46-
47-
*Note:* dataset options take precedence over element options.
39+
Options may be configured directly on the dataset. The dataset options can be changed at multiple different levels. See [options](../general/options.md#dataset-level-options) for details on how the options are resolved.
4840

4941
The following example would set the `showLine` option to 'false' for all line datasets except for those overridden by options passed to the dataset on creation.
5042

docs/docs/general/options.md

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,9 +2,74 @@
22
title: Options
33
---
44

5+
## Option resolution
6+
7+
Options are resolved from top to bottom, using a context dependent route.
8+
9+
### Chart level options
10+
11+
* options
12+
* defaults.controllers[`config.type`]
13+
* defaults
14+
15+
### Dataset level options
16+
17+
`dataset.type` defaults to `config.type`, if not specified.
18+
19+
* dataset
20+
* options.datasets[`dataset.type`]
21+
* options.controllers[`dataset.type`].datasets
22+
* options
23+
* defaults.datasets[`dataset.type`]
24+
* defaults.controllers[`dataset.type`].datasets
25+
* defaults
26+
27+
### Dataset animation options
28+
29+
* dataset.animation
30+
* options.controllers[`dataset.type`].datasets.animation
31+
* options.animation
32+
* defaults.controllers[`dataset.type`].datasets.animation
33+
* defaults.animation
34+
35+
### Dataset element level options
36+
37+
Each scope is looked up with `elementType` prefix in the option name first, then wihtout the prefix. For example, `radius` for `point` element is looked up using `pointRadius` and if that does not hit, then `radius`.
38+
39+
* dataset
40+
* options.datasets[`dataset.type`]
41+
* options.controllers[`dataset.type`].datasets
42+
* options.controllers[`dataset.type`].elements[`elementType`]
43+
* options.elements[`elementType`]
44+
* options
45+
* defaults.datasets[`dataset.type`]
46+
* defaults.controllers[`dataset.type`].datasets
47+
* defaults.controllers[`dataset.type`].elements[`elementType`]
48+
* defaults.elements[`elementType`]
49+
* defaults
50+
51+
### Scale options
52+
53+
* options.scales
54+
* defaults.controllers[`config.type`].scales
55+
* defaults.controllers[`dataset.type`].scales
56+
* defaults.scales
57+
58+
### Plugin options
59+
60+
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.
61+
62+
* options.plugins[`plugin.id`]
63+
* options.controllers[`config.type`].plugins[`plugin.id`]
64+
* (options.[`...plugin.additionalOptionScopes`])
65+
* defaults.controllers[`config.type`].plugins[`plugin.id`]
66+
* defaults.plugins[`plugin.id`]
67+
* (defaults.[`...plugin.additionalOptionScopes`])
68+
569
## Scriptable Options
670

771
Scriptable options also accept a function which is called for each of the underlying data values and that takes the unique argument `context` representing contextual information (see [option context](options.md#option-context)).
72+
A resolver is passed as second parameter, that can be used to access other options in the same context.
873

974
Example:
1075

@@ -15,6 +80,10 @@ color: function(context) {
1580
return value < 0 ? 'red' : // draw negative values in red
1681
index % 2 ? 'blue' : // else, alternate values in blue and green
1782
'green';
83+
},
84+
borderColor: function(context, options) {
85+
var color = options.color; // resolve the value of another scriptable option: 'red', 'blue' or 'green'
86+
return Chart.helpers.color(color).lighten(0.2);
1887
}
1988
```
2089

@@ -64,6 +133,7 @@ In addition to [chart](#chart)
64133
* `dataset`: dataset at index `datasetIndex`
65134
* `datasetIndex`: index of the current dataset
66135
* `index`: getter for `datasetIndex`
136+
* `mode`: the update mode
67137
* `type`: `'dataset'`
68138

69139
### data
@@ -76,6 +146,7 @@ In addition to [dataset](#dataset)
76146
* `raw`: the raw data values for the given `dataIndex` and `datasetIndex`
77147
* `element`: the element (point, arc, bar, etc.) for this data
78148
* `index`: getter for `dataIndex`
149+
* `mode`: the update mode
79150
* `type`: `'data'`
80151

81152
### scale

docs/docs/getting-started/v3-migration.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,10 @@ A number of changes were made to the configuration options passed to the `Chart`
6363

6464
* Indexable options are now looping. `backgroundColor: ['red', 'green']` will result in alternating `'red'` / `'green'` if there are more than 2 data points.
6565
* The input properties of object data can now be freely specified, see [data structures](../general/data-structures.md) for details.
66+
* Most options are resolved utilizing proxies, instead merging with defaults. In addition to easily enabling different resolution routes for different contexts, it allows using other resolved options in scriptable options.
67+
* Options are by default scriptable and indexable, unless disabled for some reason.
68+
* Scriptable options receive a option reolver as second parameter for accessing other options in same context.
69+
* Resolution falls to upper scopes, if no match is found earlier. See [options](./general/options.md) for details.
6670

6771
#### Specific changes
6872

samples/animations/delay.html

Lines changed: 12 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -62,29 +62,23 @@
6262

6363
};
6464
window.onload = function() {
65+
var delayed = false;
6566
var ctx = document.getElementById('canvas').getContext('2d');
6667
window.myBar = new Chart(ctx, {
6768
type: 'bar',
6869
data: barChartData,
6970
options: {
70-
animation: (context) => {
71-
if (context.active) {
72-
return {
73-
duration: 400
74-
};
75-
}
76-
var delay = 0;
77-
var dsIndex = context.datasetIndex;
78-
var index = context.dataIndex;
79-
if (context.parsed && !context.delayed) {
80-
delay = index * 300 + dsIndex * 100;
81-
context.delayed = true;
82-
}
83-
return {
84-
easing: 'linear',
85-
duration: 600,
86-
delay
87-
};
71+
animation: {
72+
onComplete: () => {
73+
delayed = true;
74+
},
75+
delay: (context) => {
76+
let delay = 0;
77+
if (context.type === 'data' && context.mode === 'default' && !delayed) {
78+
delay = context.dataIndex * 300 + context.datasetIndex * 100;
79+
}
80+
return delay;
81+
},
8882
},
8983
plugins: {
9084
title: {

samples/animations/loop.html

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -62,16 +62,13 @@
6262
}]
6363
},
6464
options: {
65-
animation: (context) => Object.assign({},
66-
Chart.defaults.animation,
67-
{
68-
radius: {
69-
duration: 400,
70-
easing: 'linear',
71-
loop: context.active
72-
}
65+
animation: {
66+
radius: {
67+
duration: 400,
68+
easing: 'linear',
69+
loop: (context) => context.active
7370
}
74-
),
71+
},
7572
elements: {
7673
point: {
7774
hoverRadius: 6

src/controllers/controller.bar.js

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -266,9 +266,8 @@ export default class BarController extends DatasetController {
266266
me.updateSharedOptions(sharedOptions, mode, firstOpts);
267267

268268
for (let i = start; i < start + count; i++) {
269-
const options = sharedOptions || me.resolveDataElementOptions(i, mode);
270-
const vpixels = me._calculateBarValuePixels(i, options);
271-
const ipixels = me._calculateBarIndexPixels(i, ruler, options);
269+
const vpixels = me._calculateBarValuePixels(i);
270+
const ipixels = me._calculateBarIndexPixels(i, ruler);
272271

273272
const properties = {
274273
horizontal,
@@ -280,7 +279,7 @@ export default class BarController extends DatasetController {
280279
};
281280

282281
if (includeOptions) {
283-
properties.options = options;
282+
properties.options = sharedOptions || me.resolveDataElementOptions(i, mode);
284283
}
285284
me.updateElement(bars[i], i, properties, mode);
286285
}
@@ -400,11 +399,11 @@ export default class BarController extends DatasetController {
400399
* Note: pixel values are not clamped to the scale area.
401400
* @private
402401
*/
403-
_calculateBarValuePixels(index, options) {
402+
_calculateBarValuePixels(index) {
404403
const me = this;
405404
const meta = me._cachedMeta;
406405
const vScale = meta.vScale;
407-
const {base: baseValue, minBarLength} = options;
406+
const {base: baseValue, minBarLength} = me.options;
408407
const parsed = me.getParsed(index);
409408
const custom = parsed._custom;
410409
const floating = isFloatBar(custom);
@@ -459,9 +458,10 @@ export default class BarController extends DatasetController {
459458
/**
460459
* @private
461460
*/
462-
_calculateBarIndexPixels(index, ruler, options) {
461+
_calculateBarIndexPixels(index, ruler) {
463462
const me = this;
464-
const stackCount = me.chart.options.skipNull ? me._getStackCount(index) : ruler.stackCount;
463+
const options = me.options;
464+
const stackCount = options.skipNull ? me._getStackCount(index) : ruler.stackCount;
465465
const range = options.barThickness === 'flex'
466466
? computeFlexCategoryTraits(index, ruler, options, stackCount)
467467
: computeFitCategoryTraits(index, ruler, options, stackCount);
@@ -510,20 +510,7 @@ BarController.id = 'bar';
510510
BarController.defaults = {
511511
datasetElementType: false,
512512
dataElementType: 'bar',
513-
dataElementOptions: [
514-
'backgroundColor',
515-
'borderColor',
516-
'borderSkipped',
517-
'borderWidth',
518-
'borderRadius',
519-
'barPercentage',
520-
'barThickness',
521-
'base',
522-
'categoryPercentage',
523-
'maxBarThickness',
524-
'minBarLength',
525-
'pointStyle'
526-
],
513+
527514
interaction: {
528515
mode: 'index'
529516
},

src/controllers/controller.bubble.js

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import DatasetController from '../core/core.datasetController';
2-
import {resolve} from '../helpers/helpers.options';
3-
import {resolveObjectKey} from '../helpers/helpers.core';
2+
import {resolveObjectKey, valueOrDefault} from '../helpers/helpers.core';
43

54
export default class BubbleController extends DatasetController {
65
initialize() {
@@ -107,29 +106,20 @@ export default class BubbleController extends DatasetController {
107106
* @protected
108107
*/
109108
resolveDataElementOptions(index, mode) {
110-
const me = this;
111-
const chart = me.chart;
112-
const parsed = me.getParsed(index);
109+
const parsed = this.getParsed(index);
113110
let values = super.resolveDataElementOptions(index, mode);
114111

115-
// Scriptable options
116-
const context = me.getContext(index, mode === 'active');
117-
118112
// In case values were cached (and thus frozen), we need to clone the values
119113
if (values.$shared) {
120114
values = Object.assign({}, values, {$shared: false});
121115
}
122116

123-
124117
// Custom radius resolution
118+
const radius = values.radius;
125119
if (mode !== 'active') {
126120
values.radius = 0;
127121
}
128-
values.radius += resolve([
129-
parsed && parsed._custom,
130-
me._config.radius,
131-
chart.options.elements.point.radius
132-
], context, index);
122+
values.radius += valueOrDefault(parsed && parsed._custom, radius);
133123

134124
return values;
135125
}
@@ -143,15 +133,6 @@ BubbleController.id = 'bubble';
143133
BubbleController.defaults = {
144134
datasetElementType: false,
145135
dataElementType: 'point',
146-
dataElementOptions: [
147-
'backgroundColor',
148-
'borderColor',
149-
'borderWidth',
150-
'hitRadius',
151-
'radius',
152-
'pointStyle',
153-
'rotation'
154-
],
155136
animation: {
156137
numbers: {
157138
properties: ['x', 'y', 'borderWidth', 'radius']

0 commit comments

Comments
 (0)