From: Jukka Kurkela Date: Thu, 6 Feb 2020 23:16:24 +0000 (+0200) Subject: Configurable hide/show animations (#7055) X-Git-Tag: v3.0.0-alpha~72 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=70b6eab5634bc9aedc2ae32f0298227fe3bfb807;p=thirdparty%2FChart.js.git Configurable hide/show animations (#7055) Configurable hide/show animations --- diff --git a/docs/configuration/animations.md b/docs/configuration/animations.md index d642fb3de..54696728c 100644 --- a/docs/configuration/animations.md +++ b/docs/configuration/animations.md @@ -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: diff --git a/docs/developers/api.md b/docs/developers/api.md index 4581d576b..77f184d24 100644 --- a/docs/developers/api.md +++ b/docs/developers/api.md @@ -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 -``` \ No newline at end of file +``` + +## 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. +``` diff --git a/src/controllers/controller.bar.js b/src/controllers/controller.bar.js index 2652191cb..894003e96 100644 --- a/src/controllers/controller.bar.js +++ b/src/controllers/controller.bar.js @@ -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) { diff --git a/src/controllers/controller.line.js b/src/controllers/controller.line.js index 5bbd97009..c91602079 100644 --- a/src/controllers/controller.line.js +++ b/src/controllers/controller.line.js @@ -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) { diff --git a/src/core/core.animation.js b/src/core/core.animation.js index ee98f4f14..3b2e27bb7 100644 --- a/src/core/core.animation.js +++ b/src/core/core.animation.js @@ -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; diff --git a/src/core/core.animations.js b/src/core/core.animations.js index c592b6a9a..c3f6b5415 100644 --- a/src/core/core.animations.js +++ b/src/core/core.animations.js @@ -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); } diff --git a/src/core/core.animator.js b/src/core/core.animator.js index 1c51e91e8..b2fd4b41b 100644 --- a/src/core/core.animator.js +++ b/src/core/core.animator.js @@ -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'); diff --git a/src/core/core.controller.js b/src/core/core.controller.js index ccae7a044..6d50198e8 100644 --- a/src/core/core.controller.js +++ b/src/core/core.controller.js @@ -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 */ diff --git a/src/core/core.datasetController.js b/src/core/core.datasetController.js index 4d8ca3033..7ef108a58 100644 --- a/src/core/core.datasetController.js +++ b/src/core/core.datasetController.js @@ -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; } diff --git a/src/plugins/plugin.legend.js b/src/plugins/plugin.legend.js index 6fb92379c..928608d12 100644 --- a/src/plugins/plugin.legend.js +++ b/src/plugins/plugin.legend.js @@ -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,