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/18 15:28:19 UTC

[echarts] branch graphic-animation updated: feat(graphic): add transition api

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 fb3daa2  feat(graphic): add transition api
fb3daa2 is described below

commit fb3daa222758e1fcb300126a4c302f3f27a014c7
Author: pissang <bm...@gmail.com>
AuthorDate: Thu Nov 18 23:26:30 2021 +0800

    feat(graphic): add transition api
---
 src/animation/customGraphicTransitionHelper.ts |  28 +++---
 src/chart/custom/CustomSeries.ts               |  20 ++--
 src/chart/custom/CustomView.ts                 |  10 +-
 src/component/graphic/GraphicModel.ts          |  24 +++--
 src/component/graphic/GraphicView.ts           |  58 ++++++++----
 test/graphic-transition.html                   | 124 +++++++++++++++++++++++++
 6 files changed, 207 insertions(+), 57 deletions(-)

diff --git a/src/animation/customGraphicTransitionHelper.ts b/src/animation/customGraphicTransitionHelper.ts
index 45a5938..6ae2106 100644
--- a/src/animation/customGraphicTransitionHelper.ts
+++ b/src/animation/customGraphicTransitionHelper.ts
@@ -42,13 +42,13 @@ const LEGACY_TRANSFORM_PROPS = {
 type LegacyTransformProp = keyof typeof LEGACY_TRANSFORM_PROPS;
 
 export type CustomTransitionProps = string | string[];
-export interface ElementTransitionOptionMixin {
+export interface TransitionOptionMixin {
     transition?: CustomTransitionProps;
     enterFrom?: Dictionary<unknown>;
     leaveTo?: Dictionary<unknown>;
 };
 
-export type ElementTransformTransitionOptionMixin = {
+export type ElementTransitionOptionMixin = {
     transition?: ElementRootTransitionProp | ElementRootTransitionProp[];
     enterFrom?: Dictionary<number>;
     leaveTo?: Dictionary<number>;
@@ -72,16 +72,16 @@ interface LooseElementProps extends ElementProps {
 }
 
 type TransitionElementOption = Partial<Record<TransformProp, number>> & {
-    shape?: Dictionary<any> & ElementTransitionOptionMixin
-    style?: PathStyleProps & ElementTransitionOptionMixin
-    extra?: Dictionary<any> & ElementTransitionOptionMixin
+    shape?: Dictionary<any> & TransitionOptionMixin
+    style?: PathStyleProps & TransitionOptionMixin
+    extra?: Dictionary<any> & TransitionOptionMixin
     invisible?: boolean
     silent?: boolean
     autoBatch?: boolean
     ignore?: boolean
 
     during?: (params: TransitionDuringAPI) => void
-} & ElementTransformTransitionOptionMixin;
+} & ElementTransitionOptionMixin;
 
 const transitionInnerStore = makeInner<{
     leaveToProps: ElementProps;
@@ -141,7 +141,7 @@ export interface TransitionDuringAPI<
     getStyle<T extends keyof StyleOpt>(key: T): StyleOpt[T];
 };
 
-export function updateTransition(
+export function applyUpdateTransition(
     el: Element,
     elOption: TransitionElementOption,
     animatableModel?: Model<AnimationOptionMixin>,
@@ -171,20 +171,22 @@ export function updateTransition(
     styleOpt ? el.dirty() : el.markRedraw();
 }
 
-export function leaveTransition(
+export function applyLeaveTransition(
     el: Element,
-    seriesModel: Model<AnimationOptionMixin>
+    animatableModel: Model<AnimationOptionMixin>,
+    onRemove?: () => void
 ): void {
     if (el) {
         const parent = el.parent;
         const leaveToProps = transitionInnerStore(el).leaveToProps;
         leaveToProps
-            ? updateProps(el, leaveToProps, seriesModel, {
+            ? updateProps(el, leaveToProps, animatableModel, {
                 cb: function () {
                     parent.remove(el);
+                    onRemove && onRemove();
                 }
             })
-            : parent.remove(el);
+            : (parent.remove(el), onRemove && onRemove());
     }
 }
 
@@ -433,7 +435,7 @@ function prepareShapeOrExtraTransitionFrom(
     isInit: boolean
 ): void {
 
-    const attrOpt: Dictionary<unknown> & ElementTransitionOptionMixin = (elOption as any)[mainAttr];
+    const attrOpt: Dictionary<unknown> & TransitionOptionMixin = (elOption as any)[mainAttr];
     if (!attrOpt) {
         return;
     }
@@ -498,7 +500,7 @@ function prepareShapeOrExtraAllPropsFinal(
     elOption: TransitionElementOption,
     allProps: LooseElementProps
 ): void {
-    const attrOpt: Dictionary<unknown> & ElementTransitionOptionMixin = (elOption as any)[mainAttr];
+    const attrOpt: Dictionary<unknown> & TransitionOptionMixin = (elOption as any)[mainAttr];
     if (!attrOpt) {
         return;
     }
diff --git a/src/chart/custom/CustomSeries.ts b/src/chart/custom/CustomSeries.ts
index cf72658..3ef34d0 100644
--- a/src/chart/custom/CustomSeries.ts
+++ b/src/chart/custom/CustomSeries.ts
@@ -65,8 +65,8 @@ import {
 } from '../../util/graphic';
 import { TextStyleProps } from 'zrender/src/graphic/Text';
 import {
-    ElementTransformTransitionOptionMixin,
     ElementTransitionOptionMixin,
+    TransitionOptionMixin,
     TransitionBaseDuringAPI,
     TransitionDuringAPI
 } from '../../animation/customGraphicTransitionHelper';
@@ -104,7 +104,7 @@ type ShapeMorphingOption = {
 
 export interface CustomBaseElementOption extends Partial<Pick<
     Element, TransformProp | 'silent' | 'ignore' | 'textConfig'
->>, ElementTransformTransitionOptionMixin {
+>>, ElementTransitionOptionMixin {
     // element type, required.
     type: string;
     id?: string;
@@ -116,7 +116,7 @@ export interface CustomBaseElementOption extends Partial<Pick<
     // `false` means remove the clipPath
     clipPath?: CustomBaseZRPathOption | false;
     // `extra` can be set in any el option for custom prop for annimation duration.
-    extra?: Dictionary<unknown> & ElementTransitionOptionMixin;
+    extra?: Dictionary<unknown> & TransitionOptionMixin;
     // updateDuringAnimation
     during?(params: TransitionBaseDuringAPI): void;
 };
@@ -124,7 +124,7 @@ export interface CustomBaseElementOption extends Partial<Pick<
 export interface CustomDisplayableOption extends CustomBaseElementOption, Partial<Pick<
     Displayable, 'zlevel' | 'z' | 'z2' | 'invisible'
 >> {
-    style?: ZRStyleProps & ElementTransitionOptionMixin;
+    style?: ZRStyleProps & TransitionOptionMixin;
     during?(params: TransitionDuringAPI): void;
     /**
      * @deprecated
@@ -139,7 +139,7 @@ export interface CustomDisplayableOptionOnState extends Partial<Pick<
     Displayable, TransformProp | 'textConfig' | 'z2'
 >> {
     // `false` means remove emphasis trigger.
-    style?: (ZRStyleProps & ElementTransitionOptionMixin) | false;
+    style?: (ZRStyleProps & TransitionOptionMixin) | false;
 
 
     during?(params: TransitionDuringAPI): void;
@@ -156,7 +156,7 @@ export interface CustomGroupOption extends CustomBaseElementOption {
 export interface CustomBaseZRPathOption<T extends PathProps['shape'] = PathProps['shape']>
     extends CustomDisplayableOption, ShapeMorphingOption {
     autoBatch?: boolean;
-    shape?: T & ElementTransitionOptionMixin;
+    shape?: T & TransitionOptionMixin;
     style?: PathProps['style']
     during?(params: TransitionDuringAPI<PathStyleProps, T>): void;
 }
@@ -201,22 +201,22 @@ export type CustomPathOption = CreateCustomBuitinPathOption<keyof BuiltinShapes>
     | CustomSVGPathOption;
 
 export interface CustomImageOptionOnState extends CustomDisplayableOptionOnState {
-    style?: ImageStyleProps & ElementTransitionOptionMixin;
+    style?: ImageStyleProps & TransitionOptionMixin;
 }
 export interface CustomImageOption extends CustomDisplayableOption {
     type: 'image';
-    style?: ImageStyleProps & ElementTransitionOptionMixin;
+    style?: ImageStyleProps & TransitionOptionMixin;
     emphasis?: CustomImageOptionOnState;
     blur?: CustomImageOptionOnState;
     select?: CustomImageOptionOnState;
 }
 
 export interface CustomTextOptionOnState extends CustomDisplayableOptionOnState {
-    style?: TextStyleProps & ElementTransitionOptionMixin;
+    style?: TextStyleProps & TransitionOptionMixin;
 }
 export interface CustomTextOption extends CustomDisplayableOption {
     type: 'text';
-    style?: TextStyleProps & ElementTransitionOptionMixin;
+    style?: TextStyleProps & TransitionOptionMixin;
     emphasis?: CustomTextOptionOnState;
     blur?: CustomTextOptionOnState;
     select?: CustomTextOptionOnState;
diff --git a/src/chart/custom/CustomView.ts b/src/chart/custom/CustomView.ts
index 2621341..3dcf328 100644
--- a/src/chart/custom/CustomView.ts
+++ b/src/chart/custom/CustomView.ts
@@ -90,7 +90,7 @@ import CustomSeriesModel, {
 } from './CustomSeries';
 import { PatternObject } from 'zrender/src/graphic/Pattern';
 import { CustomSeriesOption } from '../../export/option';
-import { leaveTransition, updateTransition } from '../../animation/customGraphicTransitionHelper';
+import { applyLeaveTransition, applyUpdateTransition } from '../../animation/customGraphicTransitionHelper';
 
 const EMPHASIS = 'emphasis' as const;
 const NORMAL = 'normal' as const;
@@ -218,7 +218,7 @@ export default class CustomChartView extends ChartView {
                 );
             })
             .remove(function (oldIdx) {
-                leaveTransition(oldData.getItemGraphicEl(oldIdx), customSeries);
+                applyLeaveTransition(oldData.getItemGraphicEl(oldIdx), customSeries);
             })
             .update(function (newIdx, oldIdx) {
                 const oldEl = oldData.getItemGraphicEl(oldIdx);
@@ -475,7 +475,7 @@ function updateElNormal(
         }
     }
 
-    updateTransition(el, elOption, seriesModel, dataIndex, isInit);
+    applyUpdateTransition(el, elOption, seriesModel, dataIndex, isInit);
 
 
     if (!isTextContent) {
@@ -1310,7 +1310,7 @@ 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.
-        leaveTransition(el.childAt(i), seriesModel);
+        applyLeaveTransition(el.childAt(i), seriesModel);
     }
 }
 
@@ -1364,7 +1364,7 @@ function processAddUpdate(
 function processRemove(this: DataDiffer<DiffGroupContext>, oldIndex: number): void {
     const context = this.context;
     const child = context.oldChildren[oldIndex];
-    leaveTransition(child, context.seriesModel);
+    applyLeaveTransition(child, context.seriesModel);
 }
 
 /**
diff --git a/src/component/graphic/GraphicModel.ts b/src/component/graphic/GraphicModel.ts
index 5131597..92f0160 100644
--- a/src/component/graphic/GraphicModel.ts
+++ b/src/component/graphic/GraphicModel.ts
@@ -25,7 +25,8 @@ import {
     Dictionary,
     ZRStyleProps,
     OptionId,
-    CommonTooltipOption
+    CommonTooltipOption,
+    AnimationOptionMixin
 } from '../../util/types';
 import ComponentModel from '../../model/Component';
 import Element, { ElementTextConfig } from 'zrender/src/Element';
@@ -35,6 +36,7 @@ import { ImageStyleProps } from 'zrender/src/graphic/Image';
 import GlobalModel from '../../model/Global';
 import { TextStyleProps } from 'zrender/src/graphic/Text';
 import { copyLayoutParams, mergeLayoutParam } from '../../util/layout';
+import { ElementTransitionOptionMixin, TransitionOptionMixin } from '../../animation/customGraphicTransitionHelper';
 
 interface GraphicComponentBaseElementOption extends
     Partial<Pick<
@@ -116,9 +118,11 @@ interface GraphicComponentBaseElementOption extends
 export type TransformProp = 'x' | 'y' | 'scaleX' | 'scaleY' | 'originX' | 'originY' | 'skewX' | 'skewY' | 'rotation';
 
 export interface GraphicComponentDisplayableOption extends
-    GraphicComponentBaseElementOption, Partial<Pick<Displayable, 'zlevel' | 'z' | 'z2' | 'invisible' | 'cursor'>> {
+    GraphicComponentBaseElementOption,
+    ElementTransitionOptionMixin,
+    Partial<Pick<Displayable, 'zlevel' | 'z' | 'z2' | 'invisible' | 'cursor'>> {
 
-    style?: ZRStyleProps;
+    style?: ZRStyleProps & TransitionOptionMixin
 }
 // TODO: states?
 // interface GraphicComponentDisplayableOptionOnState extends Partial<Pick<
@@ -126,7 +130,8 @@ export interface GraphicComponentDisplayableOption extends
 // >> {
 //     style?: ZRStyleProps;
 // }
-export interface GraphicComponentGroupOption extends GraphicComponentBaseElementOption {
+export interface GraphicComponentGroupOption
+    extends GraphicComponentBaseElementOption, ElementTransitionOptionMixin {
     type?: 'group';
 
     /**
@@ -141,13 +146,13 @@ export interface GraphicComponentGroupOption extends GraphicComponentBaseElement
     // TODO: Can only set focus, blur on the root element.
     // children: Omit<GraphicComponentElementOption, 'focus' | 'blurScope'>[];
     children: GraphicComponentElementOption[];
-}
+};
 export interface GraphicComponentZRPathOption extends GraphicComponentDisplayableOption {
-    shape?: PathProps['shape'];
+    shape?: PathProps['shape'] & TransitionOptionMixin;
 }
 export interface GraphicComponentImageOption extends GraphicComponentDisplayableOption {
     type?: 'image';
-    style?: ImageStyleProps;
+    style?: ImageStyleProps & TransitionOptionMixin;
 }
 // TODO: states?
 // interface GraphicComponentImageOptionOnState extends GraphicComponentDisplayableOptionOnState {
@@ -174,11 +179,10 @@ export type GraphicComponentLooseOption = (GraphicComponentOption | GraphicCompo
     mainType?: 'graphic';
 };
 
-export interface GraphicComponentOption extends ComponentOption {
+export interface GraphicComponentOption extends ComponentOption, AnimationOptionMixin {
     // Note: elements is always behind its ancestors in this elements array.
     elements?: GraphicComponentElementOption[];
-}
-;
+};
 
 export function setKeyInfoToNewElOption(
     resultItem: ReturnType<typeof modelUtil.mappingToExists>[number],
diff --git a/src/component/graphic/GraphicView.ts b/src/component/graphic/GraphicView.ts
index 8d3f5c2..e6f5f15 100644
--- a/src/component/graphic/GraphicView.ts
+++ b/src/component/graphic/GraphicView.ts
@@ -37,8 +37,9 @@ import {
     GraphicComponentGroupOption,
     GraphicComponentElementOption
 } from './GraphicModel';
+import { applyLeaveTransition, applyUpdateTransition } from '../../animation/customGraphicTransitionHelper';
 
-const _nonShapeGraphicElements = {
+const nonShapeGraphicElements = {
     // Reserved but not supported in graphic component.
     path: null as unknown,
     compoundPath: null as unknown,
@@ -48,7 +49,7 @@ const _nonShapeGraphicElements = {
     image: graphicUtil.Image,
     text: graphicUtil.Text
 } as const;
-type NonShapeGraphicElementType = keyof typeof _nonShapeGraphicElements;
+type NonShapeGraphicElementType = keyof typeof nonShapeGraphicElements;
 
 export const inner = modelUtil.makeInner<{
     widthOption: number;
@@ -152,16 +153,34 @@ export class GraphicComponentView extends ComponentView {
 
             const $action = elOption.$action || 'merge';
             if ($action === 'merge') {
-                elExisting
-                    ? elExisting.attr(elOptionCleaned)
-                    : createEl(id, targetElParent, elOptionCleaned, elMap);
+                const isInit = !elExisting;
+                let el = elExisting;
+                if (isInit) {
+                    el = createEl(id, targetElParent, elOption.type, elMap);
+                }
+                el && applyUpdateTransition(
+                    el,
+                    elOptionCleaned,
+                    graphicModel,
+                    0,  // TODO Fixed dataIndex to be 0
+                    isInit
+                );
             }
             else if ($action === 'replace') {
-                removeEl(elExisting, elMap);
-                createEl(id, targetElParent, elOptionCleaned, elMap);
+                removeEl(elExisting, elMap, graphicModel);
+                const el = createEl(id, targetElParent, elOption.type, elMap);
+                if (el) {
+                    applyUpdateTransition(
+                        el,
+                        elOptionCleaned,
+                        graphicModel,
+                        0,  // TODO Fixed dataIndex to be 0
+                        true
+                    );
+                }
             }
             else if ($action === 'remove') {
-                removeEl(elExisting, elMap);
+                removeEl(elExisting, elMap, graphicModel);
             }
 
             const el = elMap.get(id);
@@ -266,8 +285,8 @@ export class GraphicComponentView extends ComponentView {
      */
     private _clear(): void {
         const elMap = this._elMap;
-        elMap.each(function (el) {
-            removeEl(el, elMap);
+        elMap.each((el) => {
+            removeEl(el, elMap, this._lastGraphicModel);
         });
         this._elMap = zrUtil.createHashMap();
     }
@@ -279,20 +298,19 @@ export class GraphicComponentView extends ComponentView {
 function createEl(
     id: string,
     targetElParent: graphicUtil.Group,
-    elOption: GraphicComponentElementOption,
+    graphicType: string,
     elMap: ElementMap
-): void {
-    const graphicType = elOption.type;
+): Element {
 
     if (__DEV__) {
         zrUtil.assert(graphicType, 'graphic type MUST be set');
     }
 
     const Clz = (
-        zrUtil.hasOwn(_nonShapeGraphicElements, graphicType)
+        zrUtil.hasOwn(nonShapeGraphicElements, graphicType)
             // Those graphic elements are not shapes. They should not be
             // overwritten by users, so do them first.
-            ? _nonShapeGraphicElements[graphicType as NonShapeGraphicElementType]
+            ? nonShapeGraphicElements[graphicType as NonShapeGraphicElementType]
             : graphicUtil.getShapeClass(graphicType)
     ) as { new(opt: GraphicComponentElementOption): Element; };
 
@@ -300,19 +318,21 @@ function createEl(
         zrUtil.assert(Clz, 'graphic type can not be found');
     }
 
-    const el = new Clz(elOption);
+    const el = new Clz({});
     targetElParent.add(el);
     elMap.set(id, el);
     inner(el).id = id;
+
+    return el;
 }
-function removeEl(elExisting: Element, elMap: ElementMap): void {
+function removeEl(elExisting: Element, elMap: ElementMap, graphicModel: GraphicComponentModel): void {
     const existElParent = elExisting && elExisting.parent;
     if (existElParent) {
         elExisting.type === 'group' && elExisting.traverse(function (el) {
-            removeEl(el, elMap);
+            removeEl(el, elMap, graphicModel);
         });
+        applyLeaveTransition(elExisting, graphicModel);
         elMap.removeKey(inner(elExisting).id);
-        existElParent.remove(elExisting);
     }
 }
 // Remove unnecessary props to avoid potential problems.
diff --git a/test/graphic-transition.html b/test/graphic-transition.html
new file mode 100644
index 0000000..20bd92c
--- /dev/null
+++ b/test/graphic-transition.html
@@ -0,0 +1,124 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+
+
+<html>
+    <head>
+        <meta charset="utf-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        <script src="lib/simpleRequire.js"></script>
+        <script src="lib/config.js"></script>
+        <script src="lib/jquery.min.js"></script>
+        <script src="lib/facePrint.js"></script>
+        <script src="lib/testHelper.js"></script>
+        <!-- <script src="ut/lib/canteen.js"></script> -->
+        <link rel="stylesheet" href="lib/reset.css" />
+    </head>
+    <body>
+        <style>
+        </style>
+
+
+
+        <div id="main0"></div>
+        <div id="main1"></div>
+
+
+
+
+        <script>
+        require(['echarts'/*, 'map/js/china' */], function (echarts) {
+            var option = {
+                graphic: {
+                    elements: [{
+                        type: 'circle',
+                        x: 100,
+                        y: 50,
+                        shape: {
+                            cx: 0,
+                            cy: 0,
+                            r: 50
+                        },
+                        style: {
+                            fill: 'orange'
+                        }
+                    }]
+                }
+            }
+
+            var chart = testHelper.create(echarts, 'main0', {
+                title: [
+                    'Basic transition'
+                ],
+                option: option,
+                buttons: [
+                    {
+                        text: 'Move to x: 200',
+                        onclick() {
+                            chart.setOption({
+                                graphic: {
+                                    elements: [{
+                                        type: 'circle',
+                                        transition: ['x', 'y'],
+                                        x: 200
+                                    }]
+                                }
+                            })
+                        }
+                    },
+
+
+                    {
+                        text: 'Move to y: 200',
+                        onclick() {
+                            chart.setOption({
+                                graphic: {
+                                    elements: [{
+                                        type: 'circle',
+                                        transition: ['x', 'y'],
+                                        y: 200
+                                    }]
+                                }
+                            })
+                        }
+                    },
+                ]
+            });
+
+        });
+        </script>
+
+
+
+
+
+
+
+        <script>
+        require(['echarts'/*, 'map/js/china' */], function (echarts) {
+            var option;
+
+        });
+        </script>
+
+
+    </body>
+</html>
+

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