Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Option resolution with proxies #8374

Merged
merged 4 commits into from
Feb 15, 2021
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 8 additions & 16 deletions docs/docs/configuration/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,23 +10,23 @@ This concept was introduced in Chart.js 1.0 to keep configuration [DRY](https://

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.

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.
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.

```javascript
Chart.defaults.hover.mode = 'nearest';
Chart.defaults.interaction.mode = 'nearest';

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

// This chart would have the hover mode that was passed in
var chartDifferentHoverMode = new Chart(ctx, {
// This chart would have the interaction mode that was passed in
var chartDifferentInteractionMode = new Chart(ctx, {
type: 'line',
data: data,
options: {
hover: {
interaction: {
// Overrides the global setting
mode: 'index'
}
Expand All @@ -36,15 +36,7 @@ var chartDifferentHoverMode = new Chart(ctx, {

## Dataset Configuration

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:

- per dataset: dataset.*
- per chart: options.datasets[type].*
- or globally: Chart.defaults.controllers[type].datasets.*

where type corresponds to the dataset type.

*Note:* dataset options take precedence over element options.
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.

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.

Expand Down
69 changes: 69 additions & 0 deletions docs/docs/general/options.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,72 @@
title: Options
---

## Option resolution

Options are resolved from top to bottom, using a context dependent route.

### Chart level options

* options
* defaults.controllers[`config.type`]
* defaults

### Dataset level options

`dataset.type` defaults to `config.type`, if not specified.

* dataset
* options.datasets[`dataset.type`]
* options.controllers[`dataset.type`].datasets
* options
* defaults.datasets[`dataset.type`]
* defaults.controllers[`dataset.type`].datasets
* defaults

### Dataset animation options

* dataset.animation
* options.controllers[`dataset.type`].datasets.animation
* options.animation
* defaults.controllers[`dataset.type`].datasets.animation
* defaults.animation

### Dataset element level options

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`.

* dataset
* options.datasets[`dataset.type`]
* options.controllers[`dataset.type`].datasets
* options.controllers[`dataset.type`].elements[`elementType`]
* options.elements[`elementType`]
* options
* defaults.datasets[`dataset.type`]
* defaults.controllers[`dataset.type`].datasets
* defaults.controllers[`dataset.type`].elements[`elementType`]
* defaults.elements[`elementType`]
* defaults

### Scale options

* options.scales
* defaults.controllers[`config.type`].scales
* defaults.controllers[`dataset.type`].scales
* defaults.scales

### Plugin options

* options.plugins[`plugin.id`]
* options.controllers[`config.type`].plugins[`plugin.id`]
* (options.[`...plugin.additionalOptionScopes`])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It could be useful to clarify what additionalOptionScopes is

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, the section where that could link to, is still missing :)

* defaults.controllers[`config.type`].plugins[`plugin.id`]
* defaults.plugins[`plugin.id`]
* (defaults.[`...plugin.additionalOptionScopes`])

## Scriptable Options

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)).
A resolver is passed as second parameter, that can be used to access other options in the same context.

Example:

Expand All @@ -15,6 +78,10 @@ color: function(context) {
return value < 0 ? 'red' : // draw negative values in red
index % 2 ? 'blue' : // else, alternate values in blue and green
'green';
},
borderColor: function(context, options) {
var color = options.color; // resolve the value of another scriptable option: 'red', 'blue' or 'green'
return Chart.helpers.color(color).lighten(0.2);
}
```

Expand Down Expand Up @@ -64,6 +131,7 @@ In addition to [chart](#chart)
* `dataset`: dataset at index `datasetIndex`
* `datasetIndex`: index of the current dataset
* `index`: getter for `datasetIndex`
* `mode`: the update mode
* `type`: `'dataset'`

### data
Expand All @@ -76,6 +144,7 @@ In addition to [dataset](#dataset)
* `raw`: the raw data values for the given `dataIndex` and `datasetIndex`
* `element`: the element (point, arc, bar, etc.) for this data
* `index`: getter for `dataIndex`
* `mode`: the update mode
* `type`: `'data'`

### scale
Expand Down
4 changes: 4 additions & 0 deletions docs/docs/getting-started/v3-migration.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,10 @@ A number of changes were made to the configuration options passed to the `Chart`

* Indexable options are now looping. `backgroundColor: ['red', 'green']` will result in alternating `'red'` / `'green'` if there are more than 2 data points.
* The input properties of object data can now be freely specified, see [data structures](../general/data-structures.md) for details.
* 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.
* Options are by default scriptable and indexable, unless disabled for some reason.
* Scriptable options receive a option reolver as second parameter for accessing other options in same context.
* Resolution falls to upper scopes, if no match is found earlier. See [options](./general/options.md) for details.

#### Specific changes

Expand Down
30 changes: 12 additions & 18 deletions samples/animations/delay.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,29 +62,23 @@

};
window.onload = function() {
var delayed = false;
var ctx = document.getElementById('canvas').getContext('2d');
window.myBar = new Chart(ctx, {
type: 'bar',
data: barChartData,
options: {
animation: (context) => {
if (context.active) {
return {
duration: 400
};
}
var delay = 0;
var dsIndex = context.datasetIndex;
var index = context.dataIndex;
if (context.parsed && !context.delayed) {
delay = index * 300 + dsIndex * 100;
context.delayed = true;
}
return {
easing: 'linear',
duration: 600,
delay
};
animation: {
onComplete: () => {
delayed = true;
},
delay: (context) => {
let delay = 0;
if (context.type === 'data' && context.mode === 'default' && !delayed) {
delay = context.dataIndex * 300 + context.datasetIndex * 100;
}
return delay;
},
},
plugins: {
title: {
Expand Down
15 changes: 6 additions & 9 deletions samples/animations/loop.html
Original file line number Diff line number Diff line change
Expand Up @@ -62,16 +62,13 @@
}]
},
options: {
animation: (context) => Object.assign({},
Chart.defaults.animation,
{
radius: {
duration: 400,
easing: 'linear',
loop: context.active
}
animation: {
radius: {
duration: 400,
easing: 'linear',
loop: (context) => context.active
}
),
},
elements: {
point: {
hoverRadius: 6
Expand Down
31 changes: 9 additions & 22 deletions src/controllers/controller.bar.js
Original file line number Diff line number Diff line change
Expand Up @@ -266,9 +266,8 @@ export default class BarController extends DatasetController {
me.updateSharedOptions(sharedOptions, mode, firstOpts);

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

const properties = {
horizontal,
Expand All @@ -280,7 +279,7 @@ export default class BarController extends DatasetController {
};

if (includeOptions) {
properties.options = options;
properties.options = sharedOptions || me.resolveDataElementOptions(i, mode);
}
me.updateElement(bars[i], i, properties, mode);
}
Expand Down Expand Up @@ -400,11 +399,11 @@ export default class BarController extends DatasetController {
* Note: pixel values are not clamped to the scale area.
* @private
*/
_calculateBarValuePixels(index, options) {
_calculateBarValuePixels(index) {
const me = this;
const meta = me._cachedMeta;
const vScale = meta.vScale;
const {base: baseValue, minBarLength} = options;
const {base: baseValue, minBarLength} = me.options;
const parsed = me.getParsed(index);
const custom = parsed._custom;
const floating = isFloatBar(custom);
Expand Down Expand Up @@ -459,9 +458,10 @@ export default class BarController extends DatasetController {
/**
* @private
*/
_calculateBarIndexPixels(index, ruler, options) {
_calculateBarIndexPixels(index, ruler) {
const me = this;
const stackCount = me.chart.options.skipNull ? me._getStackCount(index) : ruler.stackCount;
const options = me.options;
const stackCount = options.skipNull ? me._getStackCount(index) : ruler.stackCount;
const range = options.barThickness === 'flex'
? computeFlexCategoryTraits(index, ruler, options, stackCount)
: computeFitCategoryTraits(index, ruler, options, stackCount);
Expand Down Expand Up @@ -510,20 +510,7 @@ BarController.id = 'bar';
BarController.defaults = {
datasetElementType: false,
dataElementType: 'bar',
dataElementOptions: [
'backgroundColor',
'borderColor',
'borderSkipped',
'borderWidth',
'borderRadius',
'barPercentage',
'barThickness',
'base',
'categoryPercentage',
'maxBarThickness',
'minBarLength',
'pointStyle'
],

interaction: {
mode: 'index'
},
Expand Down
27 changes: 4 additions & 23 deletions src/controllers/controller.bubble.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import DatasetController from '../core/core.datasetController';
import {resolve} from '../helpers/helpers.options';
import {resolveObjectKey} from '../helpers/helpers.core';
import {resolveObjectKey, valueOrDefault} from '../helpers/helpers.core';

export default class BubbleController extends DatasetController {
initialize() {
Expand Down Expand Up @@ -107,29 +106,20 @@ export default class BubbleController extends DatasetController {
* @protected
*/
resolveDataElementOptions(index, mode) {
const me = this;
const chart = me.chart;
const parsed = me.getParsed(index);
const parsed = this.getParsed(index);
let values = super.resolveDataElementOptions(index, mode);

// Scriptable options
const context = me.getContext(index, mode === 'active');

// In case values were cached (and thus frozen), we need to clone the values
if (values.$shared) {
values = Object.assign({}, values, {$shared: false});
}


// Custom radius resolution
const radius = values.radius;
if (mode !== 'active') {
values.radius = 0;
}
values.radius += resolve([
parsed && parsed._custom,
me._config.radius,
chart.options.elements.point.radius
], context, index);
values.radius += valueOrDefault(parsed && parsed._custom, radius);

return values;
}
Expand All @@ -143,15 +133,6 @@ BubbleController.id = 'bubble';
BubbleController.defaults = {
datasetElementType: false,
dataElementType: 'point',
dataElementOptions: [
'backgroundColor',
'borderColor',
'borderWidth',
'hitRadius',
'radius',
'pointStyle',
'rotation'
],
animation: {
numbers: {
properties: ['x', 'y', 'borderWidth', 'radius']
Expand Down
Loading