You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by sh...@apache.org on 2021/11/24 15:41:47 UTC

[echarts] branch graphic-animation updated: feat(transition): add animation config per element

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 <bm...@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-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org