| `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
| ---- | ------ | -----
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:
> **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:
```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.
+```
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) {
}
// Update Points
- if (meta.visible) {
- me.updateElements(points, 0, mode);
- }
+ me.updateElements(points, 0, mode);
}
updateElements(points, start, mode) {
'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);
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;
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) {
continue;
}
let value = values[prop];
+ let animation = running[prop];
+ if (animation) {
+ animation.cancel();
+ }
const cfg = animatedProps.get(prop);
if (!cfg || !cfg.duration) {
continue;
}
- let animation = running[prop];
- if (animation) {
- animation.cancel();
- }
running[prop] = animation = new Animation(cfg, target, prop, value);
animations.push(animation);
}
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');
*/
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');
}
}
+ /**
+ * @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
*/
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);
* @private
*/
_includeOptions(mode, sharedOptions) {
+ if (mode === 'hide' || mode === 'show') {
+ return true;
+ }
return mode !== 'resize' && !sharedOptions;
}
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,