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/01/19 08:58:30 UTC

[echarts] 01/01: wip: refacting morphing in custom series

This is an automated email from the ASF dual-hosted git repository.

shenyi pushed a commit to branch enhance-morph
in repository https://gitbox.apache.org/repos/asf/echarts.git

commit 29a18593242cdcc337452ce5b3558f69b93c1dea
Author: pissang <bm...@gmail.com>
AuthorDate: Tue Jan 19 16:56:57 2021 +0800

    wip: refacting morphing in custom series
---
 src/chart/custom/CustomSeries.ts               |  364 ++++
 src/chart/custom/{install.ts => CustomView.ts} | 1369 ++----------
 src/chart/custom/install.ts                    | 2763 +-----------------------
 src/chart/custom/prepare.ts                    |  424 ++++
 src/chart/custom/transitionHelper.ts           |  186 ++
 src/component/toolbox/feature/DataView.ts      |    2 +
 src/component/toolbox/feature/SaveAsImage.ts   |    2 +-
 src/component/tooltip/TooltipHTMLContent.ts    |    2 +
 src/coord/CoordinateSystem.ts                  |    2 +-
 src/core/echarts.ts                            |    9 +-
 src/export/option.ts                           |    2 +-
 src/model/Series.ts                            |    2 -
 src/util/quickSelect.ts                        |    1 -
 src/util/types.ts                              |    2 +-
 14 files changed, 1149 insertions(+), 3981 deletions(-)

diff --git a/src/chart/custom/CustomSeries.ts b/src/chart/custom/CustomSeries.ts
new file mode 100644
index 0000000..58c0da8
--- /dev/null
+++ b/src/chart/custom/CustomSeries.ts
@@ -0,0 +1,364 @@
+/*
+* 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 Displayable from 'zrender/src/graphic/Displayable';
+import { ImageStyleProps } from 'zrender/src/graphic/Image';
+import { PathProps, PathStyleProps } from 'zrender/src/graphic/Path';
+import { PatternObject } from 'zrender/src/graphic/Pattern';
+import { ZRenderType } from 'zrender/src/zrender';
+import { BarGridLayoutOptionForCustomSeries, BarGridLayoutResult } from '../../layout/barGrid';
+import {
+    BlurScope,
+    CallbackDataParams,
+    DecalObject,
+    Dictionary,
+    DimensionLoose,
+    ItemStyleOption,
+    LabelOption,
+    OptionDataValue,
+    OrdinalRawValue,
+    ParsedValue,
+    SeriesDataType,
+    SeriesEncodeOptionMixin,
+    SeriesOnCalendarOptionMixin,
+    SeriesOnCartesianOptionMixin,
+    SeriesOnGeoOptionMixin,
+    SeriesOnPolarOptionMixin,
+    SeriesOnSingleOptionMixin,
+    SeriesOption,
+    TextCommonOption,
+    ZRStyleProps
+} from '../../util/types';
+import Element, { ElementProps } from 'zrender/src/Element';
+import { SeriesModel } from '../../export/api';
+import List, { DefaultDataVisual } from '../../data/List';
+import GlobalModel from '../../model/Global';
+import createListFromArray from '../helper/createListFromArray';
+import { makeInner } from '../../util/model';
+import { CoordinateSystem } from '../../coord/CoordinateSystem';
+
+
+export interface LooseElementProps extends ElementProps {
+    style?: ZRStyleProps;
+    shape?: Dictionary<unknown>;
+}
+
+export type CustomExtraElementInfo = Dictionary<unknown>;
+export const TRANSFORM_PROPS = {
+    x: 1,
+    y: 1,
+    scaleX: 1,
+    scaleY: 1,
+    originX: 1,
+    originY: 1,
+    rotation: 1
+} as const;
+export type TransformProp = keyof typeof TRANSFORM_PROPS;
+
+// Also compat with ec4, where
+// `visual('color') visual('borderColor')` is supported.
+export const STYLE_VISUAL_TYPE = {
+    color: 'fill',
+    borderColor: 'stroke'
+} as const;
+export type StyleVisualProps = keyof typeof STYLE_VISUAL_TYPE;
+
+export const NON_STYLE_VISUAL_PROPS = {
+    symbol: 1,
+    symbolSize: 1,
+    symbolKeepAspect: 1,
+    legendSymbol: 1,
+    visualMeta: 1,
+    liftZ: 1,
+    decal: 1
+} as const;
+export type NonStyleVisualProps = keyof typeof NON_STYLE_VISUAL_PROPS;
+
+// Do not declare "Dictionary" in TransitionAnyOption to restrict the type check.
+export type TransitionAnyOption = {
+    transition?: TransitionAnyProps;
+    enterFrom?: Dictionary<unknown>;
+    leaveTo?: Dictionary<unknown>;
+};
+type TransitionAnyProps = string | string[];
+type TransitionTransformOption = {
+    transition?: ElementRootTransitionProp | ElementRootTransitionProp[];
+    enterFrom?: Dictionary<unknown>;
+    leaveTo?: Dictionary<unknown>;
+};
+type ElementRootTransitionProp = TransformProp | 'shape' | 'extra' | 'style';
+type ShapeMorphingOption = {
+    /**
+     * If do shape morphing animation when type is changed.
+     * Only available on path.
+     */
+    morph?: boolean
+};
+
+export interface CustomDuringAPI {
+    // Usually other props do not need to be changed in animation during.
+    setTransform(key: TransformProp, val: unknown): CustomDuringAPI
+    getTransform(key: TransformProp): unknown;
+    setShape(key: string, val: unknown): CustomDuringAPI;
+    getShape(key: string): unknown;
+    setStyle(key: string, val: unknown): CustomDuringAPI
+    getStyle(key: string): unknown;
+    setExtra(key: string, val: unknown): CustomDuringAPI
+    getExtra(key: string): unknown
+};
+
+
+export interface CustomBaseElementOption extends Partial<Pick<
+    Element, TransformProp | 'silent' | 'ignore' | 'textConfig'
+>>, TransitionTransformOption {
+    // element type, mandatory.
+    type: string;
+    id?: string;
+    // For animation diff.
+    name?: string;
+    info?: CustomExtraElementInfo;
+    // `false` means remove the textContent.
+    textContent?: CustomTextOption | false;
+    // `false` means remove the clipPath
+    clipPath?: CustomZRPathOption | false;
+    // `extra` can be set in any el option for custom prop for annimation duration.
+    extra?: TransitionAnyOption;
+    // updateDuringAnimation
+    during?(params: CustomDuringAPI): void;
+
+    focus?: 'none' | 'self' | 'series' | ArrayLike<number>
+    blurScope?: BlurScope
+};
+export interface CustomDisplayableOption extends CustomBaseElementOption, Partial<Pick<
+    Displayable, 'zlevel' | 'z' | 'z2' | 'invisible'
+>> {
+    style?: ZRStyleProps & TransitionAnyOption;
+    // `false` means remove emphasis trigger.
+    styleEmphasis?: ZRStyleProps | false;
+    emphasis?: CustomDisplayableOptionOnState;
+    blur?: CustomDisplayableOptionOnState;
+    select?: CustomDisplayableOptionOnState;
+}
+export interface CustomDisplayableOptionOnState extends Partial<Pick<
+    Displayable, TransformProp | 'textConfig' | 'z2'
+>> {
+    // `false` means remove emphasis trigger.
+    style?: (ZRStyleProps & TransitionAnyOption) | false;
+}
+export interface CustomGroupOption extends CustomBaseElementOption {
+    type: 'group';
+    width?: number;
+    height?: number;
+    // @deprecated
+    diffChildrenByName?: boolean;
+    // Can only set focus, blur on the root element.
+    children: Omit<CustomElementOption, 'focus' | 'blurScope'>[];
+    $mergeChildren: false | 'byName' | 'byIndex';
+}
+export interface CustomZRPathOption extends CustomDisplayableOption, ShapeMorphingOption {
+    shape?: PathProps['shape'] & TransitionAnyOption;
+    style?: CustomDisplayableOption['style'] & {
+        decal?: DecalObject;
+        // Only internal usage. Any user specified value will be overwritten.
+        __decalPattern?: PatternObject;
+    };
+}
+export interface CustomSVGPathOption extends CustomDisplayableOption, ShapeMorphingOption {
+    type: 'path';
+    shape?: {
+        // SVG Path, like 'M0,0 L0,-20 L70,-1 L70,0 Z'
+        pathData?: string;
+        // "d" is the alias of `pathData` follows the SVG convention.
+        d?: string;
+        layout?: 'center' | 'cover';
+        x?: number;
+        y?: number;
+        width?: number;
+        height?: number;
+    } & TransitionAnyOption;
+}
+export interface CustomImageOption extends CustomDisplayableOption {
+    type: 'image';
+    style?: ImageStyleProps & TransitionAnyOption;
+    emphasis?: CustomImageOptionOnState;
+    blur?: CustomImageOptionOnState;
+    select?: CustomImageOptionOnState;
+}
+export interface CustomImageOptionOnState extends CustomDisplayableOptionOnState {
+    style?: ImageStyleProps & TransitionAnyOption;
+}
+export interface CustomTextOption extends CustomDisplayableOption {
+    type: 'text';
+}
+export type CustomElementOption = CustomZRPathOption | CustomSVGPathOption | CustomImageOption | CustomTextOption;
+export type CustomElementOptionOnState = CustomDisplayableOptionOnState | CustomImageOptionOnState;
+
+export interface CustomSeriesRenderItemAPI extends
+        CustomSeriesRenderItemCoordinateSystemAPI {
+
+    // Methods from ExtensionAPI.
+    // NOTE: Not using Pick<ExtensionAPI> here because we don't want to bundle ExtensionAPI into the d.ts
+    getWidth(): number
+    getHeight(): number
+    getZr(): ZRenderType
+    getDevicePixelRatio(): number
+
+    value(dim: DimensionLoose, dataIndexInside?: number): ParsedValue;
+    ordinalRawValue(dim: DimensionLoose, dataIndexInside?: number): ParsedValue | OrdinalRawValue;
+    style(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps;
+    styleEmphasis(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps;
+    visual<VT extends NonStyleVisualProps | StyleVisualProps>(
+        visualType: VT,
+        dataIndexInside?: number
+    ): VT extends NonStyleVisualProps ? DefaultDataVisual[VT]
+        : VT extends StyleVisualProps ? PathStyleProps[typeof STYLE_VISUAL_TYPE[VT]]
+        : void;
+    barLayout(opt: BarGridLayoutOptionForCustomSeries): BarGridLayoutResult;
+    currentSeriesIndices(): number[];
+    font(opt: Pick<TextCommonOption, 'fontStyle' | 'fontWeight' | 'fontSize' | 'fontFamily'>): string;
+}
+export interface CustomSeriesRenderItemParamsCoordSys {
+    type: string;
+    // And extra params for each coordinate systems.
+}
+export interface CustomSeriesRenderItemCoordinateSystemAPI {
+    coord(
+        data: OptionDataValue | OptionDataValue[],
+        clamp?: boolean
+    ): number[];
+    size?(
+        dataSize: OptionDataValue | OptionDataValue[],
+        dataItem: OptionDataValue | OptionDataValue[]
+    ): number | number[];
+}
+
+export type WrapEncodeDefRet = Dictionary<number[]>;
+
+export interface CustomSeriesRenderItemParams {
+    context: Dictionary<unknown>;
+    seriesId: string;
+    seriesName: string;
+    seriesIndex: number;
+    coordSys: CustomSeriesRenderItemParamsCoordSys;
+    dataInsideLength: number;
+    encode: WrapEncodeDefRet;
+}
+type CustomSeriesRenderItem = (
+    params: CustomSeriesRenderItemParams,
+    api: CustomSeriesRenderItemAPI
+) => CustomElementOption;
+
+interface CustomSeriesStateOption {
+    itemStyle?: ItemStyleOption;
+    label?: LabelOption;
+}
+
+export interface CustomSeriesOption extends
+    SeriesOption<never>,    // don't support StateOption in custom series.
+    SeriesEncodeOptionMixin,
+    SeriesOnCartesianOptionMixin,
+    SeriesOnPolarOptionMixin,
+    SeriesOnSingleOptionMixin,
+    SeriesOnGeoOptionMixin,
+    SeriesOnCalendarOptionMixin {
+
+    type?: 'custom'
+
+    // If set as 'none', do not depends on coord sys.
+    coordinateSystem?: string | 'none';
+
+    renderItem?: CustomSeriesRenderItem;
+
+    // Only works on polar and cartesian2d coordinate system.
+    clip?: boolean;
+}
+
+export interface LegacyCustomSeriesOption extends SeriesOption<CustomSeriesStateOption>, CustomSeriesStateOption {}
+
+export const customInnerStore = makeInner<{
+    info: CustomExtraElementInfo;
+    customPathData: string;
+    customGraphicType: string;
+    customImagePath: CustomImageOption['style']['image'];
+    // customText: string;
+    txConZ2Set: number;
+    leaveToProps: ElementProps;
+    // Can morph: "morph" specified in option and el is Path.
+    morph: boolean;
+    propsToSet: ElementProps;
+    propsToAnimate: ElementProps;
+    userDuring: CustomBaseElementOption['during'];
+}, Element>();
+
+export default class CustomSeriesModel extends SeriesModel<CustomSeriesOption> {
+
+    static type = 'series.custom';
+    readonly type = CustomSeriesModel.type;
+
+    static dependencies = ['grid', 'polar', 'geo', 'singleAxis', 'calendar'];
+
+    // preventAutoZ = true;
+
+    currentZLevel: number;
+    currentZ: number;
+
+    static defaultOption: CustomSeriesOption = {
+        coordinateSystem: 'cartesian2d', // Can be set as 'none'
+        zlevel: 0,
+        z: 2,
+        legendHoverLink: true,
+
+        // Custom series will not clip by default.
+        // Some case will use custom series to draw label
+        // For example https://echarts.apache.org/examples/en/editor.html?c=custom-gantt-flight
+        clip: false
+
+        // Cartesian coordinate system
+        // xAxisIndex: 0,
+        // yAxisIndex: 0,
+
+        // Polar coordinate system
+        // polarIndex: 0,
+
+        // Geo coordinate system
+        // geoIndex: 0,
+    };
+
+    optionUpdated() {
+        this.currentZLevel = this.get('zlevel', true);
+        this.currentZ = this.get('z', true);
+    }
+
+    getInitialData(option: CustomSeriesOption, ecModel: GlobalModel): List {
+        return createListFromArray(this.getSource(), this);
+    }
+
+    getDataParams(dataIndex: number, dataType?: SeriesDataType, el?: Element): CallbackDataParams & {
+        info: CustomExtraElementInfo
+    } {
+        const params = super.getDataParams(dataIndex, dataType) as ReturnType<CustomSeriesModel['getDataParams']>;
+        el && (params.info = customInnerStore(el).info);
+        return params;
+    }
+}
+
+export type PrepareCustomInfo = (coordSys: CoordinateSystem) => {
+    coordSys: CustomSeriesRenderItemParamsCoordSys;
+    api: CustomSeriesRenderItemCoordinateSystemAPI
+};
diff --git a/src/chart/custom/install.ts b/src/chart/custom/CustomView.ts
similarity index 55%
copy from src/chart/custom/install.ts
copy to src/chart/custom/CustomView.ts
index e95d1ae..3045746 100644
--- a/src/chart/custom/install.ts
+++ b/src/chart/custom/CustomView.ts
@@ -19,43 +19,31 @@
 
 import {
     hasOwn, assert, isString, retrieve2, retrieve3, defaults, each,
-    keys, isArrayLike, bind, isFunction, eqNaN, indexOf, clone
+    keys, bind, eqNaN, indexOf, curry
 } from 'zrender/src/core/util';
 import * as graphicUtil from '../../util/graphic';
 import { setDefaultStateProxy, enableHoverEmphasis } from '../../util/states';
 import * as labelStyleHelper from '../../label/labelStyle';
 import {getDefaultLabel} from '../helper/labelHelper';
-import createListFromArray from '../helper/createListFromArray';
-import {getLayoutOnAxis, BarGridLayoutResult, BarGridLayoutOptionForCustomSeries} from '../../layout/barGrid';
+import {getLayoutOnAxis} from '../../layout/barGrid';
 import DataDiffer, { DataDiffMode } from '../../data/DataDiffer';
-import SeriesModel from '../../model/Series';
 import Model from '../../model/Model';
 import ChartView from '../../view/Chart';
 import {createClipPath} from '../helper/createClipPathFromCoordSys';
 import {
-    EventQueryItem, ECEvent, SeriesOption, SeriesOnCartesianOptionMixin,
-    SeriesOnPolarOptionMixin, SeriesOnSingleOptionMixin, SeriesOnGeoOptionMixin,
-    SeriesOnCalendarOptionMixin, ItemStyleOption, SeriesEncodeOptionMixin,
+    EventQueryItem, ECEvent,
     DimensionLoose,
     ParsedValue,
     Dictionary,
-    CallbackDataParams,
     Payload,
     StageHandlerProgressParams,
-    LabelOption,
     ViewRootGroup,
-    OptionDataValue,
     ZRStyleProps,
     DisplayState,
     ECElement,
     DisplayStateNonNormal,
-    BlurScope,
-    SeriesDataType,
     OrdinalRawValue,
-    PayloadAnimationPart,
-    DecalObject,
-    InnerDecalObject,
-    TextCommonOption
+    InnerDecalObject
 } from '../../util/types';
 import Element, { ElementProps, ElementTextConfig } from 'zrender/src/Element';
 import prepareCartesian2d from '../../coord/cartesian/prepareCustom';
@@ -65,13 +53,11 @@ import preparePolar from '../../coord/polar/prepareCustom';
 import prepareCalendar from '../../coord/calendar/prepareCustom';
 import List, { DefaultDataVisual } from '../../data/List';
 import GlobalModel from '../../model/Global';
-import { makeInner, normalizeToArray } from '../../util/model';
 import ExtensionAPI from '../../core/ExtensionAPI';
 import Displayable from 'zrender/src/graphic/Displayable';
 import Axis2D from '../../coord/cartesian/Axis2D';
 import { RectLike } from 'zrender/src/core/BoundingRect';
-import { PathProps, PathStyleProps } from 'zrender/src/graphic/Path';
-import { ImageStyleProps } from 'zrender/src/graphic/Image';
+import { PathStyleProps } from 'zrender/src/graphic/Path';
 import { CoordinateSystem } from '../../coord/CoordinateSystem';
 import { TextStyleProps } from 'zrender/src/graphic/Text';
 import {
@@ -81,257 +67,47 @@ import {
     LegacyStyleProps,
     warnDeprecated
 } from '../../util/styleCompat';
-import Transformable from 'zrender/src/core/Transformable';
 import { ItemStyleProps } from '../../model/mixin/itemStyle';
-import { cloneValue } from 'zrender/src/animation/Animator';
 import { warn, throwError } from '../../util/log';
-import {
-    combine, isInAnyMorphing, morphPath, isCombiningPath, CombineSeparateConfig, separate, CombineSeparateResult
-} from 'zrender/src/tool/morphPath';
-import { AnimationEasing } from 'zrender/src/animation/easing';
-import * as matrix from 'zrender/src/core/matrix';
-import { PatternObject } from 'zrender/src/graphic/Pattern';
 import { createOrUpdatePatternFromDecal } from '../../util/decal';
-import { ZRenderType } from 'zrender/src/zrender';
-import { EChartsExtensionInstallRegisters } from '../../extension';
-
-
-const inner = makeInner<{
-    info: CustomExtraElementInfo;
-    customPathData: string;
-    customGraphicType: string;
-    customImagePath: CustomImageOption['style']['image'];
-    // customText: string;
-    txConZ2Set: number;
-    leaveToProps: ElementProps;
-    // Can morph: "morph" specified in option and el is Path.
-    canMorph: boolean;
-    userDuring: CustomBaseElementOption['during'];
-}, Element>();
-
-type CustomExtraElementInfo = Dictionary<unknown>;
-const TRANSFORM_PROPS = {
-    x: 1,
-    y: 1,
-    scaleX: 1,
-    scaleY: 1,
-    originX: 1,
-    originY: 1,
-    rotation: 1
-} as const;
-type TransformProp = keyof typeof TRANSFORM_PROPS;
-const transformPropNamesStr = keys(TRANSFORM_PROPS).join(', ');
-
-// Do not declare "Dictionary" in TransitionAnyOption to restrict the type check.
-type TransitionAnyOption = {
-    transition?: TransitionAnyProps;
-    enterFrom?: Dictionary<unknown>;
-    leaveTo?: Dictionary<unknown>;
-};
-type TransitionAnyProps = string | string[];
-type TransitionTransformOption = {
-    transition?: ElementRootTransitionProp | ElementRootTransitionProp[];
-    enterFrom?: Dictionary<unknown>;
-    leaveTo?: Dictionary<unknown>;
-};
-type ElementRootTransitionProp = TransformProp | 'shape' | 'extra' | 'style';
-type ShapeMorphingOption = {
-    /**
-     * If do shape morphing animation when type is changed.
-     * Only available on path.
-     */
-    morph?: boolean
-};
-
-interface CustomBaseElementOption extends Partial<Pick<
-    Element, TransformProp | 'silent' | 'ignore' | 'textConfig'
->>, TransitionTransformOption {
-    // element type, mandatory.
-    type: string;
-    id?: string;
-    // For animation diff.
-    name?: string;
-    info?: CustomExtraElementInfo;
-    // `false` means remove the textContent.
-    textContent?: CustomTextOption | false;
-    // `false` means remove the clipPath
-    clipPath?: CustomZRPathOption | false;
-    // `extra` can be set in any el option for custom prop for annimation duration.
-    extra?: TransitionAnyOption;
-    // updateDuringAnimation
-    during?(params: typeof customDuringAPI): void;
-
-    focus?: 'none' | 'self' | 'series' | ArrayLike<number>
-    blurScope?: BlurScope
-};
-interface CustomDisplayableOption extends CustomBaseElementOption, Partial<Pick<
-    Displayable, 'zlevel' | 'z' | 'z2' | 'invisible'
->> {
-    style?: ZRStyleProps & TransitionAnyOption;
-    // `false` means remove emphasis trigger.
-    styleEmphasis?: ZRStyleProps | false;
-    emphasis?: CustomDisplayableOptionOnState;
-    blur?: CustomDisplayableOptionOnState;
-    select?: CustomDisplayableOptionOnState;
-}
-interface CustomDisplayableOptionOnState extends Partial<Pick<
-    Displayable, TransformProp | 'textConfig' | 'z2'
->> {
-    // `false` means remove emphasis trigger.
-    style?: (ZRStyleProps & TransitionAnyOption) | false;
-}
-interface CustomGroupOption extends CustomBaseElementOption {
-    type: 'group';
-    width?: number;
-    height?: number;
-    // @deprecated
-    diffChildrenByName?: boolean;
-    // Can only set focus, blur on the root element.
-    children: Omit<CustomElementOption, 'focus' | 'blurScope'>[];
-    $mergeChildren: false | 'byName' | 'byIndex';
-}
-interface CustomZRPathOption extends CustomDisplayableOption, ShapeMorphingOption {
-    shape?: PathProps['shape'] & TransitionAnyOption;
-    style?: CustomDisplayableOption['style'] & {
-        decal?: DecalObject;
-        // Only internal usage. Any user specified value will be overwritten.
-        __decalPattern?: PatternObject;
-    };
-}
-interface CustomSVGPathOption extends CustomDisplayableOption, ShapeMorphingOption {
-    type: 'path';
-    shape?: {
-        // SVG Path, like 'M0,0 L0,-20 L70,-1 L70,0 Z'
-        pathData?: string;
-        // "d" is the alias of `pathData` follows the SVG convention.
-        d?: string;
-        layout?: 'center' | 'cover';
-        x?: number;
-        y?: number;
-        width?: number;
-        height?: number;
-    } & TransitionAnyOption;
-}
-interface CustomImageOption extends CustomDisplayableOption {
-    type: 'image';
-    style?: ImageStyleProps & TransitionAnyOption;
-    emphasis?: CustomImageOptionOnState;
-    blur?: CustomImageOptionOnState;
-    select?: CustomImageOptionOnState;
-}
-interface CustomImageOptionOnState extends CustomDisplayableOptionOnState {
-    style?: ImageStyleProps & TransitionAnyOption;
-}
-interface CustomTextOption extends CustomDisplayableOption {
-    type: 'text';
-}
-type CustomElementOption = CustomZRPathOption | CustomSVGPathOption | CustomImageOption | CustomTextOption;
-type CustomElementOptionOnState = CustomDisplayableOptionOnState | CustomImageOptionOnState;
-
-
-export interface CustomSeriesRenderItemAPI extends
-        CustomSeriesRenderItemCoordinateSystemAPI {
-
-    // Methods from ExtensionAPI.
-    // NOTE: Not using Pick<ExtensionAPI> here because we don't want to bundle ExtensionAPI into the d.ts
-    getWidth(): number
-    getHeight(): number
-    getZr(): ZRenderType
-    getDevicePixelRatio(): number
-
-    value(dim: DimensionLoose, dataIndexInside?: number): ParsedValue;
-    ordinalRawValue(dim: DimensionLoose, dataIndexInside?: number): ParsedValue | OrdinalRawValue;
-    style(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps;
-    styleEmphasis(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps;
-    visual<VT extends NonStyleVisualProps | StyleVisualProps>(
-        visualType: VT,
-        dataIndexInside?: number
-    ): VT extends NonStyleVisualProps ? DefaultDataVisual[VT]
-        : VT extends StyleVisualProps ? PathStyleProps[typeof STYLE_VISUAL_TYPE[VT]]
-        : void;
-    barLayout(opt: BarGridLayoutOptionForCustomSeries): BarGridLayoutResult;
-    currentSeriesIndices(): number[];
-    font(opt: Pick<TextCommonOption, 'fontStyle' | 'fontWeight' | 'fontSize' | 'fontFamily'>): string;
-}
-interface CustomSeriesRenderItemParamsCoordSys {
-    type: string;
-    // And extra params for each coordinate systems.
-}
-interface CustomSeriesRenderItemCoordinateSystemAPI {
-    coord(
-        data: OptionDataValue | OptionDataValue[],
-        clamp?: boolean
-    ): number[];
-    size?(
-        dataSize: OptionDataValue | OptionDataValue[],
-        dataItem: OptionDataValue | OptionDataValue[]
-    ): number | number[];
-}
-export interface CustomSeriesRenderItemParams {
-    context: Dictionary<unknown>;
-    seriesId: string;
-    seriesName: string;
-    seriesIndex: number;
-    coordSys: CustomSeriesRenderItemParamsCoordSys;
-    dataInsideLength: number;
-    encode: WrapEncodeDefRet;
-}
-type CustomSeriesRenderItem = (
-    params: CustomSeriesRenderItemParams,
-    api: CustomSeriesRenderItemAPI
-) => CustomElementOption;
-
-interface CustomSeriesStateOption {
-    itemStyle?: ItemStyleOption;
-    label?: LabelOption;
-}
-
-export interface CustomSeriesOption extends
-    SeriesOption<never>,    // don't support StateOption in custom series.
-    SeriesEncodeOptionMixin,
-    SeriesOnCartesianOptionMixin,
-    SeriesOnPolarOptionMixin,
-    SeriesOnSingleOptionMixin,
-    SeriesOnGeoOptionMixin,
-    SeriesOnCalendarOptionMixin {
-
-    type?: 'custom'
-
-    // If set as 'none', do not depends on coord sys.
-    coordinateSystem?: string | 'none';
-
-    renderItem?: CustomSeriesRenderItem;
-
-    // Only works on polar and cartesian2d coordinate system.
-    clip?: boolean;
-}
-
-interface LegacyCustomSeriesOption extends SeriesOption<CustomSeriesStateOption>, CustomSeriesStateOption {}
-
-
-interface LooseElementProps extends ElementProps {
-    style?: ZRStyleProps;
-    shape?: Dictionary<unknown>;
-}
+import CustomSeriesModel, {
+    CustomDuringAPI,
+    TransformProp,
+    TRANSFORM_PROPS,
+    CustomImageOption,
+    CustomBaseElementOption,
+    CustomElementOption,
+    CustomElementOptionOnState,
+    CustomSeriesRenderItemParamsCoordSys,
+    CustomSeriesRenderItemCoordinateSystemAPI,
+    CustomSVGPathOption,
+    CustomZRPathOption,
+    CustomDisplayableOption,
+    CustomSeriesRenderItemAPI,
+    CustomSeriesRenderItemParams,
+    LegacyCustomSeriesOption,
+    CustomGroupOption,
+    WrapEncodeDefRet,
+    NonStyleVisualProps,
+    StyleVisualProps,
+    STYLE_VISUAL_TYPE,
+    NON_STYLE_VISUAL_PROPS,
+    customInnerStore,
+    LooseElementProps,
+    PrepareCustomInfo
+} from './CustomSeries';
+import {applyMorphAnimation} from './transitionHelper';
+import {
+    prepareShapeOrExtraAllPropsFinal,
+    prepareShapeOrExtraTransitionFrom,
+    prepareStyleTransitionFrom,
+    prepareTransformAllPropsFinal,
+    prepareTransformTransitionFrom
+} from './prepare';
+import { isMorphing } from 'zrender/src/tool/morphPath';
+import { graphic } from '../../export/api';
 
-// Also compat with ec4, where
-// `visual('color') visual('borderColor')` is supported.
-const STYLE_VISUAL_TYPE = {
-    color: 'fill',
-    borderColor: 'stroke'
-} as const;
-type StyleVisualProps = keyof typeof STYLE_VISUAL_TYPE;
-
-const NON_STYLE_VISUAL_PROPS = {
-    symbol: 1,
-    symbolSize: 1,
-    symbolKeepAspect: 1,
-    legendSymbol: 1,
-    visualMeta: 1,
-    liftZ: 1,
-    decal: 1
-} as const;
-type NonStyleVisualProps = keyof typeof NON_STYLE_VISUAL_PROPS;
+const transformPropNamesStr = keys(TRANSFORM_PROPS).join(', ');
 
 const EMPHASIS = 'emphasis' as const;
 const NORMAL = 'normal' as const;
@@ -380,19 +156,6 @@ const attachedTxInfoTmp = {
     select: {}
 } as AttachedTxInfo;
 
-const LEGACY_TRANSFORM_PROPS = {
-    position: ['x', 'y'],
-    scale: ['scaleX', 'scaleY'],
-    origin: ['originX', 'originY']
-} as const;
-type LegacyTransformProp = keyof typeof LEGACY_TRANSFORM_PROPS;
-
-export type PrepareCustomInfo = (coordSys: CoordinateSystem) => {
-    coordSys: CustomSeriesRenderItemParamsCoordSys;
-    api: CustomSeriesRenderItemCoordinateSystemAPI
-};
-
-const tmpTransformable = new Transformable();
 
 /**
  * To reduce total package size of each coordinate systems, the modules `prepareCustom`
@@ -413,64 +176,11 @@ const prepareCustoms: Dictionary<PrepareCustomInfo> = {
     calendar: prepareCalendar
 };
 
-class CustomSeriesModel extends SeriesModel<CustomSeriesOption> {
-
-    static type = 'series.custom';
-    readonly type = CustomSeriesModel.type;
-
-    static dependencies = ['grid', 'polar', 'geo', 'singleAxis', 'calendar'];
-
-    // preventAutoZ = true;
 
-    currentZLevel: number;
-    currentZ: number;
-
-    static defaultOption: CustomSeriesOption = {
-        coordinateSystem: 'cartesian2d', // Can be set as 'none'
-        zlevel: 0,
-        z: 2,
-        legendHoverLink: true,
-
-        // Custom series will not clip by default.
-        // Some case will use custom series to draw label
-        // For example https://echarts.apache.org/examples/en/editor.html?c=custom-gantt-flight
-        clip: false
-
-        // Cartesian coordinate system
-        // xAxisIndex: 0,
-        // yAxisIndex: 0,
-
-        // Polar coordinate system
-        // polarIndex: 0,
-
-        // Geo coordinate system
-        // geoIndex: 0,
-    };
-
-    optionUpdated() {
-        this.currentZLevel = this.get('zlevel', true);
-        this.currentZ = this.get('z', true);
-    }
-
-    getInitialData(option: CustomSeriesOption, ecModel: GlobalModel): List {
-        return createListFromArray(this.getSource(), this);
-    }
-
-    getDataParams(dataIndex: number, dataType?: SeriesDataType, el?: Element): CallbackDataParams & {
-        info: CustomExtraElementInfo
-    } {
-        const params = super.getDataParams(dataIndex, dataType) as ReturnType<CustomSeriesModel['getDataParams']>;
-        el && (params.info = inner(el).info);
-        return params;
-    }
-}
-
-
-
-class CustomSeriesView extends ChartView {
+export default class CustomChartView extends ChartView {
 
     static type = 'custom';
-    readonly type = CustomSeriesView.type;
+    readonly type = CustomChartView.type;
 
     private _data: List;
 
@@ -494,6 +204,19 @@ class CustomSeriesView extends ChartView {
 
         const transOpt = customSeries.__transientTransitionOpt;
 
+        function updateMorphingPathProps(
+            dataIndex: number,
+            from: graphicUtil.Path, to: graphicUtil.Path, rawFrom: graphicUtil.Path, rawTo: graphicUtil.Path
+        ) {
+            // element has been updated if rawTo is same with to.
+            if (to !== rawTo) {
+                applyPropsDirectly(to, customInnerStore(rawTo).propsToSet);
+                applyPropsTransition(
+                    to, dataIndex, customSeries, customInnerStore(rawTo).propsToAnimate, false
+                );
+            }
+        }
+
         // Enable user to disable transition animation by both set
         // `from` and `to` dimension as `null`/`undefined`.
         if (transOpt && (transOpt.from == null || transOpt.to == null)) {
@@ -502,12 +225,11 @@ class CustomSeriesView extends ChartView {
             });
             data.each(function (newIdx) {
                 createOrUpdateItem(
-                    api, null, newIdx, renderItem(newIdx, payload), customSeries, group, data, null
+                    api, null, newIdx, renderItem(newIdx, payload), customSeries, group, data
                 );
             });
         }
         else {
-            const morphPreparation = new MorphPreparation(customSeries, transOpt);
             const diffMode: DataDiffMode = transOpt ? 'multiple' : 'oneToOne';
 
             (new DataDiffer(
@@ -521,60 +243,61 @@ class CustomSeriesView extends ChartView {
             .add(function (newIdx) {
                 createOrUpdateItem(
                     api, null, newIdx, renderItem(newIdx, payload), customSeries, group,
-                    data, null
+                    data
                 );
             })
             .remove(function (oldIdx) {
                 doRemoveEl(oldData.getItemGraphicEl(oldIdx), customSeries, group);
             })
             .update(function (newIdx, oldIdx) {
-                morphPreparation.reset('oneToOne');
-                let oldEl = oldData.getItemGraphicEl(oldIdx);
-                morphPreparation.findAndAddFrom(oldEl);
-
-                // PENDING:
-                // if may morph, currently we alway recreate the whole el.
-                // because if reuse some of the el in the group tree, the old el has to
-                // be removed from the group, and consequently we can not calculate
-                // the "global transition" of the old element.
-                // But is there performance issue?
-                if (morphPreparation.hasFrom()) {
-                    removeElementDirectly(oldEl, group);
-                    oldEl = null;
-                }
-                createOrUpdateItem(
+                const oldEl = oldData.getItemGraphicEl(oldIdx);
+
+                const newEl = createOrUpdateItem(
                     api, oldEl, newIdx, renderItem(newIdx, payload), customSeries, group,
-                    data, morphPreparation
+                    data
                 );
-                morphPreparation.applyMorphing();
+                if (oldEl !== newEl) {
+                    applyMorphAnimation(
+                        oldEl, newEl, customSeries, newIdx, curry(updateMorphingPathProps, newIdx)
+                    );
+                }
             })
             .updateManyToOne(function (newIdx, oldIndices) {
-                morphPreparation.reset('manyToOne');
+                const oldElList = [];
                 for (let i = 0; i < oldIndices.length; i++) {
                     const oldEl = oldData.getItemGraphicEl(oldIndices[i]);
-                    morphPreparation.findAndAddFrom(oldEl);
                     removeElementDirectly(oldEl, group);
+                    oldElList.push(oldEl);
                 }
-                createOrUpdateItem(
+
+                const updateMorphingPathPropsWithIdx = curry(updateMorphingPathProps, newIdx);
+
+                const newEl = createOrUpdateItem(
                     api, null, newIdx, renderItem(newIdx, payload), customSeries, group,
-                    data, morphPreparation
+                    data
+                );
+                applyMorphAnimation(
+                    oldElList, newEl, customSeries, newIdx, updateMorphingPathPropsWithIdx
                 );
-                morphPreparation.applyMorphing();
             })
             .updateOneToMany(function (newIndices, oldIdx) {
-                morphPreparation.reset('oneToMany');
                 const newLen = newIndices.length;
                 const oldEl = oldData.getItemGraphicEl(oldIdx);
-                morphPreparation.findAndAddFrom(oldEl);
                 removeElementDirectly(oldEl, group);
 
+                const newElList = [];
                 for (let i = 0; i < newLen; i++) {
-                    createOrUpdateItem(
+                    newElList.push(createOrUpdateItem(
                         api, null, newIndices[i], renderItem(newIndices[i], payload), customSeries, group,
-                        data, morphPreparation
-                    );
+                        data
+                    ));
                 }
-                morphPreparation.applyMorphing();
+
+                // TODO Different animation config in different indices?
+                const updateMorphingPathPropsWithIdx = curry(updateMorphingPathProps, newIndices[0]);
+                applyMorphAnimation(
+                    oldEl, newElList, customSeries, newIndices[0], updateMorphingPathPropsWithIdx
+                );
             })
             .execute();
         }
@@ -619,7 +342,7 @@ class CustomSeriesView extends ChartView {
         }
         for (let idx = params.start; idx < params.end; idx++) {
             const el = createOrUpdateItem(
-                null, null, idx, renderItem(idx, payload), customSeries, this.group, data, null
+                null, null, idx, renderItem(idx, payload), customSeries, this.group, data
             );
             el.traverse(setIncrementalAndHoverLayer);
         }
@@ -704,15 +427,15 @@ function createEl(elOption: CustomElementOption): Element {
         const pathData = getPathData(shape);
         // Path is also used for icon, so layout 'center' by default.
         el = graphicUtil.makePath(pathData, null, pathRect, shape.layout || 'center');
-        inner(el).customPathData = pathData;
+        customInnerStore(el).customPathData = pathData;
     }
     else if (graphicType === 'image') {
         el = new graphicUtil.Image({});
-        inner(el).customImagePath = (elOption as CustomImageOption).style.image;
+        customInnerStore(el).customImagePath = (elOption as CustomImageOption).style.image;
     }
     else if (graphicType === 'text') {
         el = new graphicUtil.Text({});
-        // inner(el).customText = (elOption.style as TextStyleProps).text;
+        // customInnerStore(el).customText = (elOption.style as TextStyleProps).text;
     }
     else if (graphicType === 'group') {
         el = new graphicUtil.Group();
@@ -732,7 +455,7 @@ function createEl(elOption: CustomElementOption): Element {
         el = new Clz();
     }
 
-    inner(el).customGraphicType = graphicType;
+    customInnerStore(el).customGraphicType = graphicType;
     el.name = elOption.name;
 
     // Compat ec4: the default z2 lift is 1. If changing the number,
@@ -800,8 +523,6 @@ function updateElNormal(
     // Can be null/undefined
     api: ExtensionAPI,
     el: Element,
-    // Whether be a morph target.
-    isMorphTo: boolean,
     dataIndex: number,
     elOption: CustomElementOption,
     styleOpt: CustomElementOption['style'],
@@ -809,21 +530,21 @@ function updateElNormal(
     seriesModel: CustomSeriesModel,
     isInit: boolean,
     isTextContent: boolean
-): ElementProps {
+): void {
     const transFromProps = {} as ElementProps;
-    const allPropsFinal = {} as ElementProps;
-    const elDisplayable = el.isGroup ? null : el as Displayable;
+    const propsToSet = {} as ElementProps;
+
+    const needsMorph = (el instanceof graphicUtil.Path)
+        && (elOption as CustomZRPathOption).morph;
 
-    // If be "morph to", delay the `updateElNormal` when all of the els in
-    // this data item processed. Because at that time we can get all of the
-    // "morph from" and make correct separate/combine.
+    // TODO Ignore shape on morphing elements.
+    prepareShapeOrExtraTransitionFrom('shape', el, elOption, transFromProps, isInit);
+    prepareShapeOrExtraAllPropsFinal('shape', elOption, propsToSet);
 
-    !isMorphTo && prepareShapeOrExtraTransitionFrom('shape', el, null, elOption, transFromProps, isInit);
-    prepareShapeOrExtraAllPropsFinal('shape', elOption, allPropsFinal);
-    !isMorphTo && prepareShapeOrExtraTransitionFrom('extra', el, null, elOption, transFromProps, isInit);
-    prepareShapeOrExtraAllPropsFinal('extra', elOption, allPropsFinal);
-    !isMorphTo && prepareTransformTransitionFrom(el, null, elOption, transFromProps, isInit);
-    prepareTransformAllPropsFinal(elOption, allPropsFinal);
+    prepareShapeOrExtraTransitionFrom('extra', el, elOption, transFromProps, isInit);
+    prepareShapeOrExtraAllPropsFinal('extra', elOption, propsToSet);
+    prepareTransformTransitionFrom(el, null, elOption, transFromProps, isInit);
+    prepareTransformAllPropsFinal(elOption, propsToSet);
 
     const txCfgOpt = attachedTxInfo && attachedTxInfo.normal.cfg;
     if (txCfgOpt) {
@@ -854,42 +575,47 @@ function updateElNormal(
         (styleOpt as CustomZRPathOption['style']).__decalPattern = decalPattern;
     }
 
-    !isMorphTo && prepareStyleTransitionFrom(el, null, elOption, styleOpt, transFromProps, isInit);
+    prepareStyleTransitionFrom(el, elOption, styleOpt, transFromProps, isInit);
 
-    if (elDisplayable) {
-        hasOwn(elOption, 'invisible') && (elDisplayable.invisible = elOption.invisible);
-    }
+    // Save the meta info for further morphing. Like apply on the sub morphing elements.
+    const store = customInnerStore(el);
+    store.morph = needsMorph;
+    store.propsToSet = propsToSet;
+    store.propsToAnimate = transFromProps;
+    store.userDuring = elOption.during;
 
-    // If `isMorphTo`, we should not update these props to el directly, otherwise,
-    // when applying morph finally, the original prop are missing for making "animation from".
-    if (!isMorphTo) {
-        applyPropsFinal(el, allPropsFinal, styleOpt);
-        applyTransitionFrom(el, dataIndex, elOption, seriesModel, transFromProps, isInit);
-    }
+    applyPropsDirectly(el, propsToSet);
+    applyPropsTransition(el, dataIndex, seriesModel, transFromProps, isInit);
+    applyMiscProps(el, elOption, isTextContent);
 
+    styleOpt ? el.dirty() : el.markRedraw();
+}
+
+function applyMiscProps(
+    el: Element, elOption: CustomElementOption, isTextContent: boolean
+) {
     // 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 (!isTextContent) {
         // `elOption.info` enables user to mount some info on
         // elements and use them in event handlers.
         // Update them only when user specified, otherwise, remain.
-        hasOwn(elOption, 'info') && (inner(el).info = elOption.info);
+        hasOwn(elOption, 'info') && (customInnerStore(el).info = elOption.info);
     }
-
-    styleOpt ? el.dirty() : el.markRedraw();
-
-    return isMorphTo ? allPropsFinal : null;
 }
 
-function applyPropsFinal(
+function applyPropsDirectly(
     el: Element,
     // Can be null/undefined
-    allPropsFinal: ElementProps,
-    styleOpt: CustomElementOption['style']
+    allPropsFinal: ElementProps
 ) {
     const elDisplayable = el.isGroup ? null : el as Displayable;
+    const styleOpt = (allPropsFinal as Displayable).style;
 
     if (elDisplayable && styleOpt) {
 
@@ -909,10 +635,10 @@ function applyPropsFinal(
         }
 
         // When style object changed, how to trade the existing animation?
-        // It is probably conplicated and not needed to cover all the cases.
+        // 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 mousewhell. At that time the init
+        //     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.
@@ -936,24 +662,22 @@ function applyPropsFinal(
     allPropsFinal && el.attr(allPropsFinal);
 }
 
-function applyTransitionFrom(
+function applyPropsTransition(
     el: Element,
     dataIndex: number,
-    elOption: CustomElementOption,
     seriesModel: CustomSeriesModel,
     // Can be null/undefined
     transFromProps: ElementProps,
     isInit: boolean
 ): void {
     if (transFromProps) {
-        // Do not use `el.updateDuringAnimation` here becuase `el.updateDuringAnimation` will
+        // NOTE: Do not use `el.updateDuringAnimation` here becuase `el.updateDuringAnimation` will
         // be called mutiple time in each animation frame. For example, if both "transform" props
         // and shape props and style props changed, it will generate three animator and called
         // one-by-one in each animation frame.
         // We use the during in `animateTo/From` params.
-        const userDuring = elOption.during;
+        const userDuring = customInnerStore(el).userDuring;
         // For simplicity, if during not specified, the previous during will not work any more.
-        inner(el).userDuring = userDuring;
         const cfgDuringCall = userDuring ? bind(duringCall, { el: el, userDuring: userDuring }) : null;
         const cfg = {
             dataIndex: dataIndex,
@@ -967,358 +691,13 @@ function applyTransitionFrom(
 }
 
 
-// See [STRATEGY_TRANSITION]
-function prepareShapeOrExtraTransitionFrom(
-    mainAttr: 'shape' | 'extra',
-    el: Element,
-    morphFromEl: graphicUtil.Path,
-    elOption: CustomElementOption,
-    transFromProps: LooseElementProps,
-    isInit: boolean
-): void {
-
-    const attrOpt: Dictionary<unknown> & TransitionAnyOption = (elOption as any)[mainAttr];
-    if (!attrOpt) {
-        return;
-    }
-
-    const elPropsInAttr = (el 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
-        // Just ignore shape animation in morphing.
-        && !(morphFromEl != null && mainAttr === 'shape')
-    ) {
-        if (attrOpt.transition) {
-            !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {});
-            const transitionKeys = normalizeToArray(attrOpt.transition);
-            for (let i = 0; i < transitionKeys.length; i++) {
-                const key = transitionKeys[i];
-                const elVal = elPropsInAttr[key];
-                if (__DEV__) {
-                    checkNonStyleTansitionRefer(key, (attrOpt as any)[key], elVal);
-                }
-                // Do not clone, see `checkNonStyleTansitionRefer`.
-                transFromPropsInAttr[key] = elVal;
-            }
-        }
-        else if (indexOf(elOption.transition, mainAttr) >= 0) {
-            !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {});
-            const elPropsInAttrKeys = keys(elPropsInAttr);
-            for (let i = 0; i < elPropsInAttrKeys.length; i++) {
-                const key = elPropsInAttrKeys[i];
-                const elVal = elPropsInAttr[key];
-                if (isNonStyleTransitionEnabled((attrOpt as any)[key], elVal)) {
-                    transFromPropsInAttr[key] = elVal;
-                }
-            }
-        }
-    }
-
-    const leaveTo = attrOpt.leaveTo;
-    if (leaveTo) {
-        const leaveToProps = getOrCreateLeaveToPropsFromEl(el);
-        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(
-    mainAttr: 'shape' | 'extra',
-    elOption: CustomElementOption,
-    allProps: LooseElementProps
-): void {
-    const attrOpt: Dictionary<unknown> & TransitionAnyOption = (elOption as any)[mainAttr];
-    if (!attrOpt) {
-        return;
-    }
-    const allPropsInAttr = allProps[mainAttr] = {} as Dictionary<unknown>;
-    const keysInAttr = keys(attrOpt);
-    for (let i = 0; i < keysInAttr.length; i++) {
-        const 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 as any)[key]);
-    }
-}
-
-// See [STRATEGY_TRANSITION].
-function prepareTransformTransitionFrom(
-    el: Element,
-    morphFromEl: graphicUtil.Path,
-    elOption: CustomElementOption,
-    transFromProps: ElementProps,
-    isInit: boolean
-): 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;
-        }
-    }
-
-    if (!isInit) {
-        // If morphing, force transition all transform props.
-        // otherwise might have incorrect morphing animation.
-        if (morphFromEl) {
-            const fromTransformable = calcOldElLocalTransformBasedOnNewElParent(morphFromEl, el);
-            setTransformPropToTransitionFrom(transFromProps, 'x', fromTransformable);
-            setTransformPropToTransitionFrom(transFromProps, 'y', fromTransformable);
-            setTransformPropToTransitionFrom(transFromProps, 'scaleX', fromTransformable);
-            setTransformPropToTransitionFrom(transFromProps, 'scaleY', fromTransformable);
-            setTransformPropToTransitionFrom(transFromProps, 'originX', fromTransformable);
-            setTransformPropToTransitionFrom(transFromProps, 'originY', fromTransformable);
-            setTransformPropToTransitionFrom(transFromProps, 'rotation', fromTransformable);
-        }
-        else if (elOption.transition) {
-            const transitionKeys = normalizeToArray(elOption.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');
-                    checkNonStyleTansitionRefer(key, elOption[key], elVal);
-                }
-                // Do not clone, see `checkNonStyleTansitionRefer`.
-                transFromProps[key] = elVal;
-            }
-        }
-        // This default transition see [STRATEGY_TRANSITION]
-        else {
-            setTransformPropToTransitionFrom(transFromProps, 'x', el);
-            setTransformPropToTransitionFrom(transFromProps, 'y', el);
-        }
-    }
-
-    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;
-        }
-    }
-}
-
-function prepareTransformAllPropsFinal(
-    elOption: CustomElementOption,
-    allProps: ElementProps
-): void {
-    setLagecyTransformProp(elOption, allProps, 'position');
-    setLagecyTransformProp(elOption, allProps, 'scale');
-    setLagecyTransformProp(elOption, allProps, 'origin');
-    setTransformProp(elOption, allProps, 'x');
-    setTransformProp(elOption, allProps, 'y');
-    setTransformProp(elOption, allProps, 'scaleX');
-    setTransformProp(elOption, allProps, 'scaleY');
-    setTransformProp(elOption, allProps, 'originX');
-    setTransformProp(elOption, allProps, 'originY');
-    setTransformProp(elOption, allProps, 'rotation');
-}
-
-// See [STRATEGY_TRANSITION].
-function prepareStyleTransitionFrom(
-    el: Element,
-    morphFromEl: graphicUtil.Path,
-    elOption: CustomElementOption,
-    styleOpt: CustomElementOption['style'],
-    transFromProps: LooseElementProps,
-    isInit: boolean
-): void {
-    if (!styleOpt) {
-        return;
-    }
-
-    // At present in "many-to-one"/"one-to-many" case, to not support "many" have
-    // different styles and make style transitions. That might be a rare case.
-    const fromEl = morphFromEl || el;
-
-    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 (styleOpt.transition) {
-            const transitionKeys = normalizeToArray(styleOpt.transition);
-            !transFromStyleProps && (transFromStyleProps = transFromProps.style = {});
-            for (let i = 0; i < transitionKeys.length; i++) {
-                const key = transitionKeys[i];
-                const elVal = (fromElStyle as any)[key];
-                // Do not clone, see `checkNonStyleTansitionRefer`.
-                (transFromStyleProps as any)[key] = elVal;
-            }
-        }
-        else if (
-            (el as Displayable).getAnimationStyleProps
-            && indexOf(elOption.transition, 'style') >= 0
-        ) {
-            const animationProps = (el as Displayable).getAnimationStyleProps();
-            const animationStyleProps = animationProps ? animationProps.style : null;
-            if (animationStyleProps) {
-                !transFromStyleProps && (transFromStyleProps = transFromProps.style = {});
-                const styleKeys = keys(styleOpt);
-                for (let i = 0; i < styleKeys.length; i++) {
-                    const key = styleKeys[i];
-                    if ((animationStyleProps as Dictionary<unknown>)[key]) {
-                        const elVal = (fromElStyle as any)[key];
-                        (transFromStyleProps as any)[key] = elVal;
-                    }
-                }
-            }
-        }
-    }
-
-    const leaveTo = styleOpt.leaveTo;
-    if (leaveTo) {
-        const leaveToKeys = keys(leaveTo);
-        const leaveToProps = getOrCreateLeaveToPropsFromEl(el);
-        const leaveToStyleProps = leaveToProps.style || (leaveToProps.style = {});
-        for (let i = 0; i < leaveToKeys.length; i++) {
-            const key = leaveToKeys[i];
-            (leaveToStyleProps as any)[key] = leaveTo[key];
-        }
-    }
-}
-
-/**
- * If make "transform"(x/y/scaleX/scaleY/orient/originX/originY) transition between
- * two path elements that have different hierarchy, before we retrieve the "from" props,
- * we have to calculate the local transition of the "oldPath" based on the parent of
- * the "newPath".
- * At present, the case only happend in "morphing". Without morphing, the transform
- * transition are all between elements in the same hierarchy, where this kind of process
- * is not needed.
- *
- * [CAVEAT]:
- * This method makes sense only if: (very tricky)
- * (1) "newEl" has been added to its final parent.
- * (2) Local transform props of "newPath.parent" are not at their final value but already
- * have been at the "from value".
- *     This is currently ensured by:
- *     (2.1) "graphicUtil.animationFrom", which will set the element to the "from value"
- *     immediately.
- *     (2.2) "morph" option is not allowed to be set on Group, so all of the groups have
- *     been finished their "updateElNormal" when calling this method in morphing process.
- */
-function calcOldElLocalTransformBasedOnNewElParent(oldEl: Element, newEl: Element): Transformable {
-    if (!oldEl || oldEl === newEl || oldEl.parent === newEl.parent) {
-        return oldEl;
-    }
-
-    // Not sure oldEl is rendered (may have "lazyUpdate"),
-    // so always call `getComputedTransform`.
-    const tmpM = tmpTransformable.transform
-        || (tmpTransformable.transform = matrix.identity([]));
-
-    const oldGlobalTransform = oldEl.getComputedTransform();
-    oldGlobalTransform
-        ? matrix.copy(tmpM, oldGlobalTransform)
-        : matrix.identity(tmpM);
-
-    const newParent = newEl.parent;
-    if (newParent) {
-        newParent.getComputedTransform();
-    }
-
-    tmpTransformable.originX = oldEl.originX;
-    tmpTransformable.originY = oldEl.originY;
-    tmpTransformable.parent = newParent;
-    tmpTransformable.decomposeTransform();
-
-    return tmpTransformable;
-}
-
-let checkNonStyleTansitionRefer: (propName: string, optVal: unknown, elVal: unknown) => void;
-if (__DEV__) {
-    checkNonStyleTansitionRefer = function (propName: string, optVal: unknown, elVal: unknown): void {
-        if (!isArrayLike(optVal)) {
-            assert(
-                optVal != null && isFinite(optVal as number),
-                'Prop `' + propName + '` must refer to a finite number or ArrayLike for transition.'
-            );
-        }
-        else {
-            // Try not to copy array for performance, but if user use the same object in different
-            // call of `renderItem`, it will casue animation transition fail.
-            assert(
-                optVal !== elVal,
-                'Prop `' + propName + '` must use different Array object each time for transition.'
-            );
-        }
-    };
-}
-
-function isNonStyleTransitionEnabled(optVal: unknown, elVal: unknown): boolean {
-    // The same as `checkNonStyleTansitionRefer`.
-    return !isArrayLike(optVal)
-        ? (optVal != null && isFinite(optVal as number))
-        : optVal !== elVal;
-}
-
-let checkTransformPropRefer: (key: string, usedIn: string) => void;
-if (__DEV__) {
-    checkTransformPropRefer = function (key: string, usedIn: string): void {
-        assert(
-            hasOwn(TRANSFORM_PROPS, key),
-            'Prop `' + key + '` is not a permitted in `' + usedIn + '`. '
-                + 'Only `' + keys(TRANSFORM_PROPS).join('`, `') + '` are permitted.'
-        );
-    };
-}
-
-function getOrCreateLeaveToPropsFromEl(el: Element): LooseElementProps {
-    const innerEl = inner(el);
-    return innerEl.leaveToProps || (innerEl.leaveToProps = {});
-}
-
 // Use it to avoid it be exposed to user.
 const tmpDuringScope = {} as {
     el: Element;
     isShapeDirty: boolean;
     isStyleDirty: boolean;
 };
-const customDuringAPI = {
+const customDuringAPI: CustomDuringAPI = {
     // Usually other props do not need to be changed in animation during.
     setTransform(key: TransformProp, val: unknown) {
         if (__DEV__) {
@@ -1424,7 +803,7 @@ function duringCall(
     // If el is remove from zr by reason like legend, during still need to called,
     // becuase el will be added back to zr and the prop value should not be incorrect.
 
-    const newstUserDuring = inner(el).userDuring;
+    const latestUserDuring = customInnerStore(el).userDuring;
     const 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' calulation logic
@@ -1432,7 +811,7 @@ function duringCall(
     // 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 (newstUserDuring !== scopeUserDuring) {
+    if (latestUserDuring !== scopeUserDuring) {
         // release
         scope.el = scope.userDuring = null;
         return;
@@ -1539,48 +918,6 @@ function updateZForEachState(
     }
 }
 
-function setLagecyTransformProp(
-    elOption: CustomElementOption,
-    targetProps: Partial<Pick<Transformable, TransformProp>>,
-    legacyName: LegacyTransformProp,
-    fromTransformable?: Transformable // If provided, retrieve from the element.
-): void {
-    const legacyArr = (elOption as any)[legacyName];
-    const xyName = LEGACY_TRANSFORM_PROPS[legacyName];
-    if (legacyArr) {
-        if (fromTransformable) {
-            targetProps[xyName[0]] = fromTransformable[xyName[0]];
-            targetProps[xyName[1]] = fromTransformable[xyName[1]];
-        }
-        else {
-            targetProps[xyName[0]] = legacyArr[0];
-            targetProps[xyName[1]] = legacyArr[1];
-        }
-    }
-}
-
-function setTransformProp(
-    elOption: CustomElementOption,
-    allProps: Partial<Pick<Transformable, TransformProp>>,
-    name: TransformProp,
-    fromTransformable?: Transformable // If provided, retrieve from the element.
-): void {
-    if (elOption[name] != null) {
-        allProps[name] = fromTransformable ? fromTransformable[name] : elOption[name];
-    }
-}
-
-function setTransformPropToTransitionFrom(
-    transitionFrom: Partial<Pick<Transformable, TransformProp>>,
-    name: TransformProp,
-    fromTransformable?: Transformable // If provided, retrieve from the element.
-): void {
-    if (fromTransformable) {
-        transitionFrom[name] = fromTransformable[name];
-    }
-}
-
-
 function makeRenderItem(
     customSeries: CustomSeriesModel,
     data: List<CustomSeriesModel>,
@@ -1890,8 +1227,6 @@ function makeRenderItem(
     }
 }
 
-type WrapEncodeDefRet = Dictionary<number[]>;
-
 function wrapEncodeDef(data: List<CustomSeriesModel>): WrapEncodeDefRet {
     const encodeDef = {} as WrapEncodeDefRet;
     each(data.dimensions, function (dimName, dataDimIndex) {
@@ -1907,13 +1242,12 @@ function wrapEncodeDef(data: List<CustomSeriesModel>): WrapEncodeDefRet {
 
 function createOrUpdateItem(
     api: ExtensionAPI,
-    el: Element,
+    existsEl: Element,
     dataIndex: number,
     elOption: CustomElementOption,
     seriesModel: CustomSeriesModel,
     group: ViewRootGroup,
-    data: List<CustomSeriesModel>,
-    morphPreparation: MorphPreparation
+    data: List<CustomSeriesModel>
 ): Element {
     // [Rule]
     // If `renderItem` returns `null`/`undefined`/`false`, remove the previous el if existing.
@@ -1924,10 +1258,10 @@ function createOrUpdateItem(
 
     // If `elOption` is `null`/`undefined`/`false` (when `renderItem` returns nothing).
     if (!elOption) {
-        removeElementDirectly(el, group);
+        removeElementDirectly(existsEl, group);
         return;
     }
-    el = doCreateOrUpdateEl(api, el, dataIndex, elOption, seriesModel, group, true, morphPreparation);
+    const el = doCreateOrUpdateEl(api, existsEl, dataIndex, elOption, seriesModel, group, true);
     el && data.setItemGraphicEl(dataIndex, el);
 
     enableHoverEmphasis(el, elOption.focus, elOption.blurScope);
@@ -1937,13 +1271,12 @@ function createOrUpdateItem(
 
 function doCreateOrUpdateEl(
     api: ExtensionAPI,
-    el: Element,
+    existsEl: Element,
     dataIndex: number,
     elOption: CustomElementOption,
     seriesModel: CustomSeriesModel,
     group: ViewRootGroup,
-    isRoot: boolean,
-    morphPreparation: MorphPreparation
+    isRoot: boolean
 ): Element {
 
     if (__DEV__) {
@@ -1952,22 +1285,23 @@ function doCreateOrUpdateEl(
 
     let toBeReplacedIdx = -1;
     if (
-        el && (
-            doesElNeedRecreate(el, elOption)
+        existsEl && (
+            doesElNeedRecreate(existsEl, elOption)
             // || (
             //     // PENDING: even in one-to-one mapping case, if el is marked as morph,
             //     // do not sure whether the el will be mapped to another el with different
             //     // hierarchy in Group tree. So always recreate el rather than reuse the el.
-            //     morphPreparation && morphPreparation.isOneToOneFrom(el)
+            //     morphHelper && morphHelper.isOneToOneFrom(el)
             // )
         )
     ) {
         // Should keep at the original index, otherwise "merge by index" will be incorrect.
-        toBeReplacedIdx = group.childrenRef().indexOf(el);
-        el = null;
+        toBeReplacedIdx = indexOf(group.childrenRef(), existsEl);
+        existsEl = null;
     }
 
-    const elIsNewCreated = !el;
+    const elIsNewCreated = !existsEl;
+    let el = existsEl;
 
     if (!el) {
         el = createEl(elOption);
@@ -1979,11 +1313,10 @@ function doCreateOrUpdateEl(
         el.clearStates();
     }
 
-    const canMorph = inner(el).canMorph = (elOption as CustomZRPathOption).morph && isPath(el);
-    const thisElIsMorphTo = canMorph && morphPreparation && morphPreparation.hasFrom();
+    const canMorph = isPath(el) && isMorphing(el);
 
     // Use update animation when morph is enabled.
-    const isInit = elIsNewCreated && !thisElIsMorphTo;
+    const isInit = elIsNewCreated && !canMorph;
 
     attachedTxInfoTmp.normal.cfg = attachedTxInfoTmp.normal.conOpt =
         attachedTxInfoTmp.emphasis.cfg = attachedTxInfoTmp.emphasis.conOpt =
@@ -2000,10 +1333,9 @@ function doCreateOrUpdateEl(
         el, dataIndex, elOption, seriesModel, isInit
     );
 
-    const pendingAllPropsFinal = updateElNormal(
+    updateElNormal(
         api,
         el,
-        thisElIsMorphTo,
         dataIndex,
         elOption,
         elOption.style,
@@ -2013,10 +1345,6 @@ function doCreateOrUpdateEl(
         false
     );
 
-    if (thisElIsMorphTo) {
-        morphPreparation.addTo(el as graphicUtil.Path, elOption, dataIndex, pendingAllPropsFinal);
-    }
-
     for (let i = 0; i < STATES.length; i++) {
         const stateName = STATES[i];
         if (stateName !== NORMAL) {
@@ -2030,7 +1358,7 @@ function doCreateOrUpdateEl(
 
     if (elOption.type === 'group') {
         mergeChildren(
-            api, el as graphicUtil.Group, dataIndex, elOption as CustomGroupOption, seriesModel, morphPreparation
+            api, el as graphicUtil.Group, dataIndex, elOption as CustomGroupOption, seriesModel
         );
     }
 
@@ -2046,7 +1374,7 @@ function doCreateOrUpdateEl(
 
 // `el` must not be null/undefined.
 function doesElNeedRecreate(el: Element, elOption: CustomElementOption): boolean {
-    const elInner = inner(el);
+    const elInner = customInnerStore(el);
     const elOptionType = elOption.type;
     const elOptionShape = (elOption as CustomZRPathOption).shape;
     const elOptionStyle = elOption.style;
@@ -2103,7 +1431,7 @@ function doCreateOrUpdateClipPath(
             el.setClipPath(clipPath);
         }
         updateElNormal(
-            null, clipPath, null, dataIndex, clipPathOpt, null, null, seriesModel, isInit, false
+            null, clipPath, dataIndex, clipPathOpt, null, null, seriesModel, isInit, false
         );
     }
     // If not define `clipPath` in option, do nothing unnecessary.
@@ -2156,7 +1484,7 @@ function doCreateOrUpdateAttachedTx(
             const txConStlOptNormal = txConOptNormal && txConOptNormal.style;
 
             updateElNormal(
-                null, textContent, null, dataIndex, txConOptNormal, txConStlOptNormal, null, seriesModel, isInit, true
+                null, textContent, dataIndex, txConOptNormal, txConStlOptNormal, null, seriesModel, isInit, true
             );
             for (let i = 0; i < STATES.length; i++) {
                 const stateName = STATES[i];
@@ -2267,8 +1595,7 @@ function mergeChildren(
     el: graphicUtil.Group,
     dataIndex: number,
     elOption: CustomGroupOption,
-    seriesModel: CustomSeriesModel,
-    morphPreparation: MorphPreparation
+    seriesModel: CustomSeriesModel
 ): void {
 
     const newChildren = elOption.children;
@@ -2290,8 +1617,7 @@ function mergeChildren(
             newChildren: newChildren || [],
             dataIndex: dataIndex,
             seriesModel: seriesModel,
-            group: el,
-            morphPreparation: morphPreparation
+            group: el
         });
         return;
     }
@@ -2309,8 +1635,7 @@ function mergeChildren(
             newChildren[index],
             seriesModel,
             el,
-            false,
-            morphPreparation
+            false
         );
     }
     for (let i = el.childCount() - 1; i >= index; i--) {
@@ -2328,7 +1653,6 @@ type DiffGroupContext = {
     dataIndex: number;
     seriesModel: CustomSeriesModel;
     group: graphicUtil.Group;
-    morphPreparation: MorphPreparation;
 };
 function diffGroupChildren(context: DiffGroupContext) {
     (new DataDiffer(
@@ -2365,8 +1689,7 @@ function processAddUpdate(
         childOption,
         context.seriesModel,
         context.group,
-        false,
-        context.morphPreparation
+        false
     );
 }
 
@@ -2382,7 +1705,7 @@ function doRemoveEl(
     group: ViewRootGroup
 ): void {
     if (el) {
-        const leaveToProps = inner(el).leaveToProps;
+        const leaveToProps = customInnerStore(el).leaveToProps;
         leaveToProps
             ? graphicUtil.updateProps(el, leaveToProps, seriesModel, {
                 cb: function () {
@@ -2412,373 +1735,3 @@ function isPath(el: Element): el is graphicUtil.Path {
 function removeElementDirectly(el: Element, group: ViewRootGroup): void {
     el && group.remove(el);
 }
-
-
-type MorphPreparationType = 'oneToOne' | 'oneToMany' | 'manyToOne';
-
-/**
- * Any morph-potential el should added by `morphPreparation.addTo(el)`.
- * And they may apply morph or not when `morphPreparation.applyMorphing()`.
- * But at least, all of the "to" elements will apply all of the updates
- * as `doCreateOrUpdateItem` did.
- */
-class MorphPreparation {
-    private _type: MorphPreparationType;
-    private _fromList: graphicUtil.Path[] = [];
-    private _toList: graphicUtil.Path[] = [];
-    private _toElOptionList: CustomElementOption[] = [];
-    private _allPropsFinalList: ElementProps[] = [];
-    private _toDataIndices: number[] = [];
-    private _transOpt: SeriesModel['__transientTransitionOpt'];
-    private _seriesModel: CustomSeriesModel;
-    // Key: `toDataIndex`, not `toIdx`
-    private _morphConfigList: CombineSeparateConfig[] = [];
-
-    constructor(
-        seriesModel: CustomSeriesModel,
-        transOpt: SeriesModel['__transientTransitionOpt']
-    ) {
-        this._seriesModel = seriesModel;
-        this._transOpt = transOpt;
-    }
-
-    hasFrom(): boolean {
-        return !!this._fromList.length;
-    }
-
-    // isOneToOneFrom(el: Element): boolean {
-    //     if (el && inner(el).canMorph) {
-    //         const fromList = this._fromList;
-    //         for (let i = 0; i < fromList.length; i++) {
-    //             if (fromList[i] === el) {
-    //                 return true;
-    //             }
-    //         }
-    //     }
-    // }
-
-    findAndAddFrom(el: Element): void {
-        if (!el) {
-            return;
-        }
-        if (inner(el).canMorph) {
-            this._fromList.push(el as graphicUtil.Path);
-        }
-        if (el.isGroup) {
-            const children = (el as graphicUtil.Group).childrenRef();
-            for (let i = 0; i < children.length; i++) {
-                this.findAndAddFrom(children[i]);
-            }
-        }
-    }
-
-    addTo(
-        path: graphicUtil.Path,
-        elOption: CustomElementOption,
-        dataIndex: number,
-        allPropsFinal: ElementProps
-    ): void {
-        if (path) {
-            this._toList.push(path);
-            this._toElOptionList.push(elOption);
-            this._toDataIndices.push(dataIndex);
-            this._allPropsFinalList.push(allPropsFinal);
-        }
-    }
-
-    applyMorphing(): void {
-        // [MORPHING_LOGIC_HINT]
-        // Pay attention to the order:
-        // (A) Apply `allPropsFinal` and `styleOption` to "to".
-        //     (Then "to" becomes to the final state.)
-        // (B) Apply `morphPath`/`combine`/`separate`.
-        //     (Based on the current state of "from" and the final state of "to".)
-        //     (Then we may get "from.subList" or "to.subList".)
-        // (C) Copy the related props from "from" to "from.subList", from "to" to "to.subList".
-        // (D) Collect `transitionFromProps` for "to" and "to.subList"
-        //     (Based on "from" or "from.subList".)
-        // (E) Apply `transitionFromProps` to "to" and "to.subList"
-        //     (It might change the prop values to the first frame value.)
-        // Case_I:
-        //     If (D) should be after (C), we use sequence: A - B - C - D - E
-        // Case_II:
-        //     If (A) should be after (D), we use sequence: D - A - B - C - E
-
-        // [MORPHING_LOGIC_HINT]
-        // zrender `morphPath`/`combine`/`separate` only manages the shape animation.
-        // Other props (like transfrom, style transition) will handled in echarts).
-
-        // [MORPHING_LOGIC_HINT]
-        // Make sure `applyPropsFinal` always be called for "to".
-
-        const type = this._type;
-        const fromList = this._fromList;
-        const toList = this._toList;
-        const toListLen = toList.length;
-        const fromListLen = fromList.length;
-
-        if (!fromListLen || !toListLen) {
-            return;
-        }
-
-        if (type === 'oneToOne') {
-            // In one-to-one case, we by default apply a simple rule:
-            // map "from" and "to" one by one.
-            // For this case: old_data_item_el and new_data_item_el
-            // has the same hierarchy of group tree but only some path type changed.
-            for (let toIdx = 0; toIdx < toListLen; toIdx++) {
-                this._oneToOneForSingleTo(toIdx, toIdx);
-            }
-        }
-
-        else if (type === 'manyToOne') {
-            // A rough strategy: if there are more than one "to", we simply divide "fromList" equally.
-            const fromSingleSegLen = Math.max(1, Math.floor(fromListLen / toListLen));
-            for (
-                let toIdx = 0, fromIdxStart = 0;
-                toIdx < toListLen;
-                toIdx++, fromIdxStart += fromSingleSegLen
-            ) {
-                const fromCount = toIdx + 1 >= toListLen
-                    ? fromListLen - fromIdxStart
-                    : fromSingleSegLen;
-                this._manyToOneForSingleTo(
-                    toIdx, fromIdxStart >= fromListLen ? null : fromIdxStart, fromCount
-                );
-            }
-        }
-
-        else if (type === 'oneToMany') {
-            // A rough strategy: if there are more than one "from", we simply divide "toList" equally.
-            const toSingleSegLen = Math.max(1, Math.floor(toListLen / fromListLen));
-            for (
-                let toIdxStart = 0, fromIdx = 0;
-                toIdxStart < toListLen;
-                toIdxStart += toSingleSegLen, fromIdx++
-            ) {
-                const toCount = toIdxStart + toSingleSegLen >= toListLen
-                    ? toListLen - toIdxStart
-                    : toSingleSegLen;
-                this._oneToManyForSingleFrom(
-                    toIdxStart, toCount, fromIdx >= fromListLen ? null : fromIdx
-                );
-            }
-        }
-    }
-
-    private _oneToOneForSingleTo(
-        // "to" must NOT be null/undefined.
-        toIdx: number,
-        // May `fromIdx >= this._fromList.length`
-        fromIdx: number
-    ): void {
-        const to = this._toList[toIdx];
-        const toElOption = this._toElOptionList[toIdx];
-        const toDataIndex = this._toDataIndices[toIdx];
-        const allPropsFinal = this._allPropsFinalList[toIdx];
-        const from = this._fromList[fromIdx];
-
-        const elAnimationConfig = this._getOrCreateMorphConfig(toDataIndex);
-        const morphDuration = elAnimationConfig.duration;
-
-        if (from && isCombiningPath(from)) {
-            applyPropsFinal(to, allPropsFinal, toElOption.style);
-
-            if (morphDuration) {
-                const combineResult = combine([from], to, elAnimationConfig, copyPropsWhenDivided);
-                this._processResultIndividuals(combineResult, toIdx, null);
-            }
-            // The target el will not be displayed and transition from multiple path.
-            // transition on the target el does not make sense.
-        }
-        else {
-            const morphFrom = (
-                morphDuration
-                // from === to usually happen in scenarios where internal update like
-                // "dataZoom", "legendToggle" happen. If from is not in any morphing,
-                // we do not need to call `morphPath`.
-                && from
-                && (from !== to || isInAnyMorphing(from))
-            ) ? from : null;
-
-            // See [Case_II] above.
-            // In this case, there is probably `from === to`. And the `transitionFromProps` collecting
-            // does not depends on morphing. So we collect `transitionFromProps` first.
-            const transFromProps = {} as ElementProps;
-            prepareShapeOrExtraTransitionFrom('shape', to, morphFrom, toElOption, transFromProps, false);
-            prepareShapeOrExtraTransitionFrom('extra', to, morphFrom, toElOption, transFromProps, false);
-            prepareTransformTransitionFrom(to, morphFrom, toElOption, transFromProps, false);
-            prepareStyleTransitionFrom(to, morphFrom, toElOption, toElOption.style, transFromProps, false);
-
-            applyPropsFinal(to, allPropsFinal, toElOption.style);
-
-            if (morphFrom) {
-                morphPath(morphFrom, to, elAnimationConfig);
-            }
-            applyTransitionFrom(to, toDataIndex, toElOption, this._seriesModel, transFromProps, false);
-        }
-    }
-
-    private _manyToOneForSingleTo(
-        // "to" must NOT be null/undefined.
-        toIdx: number,
-        // May be null.
-        fromIdxStart: number,
-        fromCount: number
-    ): void {
-        const to = this._toList[toIdx];
-        const toElOption = this._toElOptionList[toIdx];
-        const allPropsFinal = this._allPropsFinalList[toIdx];
-
-        applyPropsFinal(to, allPropsFinal, toElOption.style);
-
-        const elAnimationConfig = this._getOrCreateMorphConfig(this._toDataIndices[toIdx]);
-        if (elAnimationConfig.duration && fromIdxStart != null) {
-            const combineFromList = [];
-            for (let fromIdx = fromIdxStart; fromIdx < fromCount; fromIdx++) {
-                combineFromList.push(this._fromList[fromIdx]);
-            }
-            const combineResult = combine(combineFromList, to, elAnimationConfig, copyPropsWhenDivided);
-            this._processResultIndividuals(combineResult, toIdx, null);
-        }
-    }
-
-    private _oneToManyForSingleFrom(
-        // "to" must NOT be null/undefined.
-        toIdxStart: number,
-        toCount: number,
-        // May be null
-        fromIdx: number
-    ): void {
-        const from = fromIdx == null ? null : this._fromList[fromIdx];
-        const toList = this._toList;
-
-        const separateToList = [];
-        for (let toIdx = toIdxStart; toIdx < toCount; toIdx++) {
-            const to = toList[toIdx];
-            applyPropsFinal(to, this._allPropsFinalList[toIdx], this._toElOptionList[toIdx].style);
-            separateToList.push(to);
-        }
-
-        const elAnimationConfig = this._getOrCreateMorphConfig(this._toDataIndices[toIdxStart]);
-        if (elAnimationConfig.duration && from) {
-            const separateResult = separate(from, separateToList, elAnimationConfig, copyPropsWhenDivided);
-            this._processResultIndividuals(separateResult, toIdxStart, toCount);
-        }
-    }
-
-    private _processResultIndividuals(
-        combineSeparateResult: CombineSeparateResult,
-        toIdxStart: number,
-        toCount: number
-    ): void {
-        const isSeparate = toCount != null;
-
-        for (let i = 0; i < combineSeparateResult.count; i++) {
-            const fromIndividual = combineSeparateResult.fromIndividuals[i];
-            const toIndividual = combineSeparateResult.toIndividuals[i];
-            // Here it's a trick:
-            // For "combine" case, all of the `toIndividuals` map to the same `toIdx`.
-            // For "separate" case, the `toIndividuals` map to some certain segment of `_toList` accurately.
-            const toIdx = toIdxStart + (isSeparate ? i : 0);
-
-            const toElOption = this._toElOptionList[toIdx];
-            const dataIndex = this._toDataIndices[toIdx];
-
-            const transFromProps = {} as ElementProps;
-            prepareTransformTransitionFrom(
-                toIndividual, fromIndividual, toElOption, transFromProps, false
-            );
-            prepareStyleTransitionFrom(
-                toIndividual, fromIndividual, toElOption, toElOption.style, transFromProps, false
-            );
-            applyTransitionFrom(
-                toIndividual, dataIndex, toElOption, this._seriesModel, transFromProps, false
-            );
-        }
-    }
-
-    _getOrCreateMorphConfig(dataIndex: number): CombineSeparateConfig {
-        const morphConfigList = this._morphConfigList;
-        let config = morphConfigList[dataIndex];
-        if (config) {
-            return config;
-        }
-
-        let duration: number;
-        let easing: AnimationEasing;
-        let delay: number;
-        const seriesModel = this._seriesModel;
-        const transOpt = this._transOpt;
-
-        if (seriesModel.isAnimationEnabled()) {
-            // PENDING: refactor? this is the same logic as `src/util/graphic.ts#animateOrSetProps`.
-            let animationPayload: PayloadAnimationPart;
-            if (seriesModel && seriesModel.ecModel) {
-                const updatePayload = seriesModel.ecModel.getUpdatePayload();
-                animationPayload = (updatePayload && updatePayload.animation) as PayloadAnimationPart;
-            }
-            if (animationPayload) {
-                duration = animationPayload.duration || 0;
-                easing = animationPayload.easing || 'cubicOut';
-                delay = animationPayload.delay || 0;
-            }
-            else {
-                easing = seriesModel.get('animationEasingUpdate');
-                const delayOption = seriesModel.get('animationDelayUpdate');
-                delay = isFunction(delayOption) ? delayOption(dataIndex) : delayOption;
-                const durationOption = seriesModel.get('animationDurationUpdate');
-                duration = isFunction(durationOption) ? durationOption(dataIndex) : durationOption;
-            }
-        }
-
-        config = {
-            duration: duration || 0,
-            delay: delay,
-            easing: easing,
-            dividingMethod: transOpt ? transOpt.dividingMethod : null
-        };
-        morphConfigList[dataIndex] = config;
-
-        return config;
-    }
-
-    reset(type: MorphPreparationType): void {
-        // `this._morphConfigList` can be kept. It only related to `dataIndex`.
-        this._type = type;
-        this._fromList.length =
-            this._toList.length =
-            this._toElOptionList.length =
-            this._allPropsFinalList.length =
-            this._toDataIndices.length = 0;
-    }
-}
-
-function copyPropsWhenDivided(
-    srcPath: graphicUtil.Path,
-    tarPath: graphicUtil.Path,
-    willClone: boolean
-): void {
-    // Do not copy transform props.
-    // Sub paths are transfrom based on their host path.
-    // tarPath.x = srcPath.x;
-    // tarPath.y = srcPath.y;
-    // tarPath.scaleX = srcPath.scaleX;
-    // tarPath.scaleY = srcPath.scaleY;
-    // tarPath.originX = srcPath.originX;
-    // tarPath.originY = srcPath.originY;
-
-    // If just carry the style, will not be modifed, so do not copy.
-    tarPath.style = willClone
-        ? clone(srcPath.style)
-        : srcPath.style;
-
-    tarPath.zlevel = srcPath.zlevel;
-    tarPath.z = srcPath.z;
-    tarPath.z2 = srcPath.z2;
-}
-
-export function install(registers: EChartsExtensionInstallRegisters) {
-    registers.registerChartView(CustomSeriesView);
-    registers.registerSeriesModel(CustomSeriesModel);
-}
\ No newline at end of file
diff --git a/src/chart/custom/install.ts b/src/chart/custom/install.ts
index e95d1ae..34f2b3b 100644
--- a/src/chart/custom/install.ts
+++ b/src/chart/custom/install.ts
@@ -17,2768 +17,11 @@
 * under the License.
 */
 
-import {
-    hasOwn, assert, isString, retrieve2, retrieve3, defaults, each,
-    keys, isArrayLike, bind, isFunction, eqNaN, indexOf, clone
-} from 'zrender/src/core/util';
-import * as graphicUtil from '../../util/graphic';
-import { setDefaultStateProxy, enableHoverEmphasis } from '../../util/states';
-import * as labelStyleHelper from '../../label/labelStyle';
-import {getDefaultLabel} from '../helper/labelHelper';
-import createListFromArray from '../helper/createListFromArray';
-import {getLayoutOnAxis, BarGridLayoutResult, BarGridLayoutOptionForCustomSeries} from '../../layout/barGrid';
-import DataDiffer, { DataDiffMode } from '../../data/DataDiffer';
-import SeriesModel from '../../model/Series';
-import Model from '../../model/Model';
-import ChartView from '../../view/Chart';
-import {createClipPath} from '../helper/createClipPathFromCoordSys';
-import {
-    EventQueryItem, ECEvent, SeriesOption, SeriesOnCartesianOptionMixin,
-    SeriesOnPolarOptionMixin, SeriesOnSingleOptionMixin, SeriesOnGeoOptionMixin,
-    SeriesOnCalendarOptionMixin, ItemStyleOption, SeriesEncodeOptionMixin,
-    DimensionLoose,
-    ParsedValue,
-    Dictionary,
-    CallbackDataParams,
-    Payload,
-    StageHandlerProgressParams,
-    LabelOption,
-    ViewRootGroup,
-    OptionDataValue,
-    ZRStyleProps,
-    DisplayState,
-    ECElement,
-    DisplayStateNonNormal,
-    BlurScope,
-    SeriesDataType,
-    OrdinalRawValue,
-    PayloadAnimationPart,
-    DecalObject,
-    InnerDecalObject,
-    TextCommonOption
-} from '../../util/types';
-import Element, { ElementProps, ElementTextConfig } from 'zrender/src/Element';
-import prepareCartesian2d from '../../coord/cartesian/prepareCustom';
-import prepareGeo from '../../coord/geo/prepareCustom';
-import prepareSingleAxis from '../../coord/single/prepareCustom';
-import preparePolar from '../../coord/polar/prepareCustom';
-import prepareCalendar from '../../coord/calendar/prepareCustom';
-import List, { DefaultDataVisual } from '../../data/List';
-import GlobalModel from '../../model/Global';
-import { makeInner, normalizeToArray } from '../../util/model';
-import ExtensionAPI from '../../core/ExtensionAPI';
-import Displayable from 'zrender/src/graphic/Displayable';
-import Axis2D from '../../coord/cartesian/Axis2D';
-import { RectLike } from 'zrender/src/core/BoundingRect';
-import { PathProps, PathStyleProps } from 'zrender/src/graphic/Path';
-import { ImageStyleProps } from 'zrender/src/graphic/Image';
-import { CoordinateSystem } from '../../coord/CoordinateSystem';
-import { TextStyleProps } from 'zrender/src/graphic/Text';
-import {
-    convertToEC4StyleForCustomSerise,
-    isEC4CompatibleStyle,
-    convertFromEC4CompatibleStyle,
-    LegacyStyleProps,
-    warnDeprecated
-} from '../../util/styleCompat';
-import Transformable from 'zrender/src/core/Transformable';
-import { ItemStyleProps } from '../../model/mixin/itemStyle';
-import { cloneValue } from 'zrender/src/animation/Animator';
-import { warn, throwError } from '../../util/log';
-import {
-    combine, isInAnyMorphing, morphPath, isCombiningPath, CombineSeparateConfig, separate, CombineSeparateResult
-} from 'zrender/src/tool/morphPath';
-import { AnimationEasing } from 'zrender/src/animation/easing';
-import * as matrix from 'zrender/src/core/matrix';
-import { PatternObject } from 'zrender/src/graphic/Pattern';
-import { createOrUpdatePatternFromDecal } from '../../util/decal';
-import { ZRenderType } from 'zrender/src/zrender';
 import { EChartsExtensionInstallRegisters } from '../../extension';
-
-
-const inner = makeInner<{
-    info: CustomExtraElementInfo;
-    customPathData: string;
-    customGraphicType: string;
-    customImagePath: CustomImageOption['style']['image'];
-    // customText: string;
-    txConZ2Set: number;
-    leaveToProps: ElementProps;
-    // Can morph: "morph" specified in option and el is Path.
-    canMorph: boolean;
-    userDuring: CustomBaseElementOption['during'];
-}, Element>();
-
-type CustomExtraElementInfo = Dictionary<unknown>;
-const TRANSFORM_PROPS = {
-    x: 1,
-    y: 1,
-    scaleX: 1,
-    scaleY: 1,
-    originX: 1,
-    originY: 1,
-    rotation: 1
-} as const;
-type TransformProp = keyof typeof TRANSFORM_PROPS;
-const transformPropNamesStr = keys(TRANSFORM_PROPS).join(', ');
-
-// Do not declare "Dictionary" in TransitionAnyOption to restrict the type check.
-type TransitionAnyOption = {
-    transition?: TransitionAnyProps;
-    enterFrom?: Dictionary<unknown>;
-    leaveTo?: Dictionary<unknown>;
-};
-type TransitionAnyProps = string | string[];
-type TransitionTransformOption = {
-    transition?: ElementRootTransitionProp | ElementRootTransitionProp[];
-    enterFrom?: Dictionary<unknown>;
-    leaveTo?: Dictionary<unknown>;
-};
-type ElementRootTransitionProp = TransformProp | 'shape' | 'extra' | 'style';
-type ShapeMorphingOption = {
-    /**
-     * If do shape morphing animation when type is changed.
-     * Only available on path.
-     */
-    morph?: boolean
-};
-
-interface CustomBaseElementOption extends Partial<Pick<
-    Element, TransformProp | 'silent' | 'ignore' | 'textConfig'
->>, TransitionTransformOption {
-    // element type, mandatory.
-    type: string;
-    id?: string;
-    // For animation diff.
-    name?: string;
-    info?: CustomExtraElementInfo;
-    // `false` means remove the textContent.
-    textContent?: CustomTextOption | false;
-    // `false` means remove the clipPath
-    clipPath?: CustomZRPathOption | false;
-    // `extra` can be set in any el option for custom prop for annimation duration.
-    extra?: TransitionAnyOption;
-    // updateDuringAnimation
-    during?(params: typeof customDuringAPI): void;
-
-    focus?: 'none' | 'self' | 'series' | ArrayLike<number>
-    blurScope?: BlurScope
-};
-interface CustomDisplayableOption extends CustomBaseElementOption, Partial<Pick<
-    Displayable, 'zlevel' | 'z' | 'z2' | 'invisible'
->> {
-    style?: ZRStyleProps & TransitionAnyOption;
-    // `false` means remove emphasis trigger.
-    styleEmphasis?: ZRStyleProps | false;
-    emphasis?: CustomDisplayableOptionOnState;
-    blur?: CustomDisplayableOptionOnState;
-    select?: CustomDisplayableOptionOnState;
-}
-interface CustomDisplayableOptionOnState extends Partial<Pick<
-    Displayable, TransformProp | 'textConfig' | 'z2'
->> {
-    // `false` means remove emphasis trigger.
-    style?: (ZRStyleProps & TransitionAnyOption) | false;
-}
-interface CustomGroupOption extends CustomBaseElementOption {
-    type: 'group';
-    width?: number;
-    height?: number;
-    // @deprecated
-    diffChildrenByName?: boolean;
-    // Can only set focus, blur on the root element.
-    children: Omit<CustomElementOption, 'focus' | 'blurScope'>[];
-    $mergeChildren: false | 'byName' | 'byIndex';
-}
-interface CustomZRPathOption extends CustomDisplayableOption, ShapeMorphingOption {
-    shape?: PathProps['shape'] & TransitionAnyOption;
-    style?: CustomDisplayableOption['style'] & {
-        decal?: DecalObject;
-        // Only internal usage. Any user specified value will be overwritten.
-        __decalPattern?: PatternObject;
-    };
-}
-interface CustomSVGPathOption extends CustomDisplayableOption, ShapeMorphingOption {
-    type: 'path';
-    shape?: {
-        // SVG Path, like 'M0,0 L0,-20 L70,-1 L70,0 Z'
-        pathData?: string;
-        // "d" is the alias of `pathData` follows the SVG convention.
-        d?: string;
-        layout?: 'center' | 'cover';
-        x?: number;
-        y?: number;
-        width?: number;
-        height?: number;
-    } & TransitionAnyOption;
-}
-interface CustomImageOption extends CustomDisplayableOption {
-    type: 'image';
-    style?: ImageStyleProps & TransitionAnyOption;
-    emphasis?: CustomImageOptionOnState;
-    blur?: CustomImageOptionOnState;
-    select?: CustomImageOptionOnState;
-}
-interface CustomImageOptionOnState extends CustomDisplayableOptionOnState {
-    style?: ImageStyleProps & TransitionAnyOption;
-}
-interface CustomTextOption extends CustomDisplayableOption {
-    type: 'text';
-}
-type CustomElementOption = CustomZRPathOption | CustomSVGPathOption | CustomImageOption | CustomTextOption;
-type CustomElementOptionOnState = CustomDisplayableOptionOnState | CustomImageOptionOnState;
-
-
-export interface CustomSeriesRenderItemAPI extends
-        CustomSeriesRenderItemCoordinateSystemAPI {
-
-    // Methods from ExtensionAPI.
-    // NOTE: Not using Pick<ExtensionAPI> here because we don't want to bundle ExtensionAPI into the d.ts
-    getWidth(): number
-    getHeight(): number
-    getZr(): ZRenderType
-    getDevicePixelRatio(): number
-
-    value(dim: DimensionLoose, dataIndexInside?: number): ParsedValue;
-    ordinalRawValue(dim: DimensionLoose, dataIndexInside?: number): ParsedValue | OrdinalRawValue;
-    style(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps;
-    styleEmphasis(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps;
-    visual<VT extends NonStyleVisualProps | StyleVisualProps>(
-        visualType: VT,
-        dataIndexInside?: number
-    ): VT extends NonStyleVisualProps ? DefaultDataVisual[VT]
-        : VT extends StyleVisualProps ? PathStyleProps[typeof STYLE_VISUAL_TYPE[VT]]
-        : void;
-    barLayout(opt: BarGridLayoutOptionForCustomSeries): BarGridLayoutResult;
-    currentSeriesIndices(): number[];
-    font(opt: Pick<TextCommonOption, 'fontStyle' | 'fontWeight' | 'fontSize' | 'fontFamily'>): string;
-}
-interface CustomSeriesRenderItemParamsCoordSys {
-    type: string;
-    // And extra params for each coordinate systems.
-}
-interface CustomSeriesRenderItemCoordinateSystemAPI {
-    coord(
-        data: OptionDataValue | OptionDataValue[],
-        clamp?: boolean
-    ): number[];
-    size?(
-        dataSize: OptionDataValue | OptionDataValue[],
-        dataItem: OptionDataValue | OptionDataValue[]
-    ): number | number[];
-}
-export interface CustomSeriesRenderItemParams {
-    context: Dictionary<unknown>;
-    seriesId: string;
-    seriesName: string;
-    seriesIndex: number;
-    coordSys: CustomSeriesRenderItemParamsCoordSys;
-    dataInsideLength: number;
-    encode: WrapEncodeDefRet;
-}
-type CustomSeriesRenderItem = (
-    params: CustomSeriesRenderItemParams,
-    api: CustomSeriesRenderItemAPI
-) => CustomElementOption;
-
-interface CustomSeriesStateOption {
-    itemStyle?: ItemStyleOption;
-    label?: LabelOption;
-}
-
-export interface CustomSeriesOption extends
-    SeriesOption<never>,    // don't support StateOption in custom series.
-    SeriesEncodeOptionMixin,
-    SeriesOnCartesianOptionMixin,
-    SeriesOnPolarOptionMixin,
-    SeriesOnSingleOptionMixin,
-    SeriesOnGeoOptionMixin,
-    SeriesOnCalendarOptionMixin {
-
-    type?: 'custom'
-
-    // If set as 'none', do not depends on coord sys.
-    coordinateSystem?: string | 'none';
-
-    renderItem?: CustomSeriesRenderItem;
-
-    // Only works on polar and cartesian2d coordinate system.
-    clip?: boolean;
-}
-
-interface LegacyCustomSeriesOption extends SeriesOption<CustomSeriesStateOption>, CustomSeriesStateOption {}
-
-
-interface LooseElementProps extends ElementProps {
-    style?: ZRStyleProps;
-    shape?: Dictionary<unknown>;
-}
-
-// Also compat with ec4, where
-// `visual('color') visual('borderColor')` is supported.
-const STYLE_VISUAL_TYPE = {
-    color: 'fill',
-    borderColor: 'stroke'
-} as const;
-type StyleVisualProps = keyof typeof STYLE_VISUAL_TYPE;
-
-const NON_STYLE_VISUAL_PROPS = {
-    symbol: 1,
-    symbolSize: 1,
-    symbolKeepAspect: 1,
-    legendSymbol: 1,
-    visualMeta: 1,
-    liftZ: 1,
-    decal: 1
-} as const;
-type NonStyleVisualProps = keyof typeof NON_STYLE_VISUAL_PROPS;
-
-const EMPHASIS = 'emphasis' as const;
-const NORMAL = 'normal' as const;
-const BLUR = 'blur' as const;
-const SELECT = 'select' as const;
-const STATES = [NORMAL, EMPHASIS, BLUR, SELECT] as const;
-const PATH_ITEM_STYLE = {
-    normal: ['itemStyle'],
-    emphasis: [EMPHASIS, 'itemStyle'],
-    blur: [BLUR, 'itemStyle'],
-    select: [SELECT, 'itemStyle']
-} as const;
-const PATH_LABEL = {
-    normal: ['label'],
-    emphasis: [EMPHASIS, 'label'],
-    blur: [BLUR, 'label'],
-    select: [SELECT, 'label']
-} as const;
-// Use prefix to avoid index to be the same as el.name,
-// which will cause weird update animation.
-const GROUP_DIFF_PREFIX = 'e\0\0';
-
-type AttachedTxInfo = {
-    isLegacy: boolean;
-    normal: {
-        cfg: ElementTextConfig;
-        conOpt: CustomElementOption | false;
-    };
-    emphasis: {
-        cfg: ElementTextConfig;
-        conOpt: CustomElementOptionOnState;
-    };
-    blur: {
-        cfg: ElementTextConfig;
-        conOpt: CustomElementOptionOnState;
-    };
-    select: {
-        cfg: ElementTextConfig;
-        conOpt: CustomElementOptionOnState;
-    };
-};
-const attachedTxInfoTmp = {
-    normal: {},
-    emphasis: {},
-    blur: {},
-    select: {}
-} as AttachedTxInfo;
-
-const LEGACY_TRANSFORM_PROPS = {
-    position: ['x', 'y'],
-    scale: ['scaleX', 'scaleY'],
-    origin: ['originX', 'originY']
-} as const;
-type LegacyTransformProp = keyof typeof LEGACY_TRANSFORM_PROPS;
-
-export type PrepareCustomInfo = (coordSys: CoordinateSystem) => {
-    coordSys: CustomSeriesRenderItemParamsCoordSys;
-    api: CustomSeriesRenderItemCoordinateSystemAPI
-};
-
-const tmpTransformable = new Transformable();
-
-/**
- * To reduce total package size of each coordinate systems, the modules `prepareCustom`
- * of each coordinate systems are not required by each coordinate systems directly, but
- * required by the module `custom`.
- *
- * prepareInfoForCustomSeries {Function}: optional
- *     @return {Object} {coordSys: {...}, api: {
- *         coord: function (data, clamp) {}, // return point in global.
- *         size: function (dataSize, dataItem) {} // return size of each axis in coordSys.
- *     }}
- */
-const prepareCustoms: Dictionary<PrepareCustomInfo> = {
-    cartesian2d: prepareCartesian2d,
-    geo: prepareGeo,
-    singleAxis: prepareSingleAxis,
-    polar: preparePolar,
-    calendar: prepareCalendar
-};
-
-class CustomSeriesModel extends SeriesModel<CustomSeriesOption> {
-
-    static type = 'series.custom';
-    readonly type = CustomSeriesModel.type;
-
-    static dependencies = ['grid', 'polar', 'geo', 'singleAxis', 'calendar'];
-
-    // preventAutoZ = true;
-
-    currentZLevel: number;
-    currentZ: number;
-
-    static defaultOption: CustomSeriesOption = {
-        coordinateSystem: 'cartesian2d', // Can be set as 'none'
-        zlevel: 0,
-        z: 2,
-        legendHoverLink: true,
-
-        // Custom series will not clip by default.
-        // Some case will use custom series to draw label
-        // For example https://echarts.apache.org/examples/en/editor.html?c=custom-gantt-flight
-        clip: false
-
-        // Cartesian coordinate system
-        // xAxisIndex: 0,
-        // yAxisIndex: 0,
-
-        // Polar coordinate system
-        // polarIndex: 0,
-
-        // Geo coordinate system
-        // geoIndex: 0,
-    };
-
-    optionUpdated() {
-        this.currentZLevel = this.get('zlevel', true);
-        this.currentZ = this.get('z', true);
-    }
-
-    getInitialData(option: CustomSeriesOption, ecModel: GlobalModel): List {
-        return createListFromArray(this.getSource(), this);
-    }
-
-    getDataParams(dataIndex: number, dataType?: SeriesDataType, el?: Element): CallbackDataParams & {
-        info: CustomExtraElementInfo
-    } {
-        const params = super.getDataParams(dataIndex, dataType) as ReturnType<CustomSeriesModel['getDataParams']>;
-        el && (params.info = inner(el).info);
-        return params;
-    }
-}
-
-
-
-class CustomSeriesView extends ChartView {
-
-    static type = 'custom';
-    readonly type = CustomSeriesView.type;
-
-    private _data: List;
-
-
-    render(
-        customSeries: CustomSeriesModel,
-        ecModel: GlobalModel,
-        api: ExtensionAPI,
-        payload: Payload
-    ): void {
-        const oldData = this._data;
-        const data = customSeries.getData();
-        const group = this.group;
-        const renderItem = makeRenderItem(customSeries, data, ecModel, api);
-
-        // By default, merge mode is applied. In most cases, custom series is
-        // used in the scenario that data amount is not large but graphic elements
-        // is complicated, where merge mode is probably necessary for optimization.
-        // For example, reuse graphic elements and only update the transform when
-        // roam or data zoom according to `actionType`.
-
-        const transOpt = customSeries.__transientTransitionOpt;
-
-        // Enable user to disable transition animation by both set
-        // `from` and `to` dimension as `null`/`undefined`.
-        if (transOpt && (transOpt.from == null || transOpt.to == null)) {
-            oldData && oldData.each(function (oldIdx) {
-                doRemoveEl(oldData.getItemGraphicEl(oldIdx), customSeries, group);
-            });
-            data.each(function (newIdx) {
-                createOrUpdateItem(
-                    api, null, newIdx, renderItem(newIdx, payload), customSeries, group, data, null
-                );
-            });
-        }
-        else {
-            const morphPreparation = new MorphPreparation(customSeries, transOpt);
-            const diffMode: DataDiffMode = transOpt ? 'multiple' : 'oneToOne';
-
-            (new DataDiffer(
-                oldData ? oldData.getIndices() : [],
-                data.getIndices(),
-                createGetKey(oldData, diffMode, transOpt && transOpt.from),
-                createGetKey(data, diffMode, transOpt && transOpt.to),
-                null,
-                diffMode
-            ))
-            .add(function (newIdx) {
-                createOrUpdateItem(
-                    api, null, newIdx, renderItem(newIdx, payload), customSeries, group,
-                    data, null
-                );
-            })
-            .remove(function (oldIdx) {
-                doRemoveEl(oldData.getItemGraphicEl(oldIdx), customSeries, group);
-            })
-            .update(function (newIdx, oldIdx) {
-                morphPreparation.reset('oneToOne');
-                let oldEl = oldData.getItemGraphicEl(oldIdx);
-                morphPreparation.findAndAddFrom(oldEl);
-
-                // PENDING:
-                // if may morph, currently we alway recreate the whole el.
-                // because if reuse some of the el in the group tree, the old el has to
-                // be removed from the group, and consequently we can not calculate
-                // the "global transition" of the old element.
-                // But is there performance issue?
-                if (morphPreparation.hasFrom()) {
-                    removeElementDirectly(oldEl, group);
-                    oldEl = null;
-                }
-                createOrUpdateItem(
-                    api, oldEl, newIdx, renderItem(newIdx, payload), customSeries, group,
-                    data, morphPreparation
-                );
-                morphPreparation.applyMorphing();
-            })
-            .updateManyToOne(function (newIdx, oldIndices) {
-                morphPreparation.reset('manyToOne');
-                for (let i = 0; i < oldIndices.length; i++) {
-                    const oldEl = oldData.getItemGraphicEl(oldIndices[i]);
-                    morphPreparation.findAndAddFrom(oldEl);
-                    removeElementDirectly(oldEl, group);
-                }
-                createOrUpdateItem(
-                    api, null, newIdx, renderItem(newIdx, payload), customSeries, group,
-                    data, morphPreparation
-                );
-                morphPreparation.applyMorphing();
-            })
-            .updateOneToMany(function (newIndices, oldIdx) {
-                morphPreparation.reset('oneToMany');
-                const newLen = newIndices.length;
-                const oldEl = oldData.getItemGraphicEl(oldIdx);
-                morphPreparation.findAndAddFrom(oldEl);
-                removeElementDirectly(oldEl, group);
-
-                for (let i = 0; i < newLen; i++) {
-                    createOrUpdateItem(
-                        api, null, newIndices[i], renderItem(newIndices[i], payload), customSeries, group,
-                        data, morphPreparation
-                    );
-                }
-                morphPreparation.applyMorphing();
-            })
-            .execute();
-        }
-
-        // Do clipping
-        const clipPath = customSeries.get('clip', true)
-            ? createClipPath(customSeries.coordinateSystem, false, customSeries)
-            : null;
-        if (clipPath) {
-            group.setClipPath(clipPath);
-        }
-        else {
-            group.removeClipPath();
-        }
-
-        this._data = data;
-    }
-
-    incrementalPrepareRender(
-        customSeries: CustomSeriesModel,
-        ecModel: GlobalModel,
-        api: ExtensionAPI
-    ): void {
-        this.group.removeAll();
-        this._data = null;
-    }
-
-    incrementalRender(
-        params: StageHandlerProgressParams,
-        customSeries: CustomSeriesModel,
-        ecModel: GlobalModel,
-        api: ExtensionAPI,
-        payload: Payload
-    ): void {
-        const data = customSeries.getData();
-        const renderItem = makeRenderItem(customSeries, data, ecModel, api);
-        function setIncrementalAndHoverLayer(el: Displayable) {
-            if (!el.isGroup) {
-                el.incremental = true;
-                el.ensureState('emphasis').hoverLayer = true;
-            }
-        }
-        for (let idx = params.start; idx < params.end; idx++) {
-            const el = createOrUpdateItem(
-                null, null, idx, renderItem(idx, payload), customSeries, this.group, data, null
-            );
-            el.traverse(setIncrementalAndHoverLayer);
-        }
-    }
-
-    filterForExposedEvent(
-        eventType: string, query: EventQueryItem, targetEl: Element, packedEvent: ECEvent
-    ): boolean {
-        const elementName = query.element;
-        if (elementName == null || targetEl.name === elementName) {
-            return true;
-        }
-
-        // Enable to give a name on a group made by `renderItem`, and listen
-        // events that triggerd by its descendents.
-        while ((targetEl = (targetEl.__hostTarget || targetEl.parent)) && targetEl !== this.group) {
-            if (targetEl.name === elementName) {
-                return true;
-            }
-        }
-
-        return false;
-    }
-}
-
-
-function createGetKey(
-    data: List,
-    diffMode: DataDiffMode,
-    dimension: DimensionLoose
-) {
-    if (!data) {
-        return;
-    }
-
-    if (diffMode === 'oneToOne') {
-        return function (rawIdx: number, dataIndex: number) {
-            return data.getId(dataIndex);
-        };
-    }
-
-    const diffByDimName = data.getDimension(dimension);
-    const dimInfo = data.getDimensionInfo(diffByDimName);
-
-    if (!dimInfo) {
-        let errMsg = '';
-        if (__DEV__) {
-            errMsg = `${dimension} is not a valid dimension.`;
-        }
-        throwError(errMsg);
-    }
-    const ordinalMeta = dimInfo.ordinalMeta;
-    return function (rawIdx: number, dataIndex: number) {
-        let key = data.get(diffByDimName, dataIndex);
-        if (ordinalMeta) {
-            key = ordinalMeta.categories[key as number];
-        }
-        return (key == null || eqNaN(key))
-            ? rawIdx + ''
-            : '_ec_' + key;
-    };
-}
-
-
-function createEl(elOption: CustomElementOption): Element {
-    const graphicType = elOption.type;
-    let el;
-
-    // Those graphic elements are not shapes. They should not be
-    // overwritten by users, so do them first.
-    if (graphicType === 'path') {
-        const shape = (elOption as CustomSVGPathOption).shape;
-        // Using pathRect brings convenience to users sacle svg path.
-        const pathRect = (shape.width != null && shape.height != null)
-            ? {
-                x: shape.x || 0,
-                y: shape.y || 0,
-                width: shape.width,
-                height: shape.height
-            } as RectLike
-            : null;
-        const pathData = getPathData(shape);
-        // Path is also used for icon, so layout 'center' by default.
-        el = graphicUtil.makePath(pathData, null, pathRect, shape.layout || 'center');
-        inner(el).customPathData = pathData;
-    }
-    else if (graphicType === 'image') {
-        el = new graphicUtil.Image({});
-        inner(el).customImagePath = (elOption as CustomImageOption).style.image;
-    }
-    else if (graphicType === 'text') {
-        el = new graphicUtil.Text({});
-        // inner(el).customText = (elOption.style as TextStyleProps).text;
-    }
-    else if (graphicType === 'group') {
-        el = new graphicUtil.Group();
-    }
-    else if (graphicType === 'compoundPath') {
-        throw new Error('"compoundPath" is not supported yet.');
-    }
-    else {
-        const Clz = graphicUtil.getShapeClass(graphicType);
-        if (!Clz) {
-            let errMsg = '';
-            if (__DEV__) {
-                errMsg = 'graphic type "' + graphicType + '" can not be found.';
-            }
-            throwError(errMsg);
-        }
-        el = new Clz();
-    }
-
-    inner(el).customGraphicType = graphicType;
-    el.name = elOption.name;
-
-    // Compat ec4: the default z2 lift is 1. If changing the number,
-    // some cases probably be broken: hierarchy layout along z, like circle packing,
-    // where emphasis only intending to modify color/border rather than lift z2.
-    (el as ECElement).z2EmphasisLift = 1;
-    (el as ECElement).z2SelectLift = 1;
-
-    return el;
-}
-
-/**
- * ----------------------------------------------------------
- * [STRATEGY_MERGE] Merge properties or erase all properties:
- *
- * Based on the fact that the existing zr element probably be reused, we now consider whether
- * merge or erase all properties to the exsiting elements.
- * That is, if a certain props is not specified in the lastest return of `renderItem`:
- * + "Merge" means that do not modify the value on the existing element.
- * + "Erase all" means that use a default value to the existing element.
- *
- * "Merge" might bring some unexpected state retaining for users and "erase all" seams to be
- * more safe. "erase all" force users to specify all of the props each time, which is recommanded
- * in most cases.
- * But "erase all" theoretically disables the chance of performance optimization (e.g., just
- * generete shape and style at the first time rather than always do that).
- * So we still use "merge" rather than "erase all". If users need "erase all", they can
- * simple always set all of the props each time.
- * Some "object-like" config like `textConfig`, `textContent`, `style` which are not needed for
- * every elment, so we replace them only when user specify them. And the that is a total replace.
- *
- * TODO: there is no hint of 'isFirst' to users. So the performance enhancement can not be
- * performed yet. Consider the case:
- * (1) setOption to "mergeChildren" with a smaller children count
- * (2) Use dataZoom to make an item disappear.
- * (3) User dataZoom to make the item display again. At that time, renderItem need to return the
- * full option rather than partial option to recreate the element.
- *
- * ----------------------------------------------
- * [STRATEGY_NULL] `hasOwnProperty` or `== null`:
- *
- * Ditinguishing "own property" probably bring little trouble to user when make el options.
- * So we  trade a {xx: null} or {xx: undefined} as "not specified" if possible rather than
- * "set them to null/undefined". In most cases, props can not be cleared. Some typicall
- * clearable props like `style`/`textConfig`/`textContent` we enable `false` to means
- * "clear". In some othere special cases that the prop is able to set as null/undefined,
- * but not suitable to use `false`, `hasOwnProperty` is checked.
- *
- * ---------------------------------------------
- * [STRATEGY_TRANSITION] The rule of transition:
- * + For props on the root level of a element:
- *      If there is no `transition` specified, tansform props will be transitioned by default,
- *      which is the same as the previous setting in echarts4 and suitable for the scenario
- *      of dataZoom change.
- *      If `transition` specified, only the specified props will be transitioned.
- * + For props in `shape` and `style`:
- *      Only props specified in `transition` will be transitioned.
- * + Break:
- *      Since ec5, do not make transition to shape by default, because it might result in
- *      performance issue (especially `points` of polygon) and do not necessary in most cases.
- *
- * @return if `isMorphTo`, return `allPropsFinal`.
- */
-function updateElNormal(
-    // Can be null/undefined
-    api: ExtensionAPI,
-    el: Element,
-    // Whether be a morph target.
-    isMorphTo: boolean,
-    dataIndex: number,
-    elOption: CustomElementOption,
-    styleOpt: CustomElementOption['style'],
-    attachedTxInfo: AttachedTxInfo,
-    seriesModel: CustomSeriesModel,
-    isInit: boolean,
-    isTextContent: boolean
-): ElementProps {
-    const transFromProps = {} as ElementProps;
-    const allPropsFinal = {} as ElementProps;
-    const elDisplayable = el.isGroup ? null : el as Displayable;
-
-    // If be "morph to", delay the `updateElNormal` when all of the els in
-    // this data item processed. Because at that time we can get all of the
-    // "morph from" and make correct separate/combine.
-
-    !isMorphTo && prepareShapeOrExtraTransitionFrom('shape', el, null, elOption, transFromProps, isInit);
-    prepareShapeOrExtraAllPropsFinal('shape', elOption, allPropsFinal);
-    !isMorphTo && prepareShapeOrExtraTransitionFrom('extra', el, null, elOption, transFromProps, isInit);
-    prepareShapeOrExtraAllPropsFinal('extra', elOption, allPropsFinal);
-    !isMorphTo && prepareTransformTransitionFrom(el, null, elOption, transFromProps, isInit);
-    prepareTransformAllPropsFinal(elOption, allPropsFinal);
-
-    const txCfgOpt = attachedTxInfo && attachedTxInfo.normal.cfg;
-    if (txCfgOpt) {
-        // PENDING: whether use user object directly rather than clone?
-        // TODO:5.0 textConfig transition animation?
-        el.setTextConfig(txCfgOpt);
-    }
-
-    if (el.type === 'text' && styleOpt) {
-        const textOptionStyle = styleOpt as TextStyleProps;
-        // Compatible with ec4: if `textFill` or `textStroke` exists use them.
-        hasOwn(textOptionStyle, 'textFill') && (
-            textOptionStyle.fill = (textOptionStyle as any).textFill
-        );
-        hasOwn(textOptionStyle, 'textStroke') && (
-            textOptionStyle.stroke = (textOptionStyle as any).textStroke
-        );
-    }
-
-    if (styleOpt) {
-        let decalPattern;
-        const decalObj = isPath(el) ? (styleOpt as CustomZRPathOption['style']).decal : null;
-        if (api && decalObj) {
-            (decalObj as InnerDecalObject).dirty = true;
-            decalPattern = createOrUpdatePatternFromDecal(decalObj, api);
-        }
-        // Always overwrite in case user specify this prop.
-        (styleOpt as CustomZRPathOption['style']).__decalPattern = decalPattern;
-    }
-
-    !isMorphTo && prepareStyleTransitionFrom(el, null, elOption, styleOpt, transFromProps, isInit);
-
-    if (elDisplayable) {
-        hasOwn(elOption, 'invisible') && (elDisplayable.invisible = elOption.invisible);
-    }
-
-    // If `isMorphTo`, we should not update these props to el directly, otherwise,
-    // when applying morph finally, the original prop are missing for making "animation from".
-    if (!isMorphTo) {
-        applyPropsFinal(el, allPropsFinal, styleOpt);
-        applyTransitionFrom(el, dataIndex, elOption, seriesModel, transFromProps, isInit);
-    }
-
-    // Merge by default.
-    hasOwn(elOption, 'silent') && (el.silent = elOption.silent);
-    hasOwn(elOption, 'ignore') && (el.ignore = elOption.ignore);
-
-    if (!isTextContent) {
-        // `elOption.info` enables user to mount some info on
-        // elements and use them in event handlers.
-        // Update them only when user specified, otherwise, remain.
-        hasOwn(elOption, 'info') && (inner(el).info = elOption.info);
-    }
-
-    styleOpt ? el.dirty() : el.markRedraw();
-
-    return isMorphTo ? allPropsFinal : null;
-}
-
-function applyPropsFinal(
-    el: Element,
-    // Can be null/undefined
-    allPropsFinal: ElementProps,
-    styleOpt: CustomElementOption['style']
-) {
-    const elDisplayable = el.isGroup ? null : el as Displayable;
-
-    if (elDisplayable && styleOpt) {
-
-        const decalPattern = (styleOpt as CustomZRPathOption['style']).__decalPattern;
-        let originalDecalObj;
-        if (decalPattern) {
-            originalDecalObj = (styleOpt as CustomZRPathOption['style']).decal;
-            (styleOpt as any).decal = decalPattern;
-        }
-
-        // PENDING: here the input style object is used directly.
-        // Good for performance but bad for compatibility control.
-        elDisplayable.useStyle(styleOpt);
-
-        if (decalPattern) {
-            (styleOpt as CustomZRPathOption['style']).decal = originalDecalObj;
-        }
-
-        // When style object changed, how to trade the existing animation?
-        // It is probably conplicated 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 mousewhell. 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 = elDisplayable.animators;
-        for (let i = 0; i < animators.length; i++) {
-            const animator = animators[i];
-            // targetName is the "topKey".
-            if (animator.targetName === 'style') {
-                animator.changeTarget(elDisplayable.style);
-            }
-        }
-    }
-
-    // Set el to the final state firstly.
-    allPropsFinal && el.attr(allPropsFinal);
-}
-
-function applyTransitionFrom(
-    el: Element,
-    dataIndex: number,
-    elOption: CustomElementOption,
-    seriesModel: CustomSeriesModel,
-    // Can be null/undefined
-    transFromProps: ElementProps,
-    isInit: boolean
-): void {
-    if (transFromProps) {
-        // Do not use `el.updateDuringAnimation` here becuase `el.updateDuringAnimation` will
-        // be called mutiple time in each animation frame. For example, if both "transform" props
-        // and shape props and style props changed, it will generate three animator and called
-        // one-by-one in each animation frame.
-        // We use the during in `animateTo/From` params.
-        const userDuring = elOption.during;
-        // For simplicity, if during not specified, the previous during will not work any more.
-        inner(el).userDuring = userDuring;
-        const cfgDuringCall = userDuring ? bind(duringCall, { el: el, userDuring: userDuring }) : null;
-        const cfg = {
-            dataIndex: dataIndex,
-            isFrom: true,
-            during: cfgDuringCall
-        };
-        isInit
-            ? graphicUtil.initProps(el, transFromProps, seriesModel, cfg)
-            : graphicUtil.updateProps(el, transFromProps, seriesModel, cfg);
-    }
-}
-
-
-// See [STRATEGY_TRANSITION]
-function prepareShapeOrExtraTransitionFrom(
-    mainAttr: 'shape' | 'extra',
-    el: Element,
-    morphFromEl: graphicUtil.Path,
-    elOption: CustomElementOption,
-    transFromProps: LooseElementProps,
-    isInit: boolean
-): void {
-
-    const attrOpt: Dictionary<unknown> & TransitionAnyOption = (elOption as any)[mainAttr];
-    if (!attrOpt) {
-        return;
-    }
-
-    const elPropsInAttr = (el 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
-        // Just ignore shape animation in morphing.
-        && !(morphFromEl != null && mainAttr === 'shape')
-    ) {
-        if (attrOpt.transition) {
-            !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {});
-            const transitionKeys = normalizeToArray(attrOpt.transition);
-            for (let i = 0; i < transitionKeys.length; i++) {
-                const key = transitionKeys[i];
-                const elVal = elPropsInAttr[key];
-                if (__DEV__) {
-                    checkNonStyleTansitionRefer(key, (attrOpt as any)[key], elVal);
-                }
-                // Do not clone, see `checkNonStyleTansitionRefer`.
-                transFromPropsInAttr[key] = elVal;
-            }
-        }
-        else if (indexOf(elOption.transition, mainAttr) >= 0) {
-            !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {});
-            const elPropsInAttrKeys = keys(elPropsInAttr);
-            for (let i = 0; i < elPropsInAttrKeys.length; i++) {
-                const key = elPropsInAttrKeys[i];
-                const elVal = elPropsInAttr[key];
-                if (isNonStyleTransitionEnabled((attrOpt as any)[key], elVal)) {
-                    transFromPropsInAttr[key] = elVal;
-                }
-            }
-        }
-    }
-
-    const leaveTo = attrOpt.leaveTo;
-    if (leaveTo) {
-        const leaveToProps = getOrCreateLeaveToPropsFromEl(el);
-        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(
-    mainAttr: 'shape' | 'extra',
-    elOption: CustomElementOption,
-    allProps: LooseElementProps
-): void {
-    const attrOpt: Dictionary<unknown> & TransitionAnyOption = (elOption as any)[mainAttr];
-    if (!attrOpt) {
-        return;
-    }
-    const allPropsInAttr = allProps[mainAttr] = {} as Dictionary<unknown>;
-    const keysInAttr = keys(attrOpt);
-    for (let i = 0; i < keysInAttr.length; i++) {
-        const 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 as any)[key]);
-    }
-}
-
-// See [STRATEGY_TRANSITION].
-function prepareTransformTransitionFrom(
-    el: Element,
-    morphFromEl: graphicUtil.Path,
-    elOption: CustomElementOption,
-    transFromProps: ElementProps,
-    isInit: boolean
-): 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;
-        }
-    }
-
-    if (!isInit) {
-        // If morphing, force transition all transform props.
-        // otherwise might have incorrect morphing animation.
-        if (morphFromEl) {
-            const fromTransformable = calcOldElLocalTransformBasedOnNewElParent(morphFromEl, el);
-            setTransformPropToTransitionFrom(transFromProps, 'x', fromTransformable);
-            setTransformPropToTransitionFrom(transFromProps, 'y', fromTransformable);
-            setTransformPropToTransitionFrom(transFromProps, 'scaleX', fromTransformable);
-            setTransformPropToTransitionFrom(transFromProps, 'scaleY', fromTransformable);
-            setTransformPropToTransitionFrom(transFromProps, 'originX', fromTransformable);
-            setTransformPropToTransitionFrom(transFromProps, 'originY', fromTransformable);
-            setTransformPropToTransitionFrom(transFromProps, 'rotation', fromTransformable);
-        }
-        else if (elOption.transition) {
-            const transitionKeys = normalizeToArray(elOption.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');
-                    checkNonStyleTansitionRefer(key, elOption[key], elVal);
-                }
-                // Do not clone, see `checkNonStyleTansitionRefer`.
-                transFromProps[key] = elVal;
-            }
-        }
-        // This default transition see [STRATEGY_TRANSITION]
-        else {
-            setTransformPropToTransitionFrom(transFromProps, 'x', el);
-            setTransformPropToTransitionFrom(transFromProps, 'y', el);
-        }
-    }
-
-    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;
-        }
-    }
-}
-
-function prepareTransformAllPropsFinal(
-    elOption: CustomElementOption,
-    allProps: ElementProps
-): void {
-    setLagecyTransformProp(elOption, allProps, 'position');
-    setLagecyTransformProp(elOption, allProps, 'scale');
-    setLagecyTransformProp(elOption, allProps, 'origin');
-    setTransformProp(elOption, allProps, 'x');
-    setTransformProp(elOption, allProps, 'y');
-    setTransformProp(elOption, allProps, 'scaleX');
-    setTransformProp(elOption, allProps, 'scaleY');
-    setTransformProp(elOption, allProps, 'originX');
-    setTransformProp(elOption, allProps, 'originY');
-    setTransformProp(elOption, allProps, 'rotation');
-}
-
-// See [STRATEGY_TRANSITION].
-function prepareStyleTransitionFrom(
-    el: Element,
-    morphFromEl: graphicUtil.Path,
-    elOption: CustomElementOption,
-    styleOpt: CustomElementOption['style'],
-    transFromProps: LooseElementProps,
-    isInit: boolean
-): void {
-    if (!styleOpt) {
-        return;
-    }
-
-    // At present in "many-to-one"/"one-to-many" case, to not support "many" have
-    // different styles and make style transitions. That might be a rare case.
-    const fromEl = morphFromEl || el;
-
-    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 (styleOpt.transition) {
-            const transitionKeys = normalizeToArray(styleOpt.transition);
-            !transFromStyleProps && (transFromStyleProps = transFromProps.style = {});
-            for (let i = 0; i < transitionKeys.length; i++) {
-                const key = transitionKeys[i];
-                const elVal = (fromElStyle as any)[key];
-                // Do not clone, see `checkNonStyleTansitionRefer`.
-                (transFromStyleProps as any)[key] = elVal;
-            }
-        }
-        else if (
-            (el as Displayable).getAnimationStyleProps
-            && indexOf(elOption.transition, 'style') >= 0
-        ) {
-            const animationProps = (el as Displayable).getAnimationStyleProps();
-            const animationStyleProps = animationProps ? animationProps.style : null;
-            if (animationStyleProps) {
-                !transFromStyleProps && (transFromStyleProps = transFromProps.style = {});
-                const styleKeys = keys(styleOpt);
-                for (let i = 0; i < styleKeys.length; i++) {
-                    const key = styleKeys[i];
-                    if ((animationStyleProps as Dictionary<unknown>)[key]) {
-                        const elVal = (fromElStyle as any)[key];
-                        (transFromStyleProps as any)[key] = elVal;
-                    }
-                }
-            }
-        }
-    }
-
-    const leaveTo = styleOpt.leaveTo;
-    if (leaveTo) {
-        const leaveToKeys = keys(leaveTo);
-        const leaveToProps = getOrCreateLeaveToPropsFromEl(el);
-        const leaveToStyleProps = leaveToProps.style || (leaveToProps.style = {});
-        for (let i = 0; i < leaveToKeys.length; i++) {
-            const key = leaveToKeys[i];
-            (leaveToStyleProps as any)[key] = leaveTo[key];
-        }
-    }
-}
-
-/**
- * If make "transform"(x/y/scaleX/scaleY/orient/originX/originY) transition between
- * two path elements that have different hierarchy, before we retrieve the "from" props,
- * we have to calculate the local transition of the "oldPath" based on the parent of
- * the "newPath".
- * At present, the case only happend in "morphing". Without morphing, the transform
- * transition are all between elements in the same hierarchy, where this kind of process
- * is not needed.
- *
- * [CAVEAT]:
- * This method makes sense only if: (very tricky)
- * (1) "newEl" has been added to its final parent.
- * (2) Local transform props of "newPath.parent" are not at their final value but already
- * have been at the "from value".
- *     This is currently ensured by:
- *     (2.1) "graphicUtil.animationFrom", which will set the element to the "from value"
- *     immediately.
- *     (2.2) "morph" option is not allowed to be set on Group, so all of the groups have
- *     been finished their "updateElNormal" when calling this method in morphing process.
- */
-function calcOldElLocalTransformBasedOnNewElParent(oldEl: Element, newEl: Element): Transformable {
-    if (!oldEl || oldEl === newEl || oldEl.parent === newEl.parent) {
-        return oldEl;
-    }
-
-    // Not sure oldEl is rendered (may have "lazyUpdate"),
-    // so always call `getComputedTransform`.
-    const tmpM = tmpTransformable.transform
-        || (tmpTransformable.transform = matrix.identity([]));
-
-    const oldGlobalTransform = oldEl.getComputedTransform();
-    oldGlobalTransform
-        ? matrix.copy(tmpM, oldGlobalTransform)
-        : matrix.identity(tmpM);
-
-    const newParent = newEl.parent;
-    if (newParent) {
-        newParent.getComputedTransform();
-    }
-
-    tmpTransformable.originX = oldEl.originX;
-    tmpTransformable.originY = oldEl.originY;
-    tmpTransformable.parent = newParent;
-    tmpTransformable.decomposeTransform();
-
-    return tmpTransformable;
-}
-
-let checkNonStyleTansitionRefer: (propName: string, optVal: unknown, elVal: unknown) => void;
-if (__DEV__) {
-    checkNonStyleTansitionRefer = function (propName: string, optVal: unknown, elVal: unknown): void {
-        if (!isArrayLike(optVal)) {
-            assert(
-                optVal != null && isFinite(optVal as number),
-                'Prop `' + propName + '` must refer to a finite number or ArrayLike for transition.'
-            );
-        }
-        else {
-            // Try not to copy array for performance, but if user use the same object in different
-            // call of `renderItem`, it will casue animation transition fail.
-            assert(
-                optVal !== elVal,
-                'Prop `' + propName + '` must use different Array object each time for transition.'
-            );
-        }
-    };
-}
-
-function isNonStyleTransitionEnabled(optVal: unknown, elVal: unknown): boolean {
-    // The same as `checkNonStyleTansitionRefer`.
-    return !isArrayLike(optVal)
-        ? (optVal != null && isFinite(optVal as number))
-        : optVal !== elVal;
-}
-
-let checkTransformPropRefer: (key: string, usedIn: string) => void;
-if (__DEV__) {
-    checkTransformPropRefer = function (key: string, usedIn: string): void {
-        assert(
-            hasOwn(TRANSFORM_PROPS, key),
-            'Prop `' + key + '` is not a permitted in `' + usedIn + '`. '
-                + 'Only `' + keys(TRANSFORM_PROPS).join('`, `') + '` are permitted.'
-        );
-    };
-}
-
-function getOrCreateLeaveToPropsFromEl(el: Element): LooseElementProps {
-    const innerEl = inner(el);
-    return innerEl.leaveToProps || (innerEl.leaveToProps = {});
-}
-
-// Use it to avoid it be exposed to user.
-const tmpDuringScope = {} as {
-    el: Element;
-    isShapeDirty: boolean;
-    isStyleDirty: boolean;
-};
-const customDuringAPI = {
-    // Usually other props do not need to be changed in animation during.
-    setTransform(key: TransformProp, val: unknown) {
-        if (__DEV__) {
-            assert(hasOwn(TRANSFORM_PROPS, key), 'Only ' + transformPropNamesStr + ' available in `setTransform`.');
-        }
-        tmpDuringScope.el[key] = val as number;
-        return this;
-    },
-    getTransform(key: TransformProp): unknown {
-        if (__DEV__) {
-            assert(hasOwn(TRANSFORM_PROPS, key), 'Only ' + transformPropNamesStr + ' available in `getTransform`.');
-        }
-        return tmpDuringScope.el[key];
-    },
-    setShape(key: string, val: unknown) {
-        if (__DEV__) {
-            assertNotReserved(key);
-        }
-        const shape = (tmpDuringScope.el as graphicUtil.Path).shape
-            || ((tmpDuringScope.el as graphicUtil.Path).shape = {});
-        shape[key] = val;
-        tmpDuringScope.isShapeDirty = true;
-        return this;
-    },
-    getShape(key: string): unknown {
-        if (__DEV__) {
-            assertNotReserved(key);
-        }
-        const shape = (tmpDuringScope.el as graphicUtil.Path).shape;
-        if (shape) {
-            return shape[key];
-        }
-    },
-    setStyle(key: string, val: unknown) {
-        if (__DEV__) {
-            assertNotReserved(key);
-        }
-        const style = (tmpDuringScope.el as Displayable).style;
-        if (style) {
-            if (__DEV__) {
-                if (eqNaN(val)) {
-                    warn('style.' + key + ' must not be assigned with NaN.');
-                }
-            }
-            style[key] = val;
-            tmpDuringScope.isStyleDirty = true;
-        }
-        return this;
-    },
-    getStyle(key: string): unknown {
-        if (__DEV__) {
-            assertNotReserved(key);
-        }
-        const style = (tmpDuringScope.el as Displayable).style;
-        if (style) {
-            return style[key];
-        }
-    },
-    setExtra(key: string, val: unknown) {
-        if (__DEV__) {
-            assertNotReserved(key);
-        }
-        const extra = (tmpDuringScope.el as LooseElementProps).extra
-            || ((tmpDuringScope.el as LooseElementProps).extra = {});
-        extra[key] = val;
-        return this;
-    },
-    getExtra(key: string): unknown {
-        if (__DEV__) {
-            assertNotReserved(key);
-        }
-        const extra = (tmpDuringScope.el as LooseElementProps).extra;
-        if (extra) {
-            return extra[key];
-        }
-    }
-};
-
-function assertNotReserved(key: string) {
-    if (__DEV__) {
-        if (key === 'transition' || key === 'enterFrom' || key === 'leaveTo') {
-            throw new Error('key must not be "' + key + '"');
-        }
-    }
-}
-
-function duringCall(
-    this: {
-        el: Element;
-        userDuring: CustomBaseElementOption['during']
-    }
-): void {
-    // 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.
-    const scope = this;
-    const el = scope.el;
-    if (!el) {
-        return;
-    }
-    // If el is remove from zr by reason like legend, during still need to called,
-    // becuase el will be added back to zr and the prop value should not be incorrect.
-
-    const newstUserDuring = inner(el).userDuring;
-    const 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' calulation 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 (newstUserDuring !== scopeUserDuring) {
-        // release
-        scope.el = scope.userDuring = null;
-        return;
-    }
-
-    tmpDuringScope.el = el;
-    tmpDuringScope.isShapeDirty = false;
-    tmpDuringScope.isStyleDirty = false;
-
-    // Give no `this` to user in "during" calling.
-    scopeUserDuring(customDuringAPI);
-
-    if (tmpDuringScope.isShapeDirty && (el as graphicUtil.Path).dirtyShape) {
-        (el as graphicUtil.Path).dirtyShape();
-    }
-    if (tmpDuringScope.isStyleDirty && (el as Displayable).dirtyStyle) {
-        (el as Displayable).dirtyStyle();
-    }
-    // markRedraw() will be called by default in during.
-    // FIXME `this.markRedraw();` directly ?
-
-    // 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 updateElOnState(
-    state: DisplayStateNonNormal,
-    el: Element,
-    elStateOpt: CustomElementOptionOnState,
-    styleOpt: CustomElementOptionOnState['style'],
-    attachedTxInfo: AttachedTxInfo,
-    isRoot: boolean,
-    isTextContent: boolean
-): void {
-    const elDisplayable = el.isGroup ? null : el as Displayable;
-    const txCfgOpt = attachedTxInfo && attachedTxInfo[state].cfg;
-
-    // PENDING:5.0 support customize scale change and transition animation?
-
-    if (elDisplayable) {
-        // By default support auto lift color when hover whether `emphasis` specified.
-        const stateObj = elDisplayable.ensureState(state);
-        if (styleOpt === false) {
-            const existingEmphasisState = elDisplayable.getState(state);
-            if (existingEmphasisState) {
-                existingEmphasisState.style = null;
-            }
-        }
-        else {
-            // style is needed to enable defaut emphasis.
-            stateObj.style = styleOpt || null;
-        }
-        // If `elOption.styleEmphasis` or `elOption.emphasis.style` is `false`,
-        // remove hover style.
-        // If `elOption.textConfig` or `elOption.emphasis.textConfig` is null/undefined, it does not
-        // make sense. So for simplicity, we do not ditinguish `hasOwnProperty` and null/undefined.
-        if (txCfgOpt) {
-            stateObj.textConfig = txCfgOpt;
-        }
-
-        setDefaultStateProxy(elDisplayable);
-    }
-}
-
-function updateZ(
-    el: Element,
-    elOption: CustomElementOption,
-    seriesModel: CustomSeriesModel,
-    attachedTxInfo: AttachedTxInfo
-): void {
-    // Group not support textContent and not support z yet.
-    if (el.isGroup) {
-        return;
-    }
-
-    const elDisplayable = el as Displayable;
-    const currentZ = seriesModel.currentZ;
-    const currentZLevel = seriesModel.currentZLevel;
-    // Always erase.
-    elDisplayable.z = currentZ;
-    elDisplayable.zlevel = currentZLevel;
-    // z2 must not be null/undefined, otherwise sort error may occur.
-    const optZ2 = elOption.z2;
-    optZ2 != null && (elDisplayable.z2 = optZ2 || 0);
-
-    for (let i = 0; i < STATES.length; i++) {
-        updateZForEachState(elDisplayable, elOption, STATES[i]);
-    }
-}
-
-function updateZForEachState(
-    elDisplayable: Displayable,
-    elOption: CustomDisplayableOption,
-    state: DisplayState
-): void {
-    const isNormal = state === NORMAL;
-    const elStateOpt = isNormal ? elOption : retrieveStateOption(elOption, state as DisplayStateNonNormal);
-    const optZ2 = elStateOpt ? elStateOpt.z2 : null;
-    let stateObj;
-    if (optZ2 != null) {
-        // Do not `ensureState` until required.
-        stateObj = isNormal ? elDisplayable : elDisplayable.ensureState(state);
-        stateObj.z2 = optZ2 || 0;
-    }
-}
-
-function setLagecyTransformProp(
-    elOption: CustomElementOption,
-    targetProps: Partial<Pick<Transformable, TransformProp>>,
-    legacyName: LegacyTransformProp,
-    fromTransformable?: Transformable // If provided, retrieve from the element.
-): void {
-    const legacyArr = (elOption as any)[legacyName];
-    const xyName = LEGACY_TRANSFORM_PROPS[legacyName];
-    if (legacyArr) {
-        if (fromTransformable) {
-            targetProps[xyName[0]] = fromTransformable[xyName[0]];
-            targetProps[xyName[1]] = fromTransformable[xyName[1]];
-        }
-        else {
-            targetProps[xyName[0]] = legacyArr[0];
-            targetProps[xyName[1]] = legacyArr[1];
-        }
-    }
-}
-
-function setTransformProp(
-    elOption: CustomElementOption,
-    allProps: Partial<Pick<Transformable, TransformProp>>,
-    name: TransformProp,
-    fromTransformable?: Transformable // If provided, retrieve from the element.
-): void {
-    if (elOption[name] != null) {
-        allProps[name] = fromTransformable ? fromTransformable[name] : elOption[name];
-    }
-}
-
-function setTransformPropToTransitionFrom(
-    transitionFrom: Partial<Pick<Transformable, TransformProp>>,
-    name: TransformProp,
-    fromTransformable?: Transformable // If provided, retrieve from the element.
-): void {
-    if (fromTransformable) {
-        transitionFrom[name] = fromTransformable[name];
-    }
-}
-
-
-function makeRenderItem(
-    customSeries: CustomSeriesModel,
-    data: List<CustomSeriesModel>,
-    ecModel: GlobalModel,
-    api: ExtensionAPI
-) {
-    const renderItem = customSeries.get('renderItem');
-    const coordSys = customSeries.coordinateSystem;
-    let prepareResult = {} as ReturnType<PrepareCustomInfo>;
-
-    if (coordSys) {
-        if (__DEV__) {
-            assert(renderItem, 'series.render is required.');
-            assert(
-                coordSys.prepareCustoms || prepareCustoms[coordSys.type],
-                'This coordSys does not support custom series.'
-            );
-        }
-
-        // `coordSys.prepareCustoms` is used for external coord sys like bmap.
-        prepareResult = coordSys.prepareCustoms
-            ? coordSys.prepareCustoms(coordSys)
-            : prepareCustoms[coordSys.type](coordSys);
-    }
-
-    const userAPI = defaults({
-        getWidth: api.getWidth,
-        getHeight: api.getHeight,
-        getZr: api.getZr,
-        getDevicePixelRatio: api.getDevicePixelRatio,
-        value: value,
-        style: style,
-        ordinalRawValue: ordinalRawValue,
-        styleEmphasis: styleEmphasis,
-        visual: visual,
-        barLayout: barLayout,
-        currentSeriesIndices: currentSeriesIndices,
-        font: font
-    }, prepareResult.api || {}) as CustomSeriesRenderItemAPI;
-
-    const userParams: CustomSeriesRenderItemParams = {
-        // The life cycle of context: current round of rendering.
-        // The global life cycle is probably not necessary, because
-        // user can store global status by themselves.
-        context: {},
-        seriesId: customSeries.id,
-        seriesName: customSeries.name,
-        seriesIndex: customSeries.seriesIndex,
-        coordSys: prepareResult.coordSys,
-        dataInsideLength: data.count(),
-        encode: wrapEncodeDef(customSeries.getData())
-    };
-
-    // If someday intending to refactor them to a class, should consider do not
-    // break change: currently these attribute member are encapsulated in a closure
-    // so that do not need to force user to call these method with a scope.
-
-    // Do not support call `api` asynchronously without dataIndexInside input.
-    let currDataIndexInside: number;
-    let currItemModel: Model<LegacyCustomSeriesOption>;
-    let currItemStyleModels: Partial<Record<DisplayState, Model<LegacyCustomSeriesOption['itemStyle']>>> = {};
-    let currLabelModels: Partial<Record<DisplayState, Model<LegacyCustomSeriesOption['label']>>> = {};
-
-    const seriesItemStyleModels = {} as Record<DisplayState, Model<LegacyCustomSeriesOption['itemStyle']>>;
-
-    const seriesLabelModels = {} as Record<DisplayState, Model<LegacyCustomSeriesOption['label']>>;
-
-    for (let i = 0; i < STATES.length; i++) {
-        const stateName = STATES[i];
-        seriesItemStyleModels[stateName] = (customSeries as Model<LegacyCustomSeriesOption>)
-            .getModel(PATH_ITEM_STYLE[stateName]);
-        seriesLabelModels[stateName] = (customSeries as Model<LegacyCustomSeriesOption>)
-            .getModel(PATH_LABEL[stateName]);
-    }
-
-    function getItemModel(dataIndexInside: number): Model<LegacyCustomSeriesOption> {
-        return dataIndexInside === currDataIndexInside
-            ? (currItemModel || (currItemModel = data.getItemModel(dataIndexInside)))
-            : data.getItemModel(dataIndexInside);
-    }
-    function getItemStyleModel(dataIndexInside: number, state: DisplayState) {
-        return !data.hasItemOption
-            ? seriesItemStyleModels[state]
-            : dataIndexInside === currDataIndexInside
-            ? (currItemStyleModels[state] || (
-                currItemStyleModels[state] = getItemModel(dataIndexInside).getModel(PATH_ITEM_STYLE[state])
-            ))
-            : getItemModel(dataIndexInside).getModel(PATH_ITEM_STYLE[state]);
-    }
-    function getLabelModel(dataIndexInside: number, state: DisplayState) {
-        return !data.hasItemOption
-            ? seriesLabelModels[state]
-            : dataIndexInside === currDataIndexInside
-            ? (currLabelModels[state] || (
-                currLabelModels[state] = getItemModel(dataIndexInside).getModel(PATH_LABEL[state])
-            ))
-            : getItemModel(dataIndexInside).getModel(PATH_LABEL[state]);
-    }
-
-    return function (dataIndexInside: number, payload: Payload): CustomElementOption {
-        currDataIndexInside = dataIndexInside;
-        currItemModel = null;
-        currItemStyleModels = {};
-        currLabelModels = {};
-
-        return renderItem && renderItem(
-            defaults({
-                dataIndexInside: dataIndexInside,
-                dataIndex: data.getRawIndex(dataIndexInside),
-                // Can be used for optimization when zoom or roam.
-                actionType: payload ? payload.type : null
-            }, userParams),
-            userAPI
-        );
-    };
-
-    /**
-     * @public
-     * @param dim by default 0.
-     * @param dataIndexInside by default `currDataIndexInside`.
-     */
-    function value(dim?: DimensionLoose, dataIndexInside?: number): ParsedValue {
-        dataIndexInside == null && (dataIndexInside = currDataIndexInside);
-        return data.get(data.getDimension(dim || 0), dataIndexInside);
-    }
-
-    /**
-     * @public
-     * @param dim by default 0.
-     * @param dataIndexInside by default `currDataIndexInside`.
-     */
-    function ordinalRawValue(dim?: DimensionLoose, dataIndexInside?: number): ParsedValue | OrdinalRawValue {
-        dataIndexInside == null && (dataIndexInside = currDataIndexInside);
-        const dimInfo = data.getDimensionInfo(dim || 0);
-        if (!dimInfo) {
-            return;
-        }
-        const val = data.get(dimInfo.name, dataIndexInside);
-        const ordinalMeta = dimInfo && dimInfo.ordinalMeta;
-        return ordinalMeta
-            ? ordinalMeta.categories[val as number]
-            : val;
-    }
-
-    /**
-     * @deprecated The orgininal intention of `api.style` is enable to set itemStyle
-     * like other series. But it not necessary and not easy to give a strict definition
-     * of what it return. And since echarts5 it needs to be make compat work. So
-     * deprecates it since echarts5.
-     *
-     * By default, `visual` is applied to style (to support visualMap).
-     * `visual.color` is applied at `fill`. If user want apply visual.color on `stroke`,
-     * it can be implemented as:
-     * `api.style({stroke: api.visual('color'), fill: null})`;
-     *
-     * [Compat]: since ec5, RectText has been separated from its hosts el.
-     * so `api.style()` will only return the style from `itemStyle` but not handle `label`
-     * any more. But `series.label` config is never published in doc.
-     * We still compat it in `api.style()`. But not encourage to use it and will still not
-     * to pulish it to doc.
-     * @public
-     * @param dataIndexInside by default `currDataIndexInside`.
-     */
-    function style(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps {
-        if (__DEV__) {
-            warnDeprecated('api.style', 'Please write literal style directly instead.');
-        }
-
-        dataIndexInside == null && (dataIndexInside = currDataIndexInside);
-
-        const style = data.getItemVisual(dataIndexInside, 'style');
-        const visualColor = style && style.fill;
-        const opacity = style && style.opacity;
-
-        let itemStyle = getItemStyleModel(dataIndexInside, NORMAL).getItemStyle();
-        visualColor != null && (itemStyle.fill = visualColor);
-        opacity != null && (itemStyle.opacity = opacity);
-
-        const opt = {inheritColor: isString(visualColor) ? visualColor : '#000'};
-        const labelModel = getLabelModel(dataIndexInside, NORMAL);
-        // Now that the feture of "auto adjust text fill/stroke" has been migrated to zrender
-        // since ec5, we should set `isAttached` as `false` here and make compat in
-        // `convertToEC4StyleForCustomSerise`.
-        const textStyle = labelStyleHelper.createTextStyle(labelModel, null, opt, false, true);
-        textStyle.text = labelModel.getShallow('show')
-            ? retrieve2(
-                customSeries.getFormattedLabel(dataIndexInside, NORMAL),
-                getDefaultLabel(data, dataIndexInside)
-            )
-            : null;
-        const textConfig = labelStyleHelper.createTextConfig(labelModel, opt, false);
-
-        preFetchFromExtra(userProps, itemStyle);
-        itemStyle = convertToEC4StyleForCustomSerise(itemStyle, textStyle, textConfig);
-
-        userProps && applyUserPropsAfter(itemStyle, userProps);
-        (itemStyle as LegacyStyleProps).legacy = true;
-
-        return itemStyle;
-    }
-
-    /**
-     * @deprecated The reason see `api.style()`
-     * @public
-     * @param dataIndexInside by default `currDataIndexInside`.
-     */
-    function styleEmphasis(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps {
-        if (__DEV__) {
-            warnDeprecated('api.styleEmphasis', 'Please write literal style directly instead.');
-        }
-
-        dataIndexInside == null && (dataIndexInside = currDataIndexInside);
-
-        let itemStyle = getItemStyleModel(dataIndexInside, EMPHASIS).getItemStyle();
-        const labelModel = getLabelModel(dataIndexInside, EMPHASIS);
-        const textStyle = labelStyleHelper.createTextStyle(labelModel, null, null, true, true);
-        textStyle.text = labelModel.getShallow('show')
-            ? retrieve3(
-                customSeries.getFormattedLabel(dataIndexInside, EMPHASIS),
-                customSeries.getFormattedLabel(dataIndexInside, NORMAL),
-                getDefaultLabel(data, dataIndexInside)
-            )
-            : null;
-        const textConfig = labelStyleHelper.createTextConfig(labelModel, null, true);
-
-        preFetchFromExtra(userProps, itemStyle);
-        itemStyle = convertToEC4StyleForCustomSerise(itemStyle, textStyle, textConfig);
-
-        userProps && applyUserPropsAfter(itemStyle, userProps);
-        (itemStyle as LegacyStyleProps).legacy = true;
-
-        return itemStyle;
-    }
-
-    function applyUserPropsAfter(itemStyle: ZRStyleProps, extra: ZRStyleProps): void {
-        for (const key in extra) {
-            if (hasOwn(extra, key)) {
-                (itemStyle as any)[key] = (extra as any)[key];
-            }
-        }
-    }
-
-    function preFetchFromExtra(extra: ZRStyleProps, itemStyle: ItemStyleProps): void {
-        // A trick to retrieve those props firstly, which are used to
-        // apply auto inside fill/stroke in `convertToEC4StyleForCustomSerise`.
-        // (It's not reasonable but only for a degree of compat)
-        if (extra) {
-            (extra as any).textFill && ((itemStyle as any).textFill = (extra as any).textFill);
-            (extra as any).textPosition && ((itemStyle as any).textPosition = (extra as any).textPosition);
-        }
-    }
-
-    /**
-     * @public
-     * @param dataIndexInside by default `currDataIndexInside`.
-     */
-    function visual<VT extends NonStyleVisualProps | StyleVisualProps>(
-        visualType: VT,
-        dataIndexInside?: number
-    ): VT extends NonStyleVisualProps ? DefaultDataVisual[VT]
-            : VT extends StyleVisualProps ? PathStyleProps[typeof STYLE_VISUAL_TYPE[VT]]
-            : never {
-
-        dataIndexInside == null && (dataIndexInside = currDataIndexInside);
-
-        if (hasOwn(STYLE_VISUAL_TYPE, visualType)) {
-            const style = data.getItemVisual(dataIndexInside, 'style');
-            return style
-                ? style[STYLE_VISUAL_TYPE[visualType as StyleVisualProps]] as any
-                : null;
-        }
-        // Only support these visuals. Other visual might be inner tricky
-        // for performance (like `style`), do not expose to users.
-        if (hasOwn(NON_STYLE_VISUAL_PROPS, visualType)) {
-            return data.getItemVisual(dataIndexInside, visualType as NonStyleVisualProps) as any;
-        }
-    }
-
-    /**
-     * @public
-     * @return If not support, return undefined.
-     */
-    function barLayout(
-        opt: Omit<Parameters<typeof getLayoutOnAxis>[0], 'axis'>
-    ): ReturnType<typeof getLayoutOnAxis> {
-        if (coordSys.type === 'cartesian2d') {
-            const baseAxis = coordSys.getBaseAxis() as Axis2D;
-            return getLayoutOnAxis(defaults({axis: baseAxis}, opt));
-        }
-    }
-
-    /**
-     * @public
-     */
-    function currentSeriesIndices(): ReturnType<GlobalModel['getCurrentSeriesIndices']> {
-        return ecModel.getCurrentSeriesIndices();
-    }
-
-    /**
-     * @public
-     * @return font string
-     */
-    function font(
-        opt: Parameters<typeof labelStyleHelper.getFont>[0]
-    ): ReturnType<typeof labelStyleHelper.getFont> {
-        return labelStyleHelper.getFont(opt, ecModel);
-    }
-}
-
-type WrapEncodeDefRet = Dictionary<number[]>;
-
-function wrapEncodeDef(data: List<CustomSeriesModel>): WrapEncodeDefRet {
-    const encodeDef = {} as WrapEncodeDefRet;
-    each(data.dimensions, function (dimName, dataDimIndex) {
-        const dimInfo = data.getDimensionInfo(dimName);
-        if (!dimInfo.isExtraCoord) {
-            const coordDim = dimInfo.coordDim;
-            const dataDims = encodeDef[coordDim] = encodeDef[coordDim] || [];
-            dataDims[dimInfo.coordDimIndex] = dataDimIndex;
-        }
-    });
-    return encodeDef;
-}
-
-function createOrUpdateItem(
-    api: ExtensionAPI,
-    el: Element,
-    dataIndex: number,
-    elOption: CustomElementOption,
-    seriesModel: CustomSeriesModel,
-    group: ViewRootGroup,
-    data: List<CustomSeriesModel>,
-    morphPreparation: MorphPreparation
-): Element {
-    // [Rule]
-    // If `renderItem` returns `null`/`undefined`/`false`, remove the previous el if existing.
-    //     (It seems that violate the "merge" principle, but most of users probably intuitively
-    //     regard "return;" as "show nothing element whatever", so make a exception to meet the
-    //     most cases.)
-    // The rule or "merge" see [STRATEGY_MERGE].
-
-    // If `elOption` is `null`/`undefined`/`false` (when `renderItem` returns nothing).
-    if (!elOption) {
-        removeElementDirectly(el, group);
-        return;
-    }
-    el = doCreateOrUpdateEl(api, el, dataIndex, elOption, seriesModel, group, true, morphPreparation);
-    el && data.setItemGraphicEl(dataIndex, el);
-
-    enableHoverEmphasis(el, elOption.focus, elOption.blurScope);
-
-    return el;
-}
-
-function doCreateOrUpdateEl(
-    api: ExtensionAPI,
-    el: Element,
-    dataIndex: number,
-    elOption: CustomElementOption,
-    seriesModel: CustomSeriesModel,
-    group: ViewRootGroup,
-    isRoot: boolean,
-    morphPreparation: MorphPreparation
-): Element {
-
-    if (__DEV__) {
-        assert(elOption, 'should not have an null/undefined element setting');
-    }
-
-    let toBeReplacedIdx = -1;
-    if (
-        el && (
-            doesElNeedRecreate(el, elOption)
-            // || (
-            //     // PENDING: even in one-to-one mapping case, if el is marked as morph,
-            //     // do not sure whether the el will be mapped to another el with different
-            //     // hierarchy in Group tree. So always recreate el rather than reuse the el.
-            //     morphPreparation && morphPreparation.isOneToOneFrom(el)
-            // )
-        )
-    ) {
-        // Should keep at the original index, otherwise "merge by index" will be incorrect.
-        toBeReplacedIdx = group.childrenRef().indexOf(el);
-        el = null;
-    }
-
-    const elIsNewCreated = !el;
-
-    if (!el) {
-        el = createEl(elOption);
-    }
-    else {
-        // FIMXE:NEXT unified clearState?
-        // If in some case the performance issue arised, consider
-        // do not clearState but update cached normal state directly.
-        el.clearStates();
-    }
-
-    const canMorph = inner(el).canMorph = (elOption as CustomZRPathOption).morph && isPath(el);
-    const thisElIsMorphTo = canMorph && morphPreparation && morphPreparation.hasFrom();
-
-    // Use update animation when morph is enabled.
-    const isInit = elIsNewCreated && !thisElIsMorphTo;
-
-    attachedTxInfoTmp.normal.cfg = attachedTxInfoTmp.normal.conOpt =
-        attachedTxInfoTmp.emphasis.cfg = attachedTxInfoTmp.emphasis.conOpt =
-        attachedTxInfoTmp.blur.cfg = attachedTxInfoTmp.blur.conOpt =
-        attachedTxInfoTmp.select.cfg = attachedTxInfoTmp.select.conOpt = null;
-
-    attachedTxInfoTmp.isLegacy = false;
-
-    doCreateOrUpdateAttachedTx(
-        el, dataIndex, elOption, seriesModel, isInit, attachedTxInfoTmp
-    );
-
-    doCreateOrUpdateClipPath(
-        el, dataIndex, elOption, seriesModel, isInit
-    );
-
-    const pendingAllPropsFinal = updateElNormal(
-        api,
-        el,
-        thisElIsMorphTo,
-        dataIndex,
-        elOption,
-        elOption.style,
-        attachedTxInfoTmp,
-        seriesModel,
-        isInit,
-        false
-    );
-
-    if (thisElIsMorphTo) {
-        morphPreparation.addTo(el as graphicUtil.Path, elOption, dataIndex, pendingAllPropsFinal);
-    }
-
-    for (let i = 0; i < STATES.length; i++) {
-        const stateName = STATES[i];
-        if (stateName !== NORMAL) {
-            const otherStateOpt = retrieveStateOption(elOption, stateName);
-            const otherStyleOpt = retrieveStyleOptionOnState(elOption, otherStateOpt, stateName);
-            updateElOnState(stateName, el, otherStateOpt, otherStyleOpt, attachedTxInfoTmp, isRoot, false);
-        }
-    }
-
-    updateZ(el, elOption, seriesModel, attachedTxInfoTmp);
-
-    if (elOption.type === 'group') {
-        mergeChildren(
-            api, el as graphicUtil.Group, dataIndex, elOption as CustomGroupOption, seriesModel, morphPreparation
-        );
-    }
-
-    if (toBeReplacedIdx >= 0) {
-        group.replaceAt(el, toBeReplacedIdx);
-    }
-    else {
-        group.add(el);
-    }
-
-    return el;
-}
-
-// `el` must not be null/undefined.
-function doesElNeedRecreate(el: Element, elOption: CustomElementOption): boolean {
-    const elInner = inner(el);
-    const elOptionType = elOption.type;
-    const elOptionShape = (elOption as CustomZRPathOption).shape;
-    const elOptionStyle = elOption.style;
-    return (
-        // If `elOptionType` is `null`, follow the merge principle.
-        (elOptionType != null
-            && elOptionType !== elInner.customGraphicType
-        )
-        || (elOptionType === 'path'
-            && hasOwnPathData(elOptionShape)
-            && getPathData(elOptionShape) !== elInner.customPathData
-        )
-        || (elOptionType === 'image'
-            && hasOwn(elOptionStyle, 'image')
-            && (elOptionStyle as CustomImageOption['style']).image !== elInner.customImagePath
-        )
-        // // FIXME test and remove this restriction?
-        // || (elOptionType === 'text'
-        //     && hasOwn(elOptionStyle, 'text')
-        //     && (elOptionStyle as TextStyleProps).text !== elInner.customText
-        // )
-    );
-}
-
-function doCreateOrUpdateClipPath(
-    el: Element,
-    dataIndex: number,
-    elOption: CustomElementOption,
-    seriesModel: CustomSeriesModel,
-    isInit: boolean
-): void {
-    // Based on the "merge" principle, if no clipPath provided,
-    // do nothing. The exists clip will be totally removed only if
-    // `el.clipPath` is `false`. Otherwise it will be merged/replaced.
-    const clipPathOpt = elOption.clipPath;
-    if (clipPathOpt === false) {
-        if (el && el.getClipPath()) {
-            el.removeClipPath();
-        }
-    }
-    else if (clipPathOpt) {
-        let clipPath = el.getClipPath();
-        if (clipPath && doesElNeedRecreate(clipPath, clipPathOpt)) {
-            clipPath = null;
-        }
-        if (!clipPath) {
-            clipPath = createEl(clipPathOpt) as graphicUtil.Path;
-            if (__DEV__) {
-                assert(
-                    clipPath instanceof graphicUtil.Path,
-                    'Only any type of `path` can be used in `clipPath`, rather than ' + clipPath.type + '.'
-                );
-            }
-            el.setClipPath(clipPath);
-        }
-        updateElNormal(
-            null, clipPath, null, dataIndex, clipPathOpt, null, null, seriesModel, isInit, false
-        );
-    }
-    // If not define `clipPath` in option, do nothing unnecessary.
-}
-
-function doCreateOrUpdateAttachedTx(
-    el: Element,
-    dataIndex: number,
-    elOption: CustomElementOption,
-    seriesModel: CustomSeriesModel,
-    isInit: boolean,
-    attachedTxInfo: AttachedTxInfo
-): void {
-    // group do not support textContent temporarily untill necessary.
-    if (el.isGroup) {
-        return;
-    }
-
-    // Normal must be called before emphasis, for `isLegacy` detection.
-    processTxInfo(elOption, null, attachedTxInfo);
-    processTxInfo(elOption, EMPHASIS, attachedTxInfo);
-
-    // If `elOption.textConfig` or `elOption.textContent` is null/undefined, it does not make sence.
-    // So for simplicity, if "elOption hasOwnProperty of them but be null/undefined", we do not
-    // trade them as set to null to el.
-    // Especially:
-    // `elOption.textContent: false` means remove textContent.
-    // `elOption.textContent.emphasis.style: false` means remove the style from emphasis state.
-    let txConOptNormal = attachedTxInfo.normal.conOpt as CustomElementOption | false;
-    const txConOptEmphasis = attachedTxInfo.emphasis.conOpt as CustomElementOptionOnState;
-    const txConOptBlur = attachedTxInfo.blur.conOpt as CustomElementOptionOnState;
-    const txConOptSelect = attachedTxInfo.select.conOpt as CustomElementOptionOnState;
-
-    if (txConOptNormal != null || txConOptEmphasis != null || txConOptSelect != null || txConOptBlur != null) {
-        let textContent = el.getTextContent();
-        if (txConOptNormal === false) {
-            textContent && el.removeTextContent();
-        }
-        else {
-            txConOptNormal = attachedTxInfo.normal.conOpt = txConOptNormal || {type: 'text'};
-            if (!textContent) {
-                textContent = createEl(txConOptNormal) as graphicUtil.Text;
-                el.setTextContent(textContent);
-            }
-            else {
-                // If in some case the performance issue arised, consider
-                // do not clearState but update cached normal state directly.
-                textContent.clearStates();
-            }
-            const txConStlOptNormal = txConOptNormal && txConOptNormal.style;
-
-            updateElNormal(
-                null, textContent, null, dataIndex, txConOptNormal, txConStlOptNormal, null, seriesModel, isInit, true
-            );
-            for (let i = 0; i < STATES.length; i++) {
-                const stateName = STATES[i];
-                if (stateName !== NORMAL) {
-                    const txConOptOtherState = attachedTxInfo[stateName].conOpt as CustomElementOptionOnState;
-                    updateElOnState(
-                        stateName,
-                        textContent,
-                        txConOptOtherState,
-                        retrieveStyleOptionOnState(txConOptNormal, txConOptOtherState, stateName),
-                        null, false, true
-                    );
-                }
-            }
-
-            txConStlOptNormal ? textContent.dirty() : textContent.markRedraw();
-        }
-    }
-}
-
-function processTxInfo(
-    elOption: CustomElementOption,
-    state: DisplayStateNonNormal,
-    attachedTxInfo: AttachedTxInfo
-): void {
-    const stateOpt = !state ? elOption : retrieveStateOption(elOption, state);
-    const styleOpt = !state ? elOption.style : retrieveStyleOptionOnState(elOption, stateOpt, EMPHASIS);
-
-    const elType = elOption.type;
-    let txCfg = stateOpt ? stateOpt.textConfig : null;
-    const txConOptNormal = elOption.textContent;
-    let txConOpt: CustomElementOption | CustomElementOptionOnState =
-        !txConOptNormal ? null : !state ? txConOptNormal : retrieveStateOption(txConOptNormal, state);
-
-    if (styleOpt && (
-        // Because emphasis style has little info to detect legacy,
-        // if normal is legacy, emphasis is trade as legacy.
-        attachedTxInfo.isLegacy
-        || isEC4CompatibleStyle(styleOpt, elType, !!txCfg, !!txConOpt)
-    )) {
-        attachedTxInfo.isLegacy = true;
-        const convertResult = convertFromEC4CompatibleStyle(styleOpt, elType, !state);
-        // Explicitly specified `textConfig` and `textContent` has higher priority than
-        // the ones generated by legacy style. Otherwise if users use them and `api.style`
-        // at the same time, they not both work and hardly to known why.
-        if (!txCfg && convertResult.textConfig) {
-            txCfg = convertResult.textConfig;
-        }
-        if (!txConOpt && convertResult.textContent) {
-            txConOpt = convertResult.textContent;
-        }
-    }
-
-    if (!state && txConOpt) {
-        const txConOptNormal = txConOpt as CustomElementOption;
-        // `textContent: {type: 'text'}`, the "type" is easy to be missing. So we tolerate it.
-        !txConOptNormal.type && (txConOptNormal.type = 'text');
-        if (__DEV__) {
-            // Do not tolerate incorret type for forward compat.
-            txConOptNormal.type !== 'text' && assert(
-                txConOptNormal.type === 'text',
-                'textContent.type must be "text"'
-            );
-        }
-    }
-
-    const info = !state ? attachedTxInfo.normal : attachedTxInfo[state];
-    info.cfg = txCfg;
-    info.conOpt = txConOpt;
-}
-
-function retrieveStateOption(
-    elOption: CustomElementOption, state: DisplayStateNonNormal
-): CustomElementOptionOnState {
-    return !state ? elOption : elOption ? elOption[state] : null;
-}
-
-function retrieveStyleOptionOnState(
-    stateOptionNormal: CustomElementOption,
-    stateOption: CustomElementOptionOnState,
-    state: DisplayStateNonNormal
-): CustomElementOptionOnState['style'] {
-    let style = stateOption && stateOption.style;
-    if (style == null && state === EMPHASIS && stateOptionNormal) {
-        style = stateOptionNormal.styleEmphasis;
-    }
-    return style;
-}
-
-
-// Usage:
-// (1) By default, `elOption.$mergeChildren` is `'byIndex'`, which indicates that
-//     the existing children will not be removed, and enables the feature that
-//     update some of the props of some of the children simply by construct
-//     the returned children of `renderItem` like:
-//     `var children = group.children = []; children[3] = {opacity: 0.5};`
-// (2) If `elOption.$mergeChildren` is `'byName'`, add/update/remove children
-//     by child.name. But that might be lower performance.
-// (3) If `elOption.$mergeChildren` is `false`, the existing children will be
-//     replaced totally.
-// (4) If `!elOption.children`, following the "merge" principle, nothing will happen.
-//
-// For implementation simpleness, do not provide a direct way to remove sinlge
-// child (otherwise the total indicies of the children array have to be modified).
-// User can remove a single child by set its `ignore` as `true`.
-function mergeChildren(
-    api: ExtensionAPI,
-    el: graphicUtil.Group,
-    dataIndex: number,
-    elOption: CustomGroupOption,
-    seriesModel: CustomSeriesModel,
-    morphPreparation: MorphPreparation
-): void {
-
-    const newChildren = elOption.children;
-    const newLen = newChildren ? newChildren.length : 0;
-    const mergeChildren = elOption.$mergeChildren;
-    // `diffChildrenByName` has been deprecated.
-    const byName = mergeChildren === 'byName' || elOption.diffChildrenByName;
-    const notMerge = mergeChildren === false;
-
-    // For better performance on roam update, only enter if necessary.
-    if (!newLen && !byName && !notMerge) {
-        return;
-    }
-
-    if (byName) {
-        diffGroupChildren({
-            api: api,
-            oldChildren: el.children() || [],
-            newChildren: newChildren || [],
-            dataIndex: dataIndex,
-            seriesModel: seriesModel,
-            group: el,
-            morphPreparation: morphPreparation
-        });
-        return;
-    }
-
-    notMerge && el.removeAll();
-
-    // Mapping children of a group simply by index, which
-    // might be better performance.
-    let index = 0;
-    for (; index < newLen; index++) {
-        newChildren[index] && doCreateOrUpdateEl(
-            api,
-            el.childAt(index),
-            dataIndex,
-            newChildren[index],
-            seriesModel,
-            el,
-            false,
-            morphPreparation
-        );
-    }
-    for (let i = el.childCount() - 1; i >= index; i--) {
-        // 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.
-        doRemoveEl(el.childAt(i), seriesModel, el);
-    }
-}
-
-type DiffGroupContext = {
-    api: ExtensionAPI;
-    oldChildren: Element[];
-    newChildren: CustomElementOption[];
-    dataIndex: number;
-    seriesModel: CustomSeriesModel;
-    group: graphicUtil.Group;
-    morphPreparation: MorphPreparation;
-};
-function diffGroupChildren(context: DiffGroupContext) {
-    (new DataDiffer(
-        context.oldChildren,
-        context.newChildren,
-        getKey,
-        getKey,
-        context
-    ))
-        .add(processAddUpdate)
-        .update(processAddUpdate)
-        .remove(processRemove)
-        .execute();
-}
-
-function getKey(item: Element, idx: number): string {
-    const name = item && item.name;
-    return name != null ? name : GROUP_DIFF_PREFIX + idx;
-}
-
-function processAddUpdate(
-    this: DataDiffer<DiffGroupContext>,
-    newIndex: number,
-    oldIndex?: number
-): void {
-    const context = this.context;
-    const childOption = newIndex != null ? context.newChildren[newIndex] : null;
-    const child = oldIndex != null ? context.oldChildren[oldIndex] : null;
-
-    doCreateOrUpdateEl(
-        context.api,
-        child,
-        context.dataIndex,
-        childOption,
-        context.seriesModel,
-        context.group,
-        false,
-        context.morphPreparation
-    );
-}
-
-function processRemove(this: DataDiffer<DiffGroupContext>, oldIndex: number): void {
-    const context = this.context;
-    const child = context.oldChildren[oldIndex];
-    doRemoveEl(child, context.seriesModel, context.group);
-}
-
-function doRemoveEl(
-    el: Element,
-    seriesModel: CustomSeriesModel,
-    group: ViewRootGroup
-): void {
-    if (el) {
-        const leaveToProps = inner(el).leaveToProps;
-        leaveToProps
-            ? graphicUtil.updateProps(el, leaveToProps, seriesModel, {
-                cb: function () {
-                    group.remove(el);
-                }
-            })
-            : group.remove(el);
-    }
-}
-
-/**
- * @return SVG Path data.
- */
-function getPathData(shape: CustomSVGPathOption['shape']): string {
-    // "d" follows the SVG convention.
-    return shape && (shape.pathData || shape.d);
-}
-
-function hasOwnPathData(shape: CustomSVGPathOption['shape']): boolean {
-    return shape && (hasOwn(shape, 'pathData') || hasOwn(shape, 'd'));
-}
-
-function isPath(el: Element): el is graphicUtil.Path {
-    return el && el instanceof graphicUtil.Path;
-}
-
-function removeElementDirectly(el: Element, group: ViewRootGroup): void {
-    el && group.remove(el);
-}
-
-
-type MorphPreparationType = 'oneToOne' | 'oneToMany' | 'manyToOne';
-
-/**
- * Any morph-potential el should added by `morphPreparation.addTo(el)`.
- * And they may apply morph or not when `morphPreparation.applyMorphing()`.
- * But at least, all of the "to" elements will apply all of the updates
- * as `doCreateOrUpdateItem` did.
- */
-class MorphPreparation {
-    private _type: MorphPreparationType;
-    private _fromList: graphicUtil.Path[] = [];
-    private _toList: graphicUtil.Path[] = [];
-    private _toElOptionList: CustomElementOption[] = [];
-    private _allPropsFinalList: ElementProps[] = [];
-    private _toDataIndices: number[] = [];
-    private _transOpt: SeriesModel['__transientTransitionOpt'];
-    private _seriesModel: CustomSeriesModel;
-    // Key: `toDataIndex`, not `toIdx`
-    private _morphConfigList: CombineSeparateConfig[] = [];
-
-    constructor(
-        seriesModel: CustomSeriesModel,
-        transOpt: SeriesModel['__transientTransitionOpt']
-    ) {
-        this._seriesModel = seriesModel;
-        this._transOpt = transOpt;
-    }
-
-    hasFrom(): boolean {
-        return !!this._fromList.length;
-    }
-
-    // isOneToOneFrom(el: Element): boolean {
-    //     if (el && inner(el).canMorph) {
-    //         const fromList = this._fromList;
-    //         for (let i = 0; i < fromList.length; i++) {
-    //             if (fromList[i] === el) {
-    //                 return true;
-    //             }
-    //         }
-    //     }
-    // }
-
-    findAndAddFrom(el: Element): void {
-        if (!el) {
-            return;
-        }
-        if (inner(el).canMorph) {
-            this._fromList.push(el as graphicUtil.Path);
-        }
-        if (el.isGroup) {
-            const children = (el as graphicUtil.Group).childrenRef();
-            for (let i = 0; i < children.length; i++) {
-                this.findAndAddFrom(children[i]);
-            }
-        }
-    }
-
-    addTo(
-        path: graphicUtil.Path,
-        elOption: CustomElementOption,
-        dataIndex: number,
-        allPropsFinal: ElementProps
-    ): void {
-        if (path) {
-            this._toList.push(path);
-            this._toElOptionList.push(elOption);
-            this._toDataIndices.push(dataIndex);
-            this._allPropsFinalList.push(allPropsFinal);
-        }
-    }
-
-    applyMorphing(): void {
-        // [MORPHING_LOGIC_HINT]
-        // Pay attention to the order:
-        // (A) Apply `allPropsFinal` and `styleOption` to "to".
-        //     (Then "to" becomes to the final state.)
-        // (B) Apply `morphPath`/`combine`/`separate`.
-        //     (Based on the current state of "from" and the final state of "to".)
-        //     (Then we may get "from.subList" or "to.subList".)
-        // (C) Copy the related props from "from" to "from.subList", from "to" to "to.subList".
-        // (D) Collect `transitionFromProps` for "to" and "to.subList"
-        //     (Based on "from" or "from.subList".)
-        // (E) Apply `transitionFromProps` to "to" and "to.subList"
-        //     (It might change the prop values to the first frame value.)
-        // Case_I:
-        //     If (D) should be after (C), we use sequence: A - B - C - D - E
-        // Case_II:
-        //     If (A) should be after (D), we use sequence: D - A - B - C - E
-
-        // [MORPHING_LOGIC_HINT]
-        // zrender `morphPath`/`combine`/`separate` only manages the shape animation.
-        // Other props (like transfrom, style transition) will handled in echarts).
-
-        // [MORPHING_LOGIC_HINT]
-        // Make sure `applyPropsFinal` always be called for "to".
-
-        const type = this._type;
-        const fromList = this._fromList;
-        const toList = this._toList;
-        const toListLen = toList.length;
-        const fromListLen = fromList.length;
-
-        if (!fromListLen || !toListLen) {
-            return;
-        }
-
-        if (type === 'oneToOne') {
-            // In one-to-one case, we by default apply a simple rule:
-            // map "from" and "to" one by one.
-            // For this case: old_data_item_el and new_data_item_el
-            // has the same hierarchy of group tree but only some path type changed.
-            for (let toIdx = 0; toIdx < toListLen; toIdx++) {
-                this._oneToOneForSingleTo(toIdx, toIdx);
-            }
-        }
-
-        else if (type === 'manyToOne') {
-            // A rough strategy: if there are more than one "to", we simply divide "fromList" equally.
-            const fromSingleSegLen = Math.max(1, Math.floor(fromListLen / toListLen));
-            for (
-                let toIdx = 0, fromIdxStart = 0;
-                toIdx < toListLen;
-                toIdx++, fromIdxStart += fromSingleSegLen
-            ) {
-                const fromCount = toIdx + 1 >= toListLen
-                    ? fromListLen - fromIdxStart
-                    : fromSingleSegLen;
-                this._manyToOneForSingleTo(
-                    toIdx, fromIdxStart >= fromListLen ? null : fromIdxStart, fromCount
-                );
-            }
-        }
-
-        else if (type === 'oneToMany') {
-            // A rough strategy: if there are more than one "from", we simply divide "toList" equally.
-            const toSingleSegLen = Math.max(1, Math.floor(toListLen / fromListLen));
-            for (
-                let toIdxStart = 0, fromIdx = 0;
-                toIdxStart < toListLen;
-                toIdxStart += toSingleSegLen, fromIdx++
-            ) {
-                const toCount = toIdxStart + toSingleSegLen >= toListLen
-                    ? toListLen - toIdxStart
-                    : toSingleSegLen;
-                this._oneToManyForSingleFrom(
-                    toIdxStart, toCount, fromIdx >= fromListLen ? null : fromIdx
-                );
-            }
-        }
-    }
-
-    private _oneToOneForSingleTo(
-        // "to" must NOT be null/undefined.
-        toIdx: number,
-        // May `fromIdx >= this._fromList.length`
-        fromIdx: number
-    ): void {
-        const to = this._toList[toIdx];
-        const toElOption = this._toElOptionList[toIdx];
-        const toDataIndex = this._toDataIndices[toIdx];
-        const allPropsFinal = this._allPropsFinalList[toIdx];
-        const from = this._fromList[fromIdx];
-
-        const elAnimationConfig = this._getOrCreateMorphConfig(toDataIndex);
-        const morphDuration = elAnimationConfig.duration;
-
-        if (from && isCombiningPath(from)) {
-            applyPropsFinal(to, allPropsFinal, toElOption.style);
-
-            if (morphDuration) {
-                const combineResult = combine([from], to, elAnimationConfig, copyPropsWhenDivided);
-                this._processResultIndividuals(combineResult, toIdx, null);
-            }
-            // The target el will not be displayed and transition from multiple path.
-            // transition on the target el does not make sense.
-        }
-        else {
-            const morphFrom = (
-                morphDuration
-                // from === to usually happen in scenarios where internal update like
-                // "dataZoom", "legendToggle" happen. If from is not in any morphing,
-                // we do not need to call `morphPath`.
-                && from
-                && (from !== to || isInAnyMorphing(from))
-            ) ? from : null;
-
-            // See [Case_II] above.
-            // In this case, there is probably `from === to`. And the `transitionFromProps` collecting
-            // does not depends on morphing. So we collect `transitionFromProps` first.
-            const transFromProps = {} as ElementProps;
-            prepareShapeOrExtraTransitionFrom('shape', to, morphFrom, toElOption, transFromProps, false);
-            prepareShapeOrExtraTransitionFrom('extra', to, morphFrom, toElOption, transFromProps, false);
-            prepareTransformTransitionFrom(to, morphFrom, toElOption, transFromProps, false);
-            prepareStyleTransitionFrom(to, morphFrom, toElOption, toElOption.style, transFromProps, false);
-
-            applyPropsFinal(to, allPropsFinal, toElOption.style);
-
-            if (morphFrom) {
-                morphPath(morphFrom, to, elAnimationConfig);
-            }
-            applyTransitionFrom(to, toDataIndex, toElOption, this._seriesModel, transFromProps, false);
-        }
-    }
-
-    private _manyToOneForSingleTo(
-        // "to" must NOT be null/undefined.
-        toIdx: number,
-        // May be null.
-        fromIdxStart: number,
-        fromCount: number
-    ): void {
-        const to = this._toList[toIdx];
-        const toElOption = this._toElOptionList[toIdx];
-        const allPropsFinal = this._allPropsFinalList[toIdx];
-
-        applyPropsFinal(to, allPropsFinal, toElOption.style);
-
-        const elAnimationConfig = this._getOrCreateMorphConfig(this._toDataIndices[toIdx]);
-        if (elAnimationConfig.duration && fromIdxStart != null) {
-            const combineFromList = [];
-            for (let fromIdx = fromIdxStart; fromIdx < fromCount; fromIdx++) {
-                combineFromList.push(this._fromList[fromIdx]);
-            }
-            const combineResult = combine(combineFromList, to, elAnimationConfig, copyPropsWhenDivided);
-            this._processResultIndividuals(combineResult, toIdx, null);
-        }
-    }
-
-    private _oneToManyForSingleFrom(
-        // "to" must NOT be null/undefined.
-        toIdxStart: number,
-        toCount: number,
-        // May be null
-        fromIdx: number
-    ): void {
-        const from = fromIdx == null ? null : this._fromList[fromIdx];
-        const toList = this._toList;
-
-        const separateToList = [];
-        for (let toIdx = toIdxStart; toIdx < toCount; toIdx++) {
-            const to = toList[toIdx];
-            applyPropsFinal(to, this._allPropsFinalList[toIdx], this._toElOptionList[toIdx].style);
-            separateToList.push(to);
-        }
-
-        const elAnimationConfig = this._getOrCreateMorphConfig(this._toDataIndices[toIdxStart]);
-        if (elAnimationConfig.duration && from) {
-            const separateResult = separate(from, separateToList, elAnimationConfig, copyPropsWhenDivided);
-            this._processResultIndividuals(separateResult, toIdxStart, toCount);
-        }
-    }
-
-    private _processResultIndividuals(
-        combineSeparateResult: CombineSeparateResult,
-        toIdxStart: number,
-        toCount: number
-    ): void {
-        const isSeparate = toCount != null;
-
-        for (let i = 0; i < combineSeparateResult.count; i++) {
-            const fromIndividual = combineSeparateResult.fromIndividuals[i];
-            const toIndividual = combineSeparateResult.toIndividuals[i];
-            // Here it's a trick:
-            // For "combine" case, all of the `toIndividuals` map to the same `toIdx`.
-            // For "separate" case, the `toIndividuals` map to some certain segment of `_toList` accurately.
-            const toIdx = toIdxStart + (isSeparate ? i : 0);
-
-            const toElOption = this._toElOptionList[toIdx];
-            const dataIndex = this._toDataIndices[toIdx];
-
-            const transFromProps = {} as ElementProps;
-            prepareTransformTransitionFrom(
-                toIndividual, fromIndividual, toElOption, transFromProps, false
-            );
-            prepareStyleTransitionFrom(
-                toIndividual, fromIndividual, toElOption, toElOption.style, transFromProps, false
-            );
-            applyTransitionFrom(
-                toIndividual, dataIndex, toElOption, this._seriesModel, transFromProps, false
-            );
-        }
-    }
-
-    _getOrCreateMorphConfig(dataIndex: number): CombineSeparateConfig {
-        const morphConfigList = this._morphConfigList;
-        let config = morphConfigList[dataIndex];
-        if (config) {
-            return config;
-        }
-
-        let duration: number;
-        let easing: AnimationEasing;
-        let delay: number;
-        const seriesModel = this._seriesModel;
-        const transOpt = this._transOpt;
-
-        if (seriesModel.isAnimationEnabled()) {
-            // PENDING: refactor? this is the same logic as `src/util/graphic.ts#animateOrSetProps`.
-            let animationPayload: PayloadAnimationPart;
-            if (seriesModel && seriesModel.ecModel) {
-                const updatePayload = seriesModel.ecModel.getUpdatePayload();
-                animationPayload = (updatePayload && updatePayload.animation) as PayloadAnimationPart;
-            }
-            if (animationPayload) {
-                duration = animationPayload.duration || 0;
-                easing = animationPayload.easing || 'cubicOut';
-                delay = animationPayload.delay || 0;
-            }
-            else {
-                easing = seriesModel.get('animationEasingUpdate');
-                const delayOption = seriesModel.get('animationDelayUpdate');
-                delay = isFunction(delayOption) ? delayOption(dataIndex) : delayOption;
-                const durationOption = seriesModel.get('animationDurationUpdate');
-                duration = isFunction(durationOption) ? durationOption(dataIndex) : durationOption;
-            }
-        }
-
-        config = {
-            duration: duration || 0,
-            delay: delay,
-            easing: easing,
-            dividingMethod: transOpt ? transOpt.dividingMethod : null
-        };
-        morphConfigList[dataIndex] = config;
-
-        return config;
-    }
-
-    reset(type: MorphPreparationType): void {
-        // `this._morphConfigList` can be kept. It only related to `dataIndex`.
-        this._type = type;
-        this._fromList.length =
-            this._toList.length =
-            this._toElOptionList.length =
-            this._allPropsFinalList.length =
-            this._toDataIndices.length = 0;
-    }
-}
-
-function copyPropsWhenDivided(
-    srcPath: graphicUtil.Path,
-    tarPath: graphicUtil.Path,
-    willClone: boolean
-): void {
-    // Do not copy transform props.
-    // Sub paths are transfrom based on their host path.
-    // tarPath.x = srcPath.x;
-    // tarPath.y = srcPath.y;
-    // tarPath.scaleX = srcPath.scaleX;
-    // tarPath.scaleY = srcPath.scaleY;
-    // tarPath.originX = srcPath.originX;
-    // tarPath.originY = srcPath.originY;
-
-    // If just carry the style, will not be modifed, so do not copy.
-    tarPath.style = willClone
-        ? clone(srcPath.style)
-        : srcPath.style;
-
-    tarPath.zlevel = srcPath.zlevel;
-    tarPath.z = srcPath.z;
-    tarPath.z2 = srcPath.z2;
-}
+import CustomSeriesModel from './CustomSeries';
+import CustomChartView from './CustomView';
 
 export function install(registers: EChartsExtensionInstallRegisters) {
-    registers.registerChartView(CustomSeriesView);
+    registers.registerChartView(CustomChartView);
     registers.registerSeriesModel(CustomSeriesModel);
 }
\ No newline at end of file
diff --git a/src/chart/custom/prepare.ts b/src/chart/custom/prepare.ts
new file mode 100644
index 0000000..ba51f04
--- /dev/null
+++ b/src/chart/custom/prepare.ts
@@ -0,0 +1,424 @@
+/*
+* 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 Transformable from 'zrender/src/core/Transformable';
+import Element, { ElementProps } from 'zrender/src/Element';
+import { Dictionary } from '../../util/types';
+import {
+    CustomElementOption,
+    customInnerStore,
+    LooseElementProps,
+    TransformProp,
+    TRANSFORM_PROPS,
+    TransitionAnyOption
+} from './CustomSeries';
+import * as graphicUtil from '../../util/graphic';
+import { normalizeToArray } from '../../util/model';
+import { assert, hasOwn, indexOf, isArrayLike, keys } from 'zrender/src/core/util';
+import { cloneValue } from 'zrender/src/animation/Animator';
+import Displayable from 'zrender/src/graphic/Displayable';
+import * as matrix from 'zrender/src/core/matrix';
+import { isMorphing } from 'zrender/src/tool/morphPath';
+
+const LEGACY_TRANSFORM_PROPS = {
+    position: ['x', 'y'],
+    scale: ['scaleX', 'scaleY'],
+    origin: ['originX', 'originY']
+} as const;
+type LegacyTransformProp = keyof typeof LEGACY_TRANSFORM_PROPS;
+
+const tmpTransformable = new Transformable();
+
+function setLagecyTransformProp(
+    elOption: CustomElementOption,
+    targetProps: Partial<Pick<Transformable, TransformProp>>,
+    legacyName: LegacyTransformProp,
+    fromTransformable?: Transformable // If provided, retrieve from the element.
+): void {
+    const legacyArr = (elOption as any)[legacyName];
+    const xyName = LEGACY_TRANSFORM_PROPS[legacyName];
+    if (legacyArr) {
+        if (fromTransformable) {
+            targetProps[xyName[0]] = fromTransformable[xyName[0]];
+            targetProps[xyName[1]] = fromTransformable[xyName[1]];
+        }
+        else {
+            targetProps[xyName[0]] = legacyArr[0];
+            targetProps[xyName[1]] = legacyArr[1];
+        }
+    }
+}
+
+function setTransformProp(
+    elOption: CustomElementOption,
+    allProps: Partial<Pick<Transformable, TransformProp>>,
+    name: TransformProp,
+    fromTransformable?: Transformable // If provided, retrieve from the element.
+): void {
+    if (elOption[name] != null) {
+        allProps[name] = fromTransformable ? fromTransformable[name] : elOption[name];
+    }
+}
+
+function setTransformPropToTransitionFrom(
+    transitionFrom: Partial<Pick<Transformable, TransformProp>>,
+    name: TransformProp,
+    fromTransformable?: Transformable // If provided, retrieve from the element.
+): void {
+    if (fromTransformable) {
+        transitionFrom[name] = fromTransformable[name];
+    }
+}
+
+
+// See [STRATEGY_TRANSITION]
+export function prepareShapeOrExtraTransitionFrom(
+    mainAttr: 'shape' | 'extra',
+    el: Element,
+    elOption: CustomElementOption,
+    transFromProps: LooseElementProps,
+    isInit: boolean
+): void {
+
+    const attrOpt: Dictionary<unknown> & TransitionAnyOption = (elOption as any)[mainAttr];
+    if (!attrOpt) {
+        return;
+    }
+
+    const elPropsInAttr = (el 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 (attrOpt.transition) {
+            !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {});
+            const transitionKeys = normalizeToArray(attrOpt.transition);
+            for (let i = 0; i < transitionKeys.length; i++) {
+                const key = transitionKeys[i];
+                const elVal = elPropsInAttr[key];
+                if (__DEV__) {
+                    checkNonStyleTansitionRefer(key, (attrOpt as any)[key], elVal);
+                }
+                // Do not clone, see `checkNonStyleTansitionRefer`.
+                transFromPropsInAttr[key] = elVal;
+            }
+        }
+        else if (indexOf(elOption.transition, mainAttr) >= 0) {
+            !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {});
+            const elPropsInAttrKeys = keys(elPropsInAttr);
+            for (let i = 0; i < elPropsInAttrKeys.length; i++) {
+                const key = elPropsInAttrKeys[i];
+                const elVal = elPropsInAttr[key];
+                if (isNonStyleTransitionEnabled((attrOpt as any)[key], elVal)) {
+                    transFromPropsInAttr[key] = elVal;
+                }
+            }
+        }
+    }
+
+    const leaveTo = attrOpt.leaveTo;
+    if (leaveTo) {
+        const leaveToProps = getOrCreateLeaveToPropsFromEl(el);
+        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];
+        }
+    }
+}
+
+export function prepareShapeOrExtraAllPropsFinal(
+    mainAttr: 'shape' | 'extra',
+    elOption: CustomElementOption,
+    allProps: LooseElementProps
+): void {
+    const attrOpt: Dictionary<unknown> & TransitionAnyOption = (elOption as any)[mainAttr];
+    if (!attrOpt) {
+        return;
+    }
+    const allPropsInAttr = allProps[mainAttr] = {} as Dictionary<unknown>;
+    const keysInAttr = keys(attrOpt);
+    for (let i = 0; i < keysInAttr.length; i++) {
+        const 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 as any)[key]);
+    }
+}
+
+// See [STRATEGY_TRANSITION].
+export function prepareTransformTransitionFrom(
+    el: Element,
+    morphFromEl: graphicUtil.Path,
+    elOption: CustomElementOption,
+    transFromProps: ElementProps,
+    isInit: boolean
+): 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;
+        }
+    }
+
+    if (!isInit) {
+        // If morphing, force transition all transform props.
+        // otherwise might have incorrect morphing animation.
+        if (morphFromEl) {
+            const fromTransformable = calcOldElLocalTransformBasedOnNewElParent(morphFromEl, el);
+            setTransformPropToTransitionFrom(transFromProps, 'x', fromTransformable);
+            setTransformPropToTransitionFrom(transFromProps, 'y', fromTransformable);
+            setTransformPropToTransitionFrom(transFromProps, 'scaleX', fromTransformable);
+            setTransformPropToTransitionFrom(transFromProps, 'scaleY', fromTransformable);
+            setTransformPropToTransitionFrom(transFromProps, 'originX', fromTransformable);
+            setTransformPropToTransitionFrom(transFromProps, 'originY', fromTransformable);
+            setTransformPropToTransitionFrom(transFromProps, 'rotation', fromTransformable);
+        }
+        else if (elOption.transition) {
+            const transitionKeys = normalizeToArray(elOption.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');
+                    checkNonStyleTansitionRefer(key, elOption[key], elVal);
+                }
+                // Do not clone, see `checkNonStyleTansitionRefer`.
+                transFromProps[key] = elVal;
+            }
+        }
+        // This default transition see [STRATEGY_TRANSITION]
+        else {
+            setTransformPropToTransitionFrom(transFromProps, 'x', el);
+            setTransformPropToTransitionFrom(transFromProps, 'y', el);
+        }
+    }
+
+    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;
+        }
+    }
+}
+
+export function prepareTransformAllPropsFinal(
+    elOption: CustomElementOption,
+    allProps: ElementProps
+): void {
+    setLagecyTransformProp(elOption, allProps, 'position');
+    setLagecyTransformProp(elOption, allProps, 'scale');
+    setLagecyTransformProp(elOption, allProps, 'origin');
+    setTransformProp(elOption, allProps, 'x');
+    setTransformProp(elOption, allProps, 'y');
+    setTransformProp(elOption, allProps, 'scaleX');
+    setTransformProp(elOption, allProps, 'scaleY');
+    setTransformProp(elOption, allProps, 'originX');
+    setTransformProp(elOption, allProps, 'originY');
+    setTransformProp(elOption, allProps, 'rotation');
+}
+
+// See [STRATEGY_TRANSITION].
+export function prepareStyleTransitionFrom(
+    fromEl: Element,
+    elOption: CustomElementOption,
+    styleOpt: CustomElementOption['style'],
+    transFromProps: LooseElementProps,
+    isInit: boolean
+): void {
+    if (!styleOpt) {
+        return;
+    }
+
+    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 (styleOpt.transition) {
+            const transitionKeys = normalizeToArray(styleOpt.transition);
+            !transFromStyleProps && (transFromStyleProps = transFromProps.style = {});
+            for (let i = 0; i < transitionKeys.length; i++) {
+                const key = transitionKeys[i];
+                const elVal = (fromElStyle as any)[key];
+                // Do not clone, see `checkNonStyleTansitionRefer`.
+                (transFromStyleProps as any)[key] = elVal;
+            }
+        }
+        else if (
+            (fromEl as Displayable).getAnimationStyleProps
+            && indexOf(elOption.transition, 'style') >= 0
+        ) {
+            const animationProps = (fromEl as Displayable).getAnimationStyleProps();
+            const animationStyleProps = animationProps ? animationProps.style : null;
+            if (animationStyleProps) {
+                !transFromStyleProps && (transFromStyleProps = transFromProps.style = {});
+                const styleKeys = keys(styleOpt);
+                for (let i = 0; i < styleKeys.length; i++) {
+                    const key = styleKeys[i];
+                    if ((animationStyleProps as Dictionary<unknown>)[key]) {
+                        const elVal = (fromElStyle as any)[key];
+                        (transFromStyleProps as any)[key] = elVal;
+                    }
+                }
+            }
+        }
+    }
+
+    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];
+        }
+    }
+}
+
+/**
+ * If make "transform"(x/y/scaleX/scaleY/orient/originX/originY) transition between
+ * two path elements that have different hierarchy, before we retrieve the "from" props,
+ * we have to calculate the local transition of the "oldPath" based on the parent of
+ * the "newPath".
+ * At present, the case only happend in "morphing". Without morphing, the transform
+ * transition are all between elements in the same hierarchy, where this kind of process
+ * is not needed.
+ *
+ * [CAVEAT]:
+ * This method makes sense only if: (very tricky)
+ * (1) "newEl" has been added to its final parent.
+ * (2) Local transform props of "newPath.parent" are not at their final value but already
+ * have been at the "from value".
+ *     This is currently ensured by:
+ *     (2.1) "graphicUtil.animationFrom", which will set the element to the "from value"
+ *     immediately.
+ *     (2.2) "morph" option is not allowed to be set on Group, so all of the groups have
+ *     been finished their "updateElNormal" when calling this method in morphing process.
+ */
+function calcOldElLocalTransformBasedOnNewElParent(oldEl: Element, newEl: Element): Transformable {
+    if (!oldEl || oldEl === newEl || oldEl.parent === newEl.parent) {
+        return oldEl;
+    }
+
+    // Not sure oldEl is rendered (may have "lazyUpdate"),
+    // so always call `getComputedTransform`.
+    const tmpM = tmpTransformable.transform
+        || (tmpTransformable.transform = matrix.identity([]));
+
+    const oldGlobalTransform = oldEl.getComputedTransform();
+    oldGlobalTransform
+        ? matrix.copy(tmpM, oldGlobalTransform)
+        : matrix.identity(tmpM);
+
+    const newParent = newEl.parent;
+    if (newParent) {
+        newParent.getComputedTransform();
+    }
+
+    tmpTransformable.originX = oldEl.originX;
+    tmpTransformable.originY = oldEl.originY;
+    tmpTransformable.parent = newParent;
+    tmpTransformable.decomposeTransform();
+
+    return tmpTransformable;
+}
+
+let checkNonStyleTansitionRefer: (propName: string, optVal: unknown, elVal: unknown) => void;
+if (__DEV__) {
+    checkNonStyleTansitionRefer = function (propName: string, optVal: unknown, elVal: unknown): void {
+        if (!isArrayLike(optVal)) {
+            assert(
+                optVal != null && isFinite(optVal as number),
+                'Prop `' + propName + '` must refer to a finite number or ArrayLike for transition.'
+            );
+        }
+        else {
+            // Try not to copy array for performance, but if user use the same object in different
+            // call of `renderItem`, it will casue animation transition fail.
+            assert(
+                optVal !== elVal,
+                'Prop `' + propName + '` must use different Array object each time for transition.'
+            );
+        }
+    };
+}
+
+function isNonStyleTransitionEnabled(optVal: unknown, elVal: unknown): boolean {
+    // The same as `checkNonStyleTansitionRefer`.
+    return !isArrayLike(optVal)
+        ? (optVal != null && isFinite(optVal as number))
+        : optVal !== elVal;
+}
+
+let checkTransformPropRefer: (key: string, usedIn: string) => void;
+if (__DEV__) {
+    checkTransformPropRefer = function (key: string, usedIn: string): void {
+        assert(
+            hasOwn(TRANSFORM_PROPS, key),
+            'Prop `' + key + '` is not a permitted in `' + usedIn + '`. '
+                + 'Only `' + keys(TRANSFORM_PROPS).join('`, `') + '` are permitted.'
+        );
+    };
+}
+
+function getOrCreateLeaveToPropsFromEl(el: Element): LooseElementProps {
+    const innerEl = customInnerStore(el);
+    return innerEl.leaveToProps || (innerEl.leaveToProps = {});
+}
+
diff --git a/src/chart/custom/transitionHelper.ts b/src/chart/custom/transitionHelper.ts
new file mode 100644
index 0000000..265becf
--- /dev/null
+++ b/src/chart/custom/transitionHelper.ts
@@ -0,0 +1,186 @@
+/*
+* 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 { customInnerStore } from './CustomSeries';
+import {
+    separateMorph,
+    combineMorph,
+    morphPath,
+    isCombineMorphing,
+    SeparateConfig,
+    CombineConfig
+} from 'zrender/src/tool/morphPath';
+import { Path } from '../../util/graphic';
+import { SeriesModel } from '../../export/api';
+import Element, { ElementAnimateConfig } from 'zrender/src/Element';
+import { AnimationEasing } from 'zrender/src/animation/easing';
+import { PayloadAnimationPart } from '../../util/types';
+import { clone, isArray, isFunction } from 'zrender/src/core/util';
+
+export function getMorphAnimationConfig(seriesModel: SeriesModel, dataIndex: number): ElementAnimateConfig {
+    let duration: number;
+    let easing: AnimationEasing;
+    let delay: number;
+
+    if (seriesModel.isAnimationEnabled()) {
+        // PENDING: refactor? this is the same logic as `src/util/graphic.ts#animateOrSetProps`.
+        let animationPayload: PayloadAnimationPart;
+        if (seriesModel && seriesModel.ecModel) {
+            const updatePayload = seriesModel.ecModel.getUpdatePayload();
+            animationPayload = (updatePayload && updatePayload.animation) as PayloadAnimationPart;
+        }
+        if (animationPayload) {
+            duration = animationPayload.duration || 0;
+            easing = animationPayload.easing || 'cubicOut';
+            delay = animationPayload.delay || 0;
+        }
+        else {
+            easing = seriesModel.get('animationEasingUpdate');
+            const delayOption = seriesModel.get('animationDelayUpdate');
+            delay = isFunction(delayOption) ? delayOption(dataIndex) : delayOption;
+            const durationOption = seriesModel.get('animationDurationUpdate');
+            duration = isFunction(durationOption) ? durationOption(dataIndex) : durationOption;
+        }
+    }
+
+    const config = {
+        duration: duration || 0,
+        delay: delay,
+        easing: easing
+    };
+
+    return config;
+}
+
+function getMorphPathList(el: Element): Path[] {
+    const pathList: Path[] = [];
+    el.traverse(child => {
+        if ((child instanceof Path) && customInnerStore(child).morph) {
+            pathList.push(child);
+        }
+    });
+    return pathList;
+}
+
+export function applyMorphAnimation(
+    from: Element[] | Element,
+    to: Element[] | Element,
+    seriesModel: SeriesModel,
+    dataIndex: number,
+    updateOtherProps: (fromIndividual: Path, toIndividual: Path, rawFrom: Path, rawTo: Path) => void
+) {
+    const animationCfg = getMorphAnimationConfig(seriesModel, dataIndex);
+
+    let many: Element[];
+    let one: Element;
+    if (isArray(from)) {    // manyToOne
+        many = from;
+        one = to as Element;
+    }
+    if (isArray(to)) { // oneToMany
+        many = to;
+        one = from as Element;
+    }
+
+    if (many) {
+        // TODO mergeByName
+        const manyPathsList: Path[][] = [];
+        for (let i = 0; i < many.length; i++) {
+            manyPathsList[i] = getMorphPathList(many[i]);
+        }
+        const onePaths = getMorphPathList(one as Element);
+
+        for (let i = 0; i < onePaths.length; i++) {
+            const manyPaths: Path[] = [];
+            for (let k = 0; k < manyPathsList.length; k++) {
+                if (manyPathsList[k][i]) {
+                    manyPaths.push(manyPathsList[k][i]);
+                }
+            }
+            let fromIndividuals;
+            let toIndividuals;
+            if (many === from) {    // manyToOne
+                const res = combineMorph(
+                    manyPaths, onePaths[i], animationCfg as CombineConfig
+                );
+                fromIndividuals = res.fromIndividuals;
+                toIndividuals = res.toIndividuals;
+            }
+            else {  // oneToMany
+                const res = separateMorph(
+                    onePaths[i], manyPaths, animationCfg as SeparateConfig
+                );
+
+                fromIndividuals = res.fromIndividuals;
+                toIndividuals = res.toIndividuals;
+            }
+
+            for (let i = 0; i < fromIndividuals.length; i++) {
+                updateOtherProps(
+                    fromIndividuals[i],
+                    toIndividuals[i],
+                    many === from ? manyPaths[i] : onePaths[i],
+                    many === from ? onePaths[i] : manyPaths[i]
+                );
+            }
+        }
+    }
+    else {  // oneToOne
+        const fromPaths = getMorphPathList(from as Element);
+        const toPaths = getMorphPathList(to as Element);
+
+        for (let i = 0; i < toPaths.length; i++) {
+            if (fromPaths[i]) {
+                if (isCombineMorphing(fromPaths[i])) {
+                    // Keep doing combine animation.
+                    const {fromIndividuals, toIndividuals} = combineMorph(
+                        [fromPaths[i]], toPaths[i], animationCfg as CombineConfig
+                    );
+                    for (let k = 0; k < fromIndividuals.length; k++) {
+                        updateOtherProps(
+                            fromIndividuals[k],
+                            toIndividuals[k],
+                            fromIndividuals[k],
+                            toIndividuals[k]
+                        );
+                    }
+                }
+                else {
+                    morphPath(fromPaths[i], toPaths[i], animationCfg);
+                    updateOtherProps(fromPaths[i], toPaths[i], fromPaths[i], toPaths[i]);
+                }
+            }
+        }
+    }
+}
+
+function copyPropsWhenDivided(
+    srcPath: Path,
+    tarPath: Path,
+    willClone: boolean
+): void {
+    // If just carry the style, will not be modifed, so do not copy.
+    tarPath.style = willClone
+        ? clone(srcPath.style)
+        : srcPath.style;
+
+    tarPath.zlevel = srcPath.zlevel;
+    tarPath.z = srcPath.z;
+    tarPath.z2 = srcPath.z2;
+}
diff --git a/src/component/toolbox/feature/DataView.ts b/src/component/toolbox/feature/DataView.ts
index 15c1742..1f8c084 100644
--- a/src/component/toolbox/feature/DataView.ts
+++ b/src/component/toolbox/feature/DataView.ts
@@ -17,6 +17,8 @@
 * under the License.
 */
 
+/* global document */
+
 import * as echarts from '../../../core/echarts';
 import * as zrUtil from 'zrender/src/core/util';
 import GlobalModel from '../../../model/Global';
diff --git a/src/component/toolbox/feature/SaveAsImage.ts b/src/component/toolbox/feature/SaveAsImage.ts
index f7caa58..3656dcc 100644
--- a/src/component/toolbox/feature/SaveAsImage.ts
+++ b/src/component/toolbox/feature/SaveAsImage.ts
@@ -17,7 +17,7 @@
 * under the License.
 */
 
-/* global Uint8Array */
+/* global Uint8Array, document */
 
 import env from 'zrender/src/core/env';
 import { ToolboxFeature, ToolboxFeatureOption } from '../featureManager';
diff --git a/src/component/tooltip/TooltipHTMLContent.ts b/src/component/tooltip/TooltipHTMLContent.ts
index 8f60093..5a5e29d 100644
--- a/src/component/tooltip/TooltipHTMLContent.ts
+++ b/src/component/tooltip/TooltipHTMLContent.ts
@@ -17,6 +17,8 @@
 * under the License.
 */
 
+/* global document, window */
+
 import { isString, indexOf, map, each, bind, isArray, isDom } from 'zrender/src/core/util';
 import { toHex } from 'zrender/src/tool/color';
 import { normalizeEvent } from 'zrender/src/core/event';
diff --git a/src/coord/CoordinateSystem.ts b/src/coord/CoordinateSystem.ts
index 83708d8..3d4f833 100644
--- a/src/coord/CoordinateSystem.ts
+++ b/src/coord/CoordinateSystem.ts
@@ -26,7 +26,7 @@ import { BoundingRect } from '../util/graphic';
 import { MatrixArray } from 'zrender/src/core/matrix';
 import ComponentModel from '../model/Component';
 import { RectLike } from 'zrender/src/core/BoundingRect';
-import type { PrepareCustomInfo } from '../chart/custom/install';
+import type { PrepareCustomInfo } from '../chart/custom/CustomSeries';
 
 
 export interface CoordinateSystemCreator {
diff --git a/src/core/echarts.ts b/src/core/echarts.ts
index 4e9a06a..4b6e95a 100644
--- a/src/core/echarts.ts
+++ b/src/core/echarts.ts
@@ -29,8 +29,8 @@ import CoordinateSystemManager from './CoordinateSystem';
 import OptionManager from '../model/OptionManager';
 import backwardCompat from '../preprocessor/backwardCompat';
 import dataStack from '../processor/dataStack';
-import ComponentModel, { ComponentModelConstructor } from '../model/Component';
-import SeriesModel, { SeriesModelConstructor } from '../model/Series';
+import ComponentModel from '../model/Component';
+import SeriesModel from '../model/Series';
 import ComponentView, {ComponentViewConstructor} from '../view/Component';
 import ChartView, {ChartViewConstructor} from '../view/Chart';
 import * as graphic from '../util/graphic';
@@ -102,7 +102,6 @@ import { createLocaleObject, SYSTEM_LANG, LocaleOption } from './locale';
 import type {EChartsOption} from '../export/option';
 import { findEventDispatcher } from '../util/event';
 import decal from '../visual/decal';
-import type {MorphDividingMethod} from 'zrender/src/tool/morphPath';
 import CanvasPainter from 'zrender/src/canvas/Painter';
 import SVGPainter from 'zrender/src/svg/Painter';
 
@@ -207,7 +206,6 @@ export interface SetOptionTransitionOptItem {
     // which might bring about misleading.
     from?: SetOptionTransitionOptFinder;
     to: SetOptionTransitionOptFinder;
-    dividingMethod: MorphDividingMethod;
 }
 interface SetOptionTransitionOptFinder extends modelUtil.ModelFinderObject {
     dimension: DimensionLoose;
@@ -2391,8 +2389,7 @@ class ECharts extends Eventful {
                 // Just a temp solution: mount them on series.
                 toSeries.__transientTransitionOpt = {
                     from: fromOpt ? fromOpt.dimension : null,
-                    to: toOpt.dimension,
-                    dividingMethod: transOpt.dividingMethod
+                    to: toOpt.dimension
                 };
             });
         };
diff --git a/src/export/option.ts b/src/export/option.ts
index 9bd56ab..0aa5313 100644
--- a/src/export/option.ts
+++ b/src/export/option.ts
@@ -77,7 +77,7 @@ import type {HeatmapSeriesOption as HeatmapSeriesOptionInner} from '../chart/hea
 import type {PictorialBarSeriesOption as PictorialBarSeriesOptionInner} from '../chart/bar/PictorialBarSeries';
 import type {ThemeRiverSeriesOption as ThemeRiverSeriesOptionInner} from '../chart/themeRiver/ThemeRiverSeries';
 import type {SunburstSeriesOption as SunburstSeriesOptionInner} from '../chart/sunburst/SunburstSeries';
-import type {CustomSeriesOption as CustomSeriesOptionInner} from '../chart/custom/install';
+import type {CustomSeriesOption as CustomSeriesOptionInner} from '../chart/custom/CustomSeries';
 
 import type { GraphicComponentLooseOption as GraphicComponentOption } from '../component/graphic/install';
 import type { DatasetOption as DatasetComponentOption } from '../component/dataset/install';
diff --git a/src/model/Series.ts b/src/model/Series.ts
index 7128ec3..5222778 100644
--- a/src/model/Series.ts
+++ b/src/model/Series.ts
@@ -19,7 +19,6 @@
 
 import * as zrUtil from 'zrender/src/core/util';
 import env from 'zrender/src/core/env';
-import type {MorphDividingMethod} from 'zrender/src/tool/morphPath';
 import * as modelUtil from '../util/model';
 import {
     DataHost, DimensionName, StageHandlerProgressParams,
@@ -142,7 +141,6 @@ class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentMode
         // Both from and to can be null/undefined, which meams no transform mapping.
         from: DimensionLoose;
         to: DimensionLoose;
-        dividingMethod: MorphDividingMethod;
     };
 
     // ---------------------------------------
diff --git a/src/util/quickSelect.ts b/src/util/quickSelect.ts
index abb3822..e66fac5 100644
--- a/src/util/quickSelect.ts
+++ b/src/util/quickSelect.ts
@@ -67,7 +67,6 @@ function select<T>(arr: T[], left: number, right: number, nth: number, compareFu
 
 /**
  * @example
- *     let quickSelect = require('echarts/core/quickSelect');
  *     let arr = [5, 2, 1, 4, 3]
  *     quickSelect(arr, 3);
  *     quickSelect(arr, 0, 3, 1, function (a, b) {return a - b});
diff --git a/src/util/types.ts b/src/util/types.ts
index 797fa8b..037354f 100644
--- a/src/util/types.ts
+++ b/src/util/types.ts
@@ -185,7 +185,7 @@ export interface ViewRootGroup extends Group {
  * The echarts event type to user.
  * Also known as packedEvent.
  */
-export interface ECEvent extends ECEventData{
+export interface ECEvent extends ECEventData {
     // event type
     type: string;
     componentType?: string;


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