逐步完成前后端服务器

This commit is contained in:
2025-09-09 15:00:30 +08:00
parent fcb09432e9
commit c7dfa1e9fc
2937 changed files with 1477567 additions and 0 deletions

View File

@ -0,0 +1,243 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* AUTO-GENERATED FILE. DO NOT MODIFY.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { isFunction, isObject, retrieve2 } from 'zrender/lib/core/util.js';
import { makeInner } from '../util/model.js';
// Stored properties for further transition.
export var transitionStore = makeInner();
/**
* Return null if animation is disabled.
*/
export function getAnimationConfig(animationType, animatableModel, dataIndex,
// Extra opts can override the option in animatable model.
extraOpts,
// TODO It's only for pictorial bar now.
extraDelayParams) {
var animationPayload;
// Check if there is global animation configuration from dataZoom/resize can override the config in option.
// If animation is enabled. Will use this animation config in payload.
// If animation is disabled. Just ignore it.
if (animatableModel && animatableModel.ecModel) {
var updatePayload = animatableModel.ecModel.getUpdatePayload();
animationPayload = updatePayload && updatePayload.animation;
}
var animationEnabled = animatableModel && animatableModel.isAnimationEnabled();
var isUpdate = animationType === 'update';
if (animationEnabled) {
var duration = void 0;
var easing = void 0;
var delay = void 0;
if (extraOpts) {
duration = retrieve2(extraOpts.duration, 200);
easing = retrieve2(extraOpts.easing, 'cubicOut');
delay = 0;
} else {
duration = animatableModel.getShallow(isUpdate ? 'animationDurationUpdate' : 'animationDuration');
easing = animatableModel.getShallow(isUpdate ? 'animationEasingUpdate' : 'animationEasing');
delay = animatableModel.getShallow(isUpdate ? 'animationDelayUpdate' : 'animationDelay');
}
// animation from payload has highest priority.
if (animationPayload) {
animationPayload.duration != null && (duration = animationPayload.duration);
animationPayload.easing != null && (easing = animationPayload.easing);
animationPayload.delay != null && (delay = animationPayload.delay);
}
if (isFunction(delay)) {
delay = delay(dataIndex, extraDelayParams);
}
if (isFunction(duration)) {
duration = duration(dataIndex);
}
var config = {
duration: duration || 0,
delay: delay,
easing: easing
};
return config;
} else {
return null;
}
}
function animateOrSetProps(animationType, el, props, animatableModel, dataIndex, cb, during) {
var isFrom = false;
var removeOpt;
if (isFunction(dataIndex)) {
during = cb;
cb = dataIndex;
dataIndex = null;
} else if (isObject(dataIndex)) {
cb = dataIndex.cb;
during = dataIndex.during;
isFrom = dataIndex.isFrom;
removeOpt = dataIndex.removeOpt;
dataIndex = dataIndex.dataIndex;
}
var isRemove = animationType === 'leave';
if (!isRemove) {
// Must stop the remove animation.
el.stopAnimation('leave');
}
var animationConfig = getAnimationConfig(animationType, animatableModel, dataIndex, isRemove ? removeOpt || {} : null, animatableModel && animatableModel.getAnimationDelayParams ? animatableModel.getAnimationDelayParams(el, dataIndex) : null);
if (animationConfig && animationConfig.duration > 0) {
var duration = animationConfig.duration;
var animationDelay = animationConfig.delay;
var animationEasing = animationConfig.easing;
var animateConfig = {
duration: duration,
delay: animationDelay || 0,
easing: animationEasing,
done: cb,
force: !!cb || !!during,
// Set to final state in update/init animation.
// So the post processing based on the path shape can be done correctly.
setToFinal: !isRemove,
scope: animationType,
during: during
};
isFrom ? el.animateFrom(props, animateConfig) : el.animateTo(props, animateConfig);
} else {
el.stopAnimation();
// If `isFrom`, the props is the "from" props.
!isFrom && el.attr(props);
// Call during at least once.
during && during(1);
cb && cb();
}
}
/**
* Update graphic element properties with or without animation according to the
* configuration in series.
*
* Caution: this method will stop previous animation.
* So do not use this method to one element twice before
* animation starts, unless you know what you are doing.
* @example
* graphic.updateProps(el, {
* position: [100, 100]
* }, seriesModel, dataIndex, function () { console.log('Animation done!'); });
* // Or
* graphic.updateProps(el, {
* position: [100, 100]
* }, seriesModel, function () { console.log('Animation done!'); });
*/
function updateProps(el, props,
// TODO: TYPE AnimatableModel
animatableModel, dataIndex, cb, during) {
animateOrSetProps('update', el, props, animatableModel, dataIndex, cb, during);
}
export { updateProps };
/**
* Init graphic element properties with or without animation according to the
* configuration in series.
*
* Caution: this method will stop previous animation.
* So do not use this method to one element twice before
* animation starts, unless you know what you are doing.
*/
export function initProps(el, props, animatableModel, dataIndex, cb, during) {
animateOrSetProps('enter', el, props, animatableModel, dataIndex, cb, during);
}
/**
* If element is removed.
* It can determine if element is having remove animation.
*/
export function isElementRemoved(el) {
if (!el.__zr) {
return true;
}
for (var i = 0; i < el.animators.length; i++) {
var animator = el.animators[i];
if (animator.scope === 'leave') {
return true;
}
}
return false;
}
/**
* Remove graphic element
*/
export function removeElement(el, props, animatableModel, dataIndex, cb, during) {
// Don't do remove animation twice.
if (isElementRemoved(el)) {
return;
}
animateOrSetProps('leave', el, props, animatableModel, dataIndex, cb, during);
}
function fadeOutDisplayable(el, animatableModel, dataIndex, done) {
el.removeTextContent();
el.removeTextGuideLine();
removeElement(el, {
style: {
opacity: 0
}
}, animatableModel, dataIndex, done);
}
export function removeElementWithFadeOut(el, animatableModel, dataIndex) {
function doRemove() {
el.parent && el.parent.remove(el);
}
// Hide label and labelLine first
// TODO Also use fade out animation?
if (!el.isGroup) {
fadeOutDisplayable(el, animatableModel, dataIndex, doRemove);
} else {
el.traverse(function (disp) {
if (!disp.isGroup) {
// Can invoke doRemove multiple times.
fadeOutDisplayable(disp, animatableModel, dataIndex, doRemove);
}
});
}
}
/**
* Save old style for style transition in universalTransition module.
* It's used when element will be reused in each render.
* For chart like map, heatmap, which will always create new element.
* We don't need to save this because universalTransition can get old style from the old element
*/
export function saveOldStyle(el) {
transitionStore(el).oldStyle = el.style;
}
export function getOldStyle(el) {
return transitionStore(el).oldStyle;
}

View File

@ -0,0 +1,143 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* AUTO-GENERATED FILE. DO NOT MODIFY.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { keys, filter, each, isArray, indexOf } from 'zrender/lib/core/util.js';
import { ELEMENT_ANIMATABLE_PROPS } from './customGraphicTransition.js';
import { getAnimationConfig } from './basicTransition.js';
import { warn } from '../util/log.js';
import { makeInner } from '../util/model.js';
var getStateToRestore = makeInner();
var KEYFRAME_EXCLUDE_KEYS = ['percent', 'easing', 'shape', 'style', 'extra'];
/**
* Stop previous keyframe animation and restore the attributes.
* Avoid new keyframe animation starts with wrong internal state when the percent: 0 is not set.
*/
export function stopPreviousKeyframeAnimationAndRestore(el) {
// Stop previous keyframe animation.
el.stopAnimation('keyframe');
// Restore
el.attr(getStateToRestore(el));
}
export function applyKeyframeAnimation(el, animationOpts, animatableModel) {
if (!animatableModel.isAnimationEnabled() || !animationOpts) {
return;
}
if (isArray(animationOpts)) {
each(animationOpts, function (singleAnimationOpts) {
applyKeyframeAnimation(el, singleAnimationOpts, animatableModel);
});
return;
}
var keyframes = animationOpts.keyframes;
var duration = animationOpts.duration;
if (animatableModel && duration == null) {
// Default to use duration of config.
// NOTE: animation config from payload will be ignored because they are mainly for transitions.
var config = getAnimationConfig('enter', animatableModel, 0);
duration = config && config.duration;
}
if (!keyframes || !duration) {
return;
}
var stateToRestore = getStateToRestore(el);
each(ELEMENT_ANIMATABLE_PROPS, function (targetPropName) {
if (targetPropName && !el[targetPropName]) {
return;
}
var animator;
var endFrameIsSet = false;
// Sort keyframes by percent.
keyframes.sort(function (a, b) {
return a.percent - b.percent;
});
each(keyframes, function (kf) {
// Stop current animation.
var animators = el.animators;
var kfValues = targetPropName ? kf[targetPropName] : kf;
if (process.env.NODE_ENV !== 'production') {
if (kf.percent >= 1) {
endFrameIsSet = true;
}
}
if (!kfValues) {
return;
}
var propKeys = keys(kfValues);
if (!targetPropName) {
// PENDING performance?
propKeys = filter(propKeys, function (key) {
return indexOf(KEYFRAME_EXCLUDE_KEYS, key) < 0;
});
}
if (!propKeys.length) {
return;
}
if (!animator) {
animator = el.animate(targetPropName, animationOpts.loop, true);
animator.scope = 'keyframe';
}
for (var i = 0; i < animators.length; i++) {
// Stop all other animation that is not keyframe.
if (animators[i] !== animator && animators[i].targetName === animator.targetName) {
animators[i].stopTracks(propKeys);
}
}
targetPropName && (stateToRestore[targetPropName] = stateToRestore[targetPropName] || {});
var savedTarget = targetPropName ? stateToRestore[targetPropName] : stateToRestore;
each(propKeys, function (key) {
// Save original value.
savedTarget[key] = ((targetPropName ? el[targetPropName] : el) || {})[key];
});
animator.whenWithKeys(duration * kf.percent, kfValues, propKeys, kf.easing);
});
if (!animator) {
return;
}
if (process.env.NODE_ENV !== 'production') {
if (!endFrameIsSet) {
warn('End frame with percent: 1 is missing in the keyframeAnimation.', true);
}
}
animator.delay(animationOpts.delay || 0).duration(duration).start(animationOpts.easing);
});
}

View File

@ -0,0 +1,480 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* AUTO-GENERATED FILE. DO NOT MODIFY.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { makeInner, normalizeToArray } from '../util/model.js';
import { assert, bind, each, eqNaN, extend, hasOwn, indexOf, isArrayLike, keys, reduce } from 'zrender/lib/core/util.js';
import { cloneValue } from 'zrender/lib/animation/Animator.js';
import Displayable from 'zrender/lib/graphic/Displayable.js';
import { getAnimationConfig } from './basicTransition.js';
import { Path } from '../util/graphic.js';
import { warn } from '../util/log.js';
import { TRANSFORMABLE_PROPS } from 'zrender/lib/core/Transformable.js';
var LEGACY_TRANSFORM_PROPS_MAP = {
position: ['x', 'y'],
scale: ['scaleX', 'scaleY'],
origin: ['originX', 'originY']
};
var LEGACY_TRANSFORM_PROPS = keys(LEGACY_TRANSFORM_PROPS_MAP);
var TRANSFORM_PROPS_MAP = reduce(TRANSFORMABLE_PROPS, function (obj, key) {
obj[key] = 1;
return obj;
}, {});
var transformPropNamesStr = TRANSFORMABLE_PROPS.join(', ');
// '' means root
export var ELEMENT_ANIMATABLE_PROPS = ['', 'style', 'shape', 'extra'];
;
var transitionInnerStore = makeInner();
;
function getElementAnimationConfig(animationType, el, elOption, parentModel, dataIndex) {
var animationProp = animationType + "Animation";
var config = getAnimationConfig(animationType, parentModel, dataIndex) || {};
var userDuring = transitionInnerStore(el).userDuring;
// Only set when duration is > 0 and it's need to be animated.
if (config.duration > 0) {
// For simplicity, if during not specified, the previous during will not work any more.
config.during = userDuring ? bind(duringCall, {
el: el,
userDuring: userDuring
}) : null;
config.setToFinal = true;
config.scope = animationType;
}
extend(config, elOption[animationProp]);
return config;
}
export function applyUpdateTransition(el, elOption, animatableModel, opts) {
opts = opts || {};
var dataIndex = opts.dataIndex,
isInit = opts.isInit,
clearStyle = opts.clearStyle;
var hasAnimation = animatableModel.isAnimationEnabled();
// Save the meta info for further morphing. Like apply on the sub morphing elements.
var store = transitionInnerStore(el);
var styleOpt = elOption.style;
store.userDuring = elOption.during;
var transFromProps = {};
var propsToSet = {};
prepareTransformAllPropsFinal(el, elOption, propsToSet);
prepareShapeOrExtraAllPropsFinal('shape', elOption, propsToSet);
prepareShapeOrExtraAllPropsFinal('extra', elOption, propsToSet);
if (!isInit && hasAnimation) {
prepareTransformTransitionFrom(el, elOption, transFromProps);
prepareShapeOrExtraTransitionFrom('shape', el, elOption, transFromProps);
prepareShapeOrExtraTransitionFrom('extra', el, elOption, transFromProps);
prepareStyleTransitionFrom(el, elOption, styleOpt, transFromProps);
}
propsToSet.style = styleOpt;
applyPropsDirectly(el, propsToSet, clearStyle);
applyMiscProps(el, elOption);
if (hasAnimation) {
if (isInit) {
var enterFromProps_1 = {};
each(ELEMENT_ANIMATABLE_PROPS, function (propName) {
var prop = propName ? elOption[propName] : elOption;
if (prop && prop.enterFrom) {
if (propName) {
enterFromProps_1[propName] = enterFromProps_1[propName] || {};
}
extend(propName ? enterFromProps_1[propName] : enterFromProps_1, prop.enterFrom);
}
});
var config = getElementAnimationConfig('enter', el, elOption, animatableModel, dataIndex);
if (config.duration > 0) {
el.animateFrom(enterFromProps_1, config);
}
} else {
applyPropsTransition(el, elOption, dataIndex || 0, animatableModel, transFromProps);
}
}
// Store leave to be used in leave transition.
updateLeaveTo(el, elOption);
styleOpt ? el.dirty() : el.markRedraw();
}
export function updateLeaveTo(el, elOption) {
// Try merge to previous set leaveTo
var leaveToProps = transitionInnerStore(el).leaveToProps;
for (var i = 0; i < ELEMENT_ANIMATABLE_PROPS.length; i++) {
var propName = ELEMENT_ANIMATABLE_PROPS[i];
var prop = propName ? elOption[propName] : elOption;
if (prop && prop.leaveTo) {
if (!leaveToProps) {
leaveToProps = transitionInnerStore(el).leaveToProps = {};
}
if (propName) {
leaveToProps[propName] = leaveToProps[propName] || {};
}
extend(propName ? leaveToProps[propName] : leaveToProps, prop.leaveTo);
}
}
}
export function applyLeaveTransition(el, elOption, animatableModel, onRemove) {
if (el) {
var parent_1 = el.parent;
var leaveToProps = transitionInnerStore(el).leaveToProps;
if (leaveToProps) {
// TODO TODO use leave after leaveAnimation in series is introduced
// TODO Data index?
var config = getElementAnimationConfig('update', el, elOption, animatableModel, 0);
config.done = function () {
parent_1.remove(el);
onRemove && onRemove();
};
el.animateTo(leaveToProps, config);
} else {
parent_1.remove(el);
onRemove && onRemove();
}
}
}
export function isTransitionAll(transition) {
return transition === 'all';
}
function applyPropsDirectly(el,
// Can be null/undefined
allPropsFinal, clearStyle) {
var styleOpt = allPropsFinal.style;
if (!el.isGroup && styleOpt) {
if (clearStyle) {
el.useStyle({});
// When style object changed, how to trade the existing animation?
// It is probably complicated and not needed to cover all the cases.
// But still need consider the case:
// (1) When using init animation on `style.opacity`, and before the animation
// ended users triggers an update by mousewhel. At that time the init
// animation should better be continued rather than terminated.
// So after `useStyle` called, we should change the animation target manually
// to continue the effect of the init animation.
// (2) PENDING: If the previous animation targeted at a `val1`, and currently we need
// to update the value to `val2` and no animation declared, should be terminate
// the previous animation or just modify the target of the animation?
// Therotically That will happen not only on `style` but also on `shape` and
// `transfrom` props. But we haven't handle this case at present yet.
// (3) PENDING: Is it proper to visit `animators` and `targetName`?
var animators = el.animators;
for (var i = 0; i < animators.length; i++) {
var animator = animators[i];
// targetName is the "topKey".
if (animator.targetName === 'style') {
animator.changeTarget(el.style);
}
}
}
el.setStyle(styleOpt);
}
if (allPropsFinal) {
// Not set style here.
allPropsFinal.style = null;
// Set el to the final state firstly.
allPropsFinal && el.attr(allPropsFinal);
allPropsFinal.style = styleOpt;
}
}
function applyPropsTransition(el, elOption, dataIndex, model,
// Can be null/undefined
transFromProps) {
if (transFromProps) {
var config = getElementAnimationConfig('update', el, elOption, model, dataIndex);
if (config.duration > 0) {
el.animateFrom(transFromProps, config);
}
}
}
function applyMiscProps(el, elOption) {
// Merge by default.
hasOwn(elOption, 'silent') && (el.silent = elOption.silent);
hasOwn(elOption, 'ignore') && (el.ignore = elOption.ignore);
if (el instanceof Displayable) {
hasOwn(elOption, 'invisible') && (el.invisible = elOption.invisible);
}
if (el instanceof Path) {
hasOwn(elOption, 'autoBatch') && (el.autoBatch = elOption.autoBatch);
}
}
// Use it to avoid it be exposed to user.
var tmpDuringScope = {};
var transitionDuringAPI = {
// Usually other props do not need to be changed in animation during.
setTransform: function (key, val) {
if (process.env.NODE_ENV !== 'production') {
assert(hasOwn(TRANSFORM_PROPS_MAP, key), 'Only ' + transformPropNamesStr + ' available in `setTransform`.');
}
tmpDuringScope.el[key] = val;
return this;
},
getTransform: function (key) {
if (process.env.NODE_ENV !== 'production') {
assert(hasOwn(TRANSFORM_PROPS_MAP, key), 'Only ' + transformPropNamesStr + ' available in `getTransform`.');
}
return tmpDuringScope.el[key];
},
setShape: function (key, val) {
if (process.env.NODE_ENV !== 'production') {
assertNotReserved(key);
}
var el = tmpDuringScope.el;
var shape = el.shape || (el.shape = {});
shape[key] = val;
el.dirtyShape && el.dirtyShape();
return this;
},
getShape: function (key) {
if (process.env.NODE_ENV !== 'production') {
assertNotReserved(key);
}
var shape = tmpDuringScope.el.shape;
if (shape) {
return shape[key];
}
},
setStyle: function (key, val) {
if (process.env.NODE_ENV !== 'production') {
assertNotReserved(key);
}
var el = tmpDuringScope.el;
var style = el.style;
if (style) {
if (process.env.NODE_ENV !== 'production') {
if (eqNaN(val)) {
warn('style.' + key + ' must not be assigned with NaN.');
}
}
style[key] = val;
el.dirtyStyle && el.dirtyStyle();
}
return this;
},
getStyle: function (key) {
if (process.env.NODE_ENV !== 'production') {
assertNotReserved(key);
}
var style = tmpDuringScope.el.style;
if (style) {
return style[key];
}
},
setExtra: function (key, val) {
if (process.env.NODE_ENV !== 'production') {
assertNotReserved(key);
}
var extra = tmpDuringScope.el.extra || (tmpDuringScope.el.extra = {});
extra[key] = val;
return this;
},
getExtra: function (key) {
if (process.env.NODE_ENV !== 'production') {
assertNotReserved(key);
}
var extra = tmpDuringScope.el.extra;
if (extra) {
return extra[key];
}
}
};
function assertNotReserved(key) {
if (process.env.NODE_ENV !== 'production') {
if (key === 'transition' || key === 'enterFrom' || key === 'leaveTo') {
throw new Error('key must not be "' + key + '"');
}
}
}
function duringCall() {
// Do not provide "percent" until some requirements come.
// Because consider thies case:
// enterFrom: {x: 100, y: 30}, transition: 'x'.
// And enter duration is different from update duration.
// Thus it might be confused about the meaning of "percent" in during callback.
var scope = this;
var el = scope.el;
if (!el) {
return;
}
// If el is remove from zr by reason like legend, during still need to called,
// because el will be added back to zr and the prop value should not be incorrect.
var latestUserDuring = transitionInnerStore(el).userDuring;
var scopeUserDuring = scope.userDuring;
// Ensured a during is only called once in each animation frame.
// If a during is called multiple times in one frame, maybe some users' calculation logic
// might be wrong (not sure whether this usage exists).
// The case of a during might be called twice can be: by default there is a animator for
// 'x', 'y' when init. Before the init animation finished, call `setOption` to start
// another animators for 'style'/'shape'/'extra'.
if (latestUserDuring !== scopeUserDuring) {
// release
scope.el = scope.userDuring = null;
return;
}
tmpDuringScope.el = el;
// Give no `this` to user in "during" calling.
scopeUserDuring(transitionDuringAPI);
// FIXME: if in future meet the case that some prop will be both modified in `during` and `state`,
// consider the issue that the prop might be incorrect when return to "normal" state.
}
function prepareShapeOrExtraTransitionFrom(mainAttr, fromEl, elOption, transFromProps) {
var attrOpt = elOption[mainAttr];
if (!attrOpt) {
return;
}
var elPropsInAttr = fromEl[mainAttr];
var transFromPropsInAttr;
if (elPropsInAttr) {
var transition = elOption.transition;
var attrTransition = attrOpt.transition;
if (attrTransition) {
!transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {});
if (isTransitionAll(attrTransition)) {
extend(transFromPropsInAttr, elPropsInAttr);
} else {
var transitionKeys = normalizeToArray(attrTransition);
for (var i = 0; i < transitionKeys.length; i++) {
var key = transitionKeys[i];
var elVal = elPropsInAttr[key];
transFromPropsInAttr[key] = elVal;
}
}
} else if (isTransitionAll(transition) || indexOf(transition, mainAttr) >= 0) {
!transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {});
var elPropsInAttrKeys = keys(elPropsInAttr);
for (var i = 0; i < elPropsInAttrKeys.length; i++) {
var key = elPropsInAttrKeys[i];
var elVal = elPropsInAttr[key];
if (isNonStyleTransitionEnabled(attrOpt[key], elVal)) {
transFromPropsInAttr[key] = elVal;
}
}
}
}
}
function prepareShapeOrExtraAllPropsFinal(mainAttr, elOption, allProps) {
var attrOpt = elOption[mainAttr];
if (!attrOpt) {
return;
}
var allPropsInAttr = allProps[mainAttr] = {};
var keysInAttr = keys(attrOpt);
for (var i = 0; i < keysInAttr.length; i++) {
var key = keysInAttr[i];
// To avoid share one object with different element, and
// to avoid user modify the object inexpectedly, have to clone.
allPropsInAttr[key] = cloneValue(attrOpt[key]);
}
}
function prepareTransformTransitionFrom(el, elOption, transFromProps) {
var transition = elOption.transition;
var transitionKeys = isTransitionAll(transition) ? TRANSFORMABLE_PROPS : normalizeToArray(transition || []);
for (var i = 0; i < transitionKeys.length; i++) {
var key = transitionKeys[i];
if (key === 'style' || key === 'shape' || key === 'extra') {
continue;
}
var elVal = el[key];
if (process.env.NODE_ENV !== 'production') {
checkTransformPropRefer(key, 'el.transition');
}
// Do not clone, animator will perform that clone.
transFromProps[key] = elVal;
}
}
function prepareTransformAllPropsFinal(el, elOption, allProps) {
for (var i = 0; i < LEGACY_TRANSFORM_PROPS.length; i++) {
var legacyName = LEGACY_TRANSFORM_PROPS[i];
var xyName = LEGACY_TRANSFORM_PROPS_MAP[legacyName];
var legacyArr = elOption[legacyName];
if (legacyArr) {
allProps[xyName[0]] = legacyArr[0];
allProps[xyName[1]] = legacyArr[1];
}
}
for (var i = 0; i < TRANSFORMABLE_PROPS.length; i++) {
var key = TRANSFORMABLE_PROPS[i];
if (elOption[key] != null) {
allProps[key] = elOption[key];
}
}
}
function prepareStyleTransitionFrom(fromEl, elOption, styleOpt, transFromProps) {
if (!styleOpt) {
return;
}
var fromElStyle = fromEl.style;
var transFromStyleProps;
if (fromElStyle) {
var styleTransition = styleOpt.transition;
var elTransition = elOption.transition;
if (styleTransition && !isTransitionAll(styleTransition)) {
var transitionKeys = normalizeToArray(styleTransition);
!transFromStyleProps && (transFromStyleProps = transFromProps.style = {});
for (var i = 0; i < transitionKeys.length; i++) {
var key = transitionKeys[i];
var elVal = fromElStyle[key];
// Do not clone, see `checkNonStyleTansitionRefer`.
transFromStyleProps[key] = elVal;
}
} else if (fromEl.getAnimationStyleProps && (isTransitionAll(elTransition) || isTransitionAll(styleTransition) || indexOf(elTransition, 'style') >= 0)) {
var animationProps = fromEl.getAnimationStyleProps();
var animationStyleProps = animationProps ? animationProps.style : null;
if (animationStyleProps) {
!transFromStyleProps && (transFromStyleProps = transFromProps.style = {});
var styleKeys = keys(styleOpt);
for (var i = 0; i < styleKeys.length; i++) {
var key = styleKeys[i];
if (animationStyleProps[key]) {
var elVal = fromElStyle[key];
transFromStyleProps[key] = elVal;
}
}
}
}
}
}
function isNonStyleTransitionEnabled(optVal, elVal) {
// The same as `checkNonStyleTansitionRefer`.
return !isArrayLike(optVal) ? optVal != null && isFinite(optVal) : optVal !== elVal;
}
var checkTransformPropRefer;
if (process.env.NODE_ENV !== 'production') {
checkTransformPropRefer = function (key, usedIn) {
if (!hasOwn(TRANSFORM_PROPS_MAP, key)) {
warn('Prop `' + key + '` is not a permitted in `' + usedIn + '`. ' + 'Only `' + keys(TRANSFORM_PROPS_MAP).join('`, `') + '` are permitted.');
}
};
}

View File

@ -0,0 +1,203 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* AUTO-GENERATED FILE. DO NOT MODIFY.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
import { separateMorph, combineMorph, morphPath, isCombineMorphing } from 'zrender/lib/tool/morphPath.js';
import { Path } from '../util/graphic.js';
import { defaults, isArray } from 'zrender/lib/core/util.js';
import { getAnimationConfig } from './basicTransition.js';
import { clonePath } from 'zrender/lib/tool/path.js';
function isMultiple(elements) {
return isArray(elements[0]);
}
function prepareMorphBatches(one, many) {
var batches = [];
var batchCount = one.length;
for (var i = 0; i < batchCount; i++) {
batches.push({
one: one[i],
many: []
});
}
for (var i = 0; i < many.length; i++) {
var len = many[i].length;
var k = void 0;
for (k = 0; k < len; k++) {
batches[k % batchCount].many.push(many[i][k]);
}
}
var off = 0;
// If one has more paths than each one of many. average them.
for (var i = batchCount - 1; i >= 0; i--) {
if (!batches[i].many.length) {
var moveFrom = batches[off].many;
if (moveFrom.length <= 1) {
// Not enough
// Start from the first one.
if (off) {
off = 0;
} else {
return batches;
}
}
var len = moveFrom.length;
var mid = Math.ceil(len / 2);
batches[i].many = moveFrom.slice(mid, len);
batches[off].many = moveFrom.slice(0, mid);
off++;
}
}
return batches;
}
var pathDividers = {
clone: function (params) {
var ret = [];
// Fitting the alpha
var approxOpacity = 1 - Math.pow(1 - params.path.style.opacity, 1 / params.count);
for (var i = 0; i < params.count; i++) {
var cloned = clonePath(params.path);
cloned.setStyle('opacity', approxOpacity);
ret.push(cloned);
}
return ret;
},
// Use the default divider
split: null
};
export function applyMorphAnimation(from, to, divideShape, seriesModel, dataIndex, animateOtherProps) {
if (!from.length || !to.length) {
return;
}
var updateAnimationCfg = getAnimationConfig('update', seriesModel, dataIndex);
if (!(updateAnimationCfg && updateAnimationCfg.duration > 0)) {
return;
}
var animationDelay = seriesModel.getModel('universalTransition').get('delay');
var animationCfg = Object.assign({
// Need to setToFinal so the further calculation based on the style can be correct.
// Like emphasis color.
setToFinal: true
}, updateAnimationCfg);
var many;
var one;
if (isMultiple(from)) {
// manyToOne
many = from;
one = to;
}
if (isMultiple(to)) {
// oneToMany
many = to;
one = from;
}
function morphOneBatch(batch, fromIsMany, animateIndex, animateCount, forceManyOne) {
var batchMany = batch.many;
var batchOne = batch.one;
if (batchMany.length === 1 && !forceManyOne) {
// Is one to one
var batchFrom = fromIsMany ? batchMany[0] : batchOne;
var batchTo = fromIsMany ? batchOne : batchMany[0];
if (isCombineMorphing(batchFrom)) {
// Keep doing combine animation.
morphOneBatch({
many: [batchFrom],
one: batchTo
}, true, animateIndex, animateCount, true);
} else {
var individualAnimationCfg = animationDelay ? defaults({
delay: animationDelay(animateIndex, animateCount)
}, animationCfg) : animationCfg;
morphPath(batchFrom, batchTo, individualAnimationCfg);
animateOtherProps(batchFrom, batchTo, batchFrom, batchTo, individualAnimationCfg);
}
} else {
var separateAnimationCfg = defaults({
dividePath: pathDividers[divideShape],
individualDelay: animationDelay && function (idx, count, fromPath, toPath) {
return animationDelay(idx + animateIndex, animateCount);
}
}, animationCfg);
var _a = fromIsMany ? combineMorph(batchMany, batchOne, separateAnimationCfg) : separateMorph(batchOne, batchMany, separateAnimationCfg),
fromIndividuals = _a.fromIndividuals,
toIndividuals = _a.toIndividuals;
var count = fromIndividuals.length;
for (var k = 0; k < count; k++) {
var individualAnimationCfg = animationDelay ? defaults({
delay: animationDelay(k, count)
}, animationCfg) : animationCfg;
animateOtherProps(fromIndividuals[k], toIndividuals[k], fromIsMany ? batchMany[k] : batch.one, fromIsMany ? batch.one : batchMany[k], individualAnimationCfg);
}
}
}
var fromIsMany = many ? many === from
// Is one to one. If the path number not match. also needs do merge and separate morphing.
: from.length > to.length;
var morphBatches = many ? prepareMorphBatches(one, many) : prepareMorphBatches(fromIsMany ? to : from, [fromIsMany ? from : to]);
var animateCount = 0;
for (var i = 0; i < morphBatches.length; i++) {
animateCount += morphBatches[i].many.length;
}
var animateIndex = 0;
for (var i = 0; i < morphBatches.length; i++) {
morphOneBatch(morphBatches[i], fromIsMany, animateIndex, animateCount);
animateIndex += morphBatches[i].many.length;
}
}
export function getPathList(elements) {
if (!elements) {
return [];
}
if (isArray(elements)) {
var pathList_1 = [];
for (var i = 0; i < elements.length; i++) {
pathList_1.push(getPathList(elements[i]));
}
return pathList_1;
}
var pathList = [];
elements.traverse(function (el) {
if (el instanceof Path && !el.disableMorphing && !el.invisible && !el.ignore) {
pathList.push(el);
}
});
return pathList;
}

View File

@ -0,0 +1,649 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
/**
* AUTO-GENERATED FILE. DO NOT MODIFY.
*/
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
// Universal transitions that can animate between any shapes(series) and any properties in any amounts.
import { SERIES_UNIVERSAL_TRANSITION_PROP } from '../model/Series.js';
import { createHashMap, each, map, filter, isArray, extend } from 'zrender/lib/core/util.js';
import { applyMorphAnimation, getPathList } from './morphTransitionHelper.js';
import Path from 'zrender/lib/graphic/Path.js';
import { initProps } from '../util/graphic.js';
import DataDiffer from '../data/DataDiffer.js';
import { makeInner, normalizeToArray } from '../util/model.js';
import { warn } from '../util/log.js';
import { getAnimationConfig, getOldStyle } from './basicTransition.js';
import Displayable from 'zrender/lib/graphic/Displayable.js';
var DATA_COUNT_THRESHOLD = 1e4;
var TRANSITION_NONE = 0;
var TRANSITION_P2C = 1;
var TRANSITION_C2P = 2;
;
var getUniversalTransitionGlobalStore = makeInner();
function getDimension(data, visualDimension) {
var dimensions = data.dimensions;
for (var i = 0; i < dimensions.length; i++) {
var dimInfo = data.getDimensionInfo(dimensions[i]);
if (dimInfo && dimInfo.otherDims[visualDimension] === 0) {
return dimensions[i];
}
}
}
// get value by dimension. (only get value of itemGroupId or childGroupId, so convert it to string)
function getValueByDimension(data, dataIndex, dimension) {
var dimInfo = data.getDimensionInfo(dimension);
var dimOrdinalMeta = dimInfo && dimInfo.ordinalMeta;
if (dimInfo) {
var value = data.get(dimInfo.name, dataIndex);
if (dimOrdinalMeta) {
return dimOrdinalMeta.categories[value] || value + '';
}
return value + '';
}
}
function getGroupId(data, dataIndex, dataGroupId, isChild) {
// try to get groupId from encode
var visualDimension = isChild ? 'itemChildGroupId' : 'itemGroupId';
var groupIdDim = getDimension(data, visualDimension);
if (groupIdDim) {
var groupId = getValueByDimension(data, dataIndex, groupIdDim);
return groupId;
}
// try to get groupId from raw data item
var rawDataItem = data.getRawDataItem(dataIndex);
var property = isChild ? 'childGroupId' : 'groupId';
if (rawDataItem && rawDataItem[property]) {
return rawDataItem[property] + '';
}
// fallback
if (isChild) {
return;
}
// try to use series.dataGroupId as groupId, otherwise use dataItem's id as groupId
return dataGroupId || data.getId(dataIndex);
}
// flatten all data items from different serieses into one arrary
function flattenDataDiffItems(list) {
var items = [];
each(list, function (seriesInfo) {
var data = seriesInfo.data;
var dataGroupId = seriesInfo.dataGroupId;
if (data.count() > DATA_COUNT_THRESHOLD) {
if (process.env.NODE_ENV !== 'production') {
warn('Universal transition is disabled on large data > 10k.');
}
return;
}
var indices = data.getIndices();
for (var dataIndex = 0; dataIndex < indices.length; dataIndex++) {
items.push({
data: data,
groupId: getGroupId(data, dataIndex, dataGroupId, false),
childGroupId: getGroupId(data, dataIndex, dataGroupId, true),
divide: seriesInfo.divide,
dataIndex: dataIndex
});
}
});
return items;
}
function fadeInElement(newEl, newSeries, newIndex) {
newEl.traverse(function (el) {
if (el instanceof Path) {
// TODO use fade in animation for target element.
initProps(el, {
style: {
opacity: 0
}
}, newSeries, {
dataIndex: newIndex,
isFrom: true
});
}
});
}
function removeEl(el) {
if (el.parent) {
// Bake parent transform to element.
// So it can still have proper transform to transition after it's removed.
var computedTransform = el.getComputedTransform();
el.setLocalTransform(computedTransform);
el.parent.remove(el);
}
}
function stopAnimation(el) {
el.stopAnimation();
if (el.isGroup) {
el.traverse(function (child) {
child.stopAnimation();
});
}
}
function animateElementStyles(el, dataIndex, seriesModel) {
var animationConfig = getAnimationConfig('update', seriesModel, dataIndex);
animationConfig && el.traverse(function (child) {
if (child instanceof Displayable) {
var oldStyle = getOldStyle(child);
if (oldStyle) {
child.animateFrom({
style: oldStyle
}, animationConfig);
}
}
});
}
function isAllIdSame(oldDiffItems, newDiffItems) {
var len = oldDiffItems.length;
if (len !== newDiffItems.length) {
return false;
}
for (var i = 0; i < len; i++) {
var oldItem = oldDiffItems[i];
var newItem = newDiffItems[i];
if (oldItem.data.getId(oldItem.dataIndex) !== newItem.data.getId(newItem.dataIndex)) {
return false;
}
}
return true;
}
function transitionBetween(oldList, newList, api) {
var oldDiffItems = flattenDataDiffItems(oldList);
var newDiffItems = flattenDataDiffItems(newList);
function updateMorphingPathProps(from, to, rawFrom, rawTo, animationCfg) {
if (rawFrom || from) {
to.animateFrom({
style: rawFrom && rawFrom !== from
// dividingMethod like clone may override the style(opacity)
// So extend it to raw style.
? extend(extend({}, rawFrom.style), from.style) : from.style
}, animationCfg);
}
}
var hasMorphAnimation = false;
/**
* With groupId and childGroupId, we can build parent-child relationships between dataItems.
* However, we should mind the parent-child "direction" between old and new options.
*
* For example, suppose we have two dataItems from two series.data:
*
* dataA: [ dataB: [
* { {
* value: 5, value: 3,
* groupId: 'creatures', groupId: 'animals',
* childGroupId: 'animals' childGroupId: 'dogs'
* }, },
* ... ...
* ] ]
*
* where dataA is belong to optionA and dataB is belong to optionB.
*
* When we `setOption(optionB)` from optionA, we choose childGroupId of dataItemA and groupId of
* dataItemB as keys so the two keys are matched (both are 'animals'), then universalTransition
* will work. This derection is "parent -> child".
*
* If we `setOption(optionA)` from optionB, we also choose groupId of dataItemB and childGroupId
* of dataItemA as keys and universalTransition will work. This derection is "child -> parent".
*
* If there is no childGroupId specified, which means no multiLevelDrillDown/Up is needed and no
* parent-child relationship exists. This direction is "none".
*
* So we need to know whether to use groupId or childGroupId as the key when we call the keyGetter
* functions. Thus, we need to decide the direction first.
*
* The rule is:
*
* if (all childGroupIds in oldDiffItems and all groupIds in newDiffItems have common value) {
* direction = 'parent -> child';
* } else if (all groupIds in oldDiffItems and all childGroupIds in newDiffItems have common value) {
* direction = 'child -> parent';
* } else {
* direction = 'none';
* }
*/
var direction = TRANSITION_NONE;
// find all groupIds and childGroupIds from oldDiffItems
var oldGroupIds = createHashMap();
var oldChildGroupIds = createHashMap();
oldDiffItems.forEach(function (item) {
item.groupId && oldGroupIds.set(item.groupId, true);
item.childGroupId && oldChildGroupIds.set(item.childGroupId, true);
});
// traverse newDiffItems and decide the direction according to the rule
for (var i = 0; i < newDiffItems.length; i++) {
var newGroupId = newDiffItems[i].groupId;
if (oldChildGroupIds.get(newGroupId)) {
direction = TRANSITION_P2C;
break;
}
var newChildGroupId = newDiffItems[i].childGroupId;
if (newChildGroupId && oldGroupIds.get(newChildGroupId)) {
direction = TRANSITION_C2P;
break;
}
}
function createKeyGetter(isOld, onlyGetId) {
return function (diffItem) {
var data = diffItem.data;
var dataIndex = diffItem.dataIndex;
// TODO if specified dim
if (onlyGetId) {
return data.getId(dataIndex);
}
if (isOld) {
return direction === TRANSITION_P2C ? diffItem.childGroupId : diffItem.groupId;
} else {
return direction === TRANSITION_C2P ? diffItem.childGroupId : diffItem.groupId;
}
};
}
// Use id if it's very likely to be an one to one animation
// It's more robust than groupId
// TODO Check if key dimension is specified.
var useId = isAllIdSame(oldDiffItems, newDiffItems);
var isElementStillInChart = {};
if (!useId) {
// We may have different diff strategy with basicTransition if we use other dimension as key.
// If so, we can't simply check if oldEl is same with newEl. We need a map to check if oldEl is still being used in the new chart.
// We can't use the elements that already being morphed. Let it keep it's original basic transition.
for (var i = 0; i < newDiffItems.length; i++) {
var newItem = newDiffItems[i];
var el = newItem.data.getItemGraphicEl(newItem.dataIndex);
if (el) {
isElementStillInChart[el.id] = true;
}
}
}
function updateOneToOne(newIndex, oldIndex) {
var oldItem = oldDiffItems[oldIndex];
var newItem = newDiffItems[newIndex];
var newSeries = newItem.data.hostModel;
// TODO Mark this elements is morphed and don't morph them anymore
var oldEl = oldItem.data.getItemGraphicEl(oldItem.dataIndex);
var newEl = newItem.data.getItemGraphicEl(newItem.dataIndex);
// Can't handle same elements.
if (oldEl === newEl) {
newEl && animateElementStyles(newEl, newItem.dataIndex, newSeries);
return;
}
if (
// We can't use the elements that already being morphed
oldEl && isElementStillInChart[oldEl.id]) {
return;
}
if (newEl) {
// TODO: If keep animating the group in case
// some of the elements don't want to be morphed.
// TODO Label?
stopAnimation(newEl);
if (oldEl) {
stopAnimation(oldEl);
// If old element is doing leaving animation. stop it and remove it immediately.
removeEl(oldEl);
hasMorphAnimation = true;
applyMorphAnimation(getPathList(oldEl), getPathList(newEl), newItem.divide, newSeries, newIndex, updateMorphingPathProps);
} else {
fadeInElement(newEl, newSeries, newIndex);
}
}
// else keep oldEl leaving animation.
}
new DataDiffer(oldDiffItems, newDiffItems, createKeyGetter(true, useId), createKeyGetter(false, useId), null, 'multiple').update(updateOneToOne).updateManyToOne(function (newIndex, oldIndices) {
var newItem = newDiffItems[newIndex];
var newData = newItem.data;
var newSeries = newData.hostModel;
var newEl = newData.getItemGraphicEl(newItem.dataIndex);
var oldElsList = filter(map(oldIndices, function (idx) {
return oldDiffItems[idx].data.getItemGraphicEl(oldDiffItems[idx].dataIndex);
}), function (oldEl) {
return oldEl && oldEl !== newEl && !isElementStillInChart[oldEl.id];
});
if (newEl) {
stopAnimation(newEl);
if (oldElsList.length) {
// If old element is doing leaving animation. stop it and remove it immediately.
each(oldElsList, function (oldEl) {
stopAnimation(oldEl);
removeEl(oldEl);
});
hasMorphAnimation = true;
applyMorphAnimation(getPathList(oldElsList), getPathList(newEl), newItem.divide, newSeries, newIndex, updateMorphingPathProps);
} else {
fadeInElement(newEl, newSeries, newItem.dataIndex);
}
}
// else keep oldEl leaving animation.
}).updateOneToMany(function (newIndices, oldIndex) {
var oldItem = oldDiffItems[oldIndex];
var oldEl = oldItem.data.getItemGraphicEl(oldItem.dataIndex);
// We can't use the elements that already being morphed
if (oldEl && isElementStillInChart[oldEl.id]) {
return;
}
var newElsList = filter(map(newIndices, function (idx) {
return newDiffItems[idx].data.getItemGraphicEl(newDiffItems[idx].dataIndex);
}), function (el) {
return el && el !== oldEl;
});
var newSeris = newDiffItems[newIndices[0]].data.hostModel;
if (newElsList.length) {
each(newElsList, function (newEl) {
return stopAnimation(newEl);
});
if (oldEl) {
stopAnimation(oldEl);
// If old element is doing leaving animation. stop it and remove it immediately.
removeEl(oldEl);
hasMorphAnimation = true;
applyMorphAnimation(getPathList(oldEl), getPathList(newElsList), oldItem.divide,
// Use divide on old.
newSeris, newIndices[0], updateMorphingPathProps);
} else {
each(newElsList, function (newEl) {
return fadeInElement(newEl, newSeris, newIndices[0]);
});
}
}
// else keep oldEl leaving animation.
}).updateManyToMany(function (newIndices, oldIndices) {
// If two data are same and both have groupId.
// Normally they should be diff by id.
new DataDiffer(oldIndices, newIndices, function (rawIdx) {
return oldDiffItems[rawIdx].data.getId(oldDiffItems[rawIdx].dataIndex);
}, function (rawIdx) {
return newDiffItems[rawIdx].data.getId(newDiffItems[rawIdx].dataIndex);
}).update(function (newIndex, oldIndex) {
// Use the original index
updateOneToOne(newIndices[newIndex], oldIndices[oldIndex]);
}).execute();
}).execute();
if (hasMorphAnimation) {
each(newList, function (_a) {
var data = _a.data;
var seriesModel = data.hostModel;
var view = seriesModel && api.getViewOfSeriesModel(seriesModel);
var animationCfg = getAnimationConfig('update', seriesModel, 0); // use 0 index.
if (view && seriesModel.isAnimationEnabled() && animationCfg && animationCfg.duration > 0) {
view.group.traverse(function (el) {
if (el instanceof Path && !el.animators.length) {
// We can't accept there still exists element that has no animation
// if universalTransition is enabled
el.animateFrom({
style: {
opacity: 0
}
}, animationCfg);
}
});
}
});
}
}
function getSeriesTransitionKey(series) {
var seriesKey = series.getModel('universalTransition').get('seriesKey');
if (!seriesKey) {
// Use series id by default.
return series.id;
}
return seriesKey;
}
function convertArraySeriesKeyToString(seriesKey) {
if (isArray(seriesKey)) {
// Order independent.
return seriesKey.sort().join(',');
}
return seriesKey;
}
function getDivideShapeFromData(data) {
if (data.hostModel) {
return data.hostModel.getModel('universalTransition').get('divideShape');
}
}
function findTransitionSeriesBatches(globalStore, params) {
var updateBatches = createHashMap();
var oldDataMap = createHashMap();
// Map that only store key in array seriesKey.
// Which is used to query the old data when transition from one to multiple series.
var oldDataMapForSplit = createHashMap();
each(globalStore.oldSeries, function (series, idx) {
var oldDataGroupId = globalStore.oldDataGroupIds[idx];
var oldData = globalStore.oldData[idx];
var transitionKey = getSeriesTransitionKey(series);
var transitionKeyStr = convertArraySeriesKeyToString(transitionKey);
oldDataMap.set(transitionKeyStr, {
dataGroupId: oldDataGroupId,
data: oldData
});
if (isArray(transitionKey)) {
// Same key can't in different array seriesKey.
each(transitionKey, function (key) {
oldDataMapForSplit.set(key, {
key: transitionKeyStr,
dataGroupId: oldDataGroupId,
data: oldData
});
});
}
});
function checkTransitionSeriesKeyDuplicated(transitionKeyStr) {
if (updateBatches.get(transitionKeyStr)) {
warn("Duplicated seriesKey in universalTransition " + transitionKeyStr);
}
}
each(params.updatedSeries, function (series) {
if (series.isUniversalTransitionEnabled() && series.isAnimationEnabled()) {
var newDataGroupId = series.get('dataGroupId');
var newData = series.getData();
var transitionKey = getSeriesTransitionKey(series);
var transitionKeyStr = convertArraySeriesKeyToString(transitionKey);
// Only transition between series with same id.
var oldData = oldDataMap.get(transitionKeyStr);
// string transition key is the best match.
if (oldData) {
if (process.env.NODE_ENV !== 'production') {
checkTransitionSeriesKeyDuplicated(transitionKeyStr);
}
// TODO check if data is same?
updateBatches.set(transitionKeyStr, {
oldSeries: [{
dataGroupId: oldData.dataGroupId,
divide: getDivideShapeFromData(oldData.data),
data: oldData.data
}],
newSeries: [{
dataGroupId: newDataGroupId,
divide: getDivideShapeFromData(newData),
data: newData
}]
});
} else {
// Transition from multiple series.
// e.g. 'female', 'male' -> ['female', 'male']
if (isArray(transitionKey)) {
if (process.env.NODE_ENV !== 'production') {
checkTransitionSeriesKeyDuplicated(transitionKeyStr);
}
var oldSeries_1 = [];
each(transitionKey, function (key) {
var oldData = oldDataMap.get(key);
if (oldData.data) {
oldSeries_1.push({
dataGroupId: oldData.dataGroupId,
divide: getDivideShapeFromData(oldData.data),
data: oldData.data
});
}
});
if (oldSeries_1.length) {
updateBatches.set(transitionKeyStr, {
oldSeries: oldSeries_1,
newSeries: [{
dataGroupId: newDataGroupId,
data: newData,
divide: getDivideShapeFromData(newData)
}]
});
}
} else {
// Try transition to multiple series.
// e.g. ['female', 'male'] -> 'female', 'male'
var oldData_1 = oldDataMapForSplit.get(transitionKey);
if (oldData_1) {
var batch = updateBatches.get(oldData_1.key);
if (!batch) {
batch = {
oldSeries: [{
dataGroupId: oldData_1.dataGroupId,
data: oldData_1.data,
divide: getDivideShapeFromData(oldData_1.data)
}],
newSeries: []
};
updateBatches.set(oldData_1.key, batch);
}
batch.newSeries.push({
dataGroupId: newDataGroupId,
data: newData,
divide: getDivideShapeFromData(newData)
});
}
}
}
}
});
return updateBatches;
}
function querySeries(series, finder) {
for (var i = 0; i < series.length; i++) {
var found = finder.seriesIndex != null && finder.seriesIndex === series[i].seriesIndex || finder.seriesId != null && finder.seriesId === series[i].id;
if (found) {
return i;
}
}
}
function transitionSeriesFromOpt(transitionOpt, globalStore, params, api) {
var from = [];
var to = [];
each(normalizeToArray(transitionOpt.from), function (finder) {
var idx = querySeries(globalStore.oldSeries, finder);
if (idx >= 0) {
from.push({
dataGroupId: globalStore.oldDataGroupIds[idx],
data: globalStore.oldData[idx],
// TODO can specify divideShape in transition.
divide: getDivideShapeFromData(globalStore.oldData[idx]),
groupIdDim: finder.dimension
});
}
});
each(normalizeToArray(transitionOpt.to), function (finder) {
var idx = querySeries(params.updatedSeries, finder);
if (idx >= 0) {
var data = params.updatedSeries[idx].getData();
to.push({
dataGroupId: globalStore.oldDataGroupIds[idx],
data: data,
divide: getDivideShapeFromData(data),
groupIdDim: finder.dimension
});
}
});
if (from.length > 0 && to.length > 0) {
transitionBetween(from, to, api);
}
}
export function installUniversalTransition(registers) {
registers.registerUpdateLifecycle('series:beforeupdate', function (ecMOdel, api, params) {
each(normalizeToArray(params.seriesTransition), function (transOpt) {
each(normalizeToArray(transOpt.to), function (finder) {
var series = params.updatedSeries;
for (var i = 0; i < series.length; i++) {
if (finder.seriesIndex != null && finder.seriesIndex === series[i].seriesIndex || finder.seriesId != null && finder.seriesId === series[i].id) {
series[i][SERIES_UNIVERSAL_TRANSITION_PROP] = true;
}
}
});
});
});
registers.registerUpdateLifecycle('series:transition', function (ecModel, api, params) {
// TODO api provide an namespace that can save stuff per instance
var globalStore = getUniversalTransitionGlobalStore(api);
// TODO multiple to multiple series.
if (globalStore.oldSeries && params.updatedSeries && params.optionChanged) {
// TODO transitionOpt was used in an old implementation and can be removed now
// Use give transition config if its' give;
var transitionOpt = params.seriesTransition;
if (transitionOpt) {
each(normalizeToArray(transitionOpt), function (opt) {
transitionSeriesFromOpt(opt, globalStore, params, api);
});
} else {
// Else guess from series based on transition series key.
var updateBatches_1 = findTransitionSeriesBatches(globalStore, params);
each(updateBatches_1.keys(), function (key) {
var batch = updateBatches_1.get(key);
transitionBetween(batch.oldSeries, batch.newSeries, api);
});
}
// Reset
each(params.updatedSeries, function (series) {
// Reset;
if (series[SERIES_UNIVERSAL_TRANSITION_PROP]) {
series[SERIES_UNIVERSAL_TRANSITION_PROP] = false;
}
});
}
// Save all series of current update. Not only the updated one.
var allSeries = ecModel.getSeries();
var savedSeries = globalStore.oldSeries = [];
var savedDataGroupIds = globalStore.oldDataGroupIds = [];
var savedData = globalStore.oldData = [];
for (var i = 0; i < allSeries.length; i++) {
var data = allSeries[i].getData();
// Only save the data that can have transition.
// Avoid large data costing too much extra memory
if (data.count() < DATA_COUNT_THRESHOLD) {
savedSeries.push(allSeries[i]);
savedDataGroupIds.push(allSeries[i].get('dataGroupId'));
savedData.push(data);
}
}
});
}