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/21 07:36:25 UTC

[echarts] branch graphic-animation updated: feat(transition) optimize enterFrom and leaveTo. fix style loose in custom

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 b30b63f  feat(transition) optimize enterFrom and leaveTo. fix style loose in custom
b30b63f is described below

commit b30b63f764e4a1e88075018c1f39a288eba05d47
Author: pissang <bm...@gmail.com>
AuthorDate: Sun Nov 21 15:31:47 2021 +0800

    feat(transition) optimize enterFrom and leaveTo. fix style loose in custom
---
 src/animation/customGraphicTransition.ts | 239 +++++++++++++++----------------
 src/chart/custom/CustomView.ts           |  33 +----
 src/component/graphic/GraphicView.ts     |  14 +-
 test/graphic-transition.html             |  92 +++++++-----
 4 files changed, 188 insertions(+), 190 deletions(-)

diff --git a/src/animation/customGraphicTransition.ts b/src/animation/customGraphicTransition.ts
index ef602bf..5bcba64 100644
--- a/src/animation/customGraphicTransition.ts
+++ b/src/animation/customGraphicTransition.ts
@@ -52,11 +52,14 @@ type TransformProp = keyof typeof TRANSFORM_PROPS_MAP;
 const TRANSFORM_PROPS = keys(TRANSFORM_PROPS_MAP);
 const transformPropNamesStr = TRANSFORM_PROPS.join(', ');
 
+// '' means root
+const ELEMENT_TRANSITION_PROPS = ['', 'style', 'shape', 'extra'] as const;
+
 export type TransitionProps = string | string[];
 export type ElementRootTransitionProp = TransformProp | 'shape' | 'extra' | 'style';
 
 export interface TransitionOptionMixin {
-    transition?: TransitionProps | 'all';
+    transition?: TransitionProps | 'all'
     enterFrom?: Dictionary<unknown>;
     leaveTo?: Dictionary<unknown>;
 };
@@ -106,13 +109,17 @@ export interface TransitionDuringAPI<
     getStyle<T extends keyof StyleOpt>(key: T): StyleOpt[T];
 };
 
+
 export function applyUpdateTransition(
     el: Element,
     elOption: TransitionElementOption,
     animatableModel?: Model<AnimationOptionMixin>,
-    dataIndex?: number,
-    isInit?: boolean
+    opts?: { dataIndex?: number, isInit?: boolean, clearStyle?: boolean}
 ) {
+    opts = opts || {};
+    const {dataIndex, isInit, clearStyle} = opts;
+
+    const hasAnimation = animatableModel.isAnimationEnabled();
     // Save the meta info for further morphing. Like apply on the sub morphing elements.
     const store = transitionInnerStore(el);
     const styleOpt = elOption.style;
@@ -121,24 +128,67 @@ export function applyUpdateTransition(
     const transFromProps = {} as ElementProps;
     const propsToSet = {} as ElementProps;
 
-    prepareShapeOrExtraTransitionFrom('shape', el, elOption, transFromProps, isInit);
-    prepareShapeOrExtraAllPropsFinal('shape', elOption, propsToSet);
-
-    prepareTransformTransitionFrom(el, elOption, transFromProps, isInit);
     prepareTransformAllPropsFinal(el, elOption, propsToSet);
-
-    prepareShapeOrExtraTransitionFrom('extra', el, elOption, transFromProps, isInit);
+    prepareShapeOrExtraAllPropsFinal('shape', elOption, propsToSet);
     prepareShapeOrExtraAllPropsFinal('extra', elOption, propsToSet);
 
-    prepareStyleTransitionFrom(el, elOption, styleOpt, transFromProps, isInit);
+    if (!isInit && hasAnimation) {
+        prepareTransformTransitionFrom(el, elOption, transFromProps);
+        prepareShapeOrExtraTransitionFrom('shape', el, elOption, transFromProps);
+        prepareShapeOrExtraTransitionFrom('extra', el, elOption, transFromProps);
+        prepareStyleTransitionFrom(el, elOption, styleOpt, transFromProps);
+    }
+
     (propsToSet as DisplayableProps).style = styleOpt;
-    applyPropsDirectly(el, propsToSet);
-    applyPropsTransition(el, dataIndex, animatableModel, transFromProps, isInit);
+
+    applyPropsDirectly(el, propsToSet, clearStyle);
     applyMiscProps(el, elOption);
 
+    if (hasAnimation) {
+        if (isInit) {
+            const enterFromProps: ElementProps = {};
+            for (let i = 0; i < ELEMENT_TRANSITION_PROPS.length; i++) {
+                const propName = ELEMENT_TRANSITION_PROPS[i];
+                const prop: TransitionOptionMixin = propName ? elOption[propName] : elOption;
+                if (prop && prop.enterFrom) {
+                    if (propName) {
+                        (enterFromProps as any)[propName] = (enterFromProps as any)[propName] || {};
+                    }
+                    extend(propName ? (enterFromProps as any)[propName] : enterFromProps, prop.enterFrom);
+                }
+            }
+            initProps(el, enterFromProps, animatableModel, {
+                dataIndex: dataIndex || 0, isFrom: true
+            });
+        }
+        else {
+            applyPropsTransition(el, dataIndex || 0, animatableModel, transFromProps);
+        }
+    }
+    // Store leave to be used in leave transition.
+    updateLeaveTo(el, elOption);
+
     styleOpt ? el.dirty() : el.markRedraw();
 }
 
+export function updateLeaveTo(el: Element, elOption: TransitionElementOption) {
+    // Try merge to previous set leaveTo
+    let leaveToProps: ElementProps = transitionInnerStore(el).leaveToProps;
+    for (let i = 0; i < ELEMENT_TRANSITION_PROPS.length; i++) {
+        const propName = ELEMENT_TRANSITION_PROPS[i];
+        const prop: TransitionOptionMixin = propName ? elOption[propName] : elOption;
+        if (prop && prop.leaveTo) {
+            if (!leaveToProps) {
+                leaveToProps = transitionInnerStore(el).leaveToProps = {};
+            }
+            if (propName) {
+                (leaveToProps as any)[propName] = (leaveToProps as any)[propName] || {};
+            }
+            extend(propName ? (leaveToProps as any)[propName] : leaveToProps, prop.leaveTo);
+        }
+    }
+}
+
 export function applyLeaveTransition(
     el: Element,
     animatableModel: Model<AnimationOptionMixin>,
@@ -166,13 +216,38 @@ export function isTransitionAll(transition: TransitionProps): transition is 'all
 function applyPropsDirectly(
     el: Element,
     // Can be null/undefined
-    allPropsFinal: ElementProps
+    allPropsFinal: ElementProps,
+    clearStyle: boolean
 ) {
-    const elDisplayable = el.isGroup ? null : el as Displayable;
     const styleOpt = (allPropsFinal as Displayable).style;
-
-    if (elDisplayable && styleOpt) {
-        elDisplayable.setStyle(styleOpt);
+    if (!el.isGroup && styleOpt) {
+        if (clearStyle) {
+            (el as Displayable).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`?
+            const animators = el.animators;
+            for (let i = 0; i < animators.length; i++) {
+                const animator = animators[i];
+                // targetName is the "topKey".
+                if (animator.targetName === 'style') {
+                    animator.changeTarget((el as Displayable).style);
+                }
+            }
+        }
+        (el as Displayable).setStyle(styleOpt);
     }
 
     if (allPropsFinal) {
@@ -189,8 +264,7 @@ function applyPropsTransition(
     dataIndex: number,
     model: Model<AnimationOptionMixin>,
     // Can be null/undefined
-    transFromProps: ElementProps,
-    isInit: boolean
+    transFromProps: ElementProps
 ): void {
     if (transFromProps) {
         // NOTE: Do not use `el.updateDuringAnimation` here becuase `el.updateDuringAnimation` will
@@ -206,9 +280,7 @@ function applyPropsTransition(
             isFrom: true,
             during: cfgDuringCall
         };
-        isInit
-            ? initProps(el, transFromProps, model, cfg)
-            : updateProps(el, transFromProps, model, cfg);
+        updateProps(el, transFromProps, model, cfg);
     }
 }
 
@@ -377,9 +449,8 @@ function duringCall(
 function prepareShapeOrExtraTransitionFrom(
     mainAttr: 'shape' | 'extra',
     fromEl: Element,
-    elOption: TransitionElementOption,
-    transFromProps: LooseElementProps,
-    isInit: boolean
+    elOption: ElementTransitionOptionMixin,
+    transFromProps: LooseElementProps
 ): void {
 
     const attrOpt: Dictionary<unknown> & TransitionOptionMixin = (elOption as any)[mainAttr];
@@ -390,21 +461,8 @@ function prepareShapeOrExtraTransitionFrom(
     const elPropsInAttr = (fromEl as LooseElementProps)[mainAttr];
     let transFromPropsInAttr: Dictionary<unknown>;
 
-    const enterFrom = attrOpt.enterFrom;
-    if (isInit && enterFrom) {
-        !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {});
-        const enterFromKeys = keys(enterFrom);
-        for (let i = 0; i < enterFromKeys.length; i++) {
-            // `enterFrom` props are not necessarily also declared in `shape`/`style`/...,
-            // for example, `opacity` can only declared in `enterFrom` but not in `style`.
-            const key = enterFromKeys[i];
-            // Do not clone, animator will perform that clone.
-            transFromPropsInAttr[key] = enterFrom[key];
-        }
-    }
-
 
-    if (!isInit && elPropsInAttr) {
+    if (elPropsInAttr) {
         const transition = elOption.transition;
         const attrTransition = attrOpt.transition;
         if (attrTransition) {
@@ -433,17 +491,6 @@ function prepareShapeOrExtraTransitionFrom(
             }
         }
     }
-
-    const leaveTo = attrOpt.leaveTo;
-    if (leaveTo) {
-        const leaveToProps = getOrCreateLeaveToPropsFromEl(fromEl);
-        const leaveToPropsInAttr: Dictionary<unknown> = leaveToProps[mainAttr] || (leaveToProps[mainAttr] = {});
-        const leaveToKeys = keys(leaveTo);
-        for (let i = 0; i < leaveToKeys.length; i++) {
-            const key = leaveToKeys[i];
-            leaveToPropsInAttr[key] = leaveTo[key];
-        }
-    }
 }
 
 function prepareShapeOrExtraAllPropsFinal(
@@ -468,52 +515,23 @@ function prepareShapeOrExtraAllPropsFinal(
 function prepareTransformTransitionFrom(
     el: Element,
     elOption: TransitionElementOption,
-    transFromProps: ElementProps,
-    isInit: boolean
+    transFromProps: ElementProps
 ): void {
-    const enterFrom = elOption.enterFrom;
-    if (isInit && enterFrom) {
-        const enterFromKeys = keys(enterFrom);
-        for (let i = 0; i < enterFromKeys.length; i++) {
-            const key = enterFromKeys[i] as TransformProp;
-            if (__DEV__) {
-                checkTransformPropRefer(key, 'el.enterFrom');
-            }
-            // Do not clone, animator will perform that clone.
-            transFromProps[key] = enterFrom[key] as number;
+    const transition = elOption.transition;
+    const transitionKeys = isTransitionAll(transition)
+        ? TRANSFORM_PROPS
+        : normalizeToArray(transition || []);
+    for (let i = 0; i < transitionKeys.length; i++) {
+        const key = transitionKeys[i];
+        if (key === 'style' || key === 'shape' || key === 'extra') {
+            continue;
         }
-    }
-
-    if (!isInit) {
-        const transition = elOption.transition;
-        const transitionKeys = isTransitionAll(transition)
-            ? TRANSFORM_PROPS
-            : normalizeToArray(transition || []);
-        for (let i = 0; i < transitionKeys.length; i++) {
-            const key = transitionKeys[i];
-            if (key === 'style' || key === 'shape' || key === 'extra') {
-                continue;
-            }
-            const elVal = el[key];
-            if (__DEV__) {
-                checkTransformPropRefer(key, 'el.transition');
-            }
-            // Do not clone, animator will perform that clone.
-            transFromProps[key] = elVal;
-        }
-    }
-
-    const leaveTo = elOption.leaveTo;
-    if (leaveTo) {
-        const leaveToProps = getOrCreateLeaveToPropsFromEl(el);
-        const leaveToKeys = keys(leaveTo);
-        for (let i = 0; i < leaveToKeys.length; i++) {
-            const key = leaveToKeys[i] as TransformProp;
-            if (__DEV__) {
-                checkTransformPropRefer(key, 'el.leaveTo');
-            }
-            leaveToProps[key] = leaveTo[key] as number;
+        const elVal = el[key];
+        if (__DEV__) {
+            checkTransformPropRefer(key, 'el.transition');
         }
+        // Do not clone, animator will perform that clone.
+        transFromProps[key] = elVal;
     }
 }
 
@@ -544,8 +562,7 @@ function prepareStyleTransitionFrom(
     fromEl: Element,
     elOption: TransitionElementOption,
     styleOpt: TransitionElementOption['style'],
-    transFromProps: LooseElementProps,
-    isInit: boolean
+    transFromProps: LooseElementProps
 ): void {
     if (!styleOpt) {
         return;
@@ -554,18 +571,7 @@ function prepareStyleTransitionFrom(
     const fromElStyle = (fromEl as LooseElementProps).style as LooseElementProps['style'];
     let transFromStyleProps: LooseElementProps['style'];
 
-    const enterFrom = styleOpt.enterFrom;
-    if (isInit && enterFrom) {
-        const enterFromKeys = keys(enterFrom);
-        !transFromStyleProps && (transFromStyleProps = transFromProps.style = {});
-        for (let i = 0; i < enterFromKeys.length; i++) {
-            const key = enterFromKeys[i];
-            // Do not clone, animator will perform that clone.
-            (transFromStyleProps as any)[key] = enterFrom[key];
-        }
-    }
-
-    if (!isInit && fromElStyle) {
+    if (fromElStyle) {
         const styleTransition = styleOpt.transition;
         const elTransition = elOption.transition;
         if (styleTransition && !isTransitionAll(styleTransition)) {
@@ -601,17 +607,6 @@ function prepareStyleTransitionFrom(
             }
         }
     }
-
-    const leaveTo = styleOpt.leaveTo;
-    if (leaveTo) {
-        const leaveToKeys = keys(leaveTo);
-        const leaveToProps = getOrCreateLeaveToPropsFromEl(fromEl);
-        const leaveToStyleProps = leaveToProps.style || (leaveToProps.style = {});
-        for (let i = 0; i < leaveToKeys.length; i++) {
-            const key = leaveToKeys[i];
-            (leaveToStyleProps as any)[key] = leaveTo[key];
-        }
-    }
 }
 
 function isNonStyleTransitionEnabled(optVal: unknown, elVal: unknown): boolean {
@@ -629,10 +624,4 @@ if (__DEV__) {
                + 'Only `' + keys(TRANSFORM_PROPS_MAP).join('`, `') + '` are permitted.');
         }
     };
-}
-
-function getOrCreateLeaveToPropsFromEl(el: Element): LooseElementProps {
-    const innerEl = transitionInnerStore(el);
-    return innerEl.leaveToProps || (innerEl.leaveToProps = {});
-}
-
+}
\ No newline at end of file
diff --git a/src/chart/custom/CustomView.ts b/src/chart/custom/CustomView.ts
index 0024c82..cac33f7 100644
--- a/src/chart/custom/CustomView.ts
+++ b/src/chart/custom/CustomView.ts
@@ -485,36 +485,13 @@ function updateElNormal(
                 (styleOpt as PathStyleProps).decal = decalPattern;
             }
         }
-
-        // Clear style
-        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`?
-        const animators = el.animators;
-        for (let i = 0; i < animators.length; i++) {
-            const animator = animators[i];
-            // targetName is the "topKey".
-            if (animator.targetName === 'style') {
-                animator.changeTarget(el.style);
-            }
-        }
     }
 
-    applyUpdateTransition(el, elOption, seriesModel, dataIndex, isInit);
-
+    applyUpdateTransition(el, elOption, seriesModel, {
+        dataIndex,
+        isInit,
+        clearStyle: true
+    });
 
     if (!isTextContent) {
         // `elOption.info` enables user to mount some info on
diff --git a/src/component/graphic/GraphicView.ts b/src/component/graphic/GraphicView.ts
index 9d90fe0..6b8d25f 100644
--- a/src/component/graphic/GraphicView.ts
+++ b/src/component/graphic/GraphicView.ts
@@ -38,7 +38,12 @@ import {
     GraphicComponentGroupOption,
     GraphicComponentElementOption
 } from './GraphicModel';
-import { applyLeaveTransition, applyUpdateTransition, isTransitionAll } from '../../animation/customGraphicTransition';
+import {
+    applyLeaveTransition,
+    applyUpdateTransition,
+    isTransitionAll,
+    updateLeaveTo
+} from '../../animation/customGraphicTransition';
 import { updateProps } from '../../animation/basicTrasition';
 
 const nonShapeGraphicElements = {
@@ -172,8 +177,7 @@ export class GraphicComponentView extends ComponentView {
                         el,
                         elOptionCleaned,
                         graphicModel,
-                        0,  // TODO Fixed dataIndex to be 0
-                        isInit
+                        { isInit }
                     );
                     updateZ(el, elOption, globalZ, globalZLevel);
                 }
@@ -186,13 +190,13 @@ export class GraphicComponentView extends ComponentView {
                         el,
                         elOptionCleaned,
                         graphicModel,
-                        0,  // TODO Fixed dataIndex to be 0
-                        true
+                        { isInit: true}
                     );
                     updateZ(el, elOption, globalZ, globalZLevel);
                 }
             }
             else if ($action === 'remove') {
+                updateLeaveTo(elExisting, elOption);
                 removeEl(elExisting, elMap, graphicModel);
             }
 
diff --git a/test/graphic-transition.html b/test/graphic-transition.html
index 7b2cf01..497e352 100644
--- a/test/graphic-transition.html
+++ b/test/graphic-transition.html
@@ -39,6 +39,7 @@ under the License.
 
         <div id="main0"></div>
         <div id="main1"></div>
+        <div id="main2"></div>
 
 
 
@@ -184,10 +185,10 @@ under the License.
                             chart.setOption({
                                 graphic: {
                                     elements: [{
-                                        x: Math.random() * chart.getWidth(),
-                                        y: Math.random() * chart.getHeight(),
+                                        x: Math.random() * (chart.getWidth() - 100) + 50,
+                                        y: Math.random() * (chart.getHeight() - 100) + 50,
                                         shape: {
-                                            r: Math.random() * 30 + 50
+                                            r: Math.random() * 40 + 10
                                         },
                                         style: {
                                             fill: echarts.color.random()
@@ -210,47 +211,74 @@ under the License.
 
         <script>
         require(['echarts'/*, 'map/js/china' */], function (echarts) {
+
+            var elements = [{
+                type: 'circle',
+                name: 'target',
+                x: 300,
+                y: 100,
+                shape: {
+                    cx: 0,
+                    cy: 0,
+                    r: 100
+                },
+                style: {
+                    fill: 'gray'
+                }
+            }]
             var option = {
                 graphic: {
-                    elements: [{
-                        type: 'circle',
-                        x: 200,
-                        y: 200,
-                        shape: {
-                            cx: 0,
-                            cy: 0,
-                            r: 100
-                        },
-                        style: {
-                            fill: 'gray'
-                        }
-                    }]
+                    elements: elements
                 }
             }
 
-            var chart = testHelper.create(echarts, 'main1', {
+            var chart = testHelper.create(echarts, 'main2', {
                 title: [
-                    'Transition all'
+                    'Enter transition'
                 ],
                 option: option,
                 buttons: [
                     {
-                        text: 'Randomize',
+                        text: 'Create',
                         onclick() {
-                            chart.setOption({
-                                graphic: {
-                                    elements: [{
-                                        x: Math.random() * chart.getWidth(),
-                                        y: Math.random() * chart.getHeight(),
-                                        shape: {
-                                            r: Math.random() * 30 + 50
-                                        },
-                                        style: {
-                                            fill: echarts.color.random()
-                                        }
-                                    }]
+                            elements[1] = {
+                                type: 'circle',
+                                name: 'center',
+                                x: 300,
+                                y: 100,
+                                enterFrom: {
+                                    x: 0,
+                                    y: 100,
+                                    shape: {
+                                        r: 0
+                                    }
+                                },
+                                shape: {
+                                    cx: 0,
+                                    cy: 0,
+                                    r: 80
+                                },
+                                style: {
+                                    fill: 'orange'
                                 }
-                            })
+                            };
+                            chart.setOption(option)
+                        }
+                    },
+                    {
+                        text: 'Remove',
+                        onclick() {
+                            elements[1] = {
+                                leaveTo: {
+                                    x: 500,
+                                    y: 100,
+                                    shape: {
+                                        r: 0
+                                    }
+                                },
+                                $action: 'remove'
+                            };
+                            chart.setOption(option)
                         }
                     },
                 ]

---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org