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