mirror of
https://github.com/chartjs/Chart.js.git
synced 2024-10-06 04:09:08 +02:00
parent
c9573bc1e4
commit
da33b1bb27
6
package-lock.json
generated
6
package-lock.json
generated
@ -6965,6 +6965,12 @@
|
||||
"integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
|
||||
"dev": true
|
||||
},
|
||||
"promise-polyfill": {
|
||||
"version": "8.1.3",
|
||||
"resolved": "https://registry.npmjs.org/promise-polyfill/-/promise-polyfill-8.1.3.tgz",
|
||||
"integrity": "sha512-MG5r82wBzh7pSKDRa9y+vllNHz3e3d4CNj1PQE4BQYxLme0gKYYBm9YENq+UkEikyZ0XbiGWxYlVw3Rl9O/U8g==",
|
||||
"dev": true
|
||||
},
|
||||
"psl": {
|
||||
"version": "1.8.0",
|
||||
"resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz",
|
||||
|
@ -74,6 +74,7 @@
|
||||
"karma-safari-private-launcher": "^1.0.0",
|
||||
"moment": "^2.27.0",
|
||||
"pixelmatch": "^5.2.1",
|
||||
"promise-polyfill": "^8.1.3",
|
||||
"resize-observer-polyfill": "^1.5.1",
|
||||
"rollup": "^2.25.0",
|
||||
"rollup-plugin-babel": "^4.4.0",
|
||||
|
@ -40,7 +40,8 @@ module.exports = [
|
||||
input,
|
||||
plugins: [
|
||||
inject({
|
||||
ResizeObserver: 'resize-observer-polyfill'
|
||||
ResizeObserver: 'resize-observer-polyfill',
|
||||
Promise: 'promise-polyfill'
|
||||
}),
|
||||
json(),
|
||||
resolve(),
|
||||
@ -61,7 +62,8 @@ module.exports = [
|
||||
input,
|
||||
plugins: [
|
||||
inject({
|
||||
ResizeObserver: 'resize-observer-polyfill'
|
||||
ResizeObserver: 'resize-observer-polyfill',
|
||||
Promise: 'promise-polyfill'
|
||||
}),
|
||||
json(),
|
||||
resolve(),
|
||||
|
@ -219,6 +219,7 @@ export default class BarController extends DatasetController {
|
||||
|
||||
initialize() {
|
||||
const me = this;
|
||||
me.enableOptionSharing = true;
|
||||
|
||||
super.initialize();
|
||||
|
||||
@ -241,14 +242,14 @@ export default class BarController extends DatasetController {
|
||||
const horizontal = vscale.isHorizontal();
|
||||
const ruler = me._getRuler();
|
||||
const firstOpts = me.resolveDataElementOptions(start, mode);
|
||||
const sharedOptions = me.getSharedOptions(mode, rectangles[start], firstOpts);
|
||||
const sharedOptions = me.getSharedOptions(firstOpts);
|
||||
const includeOptions = me.includeOptions(mode, sharedOptions);
|
||||
|
||||
let i;
|
||||
me.updateSharedOptions(sharedOptions, mode, firstOpts);
|
||||
|
||||
for (i = 0; i < rectangles.length; i++) {
|
||||
for (let i = 0; i < rectangles.length; i++) {
|
||||
const index = start + i;
|
||||
const options = me.resolveDataElementOptions(index, mode);
|
||||
const options = sharedOptions || me.resolveDataElementOptions(index, mode);
|
||||
const vpixels = me._calculateBarValuePixels(index, options);
|
||||
const ipixels = me._calculateBarIndexPixels(index, ruler, options);
|
||||
|
||||
@ -261,19 +262,11 @@ export default class BarController extends DatasetController {
|
||||
width: horizontal ? undefined : ipixels.size
|
||||
};
|
||||
|
||||
// all borders are drawn for floating bar
|
||||
/* TODO: float bars border skipping magic
|
||||
if (me.getParsed(i)._custom) {
|
||||
model.borderSkipped = null;
|
||||
}
|
||||
*/
|
||||
if (includeOptions) {
|
||||
properties.options = options;
|
||||
}
|
||||
me.updateElement(rectangles[i], index, properties, mode);
|
||||
}
|
||||
|
||||
me.updateSharedOptions(sharedOptions, mode);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -3,6 +3,10 @@ import {resolve} from '../helpers/helpers.options';
|
||||
import {resolveObjectKey} from '../helpers/helpers.core';
|
||||
|
||||
export default class BubbleController extends DatasetController {
|
||||
initialize() {
|
||||
this.enableOptionSharing = true;
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse array of objects
|
||||
@ -69,7 +73,7 @@ export default class BubbleController extends DatasetController {
|
||||
const reset = mode === 'reset';
|
||||
const {xScale, yScale} = me._cachedMeta;
|
||||
const firstOpts = me.resolveDataElementOptions(start, mode);
|
||||
const sharedOptions = me.getSharedOptions(mode, points[start], firstOpts);
|
||||
const sharedOptions = me.getSharedOptions(firstOpts);
|
||||
const includeOptions = me.includeOptions(mode, sharedOptions);
|
||||
|
||||
for (let i = 0; i < points.length; i++) {
|
||||
@ -95,7 +99,7 @@ export default class BubbleController extends DatasetController {
|
||||
me.updateElement(point, index, properties, mode);
|
||||
}
|
||||
|
||||
me.updateSharedOptions(sharedOptions, mode);
|
||||
me.updateSharedOptions(sharedOptions, mode, firstOpts);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -44,6 +44,7 @@ export default class DoughnutController extends DatasetController {
|
||||
constructor(chart, datasetIndex) {
|
||||
super(chart, datasetIndex);
|
||||
|
||||
this.enableOptionSharing = true;
|
||||
this.innerRadius = undefined;
|
||||
this.outerRadius = undefined;
|
||||
this.offsetX = undefined;
|
||||
@ -129,7 +130,7 @@ export default class DoughnutController extends DatasetController {
|
||||
const innerRadius = animateScale ? 0 : me.innerRadius;
|
||||
const outerRadius = animateScale ? 0 : me.outerRadius;
|
||||
const firstOpts = me.resolveDataElementOptions(start, mode);
|
||||
const sharedOptions = me.getSharedOptions(mode, arcs[start], firstOpts);
|
||||
const sharedOptions = me.getSharedOptions(firstOpts);
|
||||
const includeOptions = me.includeOptions(mode, sharedOptions);
|
||||
let startAngle = opts.rotation;
|
||||
let i;
|
||||
@ -152,13 +153,13 @@ export default class DoughnutController extends DatasetController {
|
||||
innerRadius
|
||||
};
|
||||
if (includeOptions) {
|
||||
properties.options = me.resolveDataElementOptions(index, mode);
|
||||
properties.options = sharedOptions || me.resolveDataElementOptions(index, mode);
|
||||
}
|
||||
startAngle += circumference;
|
||||
|
||||
me.updateElement(arc, index, properties, mode);
|
||||
}
|
||||
me.updateSharedOptions(sharedOptions, mode);
|
||||
me.updateSharedOptions(sharedOptions, mode, firstOpts);
|
||||
}
|
||||
|
||||
calculateTotal() {
|
||||
|
@ -5,6 +5,11 @@ import {resolve} from '../helpers/helpers.options';
|
||||
|
||||
export default class LineController extends DatasetController {
|
||||
|
||||
initialize() {
|
||||
this.enableOptionSharing = true;
|
||||
super.initialize();
|
||||
}
|
||||
|
||||
update(mode) {
|
||||
const me = this;
|
||||
const meta = me._cachedMeta;
|
||||
@ -31,7 +36,7 @@ export default class LineController extends DatasetController {
|
||||
const reset = mode === 'reset';
|
||||
const {xScale, yScale, _stacked} = me._cachedMeta;
|
||||
const firstOpts = me.resolveDataElementOptions(start, mode);
|
||||
const sharedOptions = me.getSharedOptions(mode, points[start], firstOpts);
|
||||
const sharedOptions = me.getSharedOptions(firstOpts);
|
||||
const includeOptions = me.includeOptions(mode, sharedOptions);
|
||||
const spanGaps = valueOrDefault(me._config.spanGaps, me.chart.options.spanGaps);
|
||||
const maxGapLength = isNumber(spanGaps) ? spanGaps : Number.POSITIVE_INFINITY;
|
||||
@ -51,7 +56,7 @@ export default class LineController extends DatasetController {
|
||||
};
|
||||
|
||||
if (includeOptions) {
|
||||
properties.options = me.resolveDataElementOptions(index, mode);
|
||||
properties.options = sharedOptions || me.resolveDataElementOptions(index, mode);
|
||||
}
|
||||
|
||||
me.updateElement(point, index, properties, mode);
|
||||
@ -59,7 +64,7 @@ export default class LineController extends DatasetController {
|
||||
prevParsed = parsed;
|
||||
}
|
||||
|
||||
me.updateSharedOptions(sharedOptions, mode);
|
||||
me.updateSharedOptions(sharedOptions, mode, firstOpts);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -36,6 +36,7 @@ export default class Animation {
|
||||
this._prop = prop;
|
||||
this._from = from;
|
||||
this._to = to;
|
||||
this._promises = undefined;
|
||||
}
|
||||
|
||||
active() {
|
||||
@ -62,6 +63,7 @@ export default class Animation {
|
||||
// update current evaluated value, for smoother animations
|
||||
me.tick(Date.now());
|
||||
me._active = false;
|
||||
me._notify(false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -79,6 +81,7 @@ export default class Animation {
|
||||
|
||||
if (!me._active) {
|
||||
me._target[prop] = to;
|
||||
me._notify(true);
|
||||
return;
|
||||
}
|
||||
|
||||
@ -93,4 +96,19 @@ export default class Animation {
|
||||
|
||||
me._target[prop] = me._fn(from, to, factor);
|
||||
}
|
||||
|
||||
wait() {
|
||||
const promises = this._promises || (this._promises = []);
|
||||
return new Promise((res, rej) => {
|
||||
promises.push({res, rej});
|
||||
});
|
||||
}
|
||||
|
||||
_notify(resolved) {
|
||||
const method = resolved ? 'res' : 'rej';
|
||||
const promises = this._promises || [];
|
||||
for (let i = 0; i < promises.length; i++) {
|
||||
promises[i][method]();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -57,10 +57,10 @@ defaults.set('animation', {
|
||||
function copyOptions(target, values) {
|
||||
const oldOpts = target.options;
|
||||
const newOpts = values.options;
|
||||
if (!oldOpts || !newOpts || newOpts.$shared) {
|
||||
if (!oldOpts || !newOpts) {
|
||||
return;
|
||||
}
|
||||
if (oldOpts.$shared) {
|
||||
if (oldOpts.$shared && !newOpts.$shared) {
|
||||
target.options = Object.assign({}, oldOpts, newOpts, {$shared: false});
|
||||
} else {
|
||||
Object.assign(oldOpts, newOpts);
|
||||
@ -115,29 +115,25 @@ export default class Animations {
|
||||
|
||||
/**
|
||||
* Utility to handle animation of `options`.
|
||||
* This should not be called, when animating $shared options to $shared new options.
|
||||
* @private
|
||||
* @todo if new options are $shared, target.options should be replaced with those new shared
|
||||
* options after all animations have completed
|
||||
*/
|
||||
_animateOptions(target, values) {
|
||||
const newOptions = values.options;
|
||||
let animations = [];
|
||||
const options = resolveTargetOptions(target, newOptions);
|
||||
if (!options) {
|
||||
return [];
|
||||
}
|
||||
|
||||
if (!newOptions) {
|
||||
return animations;
|
||||
}
|
||||
let options = target.options;
|
||||
if (options) {
|
||||
if (options.$shared) {
|
||||
// If the current / old options are $shared, meaning other elements are
|
||||
// using the same options, we need to clone to become unique.
|
||||
target.options = options = Object.assign({}, options, {$shared: false, $animations: {}});
|
||||
}
|
||||
animations = this._createAnimations(options, newOptions);
|
||||
} else {
|
||||
target.options = newOptions;
|
||||
const animations = this._createAnimations(options, newOptions);
|
||||
if (newOptions.$shared && !options.$shared) {
|
||||
// Going from distinct options to shared options:
|
||||
// After all animations are done, assing the shared options object to the element
|
||||
// So any new updates to the shared options are observed
|
||||
awaitAll(target.$animations, newOptions).then(() => {
|
||||
target.options = newOptions;
|
||||
});
|
||||
}
|
||||
|
||||
return animations;
|
||||
}
|
||||
|
||||
@ -214,3 +210,32 @@ export default class Animations {
|
||||
}
|
||||
}
|
||||
|
||||
function awaitAll(animations, properties) {
|
||||
const running = [];
|
||||
const keys = Object.keys(properties);
|
||||
for (let i = 0; i < keys.length; i++) {
|
||||
const anim = animations[keys[i]];
|
||||
if (anim && anim.active()) {
|
||||
running.push(anim.wait());
|
||||
}
|
||||
}
|
||||
// @ts-ignore
|
||||
return Promise.all(running);
|
||||
}
|
||||
|
||||
function resolveTargetOptions(target, newOptions) {
|
||||
if (!newOptions) {
|
||||
return;
|
||||
}
|
||||
let options = target.options;
|
||||
if (!options) {
|
||||
target.options = newOptions;
|
||||
return;
|
||||
}
|
||||
if (options.$shared && !newOptions.$shared) {
|
||||
// Going from shared options to distinct one:
|
||||
// Create new options object containing the old shared values and start updating that.
|
||||
target.options = options = Object.assign({}, options, {$shared: false, $animations: {}});
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {requestAnimFrame} from '../helpers/helpers.extras';
|
||||
|
||||
/**
|
||||
* @typedef { import("./core.animation").default } Animation
|
||||
* @typedef { import("./core.controller").default } Chart
|
||||
*/
|
||||
|
||||
|
@ -608,11 +608,9 @@ class Chart {
|
||||
me._updateLayout();
|
||||
|
||||
// Can only reset the new controllers after the scales have been updated
|
||||
if (me.options.animation) {
|
||||
each(newControllers, (controller) => {
|
||||
controller.reset();
|
||||
});
|
||||
}
|
||||
each(newControllers, (controller) => {
|
||||
controller.reset();
|
||||
});
|
||||
|
||||
me._updateDatasets(mode);
|
||||
|
||||
|
@ -155,6 +155,10 @@ function optionKey(key, active) {
|
||||
return active ? 'hover' + _capitalize(key) : key;
|
||||
}
|
||||
|
||||
function isDirectUpdateMode(mode) {
|
||||
return mode === 'reset' || mode === 'none';
|
||||
}
|
||||
|
||||
export default class DatasetController {
|
||||
|
||||
/**
|
||||
@ -174,6 +178,8 @@ export default class DatasetController {
|
||||
this._parsing = false;
|
||||
this._data = undefined;
|
||||
this._objectData = undefined;
|
||||
this._sharedOptions = undefined;
|
||||
this.enableOptionSharing = false;
|
||||
|
||||
this.initialize();
|
||||
}
|
||||
@ -610,7 +616,7 @@ export default class DatasetController {
|
||||
me.configure();
|
||||
me._cachedAnimations = {};
|
||||
me._cachedDataOpts = {};
|
||||
me.update(mode);
|
||||
me.update(mode || 'default');
|
||||
meta._clip = toClip(valueOrDefault(me._config.clip, defaultClip(meta.xScale, meta.yScale, me.getMaxOverflow())));
|
||||
}
|
||||
|
||||
@ -684,6 +690,7 @@ export default class DatasetController {
|
||||
if (active) {
|
||||
me._addAutomaticHoverColors(index, options);
|
||||
}
|
||||
|
||||
return options;
|
||||
}
|
||||
|
||||
@ -718,9 +725,11 @@ export default class DatasetController {
|
||||
* @protected
|
||||
*/
|
||||
resolveDataElementOptions(index, mode) {
|
||||
mode = mode || 'default';
|
||||
const me = this;
|
||||
const active = mode === 'active';
|
||||
const cached = me._cachedDataOpts;
|
||||
const sharing = me.enableOptionSharing;
|
||||
if (cached[mode]) {
|
||||
return cached[mode];
|
||||
}
|
||||
@ -736,12 +745,12 @@ export default class DatasetController {
|
||||
if (info.cacheable) {
|
||||
// `$shared` indicades this set of options can be shared between multiple elements.
|
||||
// Sharing is used to reduce number of properties to change during animation.
|
||||
values.$shared = true;
|
||||
values.$shared = sharing;
|
||||
|
||||
// We cache options by `mode`, which can be 'active' for example. This enables us
|
||||
// to have the 'active' element options and 'default' options to switch between
|
||||
// when interacting.
|
||||
cached[mode] = values;
|
||||
cached[mode] = sharing ? Object.freeze(values) : values;
|
||||
}
|
||||
|
||||
return values;
|
||||
@ -809,17 +818,14 @@ export default class DatasetController {
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility for checking if the options are shared and should be animated separately.
|
||||
* Utility for getting the options object shared between elements
|
||||
* @protected
|
||||
*/
|
||||
getSharedOptions(mode, el, options) {
|
||||
if (!mode) {
|
||||
// store element option sharing status for usage in interactions
|
||||
this._sharedOptions = options && options.$shared;
|
||||
}
|
||||
if (mode !== 'reset' && options && options.$shared && el && el.options && el.options.$shared) {
|
||||
return {target: el.options, options};
|
||||
getSharedOptions(options) {
|
||||
if (!options.$shared) {
|
||||
return;
|
||||
}
|
||||
return this._sharedOptions || (this._sharedOptions = Object.assign({}, options));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -827,10 +833,7 @@ export default class DatasetController {
|
||||
* @protected
|
||||
*/
|
||||
includeOptions(mode, sharedOptions) {
|
||||
if (mode === 'hide' || mode === 'show') {
|
||||
return true;
|
||||
}
|
||||
return mode !== 'resize' && !sharedOptions;
|
||||
return !sharedOptions || isDirectUpdateMode(mode);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -838,7 +841,7 @@ export default class DatasetController {
|
||||
* @protected
|
||||
*/
|
||||
updateElement(element, index, properties, mode) {
|
||||
if (mode === 'reset' || mode === 'none') {
|
||||
if (isDirectUpdateMode(mode)) {
|
||||
Object.assign(element, properties);
|
||||
} else {
|
||||
this._resolveAnimations(index, mode).update(element, properties);
|
||||
@ -849,9 +852,9 @@ export default class DatasetController {
|
||||
* Utility to animate the shared options, that are potentially affecting multiple elements.
|
||||
* @protected
|
||||
*/
|
||||
updateSharedOptions(sharedOptions, mode) {
|
||||
updateSharedOptions(sharedOptions, mode, newOptions) {
|
||||
if (sharedOptions) {
|
||||
this._resolveAnimations(undefined, mode).update(sharedOptions.target, sharedOptions.options);
|
||||
this._resolveAnimations(undefined, mode).update({options: sharedOptions}, {options: newOptions});
|
||||
}
|
||||
}
|
||||
|
||||
@ -860,7 +863,8 @@ export default class DatasetController {
|
||||
*/
|
||||
_setStyle(element, index, mode, active) {
|
||||
element.active = active;
|
||||
this._resolveAnimations(index, mode, active).update(element, {options: this.getStyle(index, active)});
|
||||
const options = this.getStyle(index, active);
|
||||
this._resolveAnimations(index, mode, active).update(element, {options: this.getSharedOptions(options) || options});
|
||||
}
|
||||
|
||||
removeHoverStyle(element, datasetIndex, index) {
|
||||
|
@ -43,4 +43,40 @@ describe('Chart.animations', function() {
|
||||
options: undefined
|
||||
})).toBeUndefined();
|
||||
});
|
||||
|
||||
it('should assign shared options to target after animations complete', function(done) {
|
||||
const chart = {
|
||||
draw: function() {},
|
||||
options: {
|
||||
animation: {
|
||||
debug: false
|
||||
}
|
||||
}
|
||||
};
|
||||
const anims = new Chart.Animations(chart, {value: {duration: 100}, option: {duration: 200}});
|
||||
|
||||
const target = {
|
||||
value: 1,
|
||||
options: {
|
||||
option: 2
|
||||
}
|
||||
};
|
||||
const sharedOpts = {option: 10, $shared: true};
|
||||
|
||||
expect(anims.update(target, {
|
||||
options: sharedOpts
|
||||
})).toBeTrue();
|
||||
|
||||
expect(target.options !== sharedOpts).toBeTrue();
|
||||
|
||||
Chart.animator.start(chart);
|
||||
|
||||
setTimeout(function() {
|
||||
expect(Chart.animator.running(chart)).toBeFalse();
|
||||
expect(target.options === sharedOpts).toBeTrue();
|
||||
|
||||
Chart.animator.remove(chart);
|
||||
done();
|
||||
}, 300);
|
||||
});
|
||||
});
|
||||
|
5
types/core/index.d.ts
vendored
5
types/core/index.d.ts
vendored
@ -320,6 +320,7 @@ export class DatasetController<E extends Element = Element, DSE extends Element
|
||||
readonly chart: Chart;
|
||||
readonly index: number;
|
||||
readonly _cachedMeta: IChartMeta<E, DSE>;
|
||||
enableOptionSharing: boolean;
|
||||
|
||||
linkScales(): void;
|
||||
getAllParsedValues(scale: Scale): number[];
|
||||
@ -345,7 +346,7 @@ export class DatasetController<E extends Element = Element, DSE extends Element
|
||||
* Utility for checking if the options are shared and should be animated separately.
|
||||
* @protected
|
||||
*/
|
||||
protected getSharedOptions(mode: UpdateMode, el: E, options: any): undefined | { target: any; options: any };
|
||||
protected getSharedOptions(options: any): undefined | { any };
|
||||
/**
|
||||
* Utility for determining if `options` should be included in the updated properties
|
||||
* @protected
|
||||
@ -362,7 +363,7 @@ export class DatasetController<E extends Element = Element, DSE extends Element
|
||||
* @protected
|
||||
*/
|
||||
|
||||
protected updateSharedOptions(sharedOptions: any, mode: UpdateMode): void;
|
||||
protected updateSharedOptions(sharedOptions: any, mode: UpdateMode, newOptions: any): void;
|
||||
removeHoverStyle(element: E, datasetIndex: number, index: number): void;
|
||||
setHoverStyle(element: E, datasetIndex: number, index: number): void;
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user