]> git.ipfire.org Git - thirdparty/Chart.js.git/commitdiff
Configurable hide/show animations (#7055)
authorJukka Kurkela <jukka.kurkela@gmail.com>
Thu, 6 Feb 2020 23:16:24 +0000 (01:16 +0200)
committerGitHub <noreply@github.com>
Thu, 6 Feb 2020 23:16:24 +0000 (18:16 -0500)
Configurable hide/show animations

docs/configuration/animations.md
docs/developers/api.md
src/controllers/controller.bar.js
src/controllers/controller.line.js
src/core/core.animation.js
src/core/core.animations.js
src/core/core.animator.js
src/core/core.controller.js
src/core/core.datasetController.js
src/plugins/plugin.legend.js

index d642fb3de107efe1e3f9817ea84d94acd07a105d..54696728ccb1a181bc9fa59e90a05d270ffbdf0f 100644 (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:
index 4581d576b3e000749513476e6092548cce1551e6..77f184d2412c8815bf7c81d72c84769dbab96198 100644 (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
-```
\ 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.
+```
index 2652191cb8bdcb5c7d6fdd4eb546ed55b7cc2396..894003e9617656b03e7f22273cebe75d2e2f408f 100644 (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) {
index 5bbd97009b7a966e04ea86f0a64341b187aafd5e..c91602079b3d0e2c50bcf31aeeeffb90ddd5069e 100644 (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) {
index ee98f4f14de7aa6a1cd6de69b71032ea158f0205..3b2e27bb72e0c5baa76f7c3c1d5dcaa18a4ebacf 100644 (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;
index c592b6a9ac70b8413025a36a9c633a369be612f0..c3f6b54155632b940f50fcdb64de0f5d0cb8a574 100644 (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);
                }
index 1c51e91e848fc44eb4ca20a98e2e4a625e06b325..b2fd4b41b9f29c55c66e0ad634b33d12e4187db7 100644 (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');
index ccae7a0443bef95c42ea4e40617dd9906846e65d..6d50198e82027dd18cf851bd026b8627d05cec71 100644 (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
         */
index 4d8ca3033545196f9478b8f789594abbfc310809..7ef108a5888c6143aa837cfcdeb3a85f69980de2 100644 (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;
        }
 
index 6fb92379c7e0dd7e0b32222938607985f8e6d2c8..928608d121351138ed72c63ec7978a0b7ef59b75 100644 (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,