mirror of
https://github.com/chartjs/Chart.js.git
synced 2025-12-08 20:36:08 +00:00
Isolate properties / modes from animation options (#8332)
* Isolate properties / modes from animation options * tabs, something wrong with the linter * Update misleading variable name
This commit is contained in:
parent
284e357fd3
commit
5d5e48d01b
@ -33,21 +33,21 @@ function example() {
|
|||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
animation: {
|
animations: {
|
||||||
tension: {
|
tension: {
|
||||||
duration: 1000,
|
duration: 1000,
|
||||||
easing: 'linear',
|
easing: 'linear',
|
||||||
from: 1,
|
from: 1,
|
||||||
to: 0,
|
to: 0,
|
||||||
loop: true
|
loop: true
|
||||||
}
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
y: { // defining min and max so hiding the dataset does not change scale range
|
|
||||||
min: 0,
|
|
||||||
max: 100
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
scales: {
|
||||||
|
y: { // defining min and max so hiding the dataset does not change scale range
|
||||||
|
min: 0,
|
||||||
|
max: 100
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const chart = new Chart(ctx, cfg);
|
const chart = new Chart(ctx, cfg);
|
||||||
@ -77,24 +77,24 @@ function example() {
|
|||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
animation: {
|
transitions: {
|
||||||
show: {
|
show: {
|
||||||
x: {
|
x: {
|
||||||
from: 0
|
from: 0
|
||||||
},
|
|
||||||
y: {
|
|
||||||
from: 0
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
hide: {
|
y: {
|
||||||
x: {
|
from: 0
|
||||||
to: 0
|
}
|
||||||
},
|
},
|
||||||
y: {
|
hide: {
|
||||||
to: 0
|
x: {
|
||||||
}
|
to: 0
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
to: 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const chart = new Chart(ctx, cfg);
|
const chart = new Chart(ctx, cfg);
|
||||||
@ -107,10 +107,30 @@ function example() {
|
|||||||
</TabItem>
|
</TabItem>
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
|
||||||
## Animation Configuration
|
## Animation configuration
|
||||||
|
|
||||||
The default configuration is defined here: <a href="https://github.com/chartjs/Chart.js/blob/master/src/core/core.animations.js#L6-L55" target="_blank">core.animations.js</a>
|
Animation configuration consists of 3 keys.
|
||||||
Namespace: `options.animation`, the global options are defined in `Chart.defaults.animation`.
|
|
||||||
|
| Name | Type | Details
|
||||||
|
| ---- | ---- | -------
|
||||||
|
| animation | `object` | [animation](#animation)
|
||||||
|
| animations | `object` | [animations](#animations)
|
||||||
|
| transitions | `object` | [transitions](#transitions)
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
These paths are valid under `defaults` for global confuguration and `options` for instance configuration.
|
||||||
|
|
||||||
|
## animation
|
||||||
|
|
||||||
|
The default configuration is defined here: <a href="https://github.com/chartjs/Chart.js/blob/master/src/core/core.animations.js#L9-L56" target="_blank">core.animations.js</a>
|
||||||
|
|
||||||
|
Namespace: `options.animation`
|
||||||
|
|
||||||
| Name | Type | Default | Description
|
| Name | Type | Default | Description
|
||||||
| ---- | ---- | ------- | -----------
|
| ---- | ---- | ------- | -----------
|
||||||
@ -119,84 +139,65 @@ Namespace: `options.animation`, the global options are defined in `Chart.defaul
|
|||||||
| `debug` | `boolean` | `undefined` | Running animation count + FPS display in upper left corner of the chart.
|
| `debug` | `boolean` | `undefined` | Running animation count + FPS display in upper left corner of the chart.
|
||||||
| `delay` | `number` | `undefined` | Delay before starting the animations.
|
| `delay` | `number` | `undefined` | Delay before starting the animations.
|
||||||
| `loop` | `boolean` | `undefined` | If set to `true`, the animations loop endlessly.
|
| `loop` | `boolean` | `undefined` | If set to `true`, the animations loop endlessly.
|
||||||
| [[mode]](#animation-mode-configuration) | `object` | [defaults...](#default-modes) | Option overrides for update mode. Core modes: `'active'`, `'hide'`, `'reset'`, `'resize'`, `'show'`. See **Hide and show [mode]** example above.
|
|
||||||
| [[property]](#animation-property-configuration) | `object` | `undefined` | Option overrides for a single element `[property]`. These have precedence over `[collection]`. See **Looping tension [property]** example above.
|
|
||||||
| [[collection]](#animation-properties-collection-configuration) | `object` | [defaults...](#default-collections) | Option overrides for multiple properties, identified by `properties` array.
|
|
||||||
|
|
||||||
These defaults can be overridden in `options.animation` or `dataset.animation` and `tooltip.animation`. These keys are also [Scriptable](../general/options.md#scriptable-options).
|
These defaults can be overridden in `options.animation` or `dataset.animation` and `tooltip.animation`. These keys are also [Scriptable](../general/options.md#scriptable-options).
|
||||||
|
|
||||||
## Animation mode configuration
|
## animations
|
||||||
|
|
||||||
Mode option configures how an update mode animates the chart.
|
Animations options configures which element properties are animated and how.
|
||||||
The cores modes are `'active'`, `'hide'`, `'reset'`, `'resize'`, `'show'`.
|
In addition to the main [animation configuration](#animation-configuration), the following options are available:
|
||||||
A custom mode can be used by passing a custom `mode` to [update](../developers/api.md#updatemode).
|
|
||||||
A mode option is defined by the same options of the main [animation configuration](#animation-configuration).
|
|
||||||
|
|
||||||
### Default modes
|
Namespace: `options.animations[animation]`
|
||||||
|
|
||||||
Namespace: `options.animation`
|
|
||||||
|
|
||||||
| Mode | Option | Value | Description
|
|
||||||
| -----| ------ | ----- | -----
|
|
||||||
| `active` | duration | 400 | Override default duration to 400ms for hover animations
|
|
||||||
| `resize` | duration | 0 | Override default duration to 0ms (= no animation) for resize
|
|
||||||
| `show` | colors | `{ type: 'color', properties: ['borderColor', 'backgroundColor'], from: 'transparent' }` | Colors are faded in from transparent when dataset is shown using legend / [api](../developers/api.md#showdatasetIndex).
|
|
||||||
| `show` | visible | `{ type: 'boolean', duration: 0 }` | Dataset visiblity is immediately changed to true so the color transition from transparent is visible.
|
|
||||||
| `hide` | colors | `{ type: 'color', properties: ['borderColor', 'backgroundColor'], to: 'transparent' }` | Colors are faded to transparent when dataset id hidden using legend / [api](../developers/api.md#hidedatasetIndex).
|
|
||||||
| `hide` | visible | `{ type: 'boolean', easing: 'easeInExpo' }` | Visibility is changed to false at a very late phase of animation
|
|
||||||
|
|
||||||
## Animation property configuration
|
|
||||||
|
|
||||||
Property option configures which element property to use to animate the chart and its starting and ending values.
|
|
||||||
A property option is defined by the same options of the main [animation configuration](#animation-configuration), adding the following ones:
|
|
||||||
|
|
||||||
Namespace: `options.animation[animation]`
|
|
||||||
|
|
||||||
| Name | Type | Default | Description
|
| Name | Type | Default | Description
|
||||||
| ---- | ---- | ------- | -----------
|
| ---- | ---- | ------- | -----------
|
||||||
|
| `properties` | `string[]` | `key` | The property names this configuration applies to. Defaults to the key name of this object.
|
||||||
| `type` | `string` | `typeof property` | Type of property, determines the interpolator used. Possible values: `'number'`, `'color'` and `'boolean'`. Only really needed for `'color'`, because `typeof` does not get that right.
|
| `type` | `string` | `typeof property` | Type of property, determines the interpolator used. Possible values: `'number'`, `'color'` and `'boolean'`. Only really needed for `'color'`, because `typeof` does not get that right.
|
||||||
| `from` | `number`\|`Color`\|`boolean` | `undefined` | Start value for the animation. Current value is used when `undefined`
|
| `from` | `number`\|`Color`\|`boolean` | `undefined` | Start value for the animation. Current value is used when `undefined`
|
||||||
| `to` | `number`\|`Color`\|`boolean` | `undefined` | End value for the animation. Updated value is used when `undefined`
|
| `to` | `number`\|`Color`\|`boolean` | `undefined` | End value for the animation. Updated value is used when `undefined`
|
||||||
| `fn` | <code><T>(from: T, to: T, factor: number) => T;</code> | `undefined` | Optional custom interpolator, instead of using a predefined interpolator from `type` |
|
| `fn` | <code><T>(from: T, to: T, factor: number) => T;</code> | `undefined` | Optional custom interpolator, instead of using a predefined interpolator from `type` |
|
||||||
|
|
||||||
## Animation properties collection configuration
|
### Default animations
|
||||||
|
|
||||||
Properties collection option configures which set of element properties to use to animate the chart.
|
|
||||||
Collection can be named whatever you like, but should not collide with a `[property]` or `[mode]`.
|
|
||||||
A properties collection option is defined by the same options as the [animation property configuration](#animation-property-configuration), adding the following one:
|
|
||||||
|
|
||||||
The animation properties collection configuration can be adjusted in the `options.animation[collection]` namespace.
|
|
||||||
|
|
||||||
| Name | Type | Default | Description
|
|
||||||
| ---- | ---- | ------- | -----------
|
|
||||||
| `properties` | `string[]` | `undefined` | Set of properties to use to animate the chart.
|
|
||||||
|
|
||||||
### Default collections
|
|
||||||
|
|
||||||
| Name | Option | Value
|
| Name | Option | Value
|
||||||
| ---- | ------ | -----
|
| ---- | ------ | -----
|
||||||
| `numbers` | `type` | `'number'`
|
|
||||||
| `numbers` | `properties` | `['x', 'y', 'borderWidth', 'radius', 'tension']`
|
| `numbers` | `properties` | `['x', 'y', 'borderWidth', 'radius', 'tension']`
|
||||||
|
| `numbers` | `type` | `'number'`
|
||||||
|
| `colors` | `properties` | `['color', 'borderColor', 'backgroundColor']`
|
||||||
| `colors` | `type` | `'color'`
|
| `colors` | `type` | `'color'`
|
||||||
| `colors` | `properties` | `['borderColor', 'backgroundColor']`
|
|
||||||
|
|
||||||
Direct property configuration overrides configuration of same property in a collection.
|
|
||||||
|
|
||||||
From collections, a property gets its configuration from first one that has its name in properties.
|
|
||||||
|
|
||||||
:::note
|
:::note
|
||||||
These default collections are overridden by most dataset controllers.
|
These default animations are overridden by most of the dataset controllers.
|
||||||
:::
|
:::
|
||||||
|
|
||||||
|
## transitions
|
||||||
|
|
||||||
|
The core transitions are `'active'`, `'hide'`, `'reset'`, `'resize'`, `'show'`.
|
||||||
|
A custom transtion can be used by passing a custom `mode` to [update](../developers/api.md#updatemode).
|
||||||
|
Transition extends the main [animation configuration](#animation-configuration) and [animations configuration](#animations-configuration).
|
||||||
|
|
||||||
|
### Default transitions
|
||||||
|
|
||||||
|
Namespace: `options.transitions[mode]`
|
||||||
|
|
||||||
|
| Mode | Option | Value | Description
|
||||||
|
| -----| ------ | ----- | -----
|
||||||
|
| `'active'` | animation.duration | 400 | Override default duration to 400ms for hover animations
|
||||||
|
| `'resize'` | animation.duration | 0 | Override default duration to 0ms (= no animation) for resize
|
||||||
|
| `'show'` | animations.colors | `{ type: 'color', properties: ['borderColor', 'backgroundColor'], from: 'transparent' }` | Colors are faded in from transparent when dataset is shown using legend / [api](../developers/api.md#showdatasetIndex).
|
||||||
|
| `'show'` | animations.visible | `{ type: 'boolean', duration: 0 }` | Dataset visiblity is immediately changed to true so the color transition from transparent is visible.
|
||||||
|
| `'hide'` | animations.colors | `{ type: 'color', properties: ['borderColor', 'backgroundColor'], to: 'transparent' }` | Colors are faded to transparent when dataset id hidden using legend / [api](../developers/api.md#hidedatasetIndex).
|
||||||
|
| `'hide'` | animations.visible | `{ type: 'boolean', easing: 'easeInExpo' }` | Visibility is changed to false at a very late phase of animation
|
||||||
|
|
||||||
## Disabling animation
|
## Disabling animation
|
||||||
|
|
||||||
To disable an animation configuration, the animation node must be set to `false`, with the exception for animation modes which can be disabled by setting the `duration` to `0`.
|
To disable an animation configuration, the animation node must be set to `false`, with the exception for animation modes which can be disabled by setting the `duration` to `0`.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
chart.options.animation = false; // disables the whole animation
|
chart.options.animation = false; // disables all animations
|
||||||
chart.options.animation.active.duration = 0; // disables the animation for 'active' mode
|
chart.options.animations.colors = false; // disables animation defined by the collection of 'colors' properties
|
||||||
chart.options.animation.colors = false; // disables animation defined by the collection of 'colors' properties
|
chart.options.animations.x = false; // disables animation defined by the 'x' property
|
||||||
chart.options.animation.x = false; // disables animation defined by the 'x' property
|
chart.options.transitions.active.animation.duration = 0; // disables the animation for 'active' mode
|
||||||
```
|
```
|
||||||
|
|
||||||
## Easing
|
## Easing
|
||||||
|
|||||||
@ -28,7 +28,7 @@ myLineChart.data.datasets[0].data[2] = 50; // Would update the first dataset's v
|
|||||||
myLineChart.update(); // Calling update now animates the position of March from 90 to 50.
|
myLineChart.update(); // Calling update now animates the position of March from 90 to 50.
|
||||||
```
|
```
|
||||||
|
|
||||||
A `mode` string can be provided to indicate what should be updated and what animation configuration should be used. Core calls this method using any of `'active'`, `'hide'`, `'reset'`, `'resize'`, `'show'` or `undefined`. `'none'` is also a supported mode for skipping animations for single update. Please see [animations](../configuration/animations.mdx) docs for more details.
|
A `mode` string can be provided to indicate transition configuration should be used. Core calls this method using any of `'active'`, `'hide'`, `'reset'`, `'resize'`, `'show'` or `undefined`. `'none'` is also a supported mode for skipping animations for single update. Please see [animations](../configuration/animations.mdx) docs for more details.
|
||||||
|
|
||||||
Example:
|
Example:
|
||||||
|
|
||||||
|
|||||||
@ -33,13 +33,13 @@
|
|||||||
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
|
labels: ['January', 'February', 'March', 'April', 'May', 'June', 'July'],
|
||||||
datasets: [{
|
datasets: [{
|
||||||
label: 'My First dataset',
|
label: 'My First dataset',
|
||||||
animation: {
|
animations: {
|
||||||
y: {
|
y: {
|
||||||
duration: 2000,
|
duration: 2000,
|
||||||
delay: 100
|
delay: 500
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
backgroundColor: window.chartColors.red,
|
backgroundColor: 'rgba(170,0,0,0.1)',
|
||||||
borderColor: window.chartColors.red,
|
borderColor: window.chartColors.red,
|
||||||
data: [
|
data: [
|
||||||
randomScalingFactor(),
|
randomScalingFactor(),
|
||||||
@ -50,7 +50,8 @@
|
|||||||
randomScalingFactor(),
|
randomScalingFactor(),
|
||||||
randomScalingFactor()
|
randomScalingFactor()
|
||||||
],
|
],
|
||||||
fill: false,
|
fill: 1,
|
||||||
|
tension: 0.5
|
||||||
}, {
|
}, {
|
||||||
label: 'My Second dataset',
|
label: 'My Second dataset',
|
||||||
fill: false,
|
fill: false,
|
||||||
@ -68,10 +69,17 @@
|
|||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
animation: {
|
animations: {
|
||||||
y: {
|
y: {
|
||||||
easing: 'easeInOutElastic',
|
easing: 'easeInOutElastic',
|
||||||
from: 0
|
from: (ctx) => {
|
||||||
|
if (ctx.type === 'data') {
|
||||||
|
if (ctx.mode === 'default' && !ctx.dropped) {
|
||||||
|
ctx.dropped = true;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
responsive: true,
|
responsive: true,
|
||||||
|
|||||||
@ -62,7 +62,7 @@
|
|||||||
}]
|
}]
|
||||||
},
|
},
|
||||||
options: {
|
options: {
|
||||||
animation: {
|
animations: {
|
||||||
radius: {
|
radius: {
|
||||||
duration: 400,
|
duration: 400,
|
||||||
easing: 'linear',
|
easing: 'linear',
|
||||||
@ -74,23 +74,18 @@
|
|||||||
hoverRadius: 6
|
hoverRadius: 6
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
responsive: true,
|
interaction: {
|
||||||
|
mode: 'nearest',
|
||||||
|
axis: 'x',
|
||||||
|
intersect: false
|
||||||
|
},
|
||||||
plugins: {
|
plugins: {
|
||||||
title: {
|
title: {
|
||||||
display: true,
|
display: true,
|
||||||
text: 'Chart.js Line Chart'
|
text: 'Chart.js Line Chart'
|
||||||
},
|
},
|
||||||
tooltip: {
|
|
||||||
mode: 'nearest',
|
|
||||||
axis: 'x',
|
|
||||||
intersect: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hover: {
|
|
||||||
mode: 'nearest',
|
|
||||||
axis: 'x',
|
|
||||||
intersect: false
|
|
||||||
},
|
},
|
||||||
|
responsive: true,
|
||||||
scales: {
|
scales: {
|
||||||
x: {
|
x: {
|
||||||
display: true,
|
display: true,
|
||||||
|
|||||||
@ -520,7 +520,7 @@ BarController.defaults = {
|
|||||||
datasets: {
|
datasets: {
|
||||||
categoryPercentage: 0.8,
|
categoryPercentage: 0.8,
|
||||||
barPercentage: 0.9,
|
barPercentage: 0.9,
|
||||||
animation: {
|
animations: {
|
||||||
numbers: {
|
numbers: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
properties: ['x', 'y', 'base', 'width', 'height']
|
properties: ['x', 'y', 'base', 'width', 'height']
|
||||||
|
|||||||
@ -133,8 +133,9 @@ BubbleController.id = 'bubble';
|
|||||||
BubbleController.defaults = {
|
BubbleController.defaults = {
|
||||||
datasetElementType: false,
|
datasetElementType: false,
|
||||||
dataElementType: 'point',
|
dataElementType: 'point',
|
||||||
animation: {
|
animations: {
|
||||||
numbers: {
|
numbers: {
|
||||||
|
type: 'number',
|
||||||
properties: ['x', 'y', 'borderWidth', 'radius']
|
properties: ['x', 'y', 'borderWidth', 'radius']
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@ -329,15 +329,17 @@ DoughnutController.defaults = {
|
|||||||
datasetElementType: false,
|
datasetElementType: false,
|
||||||
dataElementType: 'arc',
|
dataElementType: 'arc',
|
||||||
animation: {
|
animation: {
|
||||||
numbers: {
|
|
||||||
type: 'number',
|
|
||||||
properties: ['circumference', 'endAngle', 'innerRadius', 'outerRadius', 'startAngle', 'x', 'y', 'offset', 'borderWidth']
|
|
||||||
},
|
|
||||||
// Boolean - Whether we animate the rotation of the Doughnut
|
// Boolean - Whether we animate the rotation of the Doughnut
|
||||||
animateRotate: true,
|
animateRotate: true,
|
||||||
// Boolean - Whether we animate scaling the Doughnut from the centre
|
// Boolean - Whether we animate scaling the Doughnut from the centre
|
||||||
animateScale: false
|
animateScale: false
|
||||||
},
|
},
|
||||||
|
animations: {
|
||||||
|
numbers: {
|
||||||
|
type: 'number',
|
||||||
|
properties: ['circumference', 'endAngle', 'innerRadius', 'outerRadius', 'startAngle', 'x', 'y', 'offset', 'borderWidth']
|
||||||
|
},
|
||||||
|
},
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
|
|
||||||
datasets: {
|
datasets: {
|
||||||
|
|||||||
@ -122,12 +122,14 @@ PolarAreaController.id = 'polarArea';
|
|||||||
PolarAreaController.defaults = {
|
PolarAreaController.defaults = {
|
||||||
dataElementType: 'arc',
|
dataElementType: 'arc',
|
||||||
animation: {
|
animation: {
|
||||||
|
animateRotate: true,
|
||||||
|
animateScale: true
|
||||||
|
},
|
||||||
|
animations: {
|
||||||
numbers: {
|
numbers: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
properties: ['x', 'y', 'startAngle', 'endAngle', 'innerRadius', 'outerRadius']
|
properties: ['x', 'y', 'startAngle', 'endAngle', 'innerRadius', 'outerRadius']
|
||||||
},
|
},
|
||||||
animateRotate: true,
|
|
||||||
animateScale: true
|
|
||||||
},
|
},
|
||||||
aspectRatio: 1,
|
aspectRatio: 1,
|
||||||
indexAxis: 'r',
|
indexAxis: 'r',
|
||||||
|
|||||||
@ -28,7 +28,7 @@ export default class Animation {
|
|||||||
|
|
||||||
this._active = true;
|
this._active = true;
|
||||||
this._fn = cfg.fn || interpolators[cfg.type || typeof from];
|
this._fn = cfg.fn || interpolators[cfg.type || typeof from];
|
||||||
this._easing = effects[cfg.easing || 'linear'];
|
this._easing = effects[cfg.easing] || effects.linear;
|
||||||
this._start = Math.floor(Date.now() + (cfg.delay || 0));
|
this._start = Math.floor(Date.now() + (cfg.delay || 0));
|
||||||
this._duration = Math.floor(cfg.duration);
|
this._duration = Math.floor(cfg.duration);
|
||||||
this._loop = !!cfg.loop;
|
this._loop = !!cfg.loop;
|
||||||
|
|||||||
@ -1,20 +1,31 @@
|
|||||||
import animator from './core.animator';
|
import animator from './core.animator';
|
||||||
import Animation from './core.animation';
|
import Animation from './core.animation';
|
||||||
import defaults from './core.defaults';
|
import defaults from './core.defaults';
|
||||||
import {isObject} from '../helpers/helpers.core';
|
import {isArray, isObject} from '../helpers/helpers.core';
|
||||||
|
|
||||||
const numbers = ['x', 'y', 'borderWidth', 'radius', 'tension'];
|
const numbers = ['x', 'y', 'borderWidth', 'radius', 'tension'];
|
||||||
const colors = ['borderColor', 'backgroundColor'];
|
const colors = ['color', 'borderColor', 'backgroundColor'];
|
||||||
const animationOptions = ['delay', 'duration', 'easing', 'fn', 'from', 'loop', 'to', 'type'];
|
|
||||||
|
|
||||||
defaults.set('animation', {
|
defaults.set('animation', {
|
||||||
// Plain properties can be overridden in each object
|
delay: undefined,
|
||||||
duration: 1000,
|
duration: 1000,
|
||||||
easing: 'easeOutQuart',
|
easing: 'easeOutQuart',
|
||||||
onProgress: undefined,
|
fn: undefined,
|
||||||
onComplete: undefined,
|
from: undefined,
|
||||||
|
loop: undefined,
|
||||||
|
to: undefined,
|
||||||
|
type: undefined,
|
||||||
|
});
|
||||||
|
|
||||||
// Property sets
|
const animationOptions = Object.keys(defaults.animation);
|
||||||
|
|
||||||
|
defaults.describe('animation', {
|
||||||
|
_fallback: false,
|
||||||
|
_indexable: false,
|
||||||
|
_scriptable: (name) => name !== 'onProgress' && name !== 'onComplete' && name !== 'fn',
|
||||||
|
});
|
||||||
|
|
||||||
|
defaults.set('animations', {
|
||||||
colors: {
|
colors: {
|
||||||
type: 'color',
|
type: 'color',
|
||||||
properties: colors
|
properties: colors
|
||||||
@ -23,60 +34,63 @@ defaults.set('animation', {
|
|||||||
type: 'number',
|
type: 'number',
|
||||||
properties: numbers
|
properties: numbers
|
||||||
},
|
},
|
||||||
|
|
||||||
// Update modes. These are overrides / additions to the above animations.
|
|
||||||
active: {
|
|
||||||
duration: 400
|
|
||||||
},
|
|
||||||
resize: {
|
|
||||||
duration: 0
|
|
||||||
},
|
|
||||||
show: {
|
|
||||||
colors: {
|
|
||||||
type: 'color',
|
|
||||||
properties: colors,
|
|
||||||
from: 'transparent'
|
|
||||||
},
|
|
||||||
visible: {
|
|
||||||
type: 'boolean',
|
|
||||||
duration: 0 // show immediately
|
|
||||||
},
|
|
||||||
},
|
|
||||||
hide: {
|
|
||||||
colors: {
|
|
||||||
type: 'color',
|
|
||||||
properties: colors,
|
|
||||||
to: 'transparent'
|
|
||||||
},
|
|
||||||
visible: {
|
|
||||||
type: 'boolean',
|
|
||||||
fn: v => v < 1 ? 0 : 1 // for keeping the dataset visible all the way through the animation
|
|
||||||
},
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
defaults.describe('animation', {
|
defaults.describe('animations', {
|
||||||
_scriptable: (name) => name !== 'onProgress' && name !== 'onComplete' && name !== 'fn',
|
|
||||||
_indexable: false,
|
|
||||||
_fallback: 'animation',
|
_fallback: 'animation',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defaults.set('transitions', {
|
||||||
|
active: {
|
||||||
|
animation: {
|
||||||
|
duration: 400
|
||||||
|
}
|
||||||
|
},
|
||||||
|
resize: {
|
||||||
|
animation: {
|
||||||
|
duration: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
show: {
|
||||||
|
animations: {
|
||||||
|
colors: {
|
||||||
|
from: 'transparent'
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
type: 'boolean',
|
||||||
|
duration: 0 // show immediately
|
||||||
|
},
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hide: {
|
||||||
|
animations: {
|
||||||
|
colors: {
|
||||||
|
to: 'transparent'
|
||||||
|
},
|
||||||
|
visible: {
|
||||||
|
type: 'boolean',
|
||||||
|
fn: v => v < 1 ? 0 : 1 // for keeping the dataset visible all the way through the animation
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
export default class Animations {
|
export default class Animations {
|
||||||
constructor(chart, animations) {
|
constructor(chart, config) {
|
||||||
this._chart = chart;
|
this._chart = chart;
|
||||||
this._properties = new Map();
|
this._properties = new Map();
|
||||||
this.configure(animations);
|
this.configure(config);
|
||||||
}
|
}
|
||||||
|
|
||||||
configure(animations) {
|
configure(config) {
|
||||||
if (!isObject(animations)) {
|
if (!isObject(config)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const animatedProps = this._properties;
|
const animatedProps = this._properties;
|
||||||
|
|
||||||
Object.getOwnPropertyNames(animations).forEach(key => {
|
Object.getOwnPropertyNames(config).forEach(key => {
|
||||||
const cfg = animations[key];
|
const cfg = config[key];
|
||||||
if (!isObject(cfg)) {
|
if (!isObject(cfg)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -85,7 +99,7 @@ export default class Animations {
|
|||||||
resolved[option] = cfg[option];
|
resolved[option] = cfg[option];
|
||||||
}
|
}
|
||||||
|
|
||||||
(cfg.properties || [key]).forEach((prop) => {
|
(isArray(cfg.properties) && cfg.properties || [key]).forEach((prop) => {
|
||||||
if (prop === key || !animatedProps.has(prop)) {
|
if (prop === key || !animatedProps.has(prop)) {
|
||||||
animatedProps.set(prop, resolved);
|
animatedProps.set(prop, resolved);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -166,11 +166,11 @@ export default class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the option scope keys for resolving dataset options.
|
* Returns the option scope keys for resolving dataset options.
|
||||||
* These keys do not include the dataset itself, because it is not under options.
|
* These keys do not include the dataset itself, because it is not under options.
|
||||||
* @param {string} datasetType
|
* @param {string} datasetType
|
||||||
* @return {string[]}
|
* @return {string[]}
|
||||||
*/
|
*/
|
||||||
datasetScopeKeys(datasetType) {
|
datasetScopeKeys(datasetType) {
|
||||||
return cachedKeys(datasetType,
|
return cachedKeys(datasetType,
|
||||||
() => [
|
() => [
|
||||||
@ -182,29 +182,34 @@ export default class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the option scope keys for resolving dataset animation options.
|
* Returns the option scope keys for resolving dataset animation options.
|
||||||
* These keys do not include the dataset itself, because it is not under options.
|
* These keys do not include the dataset itself, because it is not under options.
|
||||||
* @param {string} datasetType
|
* @param {string} datasetType
|
||||||
* @return {string[]}
|
* @param {string} transition
|
||||||
*/
|
* @return {string[]}
|
||||||
datasetAnimationScopeKeys(datasetType) {
|
*/
|
||||||
return cachedKeys(`${datasetType}.animation`,
|
datasetAnimationScopeKeys(datasetType, transition) {
|
||||||
|
return cachedKeys(`${datasetType}.transition.${transition}`,
|
||||||
() => [
|
() => [
|
||||||
`datasets.${datasetType}.animation`,
|
`datasets.${datasetType}.transitions.${transition}`,
|
||||||
`controllers.${datasetType}.animation`,
|
`controllers.${datasetType}.transitions.${transition}`,
|
||||||
`controllers.${datasetType}.datasets.animation`,
|
`controllers.${datasetType}.datasets.transitions.${transition}`,
|
||||||
'animation'
|
`transitions.${transition}`,
|
||||||
|
`datasets.${datasetType}`,
|
||||||
|
`controllers.${datasetType}`,
|
||||||
|
`controllers.${datasetType}.datasets`,
|
||||||
|
''
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the options scope keys for resolving element options that belong
|
* Returns the options scope keys for resolving element options that belong
|
||||||
* to an dataset. These keys do not include the dataset itself, because it
|
* to an dataset. These keys do not include the dataset itself, because it
|
||||||
* is not under options.
|
* is not under options.
|
||||||
* @param {string} datasetType
|
* @param {string} datasetType
|
||||||
* @param {string} elementType
|
* @param {string} elementType
|
||||||
* @return {string[]}
|
* @return {string[]}
|
||||||
*/
|
*/
|
||||||
datasetElementScopeKeys(datasetType, elementType) {
|
datasetElementScopeKeys(datasetType, elementType) {
|
||||||
return cachedKeys(`${datasetType}-${elementType}`,
|
return cachedKeys(`${datasetType}-${elementType}`,
|
||||||
() => [
|
() => [
|
||||||
@ -219,7 +224,7 @@ export default class Config {
|
|||||||
/**
|
/**
|
||||||
* Returns the options scope keys for resolving plugin options.
|
* Returns the options scope keys for resolving plugin options.
|
||||||
* @param {{id: string, additionalOptionScopes?: string[]}} plugin
|
* @param {{id: string, additionalOptionScopes?: string[]}} plugin
|
||||||
* @return {string[]}
|
* @return {string[]}
|
||||||
*/
|
*/
|
||||||
pluginScopeKeys(plugin) {
|
pluginScopeKeys(plugin) {
|
||||||
const id = plugin.id;
|
const id = plugin.id;
|
||||||
@ -233,11 +238,11 @@ export default class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Resolves the objects from options and defaults for option value resolution.
|
* Resolves the objects from options and defaults for option value resolution.
|
||||||
* @param {object} mainScope - The main scope object for options
|
* @param {object} mainScope - The main scope object for options
|
||||||
* @param {string[]} scopeKeys - The keys in resolution order
|
* @param {string[]} scopeKeys - The keys in resolution order
|
||||||
* @param {boolean} [resetCache] - reset the cache for this mainScope
|
* @param {boolean} [resetCache] - reset the cache for this mainScope
|
||||||
*/
|
*/
|
||||||
getOptionScopes(mainScope, scopeKeys, resetCache) {
|
getOptionScopes(mainScope, scopeKeys, resetCache) {
|
||||||
let cache = this._scopeCache.get(mainScope);
|
let cache = this._scopeCache.get(mainScope);
|
||||||
if (!cache || resetCache) {
|
if (!cache || resetCache) {
|
||||||
@ -267,9 +272,9 @@ export default class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the option scopes for resolving chart options
|
* Returns the option scopes for resolving chart options
|
||||||
* @return {object[]}
|
* @return {object[]}
|
||||||
*/
|
*/
|
||||||
chartOptionScopes() {
|
chartOptionScopes() {
|
||||||
return [
|
return [
|
||||||
this.options,
|
this.options,
|
||||||
@ -281,12 +286,12 @@ export default class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object[]} scopes
|
* @param {object[]} scopes
|
||||||
* @param {string[]} names
|
* @param {string[]} names
|
||||||
* @param {function|object} context
|
* @param {function|object} context
|
||||||
* @param {string[]} [prefixes]
|
* @param {string[]} [prefixes]
|
||||||
* @return {object}
|
* @return {object}
|
||||||
*/
|
*/
|
||||||
resolveNamedOptions(scopes, names, context, prefixes = ['']) {
|
resolveNamedOptions(scopes, names, context, prefixes = ['']) {
|
||||||
const result = {$shared: true};
|
const result = {$shared: true};
|
||||||
const {resolver, subPrefixes} = getResolver(this._resolverCache, scopes, prefixes);
|
const {resolver, subPrefixes} = getResolver(this._resolverCache, scopes, prefixes);
|
||||||
@ -306,10 +311,10 @@ export default class Config {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {object[]} scopes
|
* @param {object[]} scopes
|
||||||
* @param {object} [context]
|
* @param {object} [context]
|
||||||
* @param {string[]} [prefixes]
|
* @param {string[]} [prefixes]
|
||||||
*/
|
*/
|
||||||
createResolver(scopes, context, prefixes = ['']) {
|
createResolver(scopes, context, prefixes = ['']) {
|
||||||
const {resolver} = getResolver(this._resolverCache, scopes, prefixes);
|
const {resolver} = getResolver(this._resolverCache, scopes, prefixes);
|
||||||
return isObject(context)
|
return isObject(context)
|
||||||
@ -342,7 +347,7 @@ function needContext(proxy, names) {
|
|||||||
|
|
||||||
for (const prop of names) {
|
for (const prop of names) {
|
||||||
if ((isScriptable(prop) && isFunction(proxy[prop]))
|
if ((isScriptable(prop) && isFunction(proxy[prop]))
|
||||||
|| (isIndexable(prop) && isArray(proxy[prop]))) {
|
|| (isIndexable(prop) && isArray(proxy[prop]))) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -763,11 +763,11 @@ export default class DatasetController {
|
|||||||
/**
|
/**
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
_resolveAnimations(index, mode, active) {
|
_resolveAnimations(index, transition, active) {
|
||||||
const me = this;
|
const me = this;
|
||||||
const chart = me.chart;
|
const chart = me.chart;
|
||||||
const cache = me._cachedDataOpts;
|
const cache = me._cachedDataOpts;
|
||||||
const cacheKey = 'animation-' + mode;
|
const cacheKey = `animation-${transition}`;
|
||||||
const cached = cache[cacheKey];
|
const cached = cache[cacheKey];
|
||||||
if (cached) {
|
if (cached) {
|
||||||
return cached;
|
return cached;
|
||||||
@ -775,11 +775,11 @@ export default class DatasetController {
|
|||||||
let options;
|
let options;
|
||||||
if (chart.options.animation !== false) {
|
if (chart.options.animation !== false) {
|
||||||
const config = me.chart.config;
|
const config = me.chart.config;
|
||||||
const scopeKeys = config.datasetAnimationScopeKeys(me._type);
|
const scopeKeys = config.datasetAnimationScopeKeys(me._type, transition);
|
||||||
const scopes = config.getOptionScopes(me.getDataset().animation, scopeKeys);
|
const scopes = config.getOptionScopes(me.getDataset(), scopeKeys);
|
||||||
options = config.createResolver(scopes, me.getContext(index, active, mode));
|
options = config.createResolver(scopes, me.getContext(index, active, transition));
|
||||||
}
|
}
|
||||||
const animations = new Animations(chart, options && options[mode] || options);
|
const animations = new Animations(chart, options && options.animations);
|
||||||
if (options && options._cacheable) {
|
if (options && options._cacheable) {
|
||||||
cache[cacheKey] = Object.freeze(animations);
|
cache[cacheKey] = Object.freeze(animations);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -83,12 +83,16 @@ defaults.route('scale.ticks', 'color', '', 'color');
|
|||||||
defaults.route('scale.gridLines', 'color', '', 'borderColor');
|
defaults.route('scale.gridLines', 'color', '', 'borderColor');
|
||||||
defaults.route('scale.scaleLabel', 'color', '', 'color');
|
defaults.route('scale.scaleLabel', 'color', '', 'color');
|
||||||
|
|
||||||
defaults.describe('scales', {
|
defaults.describe('scale', {
|
||||||
_fallback: 'scale',
|
_fallback: false,
|
||||||
_scriptable: (name) => !name.startsWith('before') && !name.startsWith('after') && name !== 'callback' && name !== 'parser',
|
_scriptable: (name) => !name.startsWith('before') && !name.startsWith('after') && name !== 'callback' && name !== 'parser',
|
||||||
_indexable: (name) => name !== 'borderDash' && name !== 'tickBorderDash',
|
_indexable: (name) => name !== 'borderDash' && name !== 'tickBorderDash',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
defaults.describe('scales', {
|
||||||
|
_fallback: 'scale',
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new array containing numItems from arr
|
* Returns a new array containing numItems from arr
|
||||||
* @param {any[]} arr
|
* @param {any[]} arr
|
||||||
|
|||||||
@ -1,18 +1,25 @@
|
|||||||
import {defined, isArray, isFunction, isObject, resolveObjectKey, valueOrDefault, _capitalize} from './helpers.core';
|
import {defined, isArray, isFunction, isObject, resolveObjectKey, _capitalize} from './helpers.core';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a Proxy for resolving raw values for options.
|
* Creates a Proxy for resolving raw values for options.
|
||||||
* @param {object[]} scopes - The option scopes to look for values, in resolution order
|
* @param {object[]} scopes - The option scopes to look for values, in resolution order
|
||||||
* @param {string[]} [prefixes] - The prefixes for values, in resolution order.
|
* @param {string[]} [prefixes] - The prefixes for values, in resolution order.
|
||||||
|
* @param {object[]} [rootScopes] - The root option scopes
|
||||||
|
* @param {string|boolean} [fallback] - Parent scopes fallback
|
||||||
* @returns Proxy
|
* @returns Proxy
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
export function _createResolver(scopes, prefixes = ['']) {
|
export function _createResolver(scopes, prefixes = [''], rootScopes = scopes, fallback) {
|
||||||
|
if (!defined(fallback)) {
|
||||||
|
fallback = _resolve('_fallback', scopes);
|
||||||
|
}
|
||||||
const cache = {
|
const cache = {
|
||||||
[Symbol.toStringTag]: 'Object',
|
[Symbol.toStringTag]: 'Object',
|
||||||
_cacheable: true,
|
_cacheable: true,
|
||||||
_scopes: scopes,
|
_scopes: scopes,
|
||||||
override: (scope) => _createResolver([scope, ...scopes], prefixes),
|
_rootScopes: rootScopes,
|
||||||
|
_fallback: fallback,
|
||||||
|
override: (scope) => _createResolver([scope, ...scopes], prefixes, rootScopes, fallback),
|
||||||
};
|
};
|
||||||
return new Proxy(cache, {
|
return new Proxy(cache, {
|
||||||
/**
|
/**
|
||||||
@ -20,7 +27,7 @@ export function _createResolver(scopes, prefixes = ['']) {
|
|||||||
*/
|
*/
|
||||||
get(target, prop) {
|
get(target, prop) {
|
||||||
return _cached(target, prop,
|
return _cached(target, prop,
|
||||||
() => _resolveWithPrefixes(prop, prefixes, scopes));
|
() => _resolveWithPrefixes(prop, prefixes, scopes, target));
|
||||||
},
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -186,7 +193,7 @@ function _resolveScriptable(prop, value, target, receiver) {
|
|||||||
_stack.delete(prop);
|
_stack.delete(prop);
|
||||||
if (isObject(value)) {
|
if (isObject(value)) {
|
||||||
// When scriptable option returns an object, create a resolver on that.
|
// When scriptable option returns an object, create a resolver on that.
|
||||||
value = createSubResolver(_proxy._scopes, prop, value);
|
value = createSubResolver(_proxy._scopes, _proxy, prop, value);
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
@ -202,64 +209,69 @@ function _resolveArray(prop, value, target, isIndexable) {
|
|||||||
const scopes = _proxy._scopes.filter(s => s !== arr);
|
const scopes = _proxy._scopes.filter(s => s !== arr);
|
||||||
value = [];
|
value = [];
|
||||||
for (const item of arr) {
|
for (const item of arr) {
|
||||||
const resolver = createSubResolver(scopes, prop, item);
|
const resolver = createSubResolver(scopes, _proxy, prop, item);
|
||||||
value.push(_attachContext(resolver, _context, _subProxy && _subProxy[prop]));
|
value.push(_attachContext(resolver, _context, _subProxy && _subProxy[prop]));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createSubResolver(parentScopes, prop, value) {
|
function resolveFallback(fallback, prop, value) {
|
||||||
const set = new Set([value]);
|
return isFunction(fallback) ? fallback(prop, value) : fallback;
|
||||||
const lookupScopes = [value, ...parentScopes];
|
}
|
||||||
const {keys, includeParents} = _resolveSubKeys(lookupScopes, prop, value);
|
|
||||||
while (keys.length) {
|
|
||||||
const key = keys.shift();
|
|
||||||
for (const item of lookupScopes) {
|
|
||||||
const scope = resolveObjectKey(item, key);
|
|
||||||
if (scope) {
|
|
||||||
set.add(scope);
|
|
||||||
// fallback detour?
|
|
||||||
const fallback = scope._fallback;
|
|
||||||
if (defined(fallback)) {
|
|
||||||
keys.push(...resolveFallback(fallback, key, scope).filter(k => k !== key));
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (key !== prop && scope === false) {
|
const getScope = (key, parent) => key === true ? parent : resolveObjectKey(parent, key);
|
||||||
// If any of the fallback scopes is explicitly false, return false
|
|
||||||
// For example, options.hover falls back to options.interaction, when
|
function addScopes(set, parentScopes, key, parentFallback) {
|
||||||
// options.interaction is false, options.hover will also resolve as false.
|
for (const parent of parentScopes) {
|
||||||
return false;
|
const scope = getScope(key, parent);
|
||||||
|
if (scope) {
|
||||||
|
set.add(scope);
|
||||||
|
const fallback = scope._fallback;
|
||||||
|
if (defined(fallback) && fallback !== key && fallback !== parentFallback) {
|
||||||
|
// When we reach the descriptor that defines a new _fallback, return that.
|
||||||
|
// The fallback will resume to that new scope.
|
||||||
|
return fallback;
|
||||||
}
|
}
|
||||||
|
} else if (scope === false && key !== 'fill') {
|
||||||
|
// Fallback to `false` results to `false`, expect for `fill`.
|
||||||
|
// The special case (fill) should be handled through descriptors.
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (includeParents) {
|
return false;
|
||||||
parentScopes.forEach(set.add, set);
|
}
|
||||||
|
|
||||||
|
function createSubResolver(parentScopes, resolver, prop, value) {
|
||||||
|
const rootScopes = resolver._rootScopes;
|
||||||
|
const fallback = resolveFallback(resolver._fallback, prop, value);
|
||||||
|
const allScopes = [...parentScopes, ...rootScopes];
|
||||||
|
const set = new Set([value]);
|
||||||
|
let key = prop;
|
||||||
|
while (key !== false) {
|
||||||
|
key = addScopes(set, allScopes, key, fallback);
|
||||||
|
if (key === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return _createResolver([...set]);
|
if (defined(fallback) && fallback !== prop) {
|
||||||
}
|
const fallbackScopes = allScopes;
|
||||||
|
key = fallback;
|
||||||
function resolveFallback(fallback, prop, value) {
|
while (key !== false) {
|
||||||
const resolved = isFunction(fallback) ? fallback(prop, value) : fallback;
|
key = addScopes(set, fallbackScopes, key, fallback);
|
||||||
return isArray(resolved) ? resolved : typeof resolved === 'string' ? [resolved] : [];
|
}
|
||||||
}
|
|
||||||
|
|
||||||
function _resolveSubKeys(parentScopes, prop, value) {
|
|
||||||
const fallback = valueOrDefault(_resolve('_fallback', parentScopes.map(scope => scope[prop] || scope)), true);
|
|
||||||
const keys = [prop];
|
|
||||||
if (defined(fallback)) {
|
|
||||||
keys.push(...resolveFallback(fallback, prop, value));
|
|
||||||
}
|
}
|
||||||
return {keys: keys.filter(v => v), includeParents: fallback !== false && fallback !== prop};
|
return _createResolver([...set], [''], rootScopes, fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
function _resolveWithPrefixes(prop, prefixes, scopes) {
|
|
||||||
|
function _resolveWithPrefixes(prop, prefixes, scopes, proxy) {
|
||||||
let value;
|
let value;
|
||||||
for (const prefix of prefixes) {
|
for (const prefix of prefixes) {
|
||||||
value = _resolve(readKey(prefix, prop), scopes);
|
value = _resolve(readKey(prefix, prop), scopes);
|
||||||
if (defined(value)) {
|
if (defined(value)) {
|
||||||
return (needsSubResolver(prop, value))
|
return needsSubResolver(prop, value)
|
||||||
? createSubResolver(scopes, prop, value)
|
? createSubResolver(scopes, proxy, prop, value)
|
||||||
: value;
|
: value;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -385,9 +385,11 @@ export class Tooltip extends Element {
|
|||||||
|
|
||||||
const chart = me._chart;
|
const chart = me._chart;
|
||||||
const options = me.options;
|
const options = me.options;
|
||||||
const opts = options.enabled && chart.options.animation && options.animation;
|
const opts = options.enabled && chart.options.animation && options.animations;
|
||||||
const animations = new Animations(me._chart, opts);
|
const animations = new Animations(me._chart, opts);
|
||||||
me._cachedAnimations = Object.freeze(animations);
|
if (opts._cacheable) {
|
||||||
|
me._cachedAnimations = Object.freeze(animations);
|
||||||
|
}
|
||||||
|
|
||||||
return animations;
|
return animations;
|
||||||
}
|
}
|
||||||
@ -1108,6 +1110,8 @@ export default {
|
|||||||
animation: {
|
animation: {
|
||||||
duration: 400,
|
duration: 400,
|
||||||
easing: 'easeOutQuart',
|
easing: 'easeOutQuart',
|
||||||
|
},
|
||||||
|
animations: {
|
||||||
numbers: {
|
numbers: {
|
||||||
type: 'number',
|
type: 'number',
|
||||||
properties: ['x', 'y', 'width', 'height', 'caretX', 'caretY'],
|
properties: ['x', 'y', 'width', 'height', 'caretX', 'caretY'],
|
||||||
@ -1203,6 +1207,12 @@ export default {
|
|||||||
callbacks: {
|
callbacks: {
|
||||||
_scriptable: false,
|
_scriptable: false,
|
||||||
_indexable: false,
|
_indexable: false,
|
||||||
|
},
|
||||||
|
animation: {
|
||||||
|
_fallback: false
|
||||||
|
},
|
||||||
|
animations: {
|
||||||
|
_fallback: 'animation'
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@ -742,6 +742,18 @@ describe('Chart.DatasetController', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('_resolveAnimations', function() {
|
describe('_resolveAnimations', function() {
|
||||||
|
function animationsExpectations(anims, props) {
|
||||||
|
for (const [prop, opts] of Object.entries(props)) {
|
||||||
|
const anim = anims._properties.get(prop);
|
||||||
|
expect(anim).withContext(prop).toBeInstanceOf(Object);
|
||||||
|
if (anim) {
|
||||||
|
for (const [name, value] of Object.entries(opts)) {
|
||||||
|
expect(anim[name]).withContext('"' + name + '" of ' + JSON.stringify(anim)).toEqual(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
it('should resolve to empty Animations when globally disabled', function() {
|
it('should resolve to empty Animations when globally disabled', function() {
|
||||||
const chart = acquireChart({
|
const chart = acquireChart({
|
||||||
type: 'line',
|
type: 'line',
|
||||||
@ -778,5 +790,70 @@ describe('Chart.DatasetController', function() {
|
|||||||
|
|
||||||
expect(controller._resolveAnimations(0)._properties.size).toEqual(0);
|
expect(controller._resolveAnimations(0)._properties.size).toEqual(0);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should fallback properly', function() {
|
||||||
|
const chart = acquireChart({
|
||||||
|
type: 'line',
|
||||||
|
data: {
|
||||||
|
datasets: [{
|
||||||
|
data: [1],
|
||||||
|
animation: {
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
}, {
|
||||||
|
type: 'bar',
|
||||||
|
data: [2]
|
||||||
|
}]
|
||||||
|
},
|
||||||
|
options: {
|
||||||
|
animation: {
|
||||||
|
delay: 100
|
||||||
|
},
|
||||||
|
animations: {
|
||||||
|
x: {
|
||||||
|
delay: 200
|
||||||
|
}
|
||||||
|
},
|
||||||
|
transitions: {
|
||||||
|
show: {
|
||||||
|
x: {
|
||||||
|
delay: 300
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
datasets: {
|
||||||
|
bar: {
|
||||||
|
animation: {
|
||||||
|
duration: 500
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const controller = chart.getDatasetMeta(0).controller;
|
||||||
|
|
||||||
|
expect(Chart.defaults.animation.duration).toEqual(1000);
|
||||||
|
|
||||||
|
const def0 = controller._resolveAnimations(0, 'default', false);
|
||||||
|
animationsExpectations(def0, {
|
||||||
|
x: {
|
||||||
|
delay: 200,
|
||||||
|
duration: 200
|
||||||
|
},
|
||||||
|
y: {
|
||||||
|
delay: 100,
|
||||||
|
duration: 200
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const controller2 = chart.getDatasetMeta(1).controller;
|
||||||
|
const def1 = controller2._resolveAnimations(0, 'default', false);
|
||||||
|
animationsExpectations(def1, {
|
||||||
|
x: {
|
||||||
|
delay: 200,
|
||||||
|
duration: 500
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@ -17,7 +17,10 @@ describe('Chart.helpers.config', function() {
|
|||||||
expect(resolver.hoverColor).toEqual(defaults.hoverColor);
|
expect(resolver.hoverColor).toEqual(defaults.hoverColor);
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should resolve to parent scopes', function() {
|
it('should resolve to parent scopes, when _fallback is true', function() {
|
||||||
|
const descriptors = {
|
||||||
|
_fallback: true
|
||||||
|
};
|
||||||
const defaults = {
|
const defaults = {
|
||||||
root: true,
|
root: true,
|
||||||
sub: {
|
sub: {
|
||||||
@ -28,7 +31,7 @@ describe('Chart.helpers.config', function() {
|
|||||||
child: 'sub default comes before this',
|
child: 'sub default comes before this',
|
||||||
opt: 'opt'
|
opt: 'opt'
|
||||||
};
|
};
|
||||||
const resolver = _createResolver([options, defaults]);
|
const resolver = _createResolver([options, defaults, descriptors]);
|
||||||
const sub = resolver.sub;
|
const sub = resolver.sub;
|
||||||
expect(sub.root).toEqual(true);
|
expect(sub.root).toEqual(true);
|
||||||
expect(sub.child).toEqual(true);
|
expect(sub.child).toEqual(true);
|
||||||
@ -125,10 +128,9 @@ describe('Chart.helpers.config', function() {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not fallback when _fallback is false', function() {
|
it('should not fallback by default', function() {
|
||||||
const defaults = {
|
const defaults = {
|
||||||
hover: {
|
hover: {
|
||||||
_fallback: false,
|
|
||||||
a: 'defaults.hover'
|
a: 'defaults.hover'
|
||||||
},
|
},
|
||||||
controllers: {
|
controllers: {
|
||||||
@ -252,16 +254,23 @@ describe('Chart.helpers.config', function() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should fallback throuhg multiple routes', function() {
|
it('should fallback throuhg multiple routes', function() {
|
||||||
|
const descriptors = {
|
||||||
|
_fallback: 'level1',
|
||||||
|
level1: {
|
||||||
|
_fallback: 'root'
|
||||||
|
},
|
||||||
|
level2: {
|
||||||
|
_fallback: 'level1'
|
||||||
|
}
|
||||||
|
};
|
||||||
const defaults = {
|
const defaults = {
|
||||||
root: {
|
root: {
|
||||||
a: 'root'
|
a: 'root'
|
||||||
},
|
},
|
||||||
level1: {
|
level1: {
|
||||||
_fallback: 'root',
|
|
||||||
b: 'level1',
|
b: 'level1',
|
||||||
},
|
},
|
||||||
level2: {
|
level2: {
|
||||||
_fallback: 'level1',
|
|
||||||
level1: {
|
level1: {
|
||||||
g: 'level2.level1'
|
g: 'level2.level1'
|
||||||
},
|
},
|
||||||
@ -277,7 +286,7 @@ describe('Chart.helpers.config', function() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
const resolver = _createResolver([defaults]);
|
const resolver = _createResolver([defaults, descriptors]);
|
||||||
expect(resolver.level1).toEqualOptions({
|
expect(resolver.level1).toEqualOptions({
|
||||||
a: 'root',
|
a: 'root',
|
||||||
b: 'level1',
|
b: 'level1',
|
||||||
@ -292,7 +301,7 @@ describe('Chart.helpers.config', function() {
|
|||||||
expect(resolver.level2.sublevel1).toEqualOptions({
|
expect(resolver.level2.sublevel1).toEqualOptions({
|
||||||
a: 'root',
|
a: 'root',
|
||||||
b: 'level1',
|
b: 'level1',
|
||||||
c: 'level2', // TODO: this should be undefined
|
c: undefined,
|
||||||
d: 'sublevel1',
|
d: 'sublevel1',
|
||||||
e: undefined,
|
e: undefined,
|
||||||
f: undefined,
|
f: undefined,
|
||||||
@ -301,7 +310,7 @@ describe('Chart.helpers.config', function() {
|
|||||||
expect(resolver.level2.sublevel2).toEqualOptions({
|
expect(resolver.level2.sublevel2).toEqualOptions({
|
||||||
a: 'root',
|
a: 'root',
|
||||||
b: 'level1',
|
b: 'level1',
|
||||||
c: 'level2', // TODO: this should be undefined
|
c: undefined,
|
||||||
d: undefined,
|
d: undefined,
|
||||||
e: 'sublevel2',
|
e: 'sublevel2',
|
||||||
f: undefined,
|
f: undefined,
|
||||||
@ -310,13 +319,129 @@ describe('Chart.helpers.config', function() {
|
|||||||
expect(resolver.level2.sublevel2.level1).toEqualOptions({
|
expect(resolver.level2.sublevel2.level1).toEqualOptions({
|
||||||
a: 'root',
|
a: 'root',
|
||||||
b: 'level1',
|
b: 'level1',
|
||||||
c: 'level2', // TODO: this should be undefined
|
c: undefined,
|
||||||
d: undefined,
|
d: undefined,
|
||||||
e: 'sublevel2', // TODO: this should be undefined
|
e: undefined,
|
||||||
f: 'sublevel2.level1',
|
f: 'sublevel2.level1',
|
||||||
g: 'level2.level1'
|
g: undefined // same key only included from immediate parents and root
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should fallback through multiple routes (animations)', function() {
|
||||||
|
const descriptors = {
|
||||||
|
animations: {
|
||||||
|
_fallback: 'animation',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const defaults = {
|
||||||
|
animation: {
|
||||||
|
duration: 1000,
|
||||||
|
easing: 'easeInQuad'
|
||||||
|
},
|
||||||
|
animations: {
|
||||||
|
colors: {
|
||||||
|
properties: ['color', 'backgroundColor'],
|
||||||
|
type: 'color'
|
||||||
|
},
|
||||||
|
numbers: {
|
||||||
|
properties: ['x', 'y'],
|
||||||
|
type: 'number'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
transitions: {
|
||||||
|
resize: {
|
||||||
|
animation: {
|
||||||
|
duration: 0
|
||||||
|
}
|
||||||
|
},
|
||||||
|
show: {
|
||||||
|
animation: {
|
||||||
|
duration: 400
|
||||||
|
},
|
||||||
|
animations: {
|
||||||
|
colors: {
|
||||||
|
from: 'transparent'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const options = {
|
||||||
|
animation: {
|
||||||
|
easing: 'linear'
|
||||||
|
},
|
||||||
|
animations: {
|
||||||
|
colors: {
|
||||||
|
properties: ['color', 'borderColor', 'backgroundColor'],
|
||||||
|
},
|
||||||
|
duration: {
|
||||||
|
properties: ['a', 'b'],
|
||||||
|
type: 'boolean'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const show = _createResolver([options, defaults.transitions.show, defaults, descriptors]);
|
||||||
|
expect(show.animation).toEqualOptions({
|
||||||
|
duration: 400,
|
||||||
|
easing: 'linear'
|
||||||
|
});
|
||||||
|
expect(show.animations.colors._scopes).toEqual([
|
||||||
|
options.animations.colors,
|
||||||
|
defaults.transitions.show.animations.colors,
|
||||||
|
defaults.animations.colors,
|
||||||
|
options.animation,
|
||||||
|
defaults.transitions.show.animation,
|
||||||
|
defaults.animation
|
||||||
|
]);
|
||||||
|
expect(show.animations.colors).toEqualOptions({
|
||||||
|
duration: 400,
|
||||||
|
from: 'transparent',
|
||||||
|
easing: 'linear',
|
||||||
|
type: 'color',
|
||||||
|
properties: ['color', 'borderColor', 'backgroundColor']
|
||||||
|
});
|
||||||
|
expect(show.animations.duration).toEqualOptions({
|
||||||
|
duration: 400,
|
||||||
|
easing: 'linear',
|
||||||
|
type: 'boolean',
|
||||||
|
properties: ['a', 'b']
|
||||||
|
});
|
||||||
|
expect(Object.getOwnPropertyNames(show.animations).filter(k => Chart.helpers.isObject(show.animations[k]))).toEqual([
|
||||||
|
'colors',
|
||||||
|
'duration',
|
||||||
|
'numbers',
|
||||||
|
]);
|
||||||
|
const def = _createResolver([options, defaults, descriptors]);
|
||||||
|
expect(def.animation).toEqualOptions({
|
||||||
|
duration: 1000,
|
||||||
|
easing: 'linear'
|
||||||
|
});
|
||||||
|
expect(def.animations.colors._scopes).toEqual([
|
||||||
|
options.animations.colors,
|
||||||
|
defaults.animations.colors,
|
||||||
|
options.animation,
|
||||||
|
defaults.animation
|
||||||
|
]);
|
||||||
|
expect(def.animations.colors).toEqualOptions({
|
||||||
|
duration: 1000,
|
||||||
|
easing: 'linear',
|
||||||
|
type: 'color',
|
||||||
|
properties: ['color', 'borderColor', 'backgroundColor']
|
||||||
|
});
|
||||||
|
expect(def.animations.duration).toEqualOptions({
|
||||||
|
duration: 1000,
|
||||||
|
easing: 'linear',
|
||||||
|
type: 'boolean',
|
||||||
|
properties: ['a', 'b']
|
||||||
|
});
|
||||||
|
expect(Object.getOwnPropertyNames(def.animations).filter(k => Chart.helpers.isObject(show.animations[k]))).toEqual([
|
||||||
|
'colors',
|
||||||
|
'duration',
|
||||||
|
'numbers',
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
92
types/index.esm.d.ts
vendored
92
types/index.esm.d.ts
vendored
@ -1335,12 +1335,9 @@ export interface HoverInteractionOptions extends CoreInteractionOptions {
|
|||||||
onHover(event: ChartEvent, elements: ActiveElement[], chart: Chart): void;
|
onHover(event: ChartEvent, elements: ActiveElement[], chart: Chart): void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CoreChartOptions extends ParsingOptions {
|
export interface CoreChartOptions extends ParsingOptions, AnimationOptions {
|
||||||
animation: Scriptable<AnimationOptions | false, ScriptableContext>;
|
|
||||||
|
|
||||||
datasets: {
|
datasets: AnimationOptions;
|
||||||
animation: Scriptable<AnimationOptions | false, ScriptableContext>;
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The base axis of the chart. 'x' for vertical charts and 'y' for horizontal charts.
|
* The base axis of the chart. 'x' for vertical charts and 'y' for horizontal charts.
|
||||||
@ -1460,76 +1457,81 @@ export type EasingFunction =
|
|||||||
| 'easeOutBounce'
|
| 'easeOutBounce'
|
||||||
| 'easeInOutBounce';
|
| 'easeInOutBounce';
|
||||||
|
|
||||||
export interface AnimationCommonSpec {
|
export type AnimationSpec = {
|
||||||
/**
|
/**
|
||||||
* The number of milliseconds an animation takes.
|
* The number of milliseconds an animation takes.
|
||||||
* @default 1000
|
* @default 1000
|
||||||
*/
|
*/
|
||||||
duration: number;
|
duration: Scriptable<number, ScriptableContext>;
|
||||||
/**
|
/**
|
||||||
* Easing function to use
|
* Easing function to use
|
||||||
* @default 'easeOutQuart'
|
* @default 'easeOutQuart'
|
||||||
*/
|
*/
|
||||||
easing: EasingFunction;
|
easing: Scriptable<EasingFunction, ScriptableContext>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Running animation count + FPS display in upper left corner of the chart.
|
* Running animation count + FPS display in upper left corner of the chart.
|
||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
debug: boolean;
|
debug: Scriptable<boolean, ScriptableContext>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Delay before starting the animations.
|
* Delay before starting the animations.
|
||||||
* @default 0
|
* @default 0
|
||||||
*/
|
*/
|
||||||
delay: number;
|
delay: Scriptable<number, ScriptableContext>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If set to true, the animations loop endlessly.
|
* If set to true, the animations loop endlessly.
|
||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
loop: boolean;
|
loop: Scriptable<boolean, ScriptableContext>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AnimationPropertySpec extends AnimationCommonSpec {
|
export type AnimationsSpec = {
|
||||||
properties: string[];
|
[name: string]: AnimationSpec & {
|
||||||
|
properties: string[];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type of property, determines the interpolator used. Possible values: 'number', 'color' and 'boolean'. Only really needed for 'color', because typeof does not get that right.
|
* Type of property, determines the interpolator used. Possible values: 'number', 'color' and 'boolean'. Only really needed for 'color', because typeof does not get that right.
|
||||||
*/
|
*/
|
||||||
type: 'color' | 'number' | 'boolean';
|
type: 'color' | 'number' | 'boolean';
|
||||||
|
|
||||||
fn: <T>(from: T, to: T, factor: number) => T;
|
fn: <T>(from: T, to: T, factor: number) => T;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Start value for the animation. Current value is used when undefined
|
* Start value for the animation. Current value is used when undefined
|
||||||
*/
|
*/
|
||||||
from: Color | number | boolean;
|
from: Scriptable<Color | number | boolean, ScriptableContext>;
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
to: Color | number | boolean;
|
to: Scriptable<Color | number | boolean, ScriptableContext>;
|
||||||
|
} | false
|
||||||
}
|
}
|
||||||
|
|
||||||
export type AnimationSpecContainer = AnimationCommonSpec & {
|
export type TransitionSpec = {
|
||||||
[prop: string]: AnimationPropertySpec | false;
|
animation: AnimationSpec;
|
||||||
};
|
animations: AnimationsSpec;
|
||||||
|
}
|
||||||
|
|
||||||
export type AnimationOptions = AnimationSpecContainer & {
|
export type TransitionsSpec = {
|
||||||
/**
|
[mode: string]: TransitionSpec
|
||||||
* Callback called on each step of an animation.
|
}
|
||||||
*/
|
|
||||||
onProgress: (this: Chart, event: AnimationEvent) => void;
|
|
||||||
/**
|
|
||||||
* Callback called when all animations are completed.
|
|
||||||
*/
|
|
||||||
onComplete: (this: Chart, event: AnimationEvent) => void;
|
|
||||||
|
|
||||||
active: AnimationSpecContainer | false;
|
export type AnimationOptions = {
|
||||||
hide: AnimationSpecContainer | false;
|
animation: AnimationSpec & {
|
||||||
reset: AnimationSpecContainer | false;
|
/**
|
||||||
resize: AnimationSpecContainer | false;
|
* Callback called on each step of an animation.
|
||||||
show: AnimationSpecContainer | false;
|
*/
|
||||||
|
onProgress: (this: Chart, event: AnimationEvent) => void;
|
||||||
|
/**
|
||||||
|
* Callback called when all animations are completed.
|
||||||
|
*/
|
||||||
|
onComplete: (this: Chart, event: AnimationEvent) => void;
|
||||||
|
};
|
||||||
|
animations: AnimationsSpec;
|
||||||
|
transitions: TransitionsSpec;
|
||||||
};
|
};
|
||||||
|
|
||||||
export interface FontSpec {
|
export interface FontSpec {
|
||||||
@ -2452,7 +2454,9 @@ export interface TooltipOptions extends CoreInteractionOptions {
|
|||||||
*/
|
*/
|
||||||
textDirection: string;
|
textDirection: string;
|
||||||
|
|
||||||
animation: Scriptable<AnimationSpecContainer, ScriptableContext>;
|
animation: AnimationSpec;
|
||||||
|
|
||||||
|
animations: AnimationsSpec;
|
||||||
|
|
||||||
callbacks: TooltipCallbacks;
|
callbacks: TooltipCallbacks;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user