| /* | 
 | * 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 { | 
 |     hasOwn, assert, isString, retrieve2, retrieve3, defaults, each, indexOf | 
 | } from 'zrender/src/core/util'; | 
 | import * as graphicUtil from '../../util/graphic'; | 
 | import { setDefaultStateProxy, toggleHoverEmphasis } from '../../util/states'; | 
 | import * as labelStyleHelper from '../../label/labelStyle'; | 
 | import {getDefaultLabel} from '../helper/labelHelper'; | 
 | import {getLayoutOnAxis} from '../../layout/barGrid'; | 
 | import DataDiffer from '../../data/DataDiffer'; | 
 | import Model from '../../model/Model'; | 
 | import ChartView from '../../view/Chart'; | 
 | import {createClipPath} from '../helper/createClipPathFromCoordSys'; | 
 | import { | 
 |     EventQueryItem, ECActionEvent, | 
 |     DimensionLoose, | 
 |     ParsedValue, | 
 |     Dictionary, | 
 |     Payload, | 
 |     StageHandlerProgressParams, | 
 |     ViewRootGroup, | 
 |     ZRStyleProps, | 
 |     DisplayState, | 
 |     ECElement, | 
 |     DisplayStateNonNormal, | 
 |     OrdinalRawValue, | 
 |     InnerDecalObject | 
 | } from '../../util/types'; | 
 | import Element, { 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 SeriesData, { DefaultDataVisual } from '../../data/SeriesData'; | 
 | import GlobalModel from '../../model/Global'; | 
 | 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 { PathStyleProps } from 'zrender/src/graphic/Path'; | 
 | import { TextStyleProps } from 'zrender/src/graphic/Text'; | 
 | import { | 
 |     convertToEC4StyleForCustomSerise, | 
 |     isEC4CompatibleStyle, | 
 |     convertFromEC4CompatibleStyle, | 
 |     LegacyStyleProps, | 
 |     warnDeprecated | 
 | } from '../../util/styleCompat'; | 
 | import { ItemStyleProps } from '../../model/mixin/itemStyle'; | 
 | import { throwError } from '../../util/log'; | 
 | import { createOrUpdatePatternFromDecal } from '../../util/decal'; | 
 | import CustomSeriesModel, { | 
 |     CustomImageOption, | 
 |     CustomElementOption, | 
 |     CustomElementOptionOnState, | 
 |     CustomSVGPathOption, | 
 |     CustomBaseZRPathOption, | 
 |     CustomDisplayableOption, | 
 |     CustomSeriesRenderItemAPI, | 
 |     CustomSeriesRenderItemParams, | 
 |     CustomGroupOption, | 
 |     WrapEncodeDefRet, | 
 |     NonStyleVisualProps, | 
 |     StyleVisualProps, | 
 |     STYLE_VISUAL_TYPE, | 
 |     NON_STYLE_VISUAL_PROPS, | 
 |     customInnerStore, | 
 |     PrepareCustomInfo, | 
 |     CustomPathOption, | 
 |     CustomRootElementOption, | 
 |     CustomSeriesOption | 
 | } from './CustomSeries'; | 
 | import { PatternObject } from 'zrender/src/graphic/Pattern'; | 
 | import { | 
 |     applyLeaveTransition, | 
 |     applyUpdateTransition, | 
 |     ElementRootTransitionProp | 
 | } from '../../animation/customGraphicTransition'; | 
 | import { | 
 |     applyKeyframeAnimation, | 
 |     stopPreviousKeyframeAnimationAndRestore | 
 | } from '../../animation/customGraphicKeyframeAnimation'; | 
 | import type SeriesModel from '../../model/Series'; | 
 |  | 
 | 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; | 
 | const DEFAULT_TRANSITION: ElementRootTransitionProp[] = ['x', 'y']; | 
 | // 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; | 
 |  | 
 |  | 
 | /** | 
 |  * 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, | 
 |     single: prepareSingleAxis, | 
 |     polar: preparePolar, | 
 |     calendar: prepareCalendar | 
 | }; | 
 |  | 
 |  | 
 | function isPath(el: Element): el is graphicUtil.Path { | 
 |     return el instanceof graphicUtil.Path; | 
 | } | 
 | function isDisplayable(el: Element) : el is Displayable { | 
 |     return el instanceof Displayable; | 
 | } | 
 | function copyElement(sourceEl: Element, targetEl: Element) { | 
 |     targetEl.copyTransform(sourceEl); | 
 |     if (isDisplayable(targetEl) && isDisplayable(sourceEl)) { | 
 |         targetEl.setStyle(sourceEl.style); | 
 |         targetEl.z = sourceEl.z; | 
 |         targetEl.z2 = sourceEl.z2; | 
 |         targetEl.zlevel = sourceEl.zlevel; | 
 |         targetEl.invisible = sourceEl.invisible; | 
 |         targetEl.ignore = sourceEl.ignore; | 
 |  | 
 |         if (isPath(targetEl) && isPath(sourceEl)) { | 
 |             targetEl.setShape(sourceEl.shape); | 
 |         } | 
 |     } | 
 | } | 
 | export default class CustomChartView extends ChartView { | 
 |  | 
 |     static type = 'custom'; | 
 |     readonly type = CustomChartView.type; | 
 |  | 
 |     private _data: SeriesData; | 
 |     private _progressiveEls: Element[]; | 
 |  | 
 |     render( | 
 |         customSeries: CustomSeriesModel, | 
 |         ecModel: GlobalModel, | 
 |         api: ExtensionAPI, | 
 |         payload: Payload | 
 |     ): void { | 
 |  | 
 |         // Clear previously rendered progressive elements. | 
 |         this._progressiveEls = null; | 
 |  | 
 |         const oldData = this._data; | 
 |         const data = customSeries.getData(); | 
 |         const group = this.group; | 
 |         const renderItem = makeRenderItem(customSeries, data, ecModel, api); | 
 |  | 
 |         if (!oldData) { | 
 |             // Previous render is incremental render or first render. | 
 |             // Needs remove the incremental rendered elements. | 
 |             group.removeAll(); | 
 |         } | 
 |  | 
 |         data.diff(oldData) | 
 |             .add(function (newIdx) { | 
 |                 createOrUpdateItem( | 
 |                     api, null, newIdx, renderItem(newIdx, payload), customSeries, group, | 
 |                     data | 
 |                 ); | 
 |             }) | 
 |             .remove(function (oldIdx) { | 
 |                 const el = oldData.getItemGraphicEl(oldIdx); | 
 |                 el && applyLeaveTransition(el, customInnerStore(el).option, customSeries); | 
 |             }) | 
 |             .update(function (newIdx, oldIdx) { | 
 |                 const oldEl = oldData.getItemGraphicEl(oldIdx); | 
 |  | 
 |                 createOrUpdateItem( | 
 |                     api, oldEl, newIdx, renderItem(newIdx, payload), customSeries, group, | 
 |                     data | 
 |                 ); | 
 |             }) | 
 |             .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); | 
 |         const progressiveEls: Element[] = this._progressiveEls = []; | 
 |  | 
 |         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 | 
 |             ); | 
 |             if (el) { | 
 |                 el.traverse(setIncrementalAndHoverLayer); | 
 |                 progressiveEls.push(el); | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     eachRendered(cb: (el: Element) => boolean | void) { | 
 |         graphicUtil.traverseElements(this._progressiveEls || this.group, cb); | 
 |     } | 
 |  | 
 |     filterForExposedEvent( | 
 |         eventType: string, query: EventQueryItem, targetEl: Element, packedEvent: ECActionEvent | 
 |     ): 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 are triggered by its descendents. | 
 |         while ((targetEl = (targetEl.__hostTarget || targetEl.parent)) && targetEl !== this.group) { | 
 |             if (targetEl.name === elementName) { | 
 |                 return true; | 
 |             } | 
 |         } | 
 |  | 
 |         return false; | 
 |     } | 
 | } | 
 |  | 
 |  | 
 | 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'); | 
 |         customInnerStore(el).customPathData = pathData; | 
 |     } | 
 |     else if (graphicType === 'image') { | 
 |         el = new graphicUtil.Image({}); | 
 |         customInnerStore(el).customImagePath = (elOption as CustomImageOption).style.image; | 
 |     } | 
 |     else if (graphicType === 'text') { | 
 |         el = new graphicUtil.Text({}); | 
 |         // customInnerStore(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(); | 
 |     } | 
 |  | 
 |     customInnerStore(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 is reused, we now consider whether | 
 |  * merge or erase all properties to the existing elements. | 
 |  * That is, if a certain props is not specified in the latest 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" seems to be | 
 |  * more safe. "erase all" forces users to specify all of the props each time, which is recommended | 
 |  * 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 | 
 |  * simply always set all of the props each time. | 
 |  * Some "object-like" config like `textConfig`, `textContent`, `style` which are not needed for | 
 |  * every element, so we replace them only when users specify them. And that is a total replace. | 
 |  * | 
 |  * TODO: There is no hint of 'isFirst' to users. So the performance enhancement cannot 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 other 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`. | 
 |  */ | 
 |  | 
 | interface InnerCustomZRPathOptionStyle extends PathStyleProps { | 
 |     __decalPattern: PatternObject | 
 | } | 
 |  | 
 | function updateElNormal( | 
 |     // Can be null/undefined | 
 |     api: ExtensionAPI, | 
 |     el: Element, | 
 |     dataIndex: number, | 
 |     elOption: CustomElementOption, | 
 |     attachedTxInfo: AttachedTxInfo, | 
 |     seriesModel: CustomSeriesModel, | 
 |     isInit: boolean | 
 | ): void { | 
 |  | 
 |     // Stop and restore before update any other attributes. | 
 |     stopPreviousKeyframeAnimationAndRestore(el); | 
 |  | 
 |     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); | 
 |     } | 
 |  | 
 |     // Default transition ['x', 'y'] | 
 |     if (elOption && elOption.transition == null) { | 
 |         elOption.transition = DEFAULT_TRANSITION; | 
 |     } | 
 |  | 
 |     // Do some normalization on style. | 
 |     const styleOpt = elOption && (elOption as CustomDisplayableOption).style; | 
 |  | 
 |     if (styleOpt) { | 
 |         if (el.type === 'text') { | 
 |             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 | 
 |             ); | 
 |         } | 
 |  | 
 |         let decalPattern; | 
 |         const decalObj = isPath(el) ? (styleOpt as CustomBaseZRPathOption['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 InnerCustomZRPathOptionStyle).__decalPattern = decalPattern; | 
 |     } | 
 |  | 
 |     if (isDisplayable(el)) { | 
 |         if (styleOpt) { | 
 |             const decalPattern = (styleOpt as InnerCustomZRPathOptionStyle).__decalPattern; | 
 |             if (decalPattern) { | 
 |                 (styleOpt as PathStyleProps).decal = decalPattern; | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     applyUpdateTransition(el, elOption, seriesModel, { | 
 |         dataIndex, | 
 |         isInit, | 
 |         clearStyle: true | 
 |     }); | 
 |  | 
 |     applyKeyframeAnimation(el, elOption.keyframeAnimation, seriesModel); | 
 | } | 
 |  | 
 | function updateElOnState( | 
 |     state: DisplayStateNonNormal, | 
 |     el: Element, | 
 |     elStateOpt: CustomElementOptionOnState, | 
 |     styleOpt: CustomElementOptionOnState['style'], | 
 |     attachedTxInfo: AttachedTxInfo | 
 | ): 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 default 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 | 
 | ): 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 as CustomDisplayableOption).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 as CustomElementOption, | 
 |         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 makeRenderItem( | 
 |     customSeries: CustomSeriesModel, | 
 |     data: SeriesData<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()) | 
 |     } as CustomSeriesRenderItemParams; | 
 |  | 
 |     // 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<CustomSeriesOption>; | 
 |     let currItemStyleModels: Partial<Record<DisplayState, Model<CustomSeriesOption['itemStyle']>>> = {}; | 
 |     let currLabelModels: Partial<Record<DisplayState, Model<CustomSeriesOption['label']>>> = {}; | 
 |  | 
 |     const seriesItemStyleModels = {} as Record<DisplayState, Model<CustomSeriesOption['itemStyle']>>; | 
 |  | 
 |     const seriesLabelModels = {} as Record<DisplayState, Model<CustomSeriesOption['label']>>; | 
 |  | 
 |     for (let i = 0; i < STATES.length; i++) { | 
 |         const stateName = STATES[i]; | 
 |         seriesItemStyleModels[stateName] = (customSeries as Model<CustomSeriesOption>) | 
 |             .getModel(PATH_ITEM_STYLE[stateName]); | 
 |         seriesLabelModels[stateName] = (customSeries as Model<CustomSeriesOption>) | 
 |             .getModel(PATH_LABEL[stateName]); | 
 |     } | 
 |  | 
 |     function getItemModel(dataIndexInside: number): Model<CustomSeriesOption> { | 
 |         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 | 
 |             } as CustomSeriesRenderItemParams, 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.getStore().get(data.getDimensionIndex(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); | 
 |         dim = dim || 0; | 
 |         const dimInfo = data.getDimensionInfo(dim); | 
 |         if (!dimInfo) { | 
 |             const dimIndex = data.getDimensionIndex(dim); | 
 |             return dimIndex >= 0 ? data.getStore().get(dimIndex, dataIndexInside) : undefined; | 
 |         } | 
 |         const val = data.get(dimInfo.name, dataIndexInside); | 
 |         const ordinalMeta = dimInfo && dimInfo.ordinalMeta; | 
 |         return ordinalMeta | 
 |             ? ordinalMeta.categories[val as number] | 
 |             : val; | 
 |     } | 
 |  | 
 |     /** | 
 |      * @deprecated The original intention of `api.style` is enable to set itemStyle | 
 |      * like other series. But it is not necessary and not easy to give a strict definition | 
 |      * of what it returns. 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 feature 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); | 
 |     } | 
 | } | 
 |  | 
 | function wrapEncodeDef(data: SeriesData<CustomSeriesModel>): WrapEncodeDefRet { | 
 |     const encodeDef = {} as WrapEncodeDefRet; | 
 |     each(data.dimensions, function (dimName) { | 
 |         const dimInfo = data.getDimensionInfo(dimName); | 
 |         if (!dimInfo.isExtraCoord) { | 
 |             const coordDim = dimInfo.coordDim; | 
 |             const dataDims = encodeDef[coordDim] = encodeDef[coordDim] || []; | 
 |             dataDims[dimInfo.coordDimIndex] = data.getDimensionIndex(dimName); | 
 |         } | 
 |     }); | 
 |     return encodeDef; | 
 | } | 
 |  | 
 | function createOrUpdateItem( | 
 |     api: ExtensionAPI, | 
 |     existsEl: Element, | 
 |     dataIndex: number, | 
 |     elOption: CustomRootElementOption, | 
 |     seriesModel: CustomSeriesModel, | 
 |     group: ViewRootGroup, | 
 |     data: SeriesData<CustomSeriesModel> | 
 | ): 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) { | 
 |         group.remove(existsEl); | 
 |         return; | 
 |     } | 
 |     const el = doCreateOrUpdateEl(api, existsEl, dataIndex, elOption, seriesModel, group); | 
 |     el && data.setItemGraphicEl(dataIndex, el); | 
 |  | 
 |     el && toggleHoverEmphasis( | 
 |         el, | 
 |         elOption.focus, | 
 |         elOption.blurScope, | 
 |         elOption.emphasisDisabled | 
 |     ); | 
 |  | 
 |     return el; | 
 | } | 
 |  | 
 | function doCreateOrUpdateEl( | 
 |     api: ExtensionAPI, | 
 |     existsEl: Element, | 
 |     dataIndex: number, | 
 |     elOption: CustomElementOption, | 
 |     seriesModel: CustomSeriesModel, | 
 |     group: ViewRootGroup | 
 | ): Element { | 
 |  | 
 |     if (__DEV__) { | 
 |         assert(elOption, 'should not have an null/undefined element setting'); | 
 |     } | 
 |  | 
 |     let toBeReplacedIdx = -1; | 
 |     const oldEl = existsEl; | 
 |     if ( | 
 |         existsEl && ( | 
 |             doesElNeedRecreate(existsEl, elOption, seriesModel) | 
 |             // || ( | 
 |             //     // 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. | 
 |             //     morphHelper && morphHelper.isOneToOneFrom(el) | 
 |             // ) | 
 |         ) | 
 |     ) { | 
 |         // Should keep at the original index, otherwise "merge by index" will be incorrect. | 
 |         toBeReplacedIdx = indexOf(group.childrenRef(), existsEl); | 
 |         existsEl = null; | 
 |     } | 
 |  | 
 |     const isInit = !existsEl; | 
 |     let el = existsEl; | 
 |  | 
 |     if (!el) { | 
 |         el = createEl(elOption); | 
 |         if (oldEl) { | 
 |             copyElement(oldEl, el); | 
 |         } | 
 |     } | 
 |     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(); | 
 |     } | 
 |  | 
 |     // Need to set morph: false explictly to disable automatically morphing. | 
 |     if ((elOption as CustomBaseZRPathOption).morph === false) { | 
 |         (el as ECElement).disableMorphing = true; | 
 |     } | 
 |     else if ((el as ECElement).disableMorphing) { | 
 |         (el as ECElement).disableMorphing = false; | 
 |     } | 
 |  | 
 |     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 | 
 |     ); | 
 |  | 
 |     updateElNormal( | 
 |         api, | 
 |         el, | 
 |         dataIndex, | 
 |         elOption, | 
 |         attachedTxInfoTmp, | 
 |         seriesModel, | 
 |         isInit | 
 |     ); | 
 |     // `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') && (customInnerStore(el).info = elOption.info); | 
 |  | 
 |     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); | 
 |         } | 
 |     } | 
 |  | 
 |     updateZ(el, elOption, seriesModel); | 
 |  | 
 |     if (elOption.type === 'group') { | 
 |         mergeChildren( | 
 |             api, el as graphicUtil.Group, dataIndex, elOption as CustomGroupOption, seriesModel | 
 |         ); | 
 |     } | 
 |  | 
 |     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, seriesModel: CustomSeriesModel): boolean { | 
 |     const elInner = customInnerStore(el); | 
 |     const elOptionType = elOption.type; | 
 |     const elOptionShape = (elOption as CustomBaseZRPathOption).shape; | 
 |     const elOptionStyle = (elOption as CustomDisplayableOption).style; | 
 |     return ( | 
 |         // Always create new if universal transition is enabled. | 
 |         // Because we do transition after render. It needs to know what old element is. Replacement will loose it. | 
 |         seriesModel.isUniversalTransitionEnabled() | 
 |         // If `elOptionType` is `null`, follow the merge principle. | 
 |         || (elOptionType != null | 
 |             && elOptionType !== elInner.customGraphicType | 
 |         ) | 
 |         || (elOptionType === 'path' | 
 |             && hasOwnPathData(elOptionShape as CustomSVGPathOption['shape']) | 
 |             && getPathData(elOptionShape as CustomSVGPathOption['shape']) !== 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 as CustomPathOption | false; | 
 |     if (clipPathOpt === false) { | 
 |         if (el && el.getClipPath()) { | 
 |             el.removeClipPath(); | 
 |         } | 
 |     } | 
 |     else if (clipPathOpt) { | 
 |         let clipPath = el.getClipPath(); | 
 |         if (clipPath && doesElNeedRecreate( | 
 |             clipPath, | 
 |             clipPathOpt, | 
 |             seriesModel | 
 |         )) { | 
 |             clipPath = null; | 
 |         } | 
 |         if (!clipPath) { | 
 |             clipPath = createEl(clipPathOpt) as graphicUtil.Path; | 
 |             if (__DEV__) { | 
 |                 assert( | 
 |                     isPath(clipPath), | 
 |                     'Only any type of `path` can be used in `clipPath`, rather than ' + clipPath.type + '.' | 
 |                 ); | 
 |             } | 
 |             el.setClipPath(clipPath); | 
 |         } | 
 |         updateElNormal( | 
 |             null, clipPath, dataIndex, clipPathOpt, null, seriesModel, isInit | 
 |         ); | 
 |     } | 
 |     // 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 does not support textContent temporarily until 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 sense. | 
 |     // 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(); | 
 |             } | 
 |  | 
 |             updateElNormal(null, textContent, dataIndex, txConOptNormal, null, seriesModel, isInit); | 
 |             const txConStlOptNormal = txConOptNormal && (txConOptNormal as CustomDisplayableOption).style; | 
 |             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 | 
 |                     ); | 
 |                 } | 
 |             } | 
 |  | 
 |             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 as CustomDisplayableOption).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 incorrcet type for forward compat. | 
 |             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 as CustomDisplayableOption)[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 as CustomDisplayableOption).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. | 
 | // (5) If `elOption.$mergeChildren` is not `false` neither `'byName'` and the | 
 | //     `el` is a group, and if any of the new child is null, it means to remove | 
 | //     the element at the same index, if exists. On the other hand, if the new | 
 | //     child is and empty object `{}`, it means to keep the element not changed. | 
 | // | 
 | // For implementation simpleness, do not provide a direct way to remove single | 
 | // child (otherwise the total indices of the children array have to be modified). | 
 | // User can remove a single child by setting its `ignore` to `true`. | 
 | function mergeChildren( | 
 |     api: ExtensionAPI, | 
 |     el: graphicUtil.Group, | 
 |     dataIndex: number, | 
 |     elOption: CustomGroupOption, | 
 |     seriesModel: CustomSeriesModel | 
 | ): 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 as CustomElementOption[] || [], | 
 |             dataIndex: dataIndex, | 
 |             seriesModel: seriesModel, | 
 |             group: el | 
 |         }); | 
 |         return; | 
 |     } | 
 |  | 
 |     notMerge && el.removeAll(); | 
 |  | 
 |     // Mapping children of a group simply by index, which | 
 |     // might be better performance. | 
 |     let index = 0; | 
 |     for (; index < newLen; index++) { | 
 |         const newChild = newChildren[index]; | 
 |         const oldChild = el.childAt(index); | 
 |         if (newChild) { | 
 |             if (newChild.ignore == null) { | 
 |                 // The old child is set to be ignored if null (see comments | 
 |                 // below). So we need to set ignore to be false back. | 
 |                 newChild.ignore = false; | 
 |             } | 
 |             doCreateOrUpdateEl( | 
 |                 api, | 
 |                 oldChild, | 
 |                 dataIndex, | 
 |                 newChild as CustomElementOption, | 
 |                 seriesModel, | 
 |                 el | 
 |             ); | 
 |         } | 
 |         else { | 
 |             if (__DEV__) { | 
 |                 assert( | 
 |                     oldChild, | 
 |                     'renderItem should not return a group containing elements' | 
 |                     + ' as null/undefined/{} if they do not exist before.' | 
 |                 ); | 
 |             } | 
 |             // If the new element option is null, it means to remove the old | 
 |             // element. But we cannot really remove the element from the group | 
 |             // directly, because the element order may not be stable when this | 
 |             // element is added back. So we set the element to be ignored. | 
 |             oldChild.ignore = true; | 
 |         } | 
 |     } | 
 |     for (let i = el.childCount() - 1; i >= index; i--) { | 
 |         const child = el.childAt(i); | 
 |         removeChildFromGroup(el, child, seriesModel); | 
 |     } | 
 | } | 
 |  | 
 | function removeChildFromGroup( | 
 |     group: graphicUtil.Group, | 
 |     child: Element, | 
 |     seriesModel: SeriesModel | 
 | ) { | 
 |     // Do not support leave elements that are not mentioned in the latest | 
 |     // `renderItem` return. Otherwise users may not have a clear and simple | 
 |     // concept that how to control all of the elements. | 
 |     child && applyLeaveTransition( | 
 |         child, | 
 |         customInnerStore(group).option, | 
 |         seriesModel | 
 |     ); | 
 | } | 
 |  | 
 | type DiffGroupContext = { | 
 |     api: ExtensionAPI; | 
 |     oldChildren: Element[]; | 
 |     newChildren: CustomElementOption[]; | 
 |     dataIndex: number; | 
 |     seriesModel: CustomSeriesModel; | 
 |     group: graphicUtil.Group; | 
 | }; | 
 | 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 | 
 |     ); | 
 | } | 
 |  | 
 | function processRemove(this: DataDiffer<DiffGroupContext>, oldIndex: number): void { | 
 |     const context = this.context; | 
 |     const child = context.oldChildren[oldIndex]; | 
 |     child && applyLeaveTransition(child, customInnerStore(child).option, context.seriesModel); | 
 | } | 
 |  | 
 | /** | 
 |  * @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')); | 
 | } |