This is an automated email from the ASF dual-hosted git repository. shenyi pushed a commit to branch graphic-animation in repository https://gitbox.apache.org/repos/asf/echarts.git
The following commit(s) were added to refs/heads/graphic-animation by this push: new 393eb30 feat(transition): add animation config per element 393eb30 is described below commit 393eb307cfb63e983e4ed9939e0820a835311df9 Author: pissang <bm2736...@gmail.com> AuthorDate: Wed Nov 24 23:36:51 2021 +0800 feat(transition): add animation config per element --- src/animation/basicTrasition.ts | 14 +++---- src/animation/customGraphicTransition.ts | 68 +++++++++++++++++++++----------- src/chart/custom/CustomSeries.ts | 5 +++ src/chart/custom/CustomView.ts | 12 +++--- src/chart/helper/Symbol.ts | 4 +- src/component/graphic/GraphicModel.ts | 23 +++++++---- src/component/graphic/GraphicView.ts | 27 +++++++------ 7 files changed, 97 insertions(+), 56 deletions(-) diff --git a/src/animation/basicTrasition.ts b/src/animation/basicTrasition.ts index bc31f39..3863b6b 100644 --- a/src/animation/basicTrasition.ts +++ b/src/animation/basicTrasition.ts @@ -55,7 +55,7 @@ type AnimateOrSetPropsOption = { * Return null if animation is disabled. */ export function getAnimationConfig( - animationType: 'init' | 'update' | 'remove', + animationType: 'enter' | 'update' | 'leave', animatableModel: Model<AnimationOptionMixin>, dataIndex: number, // Extra opts can override the option in animatable model. @@ -124,7 +124,7 @@ export function getAnimationConfig( } function animateOrSetProps<Props>( - animationType: 'init' | 'update' | 'remove', + animationType: 'enter' | 'update' | 'leave', el: Element<Props>, props: Props, animatableModel?: Model<AnimationOptionMixin> & { @@ -149,11 +149,11 @@ function animateOrSetProps<Props>( dataIndex = dataIndex.dataIndex; } - const isRemove = (animationType === 'remove'); + const isRemove = (animationType === 'leave'); if (!isRemove) { // Must stop the remove animation. - el.stopAnimation('remove'); + el.stopAnimation('leave'); } const animationConfig = getAnimationConfig( @@ -245,7 +245,7 @@ export function initProps<Props>( cb?: AnimateOrSetPropsOption['cb'] | AnimateOrSetPropsOption['during'], during?: AnimateOrSetPropsOption['during'] ) { - animateOrSetProps('init', el, props, animatableModel, dataIndex, cb, during); + animateOrSetProps('enter', el, props, animatableModel, dataIndex, cb, during); } /** @@ -258,7 +258,7 @@ export function initProps<Props>( } for (let i = 0; i < el.animators.length; i++) { const animator = el.animators[i]; - if (animator.scope === 'remove') { + if (animator.scope === 'leave') { return true; } } @@ -281,7 +281,7 @@ export function removeElement<Props>( return; } - animateOrSetProps('remove', el, props, animatableModel, dataIndex, cb, during); + animateOrSetProps('leave', el, props, animatableModel, dataIndex, cb, during); } function fadeOutDisplayable( diff --git a/src/animation/customGraphicTransition.ts b/src/animation/customGraphicTransition.ts index 543112b..e09772f 100644 --- a/src/animation/customGraphicTransition.ts +++ b/src/animation/customGraphicTransition.ts @@ -18,14 +18,14 @@ */ // Helpers for creating transitions in custom series and graphic components. -import Element, { ElementProps } from 'zrender/src/Element'; +import Element, { ElementAnimateConfig, ElementProps } from 'zrender/src/Element'; import { makeInner, normalizeToArray } from '../util/model'; import { assert, bind, each, eqNaN, extend, hasOwn, indexOf, isArrayLike, keys } from 'zrender/src/core/util'; import { cloneValue } from 'zrender/src/animation/Animator'; import Displayable, { DisplayableProps } from 'zrender/src/graphic/Displayable'; import Model from '../model/Model'; -import { initProps, updateProps } from './basicTrasition'; +import { getAnimationConfig, updateProps } from './basicTrasition'; import { Path } from '../util/graphic'; import { warn } from '../util/log'; import { AnimationOption, AnimationOptionMixin, ZRStyleProps } from '../util/types'; @@ -58,8 +58,8 @@ export const ELEMENT_ANIMATABLE_PROPS = ['', 'style', 'shape', 'extra'] as const export type TransitionProps = string | string[]; export type ElementRootTransitionProp = TransformProp | 'shape' | 'extra' | 'style'; -export interface TransitionOptionMixin<T = unknown> { - transition?: TransitionProps | 'all' +export interface TransitionOptionMixin<T = Record<string, any>> { + transition?: (keyof T & string) | ((keyof T & string)[]) | 'all' enterFrom?: T; leaveTo?: T; @@ -108,6 +108,20 @@ export interface TransitionDuringAPI< getStyle<T extends keyof StyleOpt>(key: T): StyleOpt[T]; }; +function getElementAnimationConfig( + animationType: 'enter' | 'update' | 'leave', + elOption: TransitionElementOption, + parentModel: Model<AnimationOptionMixin>, + dataIndex?: number +) { + const animationProp = `${animationType}Animation` as const; + const config: ElementAnimateConfig = getAnimationConfig(animationType, parentModel, dataIndex) || {}; + config.setToFinal = true; + config.scope = animationType; + extend(config, elOption[animationProp]); + return config; +} + export function applyUpdateTransition( el: Element, @@ -155,12 +169,13 @@ export function applyUpdateTransition( extend(propName ? (enterFromProps as any)[propName] : enterFromProps, prop.enterFrom); } }); - initProps(el, enterFromProps, animatableModel, { - dataIndex: dataIndex || 0, isFrom: true - }); + const config = getElementAnimationConfig('enter', elOption, animatableModel, dataIndex); + if (config.duration > 0) { + el.animateFrom(enterFromProps, config); + } } else { - applyPropsTransition(el, dataIndex || 0, animatableModel, transFromProps); + applyPropsTransition(el, elOption, dataIndex || 0, animatableModel, transFromProps); } } // Store leave to be used in leave transition. @@ -189,20 +204,27 @@ export function updateLeaveTo(el: Element, elOption: TransitionElementOption) { export function applyLeaveTransition( el: Element, + elOption: TransitionElementOption, animatableModel: Model<AnimationOptionMixin>, onRemove?: () => void ): void { if (el) { const parent = el.parent; const leaveToProps = transitionInnerStore(el).leaveToProps; - leaveToProps - ? updateProps(el, leaveToProps, animatableModel, { - cb: function () { - parent.remove(el); - onRemove && onRemove(); - } - }) - : (parent.remove(el), onRemove && onRemove()); + if (leaveToProps) { + // TODO TODO use leave after leaveAnimation in series is introduced + // TODO Data index? + const config = getElementAnimationConfig('update', elOption, animatableModel, 0); + config.done = () => { + parent.remove(el); + onRemove && onRemove(); + }; + el.animateTo(leaveToProps, config); + } + else { + parent.remove(el); + onRemove && onRemove(); + } } } @@ -259,6 +281,7 @@ function applyPropsDirectly( function applyPropsTransition( el: Element, + elOption: TransitionElementOption, dataIndex: number, model: Model<AnimationOptionMixin>, // Can be null/undefined @@ -273,12 +296,12 @@ function applyPropsTransition( const userDuring = transitionInnerStore(el).userDuring; // For simplicity, if during not specified, the previous during will not work any more. const cfgDuringCall = userDuring ? bind(duringCall, { el: el, userDuring: userDuring }) : null; - const cfg = { - dataIndex: dataIndex, - isFrom: true, - during: cfgDuringCall - }; - updateProps(el, transFromProps, model, cfg); + + const config = getElementAnimationConfig('update', elOption, model, dataIndex); + if (config.duration > 0) { + config.during = cfgDuringCall; + el.animateFrom(transFromProps, config); + } } } @@ -447,7 +470,6 @@ function prepareShapeOrExtraTransitionFrom( const elPropsInAttr = (fromEl as LooseElementProps)[mainAttr]; let transFromPropsInAttr: Dictionary<unknown>; - if (elPropsInAttr) { const transition = elOption.transition; const attrTransition = attrOpt.transition; diff --git a/src/chart/custom/CustomSeries.ts b/src/chart/custom/CustomSeries.ts index a415574..5a8e998 100644 --- a/src/chart/custom/CustomSeries.ts +++ b/src/chart/custom/CustomSeries.ts @@ -23,6 +23,7 @@ import { PathProps, PathStyleProps } from 'zrender/src/graphic/Path'; import { ZRenderType } from 'zrender/src/zrender'; import { BarGridLayoutOptionForCustomSeries, BarGridLayoutResult } from '../../layout/barGrid'; import { + AnimationOption, BlurScope, CallbackDataParams, Dictionary, @@ -119,6 +120,10 @@ export interface CustomBaseElementOption extends Partial<Pick< extra?: Dictionary<unknown> & TransitionOptionMixin; // updateDuringAnimation during?(params: TransitionBaseDuringAPI): void; + + enterAnimation?: AnimationOption + updateAnimation?: AnimationOption + leaveAnimation?: AnimationOption }; export interface CustomDisplayableOption extends CustomBaseElementOption, Partial<Pick< diff --git a/src/chart/custom/CustomView.ts b/src/chart/custom/CustomView.ts index cac33f7..e98b11c 100644 --- a/src/chart/custom/CustomView.ts +++ b/src/chart/custom/CustomView.ts @@ -223,7 +223,8 @@ export default class CustomChartView extends ChartView { ); }) .remove(function (oldIdx) { - applyLeaveTransition(oldData.getItemGraphicEl(oldIdx), customSeries); + const el = oldData.getItemGraphicEl(oldIdx); + applyLeaveTransition(el, customInnerStore(el).option, customSeries); }) .update(function (newIdx, oldIdx) { const oldEl = oldData.getItemGraphicEl(oldIdx); @@ -1059,8 +1060,8 @@ function doesElNeedRecreate(el: Element, elOption: CustomElementOption, seriesMo && elOptionType !== elInner.customGraphicType ) || (elOptionType === 'path' - && hasOwnPathData(elOptionShape) - && getPathData(elOptionShape) !== elInner.customPathData + && hasOwnPathData(elOptionShape as CustomSVGPathOption['shape']) + && getPathData(elOptionShape as CustomSVGPathOption['shape']) !== elInner.customPathData ) || (elOptionType === 'image' && hasOwn(elOptionStyle, 'image') @@ -1323,7 +1324,8 @@ function mergeChildren( // Do not supprot leave elements that are not mentioned in the latest // `renderItem` return. Otherwise users may not have a clear and simple // concept that how to contorl all of the elements. - applyLeaveTransition(el.childAt(i), seriesModel); + const child = el.childAt(i); + applyLeaveTransition(child, customInnerStore(el).option, seriesModel); } } @@ -1377,7 +1379,7 @@ function processAddUpdate( function processRemove(this: DataDiffer<DiffGroupContext>, oldIndex: number): void { const context = this.context; const child = context.oldChildren[oldIndex]; - applyLeaveTransition(child, context.seriesModel); + applyLeaveTransition(child, customInnerStore(child).option, context.seriesModel); } /** diff --git a/src/chart/helper/Symbol.ts b/src/chart/helper/Symbol.ts index be2a5a4..8f994eb 100644 --- a/src/chart/helper/Symbol.ts +++ b/src/chart/helper/Symbol.ts @@ -198,8 +198,8 @@ class Symbol extends graphic.Group { } if (disableAnimation) { - // Must stop remove animation manually if don't call initProps or updateProps. - this.childAt(0).stopAnimation('remove'); + // Must stop leave transition manually if don't call initProps or updateProps. + this.childAt(0).stopAnimation('leave'); } this._seriesModel = seriesModel; diff --git a/src/component/graphic/GraphicModel.ts b/src/component/graphic/GraphicModel.ts index 270b7b1..811c715 100644 --- a/src/component/graphic/GraphicModel.ts +++ b/src/component/graphic/GraphicModel.ts @@ -26,7 +26,8 @@ import { ZRStyleProps, OptionId, CommonTooltipOption, - AnimationOptionMixin + AnimationOptionMixin, + AnimationOption } from '../../util/types'; import ComponentModel from '../../model/Component'; import Element, { ElementTextConfig } from 'zrender/src/Element'; @@ -115,6 +116,9 @@ interface GraphicComponentBaseElementOption extends tooltip?: CommonTooltipOption<unknown>; + enterAnimation?: AnimationOption + updateAnimation?: AnimationOption + leaveAnimation?: AnimationOption }; @@ -223,7 +227,6 @@ export function setKeyInfoToNewElOption( newElOption.parentOption = null; } -const TRANSITION_PROPS = ['transition', 'enterFrom', 'leaveTo'] as const; function isSetLoc( obj: GraphicComponentElementOption, props: ('left' | 'right' | 'top' | 'bottom')[] @@ -282,8 +285,13 @@ function mergeNewElOptionToExist( } } +const TRANSITION_PROPS_TO_COPY = ['transition', 'enterFrom', 'leaveTo']; +const ROOT_TRANSITION_PROPS_TO_COPY = + TRANSITION_PROPS_TO_COPY.concat(['enterAnimation', 'updateAnimation', 'leaveAnimation']); function copyTransitionInfo( - target: GraphicComponentElementOption, source: GraphicComponentElementOption, targetProp?: string + target: GraphicComponentElementOption, + source: GraphicComponentElementOption, + targetProp?: string ) { if (targetProp) { if (!(target as any)[targetProp] @@ -299,10 +307,11 @@ function copyTransitionInfo( return; } - for (let i = 0; i < TRANSITION_PROPS.length; i++) { - const prop = TRANSITION_PROPS[i]; - if (target[prop] == null && source[prop] != null) { - (target as any)[prop] = source[prop]; + const props = targetProp ? TRANSITION_PROPS_TO_COPY : ROOT_TRANSITION_PROPS_TO_COPY; + for (let i = 0; i < props.length; i++) { + const prop = props[i]; + if ((target as any)[prop] == null && (source as any)[prop] != null) { + (target as any)[prop] = (source as any)[prop]; } } } diff --git a/src/component/graphic/GraphicView.ts b/src/component/graphic/GraphicView.ts index eff35f3..b5d1131 100644 --- a/src/component/graphic/GraphicView.ts +++ b/src/component/graphic/GraphicView.ts @@ -60,12 +60,11 @@ const nonShapeGraphicElements = { type NonShapeGraphicElementType = keyof typeof nonShapeGraphicElements; export const inner = modelUtil.makeInner<{ - widthOption: number; - heightOption: number; width: number; height: number; isNew: boolean; id: string; + option: GraphicComponentElementOption }, Element>(); // ------------------------ // View @@ -184,7 +183,7 @@ export class GraphicComponentView extends ComponentView { } } else if ($action === 'replace') { - removeEl(elExisting, elMap, graphicModel); + removeEl(elExisting, elOption, elMap, graphicModel); const el = createEl(id, targetElParent, elOption.type, elMap); if (el) { applyUpdateTransition( @@ -198,7 +197,7 @@ export class GraphicComponentView extends ComponentView { } else if ($action === 'remove') { updateLeaveTo(elExisting, elOption); - removeEl(elExisting, elMap, graphicModel); + removeEl(elExisting, elOption, elMap, graphicModel); } const el = elMap.get(id); @@ -217,8 +216,7 @@ export class GraphicComponentView extends ComponentView { if (el) { const elInner = inner(el); - elInner.widthOption = (elOption as GraphicComponentGroupOption).width; - elInner.heightOption = (elOption as GraphicComponentGroupOption).height; + elInner.option = elOption; setEventData(el, graphicModel, elOption); graphicUtil.setTooltipConfig({ @@ -262,11 +260,11 @@ export class GraphicComponentView extends ComponentView { const elInner = inner(el); const parentElInner = inner(parentEl); elInner.width = parsePercent( - elInner.widthOption, + (elInner.option as GraphicComponentGroupOption).width, isParentRoot ? apiWidth : parentElInner.width ) || 0; elInner.height = parsePercent( - elInner.heightOption, + (elInner.option as GraphicComponentGroupOption).height, isParentRoot ? apiHeight : parentElInner.height ) || 0; } @@ -333,7 +331,7 @@ export class GraphicComponentView extends ComponentView { private _clear(): void { const elMap = this._elMap; elMap.each((el) => { - removeEl(el, elMap, this._lastGraphicModel); + removeEl(el, inner(el).option, elMap, this._lastGraphicModel); }); this._elMap = zrUtil.createHashMap(); } @@ -373,13 +371,18 @@ function createEl( return el; } -function removeEl(elExisting: Element, elMap: ElementMap, graphicModel: GraphicComponentModel): void { +function removeEl( + elExisting: Element, + elOption: GraphicComponentElementOption, + elMap: ElementMap, + graphicModel: GraphicComponentModel +): void { const existElParent = elExisting && elExisting.parent; if (existElParent) { elExisting.type === 'group' && elExisting.traverse(function (el) { - removeEl(el, elMap, graphicModel); + removeEl(el, elOption, elMap, graphicModel); }); - applyLeaveTransition(elExisting, graphicModel); + applyLeaveTransition(elExisting, elOption, graphicModel); elMap.removeKey(inner(elExisting).id); } } --------------------------------------------------------------------- To unsubscribe, e-mail: commits-unsubscr...@echarts.apache.org For additional commands, e-mail: commits-h...@echarts.apache.org