Configurable hide/show animations (#7055)

Configurable hide/show animations
This commit is contained in:
Jukka Kurkela
2020-02-07 01:16:24 +02:00
committed by GitHub
parent 6affaa2a73
commit 70b6eab563
10 changed files with 131 additions and 54 deletions

View File

@@ -20,9 +20,10 @@ The following animation options are available. The global options for are define
| `active` | `object` | `{ duration: 400 }` | Option overrides for `active` animations (hover)
| `resize` | `object` | `{ duration: 0 }` | Option overrides for `resize` animations.
| [property] | `object` | `undefined` | Option overrides for [property].
| [collection] | `object` | `undefined` | Option overrides for multiple properties, identified by `properties` array.
| [collection] | `object` | [defaults...](#default-collections) | Option overrides for multiple properties, identified by `properties` array.
| [mode] | `object` | [defaults...](#default-modes) | Option overrides for update mode. Core modes: `'active'`, `'hide'`, `'reset'`, `'resize'`, `'show'`. A custom mode can be used by passing a custom `mode` to [update](../developers/api.md#updatemode)
Default collections:
### Default collections
| Name | Option | Value
| ---- | ------ | -----
@@ -35,6 +36,17 @@ Direct property configuration overrides configuration of same property in a coll
These defaults can be overridden in `options.animation` and `dataset.animation`.
### Default modes
| Mode | Option | Value
| -----| ------ | -----
| active | duration | 400
| resize | duration | 0
| show | colors | `{ type: 'color', properties: ['borderColor', 'backgroundColor'], from: 'transparent' }`
| | visible | `{ type: 'boolean', duration: 0 }`
| hide | colors | `{ type: 'color', properties: ['borderColor', 'backgroundColor'], to: 'transparent' }`
| | visible | `{ type: 'boolean', easing: 'easeInExpo' }`
## Easing
Available options are:

View File

@@ -28,7 +28,7 @@ myLineChart.update(); // Calling update now animates the position of March from
> **Note:** replacing the data reference (e.g. `myLineChart.data = {datasets: [...]}` only works starting **version 2.6**. Prior that, replacing the entire data object could be achieved with the following workaround: `myLineChart.config.data = {datasets: [...]}`.
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 `undefined`, `'reset'`, `'resize'` or `'active'`. `'none'` is also a supported mode for skipping animations for single update.
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.md) docs for more details.
Example:
@@ -164,4 +164,20 @@ Like [setDatasetVisibility](#setdatasetvisibility) except that it hides only a s
```javascript
chart.setDataVisibility(0, 2, false); // hides the item in dataset 0, at index 2
chart.update(); // chart now renders with item hidden
```
```
## hide(datasetIndex)
Sets the visibility for the given dataset to false. Updates the chart and animates the dataset with `'hide'` mode. This animation can be configured under the `hide` key in animation options. Please see [animations](../configuration/animations.md) docs for more details.
```javascript
chart.hide(1); // hides dataset at index 1 and does 'hide' animation.
```
## show(datasetIndex)
Sets the visibility for the given dataset to true. Updates the chart and animates the dataset with `'show'` mode. This animation can be configured under the `show` key in animation options. Please see [animations](../configuration/animations.md) docs for more details.
```javascript
chart.show(1); // shows dataset at index 1 and does 'show' animation.
```

View File

@@ -263,9 +263,9 @@ class BarController extends DatasetController {
update(mode) {
const me = this;
const rects = me._cachedMeta.data;
const meta = me._cachedMeta;
me.updateElements(rects, 0, mode);
me.updateElements(meta.data, 0, mode);
}
updateElements(rectangles, start, mode) {

View File

@@ -52,9 +52,7 @@ class LineController extends DatasetController {
}
// Update Points
if (meta.visible) {
me.updateElements(points, 0, mode);
}
me.updateElements(points, 0, mode);
}
updateElements(points, start, mode) {

View File

@@ -1,11 +1,13 @@
'use strict';
import helpers from '../helpers';
import {effects} from '../helpers/helpers.easing';
import {resolve} from '../helpers/helpers.options';
const transparent = 'transparent';
const interpolators = {
number: function(from, to, factor) {
return from + (to - from) * factor;
boolean: function(from, to, factor) {
return factor > 0.5 ? to : from;
},
color: function(from, to, factor) {
var c0 = helpers.color(from || transparent);
@@ -13,30 +15,23 @@ const interpolators = {
return c1 && c1.valid
? c1.mix(c0, factor).rgbaString()
: to;
},
number: function(from, to, factor) {
return from + (to - from) * factor;
}
};
class Animation {
constructor(cfg, target, prop, to) {
const me = this;
let from = cfg.from;
const currentValue = target[prop];
if (from === undefined) {
from = target[prop];
}
if (to === undefined) {
to = target[prop];
}
if (from === undefined) {
from = to;
} else if (to === undefined) {
to = from;
}
to = resolve([cfg.to, to, currentValue, cfg.from]);
let from = resolve([cfg.from, currentValue, to]);
me._active = true;
me._fn = cfg.fn || interpolators[cfg.type || typeof from];
me._easing = helpers.easing.effects[cfg.easing || 'linear'];
me._easing = effects[cfg.easing || 'linear'];
me._start = Math.floor(Date.now() + (cfg.delay || 0));
me._duration = Math.floor(cfg.duration);
me._loop = !!cfg.loop;

View File

@@ -5,25 +5,55 @@ import Animation from './core.animation';
import defaults from '../core/core.defaults';
import {noop, extend, isObject} from '../helpers/helpers.core';
const numbers = ['x', 'y', 'borderWidth', 'radius', 'tension'];
const colors = ['borderColor', 'backgroundColor'];
defaults._set('animation', {
// Plain properties can be overridden in each object
duration: 1000,
easing: 'easeOutQuart',
onProgress: noop,
onComplete: noop,
// Property sets
colors: {
type: 'color',
properties: colors
},
numbers: {
type: 'number',
properties: numbers
},
// Update modes. These are overrides / additions to the above animations.
active: {
duration: 400
},
resize: {
duration: 0
},
numbers: {
type: 'number',
properties: ['x', 'y', 'borderWidth', 'radius', 'tension']
show: {
colors: {
type: 'color',
properties: colors,
from: 'transparent'
},
visible: {
type: 'boolean',
duration: 0 // show immediately
},
},
colors: {
type: 'color',
properties: ['borderColor', 'backgroundColor']
},
onProgress: noop,
onComplete: noop
hide: {
colors: {
type: 'color',
properties: colors,
to: 'transparent'
},
visible: {
type: 'boolean',
easing: 'easeInExpo' // for keeping the dataset visible almost all the way through the animation
},
}
});
function copyOptions(target, values) {
@@ -132,6 +162,10 @@ export default class Animations {
continue;
}
let value = values[prop];
let animation = running[prop];
if (animation) {
animation.cancel();
}
const cfg = animatedProps.get(prop);
if (!cfg || !cfg.duration) {
@@ -140,10 +174,6 @@ export default class Animations {
continue;
}
let animation = running[prop];
if (animation) {
animation.cancel();
}
running[prop] = animation = new Animation(cfg, target, prop, value);
animations.push(animation);
}

View File

@@ -91,9 +91,10 @@ class Animator {
if (draw) {
chart.draw();
if (chart.options.animation.debug) {
drawFPS(chart, items.length, date, me._lastDate);
}
}
if (chart.options.animation.debug) {
drawFPS(chart, items.length, date, me._lastDate);
}
me._notify(chart, anims, date, 'progress');

View File

@@ -594,13 +594,14 @@ class Chart {
*/
updateDatasets(mode) {
const me = this;
const isFunction = typeof mode === 'function';
if (plugins.notify(me, 'beforeDatasetsUpdate') === false) {
return;
}
for (let i = 0, ilen = me.data.datasets.length; i < ilen; ++i) {
me.updateDataset(i, mode);
me.updateDataset(i, isFunction ? mode({datesetIndex: i}) : mode);
}
plugins.notify(me, 'afterDatasetsUpdate');
@@ -838,6 +839,30 @@ class Chart {
}
}
/**
* @private
*/
_updateDatasetVisibility(datasetIndex, visible) {
const me = this;
const mode = visible ? 'show' : 'hide';
const meta = me.getDatasetMeta(datasetIndex);
const anims = meta.controller._resolveAnimations(undefined, mode);
me.setDatasetVisibility(datasetIndex, visible);
// Animate visible state, so hide animation can be seen. This could be handled better if update / updateDataset returned a Promise.
anims.update(meta, {visible});
me.update((ctx) => ctx.datasetIndex === datasetIndex ? mode : undefined);
}
hide(datasetIndex) {
this._updateDatasetVisibility(datasetIndex, false);
}
show(datasetIndex) {
this._updateDatasetVisibility(datasetIndex, true);
}
/**
* @private
*/

View File

@@ -922,11 +922,8 @@ class DatasetController {
const chartAnim = resolve([chart.options.animation], context, index, info);
let config = helpers.mergeIf({}, [datasetAnim, chartAnim]);
if (active && config.active) {
config = helpers.extend({}, config, config.active);
}
if (mode === 'resize' && config.resize) {
config = helpers.extend({}, config, config.resize);
if (config[mode]) {
config = helpers.extend({}, config, config[mode]);
}
const animations = new Animations(chart, config);
@@ -957,6 +954,9 @@ class DatasetController {
* @private
*/
_includeOptions(mode, sharedOptions) {
if (mode === 'hide' || mode === 'show') {
return true;
}
return mode !== 'resize' && !sharedOptions;
}

View File

@@ -20,13 +20,13 @@ defaults._set('legend', {
onClick: function(e, legendItem) {
var index = legendItem.datasetIndex;
var ci = this.chart;
var meta = ci.getDatasetMeta(index);
// See controller.isDatasetVisible comment
meta.hidden = meta.hidden === null ? !ci.data.datasets[index].hidden : null;
// We hid a dataset ... rerender the chart
ci.update();
if (ci.isDatasetVisible(index)) {
ci.hide(index);
legendItem.hidden = true;
} else {
ci.show(index);
legendItem.hidden = false;
}
},
onHover: null,