| /* | 
 | * 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 Path, {PathProps} from 'zrender/src/graphic/Path'; | 
 | import Group from 'zrender/src/graphic/Group'; | 
 | import {extend, each, map} from 'zrender/src/core/util'; | 
 | import {BuiltinTextPosition} from 'zrender/src/core/types'; | 
 | import { | 
 |     Rect, | 
 |     Sector, | 
 |     updateProps, | 
 |     initProps, | 
 |     removeElementWithFadeOut, | 
 |     traverseElements | 
 | } from '../../util/graphic'; | 
 | import { getECData } from '../../util/innerStore'; | 
 | import { setStatesStylesFromModel, toggleHoverEmphasis } from '../../util/states'; | 
 | import { setLabelStyle, getLabelStatesModels, setLabelValueAnimation, labelInner } from '../../label/labelStyle'; | 
 | import {throttle} from '../../util/throttle'; | 
 | import {createClipPath} from '../helper/createClipPathFromCoordSys'; | 
 | import Sausage from '../../util/shape/sausage'; | 
 | import ChartView from '../../view/Chart'; | 
 | import SeriesData, {DefaultDataVisual} from '../../data/SeriesData'; | 
 | import GlobalModel from '../../model/Global'; | 
 | import ExtensionAPI from '../../core/ExtensionAPI'; | 
 | import { | 
 |     StageHandlerProgressParams, | 
 |     ZRElementEvent, | 
 |     ColorString, | 
 |     OrdinalSortInfo, | 
 |     Payload, | 
 |     OrdinalNumber, | 
 |     ParsedValue, | 
 |     ECElement | 
 | } from '../../util/types'; | 
 | import BarSeriesModel, {BarDataItemOption, PolarBarLabelPosition} from './BarSeries'; | 
 | import type Axis2D from '../../coord/cartesian/Axis2D'; | 
 | import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; | 
 | import type Polar from '../../coord/polar/Polar'; | 
 | import type Model from '../../model/Model'; | 
 | import { isCoordinateSystemType } from '../../coord/CoordinateSystem'; | 
 | import { getDefaultLabel, getDefaultInterpolatedLabel } from '../helper/labelHelper'; | 
 | import OrdinalScale from '../../scale/Ordinal'; | 
 | import SeriesModel from '../../model/Series'; | 
 | import {AngleAxisModel, RadiusAxisModel} from '../../coord/polar/AxisModel'; | 
 | import CartesianAxisModel from '../../coord/cartesian/AxisModel'; | 
 | import {LayoutRect} from '../../util/layout'; | 
 | import {EventCallback} from 'zrender/src/core/Eventful'; | 
 | import { warn } from '../../util/log'; | 
 | import {createSectorCalculateTextPosition, SectorTextPosition, setSectorTextRotation} from '../../label/sectorLabel'; | 
 | import { saveOldStyle } from '../../animation/basicTransition'; | 
 | import Element from 'zrender/src/Element'; | 
 |  | 
 | const mathMax = Math.max; | 
 | const mathMin = Math.min; | 
 |  | 
 | type CoordSysOfBar = BarSeriesModel['coordinateSystem']; | 
 | type RectShape = Rect['shape']; | 
 | type SectorShape = Sector['shape']; | 
 |  | 
 | type SectorLayout = SectorShape; | 
 | type RectLayout = RectShape; | 
 |  | 
 | type BarPossiblePath = Sector | Rect | Sausage; | 
 |  | 
 | type CartesianCoordArea = ReturnType<Cartesian2D['getArea']>; | 
 | type PolarCoordArea = ReturnType<Polar['getArea']>; | 
 |  | 
 | type RealtimeSortConfig = { | 
 |     baseAxis: Axis2D; | 
 |     otherAxis: Axis2D; | 
 | }; | 
 | // Return a number, based on which the ordinal sorted. | 
 | type OrderMapping = (dataIndex: number) => number; | 
 |  | 
 | function getClipArea(coord: CoordSysOfBar, data: SeriesData) { | 
 |     const coordSysClipArea = coord.getArea && coord.getArea(); | 
 |     if (isCoordinateSystemType<Cartesian2D>(coord, 'cartesian2d')) { | 
 |         const baseAxis = coord.getBaseAxis(); | 
 |         // When boundaryGap is false or using time axis. bar may exceed the grid. | 
 |         // We should not clip this part. | 
 |         // See test/bar2.html | 
 |         if (baseAxis.type !== 'category' || !baseAxis.onBand) { | 
 |             const expandWidth = data.getLayout('bandWidth'); | 
 |             if (baseAxis.isHorizontal()) { | 
 |                 (coordSysClipArea as CartesianCoordArea).x -= expandWidth; | 
 |                 (coordSysClipArea as CartesianCoordArea).width += expandWidth * 2; | 
 |             } | 
 |             else { | 
 |                 (coordSysClipArea as CartesianCoordArea).y -= expandWidth; | 
 |                 (coordSysClipArea as CartesianCoordArea).height += expandWidth * 2; | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     return coordSysClipArea as PolarCoordArea | CartesianCoordArea; | 
 | } | 
 |  | 
 | class BarView extends ChartView { | 
 |     static type = 'bar' as const; | 
 |     type = BarView.type; | 
 |  | 
 |     private _data: SeriesData; | 
 |  | 
 |     private _isLargeDraw: boolean; | 
 |  | 
 |     private _isFirstFrame: boolean; // First frame after series added | 
 |     private _onRendered: EventCallback; | 
 |  | 
 |     private _backgroundGroup: Group; | 
 |  | 
 |     private _backgroundEls: (Rect | Sector)[]; | 
 |  | 
 |     private _model: BarSeriesModel; | 
 |  | 
 |     private _progressiveEls: Element[]; | 
 |  | 
 |     constructor() { | 
 |         super(); | 
 |         this._isFirstFrame = true; | 
 |     } | 
 |  | 
 |     render(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI, payload: Payload) { | 
 |         this._model = seriesModel; | 
 |  | 
 |         this._removeOnRenderedListener(api); | 
 |  | 
 |         this._updateDrawMode(seriesModel); | 
 |  | 
 |         const coordinateSystemType = seriesModel.get('coordinateSystem'); | 
 |  | 
 |         if (coordinateSystemType === 'cartesian2d' | 
 |             || coordinateSystemType === 'polar' | 
 |         ) { | 
 |             // Clear previously rendered progressive elements. | 
 |             this._progressiveEls = null; | 
 |  | 
 |             this._isLargeDraw | 
 |                 ? this._renderLarge(seriesModel, ecModel, api) | 
 |                 : this._renderNormal(seriesModel, ecModel, api, payload); | 
 |         } | 
 |         else if (__DEV__) { | 
 |             warn('Only cartesian2d and polar supported for bar.'); | 
 |         } | 
 |     } | 
 |  | 
 |     incrementalPrepareRender(seriesModel: BarSeriesModel): void { | 
 |         this._clear(); | 
 |         this._updateDrawMode(seriesModel); | 
 |         // incremental also need to clip, otherwise might be overlow. | 
 |         // But must not set clip in each frame, otherwise all of the children will be marked redraw. | 
 |         this._updateLargeClip(seriesModel); | 
 |     } | 
 |  | 
 |     incrementalRender(params: StageHandlerProgressParams, seriesModel: BarSeriesModel): void { | 
 |         // Reset | 
 |         this._progressiveEls = []; | 
 |         // Do not support progressive in normal mode. | 
 |         this._incrementalRenderLarge(params, seriesModel); | 
 |     } | 
 |  | 
 |     eachRendered(cb: (el: Element) => boolean | void) { | 
 |         traverseElements(this._progressiveEls || this.group, cb); | 
 |     } | 
 |  | 
 |     private _updateDrawMode(seriesModel: BarSeriesModel): void { | 
 |         const isLargeDraw = seriesModel.pipelineContext.large; | 
 |         if (this._isLargeDraw == null || isLargeDraw !== this._isLargeDraw) { | 
 |             this._isLargeDraw = isLargeDraw; | 
 |             this._clear(); | 
 |         } | 
 |     } | 
 |  | 
 |     private _renderNormal( | 
 |         seriesModel: BarSeriesModel, | 
 |         ecModel: GlobalModel, | 
 |         api: ExtensionAPI, | 
 |         payload: Payload | 
 |     ): void { | 
 |         const group = this.group; | 
 |         const data = seriesModel.getData(); | 
 |         const oldData = this._data; | 
 |  | 
 |         const coord = seriesModel.coordinateSystem; | 
 |         const baseAxis = coord.getBaseAxis(); | 
 |         let isHorizontalOrRadial: boolean; | 
 |  | 
 |         if (coord.type === 'cartesian2d') { | 
 |             isHorizontalOrRadial = (baseAxis as Axis2D).isHorizontal(); | 
 |         } | 
 |         else if (coord.type === 'polar') { | 
 |             isHorizontalOrRadial = baseAxis.dim === 'angle'; | 
 |         } | 
 |  | 
 |         const animationModel = seriesModel.isAnimationEnabled() ? seriesModel : null; | 
 |  | 
 |         const realtimeSortCfg = shouldRealtimeSort(seriesModel, coord); | 
 |  | 
 |         if (realtimeSortCfg) { | 
 |             this._enableRealtimeSort(realtimeSortCfg, data, api); | 
 |         } | 
 |  | 
 |         const needsClip = seriesModel.get('clip', true) || realtimeSortCfg; | 
 |         const coordSysClipArea = getClipArea(coord, data); | 
 |         // If there is clipPath created in large mode. Remove it. | 
 |         group.removeClipPath(); | 
 |         // We don't use clipPath in normal mode because we needs a perfect animation | 
 |         // And don't want the label are clipped. | 
 |  | 
 |         const roundCap = seriesModel.get('roundCap', true); | 
 |  | 
 |         const drawBackground = seriesModel.get('showBackground', true); | 
 |         const backgroundModel = seriesModel.getModel('backgroundStyle'); | 
 |         const barBorderRadius = backgroundModel.get('borderRadius') || 0; | 
 |  | 
 |         const bgEls: BarView['_backgroundEls'] = []; | 
 |         const oldBgEls = this._backgroundEls; | 
 |  | 
 |         const isInitSort = payload && payload.isInitSort; | 
 |         const isChangeOrder = payload && payload.type === 'changeAxisOrder'; | 
 |  | 
 |         function createBackground(dataIndex: number) { | 
 |             const bgLayout = getLayout[coord.type](data, dataIndex); | 
 |             const bgEl = createBackgroundEl(coord, isHorizontalOrRadial, bgLayout); | 
 |             bgEl.useStyle(backgroundModel.getItemStyle()); | 
 |             // Only cartesian2d support borderRadius. | 
 |             if (coord.type === 'cartesian2d') { | 
 |                 (bgEl as Rect).setShape('r', barBorderRadius); | 
 |             } | 
 |             bgEls[dataIndex] = bgEl; | 
 |             return bgEl; | 
 |         }; | 
 |         data.diff(oldData) | 
 |             .add(function (dataIndex) { | 
 |                 const itemModel = data.getItemModel<BarDataItemOption>(dataIndex); | 
 |                 const layout = getLayout[coord.type](data, dataIndex, itemModel); | 
 |  | 
 |                 if (drawBackground) { | 
 |                     createBackground(dataIndex); | 
 |                 } | 
 |  | 
 |                 // If dataZoom in filteMode: 'empty', the baseValue can be set as NaN in "axisProxy". | 
 |                 if (!data.hasValue(dataIndex) || !isValidLayout[coord.type](layout)) { | 
 |                     return; | 
 |                 } | 
 |  | 
 |                 let isClipped = false; | 
 |                 if (needsClip) { | 
 |                     // Clip will modify the layout params. | 
 |                     // And return a boolean to determine if the shape are fully clipped. | 
 |                     isClipped = clip[coord.type](coordSysClipArea, layout); | 
 |                 } | 
 |  | 
 |                 const el = elementCreator[coord.type]( | 
 |                     seriesModel, | 
 |                     data, | 
 |                     dataIndex, | 
 |                     layout, | 
 |                     isHorizontalOrRadial, | 
 |                     animationModel, | 
 |                     baseAxis.model, | 
 |                     false, | 
 |                     roundCap | 
 |                 ); | 
 |                 if (realtimeSortCfg) { | 
 |                     /** | 
 |                      * Force label animation because even if the element is | 
 |                      * ignored because it's clipped, it may not be clipped after | 
 |                      * changing order. Then, if not using forceLabelAnimation, | 
 |                      * the label animation was never started, in which case, | 
 |                      * the label will be the final value and doesn't have label | 
 |                      * animation. | 
 |                      */ | 
 |                     (el as ECElement).forceLabelAnimation = true; | 
 |                 } | 
 |  | 
 |                 updateStyle( | 
 |                     el, data, dataIndex, itemModel, layout, | 
 |                     seriesModel, isHorizontalOrRadial, coord.type === 'polar' | 
 |                 ); | 
 |                 if (isInitSort) { | 
 |                     (el as Rect).attr({ shape: layout }); | 
 |                 } | 
 |                 else if (realtimeSortCfg) { | 
 |                     updateRealtimeAnimation( | 
 |                         realtimeSortCfg, | 
 |                         animationModel, | 
 |                         el as Rect, | 
 |                         layout as LayoutRect, | 
 |                         dataIndex, | 
 |                         isHorizontalOrRadial, | 
 |                         false, | 
 |                         false | 
 |                     ); | 
 |                 } | 
 |                 else { | 
 |                     initProps(el, {shape: layout} as any, seriesModel, dataIndex); | 
 |                 } | 
 |  | 
 |                 data.setItemGraphicEl(dataIndex, el); | 
 |  | 
 |                 group.add(el); | 
 |                 el.ignore = isClipped; | 
 |             }) | 
 |             .update(function (newIndex, oldIndex) { | 
 |                 const itemModel = data.getItemModel<BarDataItemOption>(newIndex); | 
 |                 const layout = getLayout[coord.type](data, newIndex, itemModel); | 
 |  | 
 |                 if (drawBackground) { | 
 |                     let bgEl: Rect | Sector; | 
 |                     if (oldBgEls.length === 0) { | 
 |                         bgEl = createBackground(oldIndex); | 
 |                     } | 
 |                     else { | 
 |                         bgEl = oldBgEls[oldIndex]; | 
 |                         bgEl.useStyle(backgroundModel.getItemStyle()); | 
 |                         // Only cartesian2d support borderRadius. | 
 |                         if (coord.type === 'cartesian2d') { | 
 |                             (bgEl as Rect).setShape('r', barBorderRadius); | 
 |                         } | 
 |                         bgEls[newIndex] = bgEl; | 
 |                     } | 
 |                     const bgLayout = getLayout[coord.type](data, newIndex); | 
 |                     const shape = createBackgroundShape(isHorizontalOrRadial, bgLayout, coord); | 
 |                     updateProps(bgEl, { shape }, animationModel, newIndex); | 
 |                 } | 
 |  | 
 |                 let el = oldData.getItemGraphicEl(oldIndex) as BarPossiblePath; | 
 |                 if (!data.hasValue(newIndex) || !isValidLayout[coord.type](layout)) { | 
 |                     group.remove(el); | 
 |                     return; | 
 |                 } | 
 |  | 
 |                 let isClipped = false; | 
 |                 if (needsClip) { | 
 |                     isClipped = clip[coord.type](coordSysClipArea, layout); | 
 |                     if (isClipped) { | 
 |                         group.remove(el); | 
 |                     } | 
 |                 } | 
 |  | 
 |                 if (!el) { | 
 |                     el = elementCreator[coord.type]( | 
 |                         seriesModel, | 
 |                         data, | 
 |                         newIndex, | 
 |                         layout, | 
 |                         isHorizontalOrRadial, | 
 |                         animationModel, | 
 |                         baseAxis.model, | 
 |                         !!el, | 
 |                         roundCap | 
 |                     ); | 
 |                 } | 
 |                 else { | 
 |                     saveOldStyle(el); | 
 |                 } | 
 |  | 
 |                 if (realtimeSortCfg) { | 
 |                     (el as ECElement).forceLabelAnimation = true; | 
 |                 } | 
 |  | 
 |                 if (isChangeOrder) { | 
 |                     const textEl = el.getTextContent(); | 
 |                     if (textEl) { | 
 |                         const labelInnerStore = labelInner(textEl); | 
 |                         if (labelInnerStore.prevValue != null) { | 
 |                             /** | 
 |                              * Set preValue to be value so that no new label | 
 |                              * should be started, otherwise, it will take a full | 
 |                              * `animationDurationUpdate` time to finish the | 
 |                              * animation, which is not expected. | 
 |                              */ | 
 |                             labelInnerStore.prevValue = labelInnerStore.value; | 
 |                         } | 
 |                     } | 
 |                 } | 
 |                 // Not change anything if only order changed. | 
 |                 // Especially not change label. | 
 |                 else { | 
 |                     updateStyle( | 
 |                         el, data, newIndex, itemModel, layout, | 
 |                         seriesModel, isHorizontalOrRadial, coord.type === 'polar' | 
 |                     ); | 
 |                 } | 
 |  | 
 |                 if (isInitSort) { | 
 |                     (el as Rect).attr({ shape: layout }); | 
 |                 } | 
 |                 else if (realtimeSortCfg) { | 
 |                     updateRealtimeAnimation( | 
 |                         realtimeSortCfg, | 
 |                         animationModel, | 
 |                         el as Rect, | 
 |                         layout as LayoutRect, | 
 |                         newIndex, | 
 |                         isHorizontalOrRadial, | 
 |                         true, | 
 |                         isChangeOrder | 
 |                     ); | 
 |                 } | 
 |                 else { | 
 |                     updateProps(el, { | 
 |                         shape: layout | 
 |                     } as any, seriesModel, newIndex, null); | 
 |                 } | 
 |  | 
 |                 data.setItemGraphicEl(newIndex, el); | 
 |                 el.ignore = isClipped; | 
 |                 group.add(el); | 
 |             }) | 
 |             .remove(function (dataIndex) { | 
 |                 const el = oldData.getItemGraphicEl(dataIndex) as Path; | 
 |                 el && removeElementWithFadeOut(el, seriesModel, dataIndex); | 
 |             }) | 
 |             .execute(); | 
 |  | 
 |         const bgGroup = this._backgroundGroup || (this._backgroundGroup = new Group()); | 
 |         bgGroup.removeAll(); | 
 |  | 
 |         for (let i = 0; i < bgEls.length; ++i) { | 
 |             bgGroup.add(bgEls[i]); | 
 |         } | 
 |         group.add(bgGroup); | 
 |         this._backgroundEls = bgEls; | 
 |  | 
 |         this._data = data; | 
 |     } | 
 |  | 
 |     private _renderLarge(seriesModel: BarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI): void { | 
 |         this._clear(); | 
 |         createLarge(seriesModel, this.group); | 
 |         this._updateLargeClip(seriesModel); | 
 |     } | 
 |  | 
 |     private _incrementalRenderLarge(params: StageHandlerProgressParams, seriesModel: BarSeriesModel): void { | 
 |         this._removeBackground(); | 
 |         createLarge(seriesModel, this.group, this._progressiveEls, true); | 
 |     } | 
 |  | 
 |     private _updateLargeClip(seriesModel: BarSeriesModel): void { | 
 |         // Use clipPath in large mode. | 
 |         const clipPath = seriesModel.get('clip', true) | 
 |             && createClipPath(seriesModel.coordinateSystem, false, seriesModel); | 
 |         const group = this.group; | 
 |         if (clipPath) { | 
 |             group.setClipPath(clipPath); | 
 |         } | 
 |         else { | 
 |             group.removeClipPath(); | 
 |         } | 
 |     } | 
 |  | 
 |     private _enableRealtimeSort( | 
 |         realtimeSortCfg: RealtimeSortConfig, | 
 |         data: ReturnType<BarSeriesModel['getData']>, | 
 |         api: ExtensionAPI | 
 |     ): void { | 
 |         // If no data in the first frame, wait for data to initSort | 
 |         if (!data.count()) { | 
 |             return; | 
 |         } | 
 |  | 
 |         const baseAxis = realtimeSortCfg.baseAxis; | 
 |  | 
 |         if (this._isFirstFrame) { | 
 |             this._dispatchInitSort(data, realtimeSortCfg, api); | 
 |             this._isFirstFrame = false; | 
 |         } | 
 |         else { | 
 |             const orderMapping = (idx: number) => { | 
 |                 const el = (data.getItemGraphicEl(idx) as Rect); | 
 |                 const shape = el && el.shape; | 
 |                 return (shape && ( | 
 |                     // The result should be consistent with the initial sort by data value. | 
 |                     // Do not support the case that both positive and negative exist. | 
 |                     Math.abs( | 
 |                         baseAxis.isHorizontal() | 
 |                             ? shape.height | 
 |                             : shape.width | 
 |                     ) | 
 |                 // If data is NaN, shape.xxx may be NaN, so use || 0 here in case | 
 |                 )) || 0; | 
 |             }; | 
 |             this._onRendered = () => { | 
 |                 this._updateSortWithinSameData(data, orderMapping, baseAxis, api); | 
 |             }; | 
 |             api.getZr().on('rendered', this._onRendered); | 
 |         } | 
 |     } | 
 |  | 
 |     private _dataSort( | 
 |         data: SeriesData<BarSeriesModel, DefaultDataVisual>, | 
 |         baseAxis: Axis2D, | 
 |         orderMapping: OrderMapping | 
 |     ): OrdinalSortInfo { | 
 |         type SortValueInfo = { | 
 |             dataIndex: number, | 
 |             mappedValue: number, | 
 |             ordinalNumber: OrdinalNumber | 
 |         }; | 
 |         const info: SortValueInfo[] = []; | 
 |         data.each(data.mapDimension(baseAxis.dim), (ordinalNumber: OrdinalNumber, dataIdx: number) => { | 
 |             let mappedValue = orderMapping(dataIdx); | 
 |             mappedValue = mappedValue == null ? NaN : mappedValue; | 
 |             info.push({ | 
 |                 dataIndex: dataIdx, | 
 |                 mappedValue, | 
 |                 ordinalNumber | 
 |             }); | 
 |         }); | 
 |  | 
 |         info.sort((a, b) => { | 
 |             // If NaN, it will be treated as min val. | 
 |             return b.mappedValue - a.mappedValue; | 
 |         }); | 
 |  | 
 |         return { | 
 |             ordinalNumbers: map(info, item => item.ordinalNumber) | 
 |         }; | 
 |     } | 
 |  | 
 |     private _isOrderChangedWithinSameData( | 
 |         data: SeriesData<BarSeriesModel, DefaultDataVisual>, | 
 |         orderMapping: OrderMapping, | 
 |         baseAxis: Axis2D | 
 |     ): boolean { | 
 |         const scale = baseAxis.scale as OrdinalScale; | 
 |         const ordinalDataDim = data.mapDimension(baseAxis.dim); | 
 |  | 
 |         let lastValue = Number.MAX_VALUE; | 
 |         for (let tickNum = 0, len = scale.getOrdinalMeta().categories.length; tickNum < len; ++tickNum) { | 
 |             const rawIdx = data.rawIndexOf(ordinalDataDim, scale.getRawOrdinalNumber(tickNum)); | 
 |             const value = rawIdx < 0 | 
 |                 // If some tick have no bar, the tick will be treated as min. | 
 |                 ? Number.MIN_VALUE | 
 |                 // PENDING: if dataZoom on baseAxis exits, is it a performance issue? | 
 |                 : orderMapping(data.indexOfRawIndex(rawIdx)); | 
 |             if (value > lastValue) { | 
 |                 return true; | 
 |             } | 
 |             lastValue = value; | 
 |         } | 
 |         return false; | 
 |     } | 
 |  | 
 |     /* | 
 |      * Consider the case when A and B changed order, whose representing | 
 |      * bars are both out of sight, we don't wish to trigger reorder action | 
 |      * as long as the order in the view doesn't change. | 
 |      */ | 
 |     private _isOrderDifferentInView( | 
 |         orderInfo: OrdinalSortInfo, | 
 |         baseAxis: Axis2D | 
 |     ): boolean { | 
 |         const scale = baseAxis.scale as OrdinalScale; | 
 |         const extent = scale.getExtent(); | 
 |  | 
 |         let tickNum = Math.max(0, extent[0]); | 
 |         const tickMax = Math.min(extent[1], scale.getOrdinalMeta().categories.length - 1); | 
 |         for (;tickNum <= tickMax; ++tickNum) { | 
 |             if (orderInfo.ordinalNumbers[tickNum] !== scale.getRawOrdinalNumber(tickNum)) { | 
 |                 return true; | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     private _updateSortWithinSameData( | 
 |         data: SeriesData<BarSeriesModel, DefaultDataVisual>, | 
 |         orderMapping: OrderMapping, | 
 |         baseAxis: Axis2D, | 
 |         api: ExtensionAPI | 
 |     ) { | 
 |         if (!this._isOrderChangedWithinSameData(data, orderMapping, baseAxis)) { | 
 |             return; | 
 |         } | 
 |  | 
 |         const sortInfo = this._dataSort(data, baseAxis, orderMapping); | 
 |  | 
 |         if (this._isOrderDifferentInView(sortInfo, baseAxis)) { | 
 |             this._removeOnRenderedListener(api); | 
 |             api.dispatchAction({ | 
 |                 type: 'changeAxisOrder', | 
 |                 componentType: baseAxis.dim + 'Axis', | 
 |                 axisId: baseAxis.index, | 
 |                 sortInfo: sortInfo | 
 |             }); | 
 |         } | 
 |     } | 
 |  | 
 |     private _dispatchInitSort( | 
 |         data: SeriesData<BarSeriesModel, DefaultDataVisual>, | 
 |         realtimeSortCfg: RealtimeSortConfig, | 
 |         api: ExtensionAPI | 
 |     ) { | 
 |         const baseAxis = realtimeSortCfg.baseAxis; | 
 |         const sortResult = this._dataSort( | 
 |             data, | 
 |             baseAxis, | 
 |             dataIdx => data.get( | 
 |                 data.mapDimension(realtimeSortCfg.otherAxis.dim), | 
 |                 dataIdx | 
 |             ) as number | 
 |         ); | 
 |         api.dispatchAction({ | 
 |             type: 'changeAxisOrder', | 
 |             componentType: baseAxis.dim + 'Axis', | 
 |             isInitSort: true, | 
 |             axisId: baseAxis.index, | 
 |             sortInfo: sortResult | 
 |         }); | 
 |     } | 
 |  | 
 |     remove(ecModel: GlobalModel, api: ExtensionAPI) { | 
 |         this._clear(this._model); | 
 |         this._removeOnRenderedListener(api); | 
 |     } | 
 |  | 
 |     dispose(ecModel: GlobalModel, api: ExtensionAPI) { | 
 |         this._removeOnRenderedListener(api); | 
 |     } | 
 |  | 
 |     private _removeOnRenderedListener(api: ExtensionAPI) { | 
 |         if (this._onRendered) { | 
 |             api.getZr().off('rendered', this._onRendered); | 
 |             this._onRendered = null; | 
 |         } | 
 |     } | 
 |  | 
 |     private _clear(model?: SeriesModel): void { | 
 |         const group = this.group; | 
 |         const data = this._data; | 
 |         if (model && model.isAnimationEnabled() && data && !this._isLargeDraw) { | 
 |             this._removeBackground(); | 
 |             this._backgroundEls = []; | 
 |  | 
 |             data.eachItemGraphicEl(function (el: Path) { | 
 |                 removeElementWithFadeOut(el, model, getECData(el).dataIndex); | 
 |             }); | 
 |         } | 
 |         else { | 
 |             group.removeAll(); | 
 |         } | 
 |         this._data = null; | 
 |         this._isFirstFrame = true; | 
 |     } | 
 |  | 
 |     private _removeBackground(): void { | 
 |         this.group.remove(this._backgroundGroup); | 
 |         this._backgroundGroup = null; | 
 |     } | 
 | } | 
 |  | 
 | interface Clipper { | 
 |     (coordSysBoundingRect: PolarCoordArea | CartesianCoordArea, layout: RectLayout | SectorLayout): boolean | 
 | } | 
 | const clip: { | 
 |     [key in 'cartesian2d' | 'polar']: Clipper | 
 | } = { | 
 |     cartesian2d(coordSysBoundingRect: CartesianCoordArea, layout: Rect['shape']) { | 
 |         const signWidth = layout.width < 0 ? -1 : 1; | 
 |         const signHeight = layout.height < 0 ? -1 : 1; | 
 |         // Needs positive width and height | 
 |         if (signWidth < 0) { | 
 |             layout.x += layout.width; | 
 |             layout.width = -layout.width; | 
 |         } | 
 |         if (signHeight < 0) { | 
 |             layout.y += layout.height; | 
 |             layout.height = -layout.height; | 
 |         } | 
 |  | 
 |         const coordSysX2 = coordSysBoundingRect.x + coordSysBoundingRect.width; | 
 |         const coordSysY2 = coordSysBoundingRect.y + coordSysBoundingRect.height; | 
 |         const x = mathMax(layout.x, coordSysBoundingRect.x); | 
 |         const x2 = mathMin(layout.x + layout.width, coordSysX2); | 
 |         const y = mathMax(layout.y, coordSysBoundingRect.y); | 
 |         const y2 = mathMin(layout.y + layout.height, coordSysY2); | 
 |  | 
 |         const xClipped = x2 < x; | 
 |         const yClipped = y2 < y; | 
 |  | 
 |         // When xClipped or yClipped, the element will be marked as `ignore`. | 
 |         // But we should also place the element at the edge of the coord sys bounding rect. | 
 |         // Because if data changed and the bar shows again, its transition animation | 
 |         // will begin at this place. | 
 |         layout.x = (xClipped && x > coordSysX2) ? x2 : x; | 
 |         layout.y = (yClipped && y > coordSysY2) ? y2 : y; | 
 |         layout.width = xClipped ? 0 : x2 - x; | 
 |         layout.height = yClipped ? 0 : y2 - y; | 
 |  | 
 |         // Reverse back | 
 |         if (signWidth < 0) { | 
 |             layout.x += layout.width; | 
 |             layout.width = -layout.width; | 
 |         } | 
 |         if (signHeight < 0) { | 
 |             layout.y += layout.height; | 
 |             layout.height = -layout.height; | 
 |         } | 
 |  | 
 |         return xClipped || yClipped; | 
 |     }, | 
 |  | 
 |     polar(coordSysClipArea: PolarCoordArea, layout: Sector['shape']) { | 
 |         const signR = layout.r0 <= layout.r ? 1 : -1; | 
 |         // Make sure r is larger than r0 | 
 |         if (signR < 0) { | 
 |             const tmp = layout.r; | 
 |             layout.r = layout.r0; | 
 |             layout.r0 = tmp; | 
 |         } | 
 |  | 
 |         const r = mathMin(layout.r, coordSysClipArea.r); | 
 |         const r0 = mathMax(layout.r0, coordSysClipArea.r0); | 
 |  | 
 |         layout.r = r; | 
 |         layout.r0 = r0; | 
 |  | 
 |         const clipped = r - r0 < 0; | 
 |  | 
 |         // Reverse back | 
 |         if (signR < 0) { | 
 |             const tmp = layout.r; | 
 |             layout.r = layout.r0; | 
 |             layout.r0 = tmp; | 
 |         } | 
 |  | 
 |         return clipped; | 
 |     } | 
 | }; | 
 |  | 
 | interface ElementCreator { | 
 |     ( | 
 |         seriesModel: BarSeriesModel, data: SeriesData, newIndex: number, | 
 |         layout: RectLayout | SectorLayout, isHorizontalOrRadial: boolean, | 
 |         animationModel: BarSeriesModel, | 
 |         axisModel: CartesianAxisModel | AngleAxisModel | RadiusAxisModel, | 
 |         isUpdate: boolean, | 
 |         roundCap?: boolean | 
 |     ): BarPossiblePath | 
 | } | 
 |  | 
 | const elementCreator: { | 
 |     [key in 'polar' | 'cartesian2d']: ElementCreator | 
 | } = { | 
 |  | 
 |     cartesian2d( | 
 |         seriesModel, data, newIndex, layout: RectLayout, isHorizontal, | 
 |         animationModel, axisModel, isUpdate, roundCap | 
 |     ) { | 
 |         const rect = new Rect({ | 
 |             shape: extend({}, layout), | 
 |             z2: 1 | 
 |         }); | 
 |         (rect as any).__dataIndex = newIndex; | 
 |  | 
 |         rect.name = 'item'; | 
 |  | 
 |         if (animationModel) { | 
 |             const rectShape = rect.shape; | 
 |             const animateProperty = isHorizontal ? 'height' : 'width' as 'width' | 'height'; | 
 |             rectShape[animateProperty] = 0; | 
 |         } | 
 |         return rect; | 
 |     }, | 
 |  | 
 |     polar( | 
 |         seriesModel, data, newIndex, layout: SectorLayout, isRadial: boolean, | 
 |         animationModel, axisModel, isUpdate, roundCap | 
 |     ) { | 
 |         const ShapeClass = (!isRadial && roundCap) ? Sausage : Sector; | 
 |         const sector = new ShapeClass({ | 
 |             shape: layout, | 
 |             z2: 1 | 
 |         }); | 
 |  | 
 |         sector.name = 'item'; | 
 |  | 
 |         const positionMap = createPolarPositionMapping(isRadial); | 
 |         sector.calculateTextPosition = createSectorCalculateTextPosition(positionMap, { | 
 |             isRoundCap: ShapeClass === Sausage | 
 |         }); | 
 |  | 
 |         // Animation | 
 |         if (animationModel) { | 
 |             const sectorShape = sector.shape; | 
 |             const animateProperty = isRadial ? 'r' : 'endAngle' as 'r' | 'endAngle'; | 
 |             const animateTarget = {} as SectorShape; | 
 |             sectorShape[animateProperty] = isRadial ? 0 : layout.startAngle; | 
 |             animateTarget[animateProperty] = layout[animateProperty]; | 
 |             (isUpdate ? updateProps : initProps)(sector, { | 
 |                 shape: animateTarget | 
 |                 // __value: typeof dataValue === 'string' ? parseInt(dataValue, 10) : dataValue | 
 |             }, animationModel); | 
 |         } | 
 |  | 
 |         return sector; | 
 |     } | 
 | }; | 
 |  | 
 | function shouldRealtimeSort( | 
 |     seriesModel: BarSeriesModel, | 
 |     coordSys: Cartesian2D | Polar | 
 | ): RealtimeSortConfig { | 
 |     const realtimeSortOption = seriesModel.get('realtimeSort', true); | 
 |     const baseAxis = coordSys.getBaseAxis() as Axis2D; | 
 |     if (__DEV__) { | 
 |         if (realtimeSortOption) { | 
 |             if (baseAxis.type !== 'category') { | 
 |                 warn('`realtimeSort` will not work because this bar series is not based on a category axis.'); | 
 |             } | 
 |             if (coordSys.type !== 'cartesian2d') { | 
 |                 warn('`realtimeSort` will not work because this bar series is not on cartesian2d.'); | 
 |             } | 
 |         } | 
 |     } | 
 |     if (realtimeSortOption && baseAxis.type === 'category' && coordSys.type === 'cartesian2d') { | 
 |         return { | 
 |             baseAxis: baseAxis as Axis2D, | 
 |             otherAxis: coordSys.getOtherAxis(baseAxis) | 
 |         }; | 
 |     } | 
 | } | 
 |  | 
 | function updateRealtimeAnimation( | 
 |     realtimeSortCfg: RealtimeSortConfig, | 
 |     seriesAnimationModel: BarSeriesModel, | 
 |     el: Rect, | 
 |     layout: LayoutRect, | 
 |     newIndex: number, | 
 |     isHorizontal: boolean, | 
 |     isUpdate: boolean, | 
 |     isChangeOrder: boolean | 
 | ) { | 
 |     let seriesTarget; | 
 |     let axisTarget; | 
 |     if (isHorizontal) { | 
 |         axisTarget = { | 
 |             x: layout.x, | 
 |             width: layout.width | 
 |         }; | 
 |         seriesTarget = { | 
 |             y: layout.y, | 
 |             height: layout.height | 
 |         }; | 
 |     } | 
 |     else { | 
 |         axisTarget = { | 
 |             y: layout.y, | 
 |             height: layout.height | 
 |         }; | 
 |         seriesTarget = { | 
 |             x: layout.x, | 
 |             width: layout.width | 
 |         }; | 
 |     } | 
 |  | 
 |     if (!isChangeOrder) { | 
 |         // Keep the original growth animation if only axis order changed. | 
 |         // Not start a new animation. | 
 |         (isUpdate ? updateProps : initProps)(el, { | 
 |             shape: seriesTarget | 
 |         }, seriesAnimationModel, newIndex, null); | 
 |     } | 
 |  | 
 |     const axisAnimationModel = seriesAnimationModel ? realtimeSortCfg.baseAxis.model : null; | 
 |     (isUpdate ? updateProps : initProps)(el, { | 
 |         shape: axisTarget | 
 |     }, axisAnimationModel, newIndex); | 
 | } | 
 |  | 
 | function checkPropertiesNotValid<T extends Record<string, any>>(obj: T, props: readonly (keyof T)[]) { | 
 |     for (let i = 0; i < props.length; i++) { | 
 |         if (!isFinite(obj[props[i]])) { | 
 |             return true; | 
 |         } | 
 |     } | 
 |     return false; | 
 | } | 
 |  | 
 |  | 
 | const rectPropties = ['x', 'y', 'width', 'height'] as const; | 
 | const polarPropties = ['cx', 'cy', 'r', 'startAngle', 'endAngle'] as const; | 
 | const isValidLayout: Record<'cartesian2d' | 'polar', (layout: RectLayout | SectorLayout) => boolean> = { | 
 |     cartesian2d(layout: RectLayout) { | 
 |         return !checkPropertiesNotValid(layout, rectPropties); | 
 |     }, | 
 |  | 
 |     polar(layout: SectorLayout) { | 
 |         return !checkPropertiesNotValid(layout, polarPropties); | 
 |     } | 
 | } as const; | 
 |  | 
 | interface GetLayout { | 
 |     (data: SeriesData, dataIndex: number, itemModel?: Model<BarDataItemOption>): RectLayout | SectorLayout | 
 | } | 
 | const getLayout: { | 
 |     [key in 'cartesian2d' | 'polar']: GetLayout | 
 | } = { | 
 |     // itemModel is only used to get borderWidth, which is not needed | 
 |     // when calculating bar background layout. | 
 |     cartesian2d(data, dataIndex, itemModel?): RectLayout { | 
 |         const layout = data.getItemLayout(dataIndex) as RectLayout; | 
 |         const fixedLineWidth = itemModel ? getLineWidth(itemModel, layout) : 0; | 
 |  | 
 |         // fix layout with lineWidth | 
 |         const signX = layout.width > 0 ? 1 : -1; | 
 |         const signY = layout.height > 0 ? 1 : -1; | 
 |         return { | 
 |             x: layout.x + signX * fixedLineWidth / 2, | 
 |             y: layout.y + signY * fixedLineWidth / 2, | 
 |             width: layout.width - signX * fixedLineWidth, | 
 |             height: layout.height - signY * fixedLineWidth | 
 |         }; | 
 |     }, | 
 |  | 
 |     polar(data, dataIndex, itemModel?): SectorLayout { | 
 |         const layout = data.getItemLayout(dataIndex); | 
 |         return { | 
 |             cx: layout.cx, | 
 |             cy: layout.cy, | 
 |             r0: layout.r0, | 
 |             r: layout.r, | 
 |             startAngle: layout.startAngle, | 
 |             endAngle: layout.endAngle, | 
 |             clockwise: layout.clockwise | 
 |         } as SectorLayout; | 
 |     } | 
 | }; | 
 |  | 
 | function isZeroOnPolar(layout: SectorLayout) { | 
 |     return layout.startAngle != null | 
 |         && layout.endAngle != null | 
 |         && layout.startAngle === layout.endAngle; | 
 | } | 
 |  | 
 | function createPolarPositionMapping(isRadial: boolean) | 
 |     : (position: PolarBarLabelPosition) => SectorTextPosition { | 
 |     return ((isRadial: boolean) => { | 
 |         const arcOrAngle = isRadial ? 'Arc' : 'Angle'; | 
 |         return (position: PolarBarLabelPosition) => { | 
 |             switch (position) { | 
 |                 case 'start': | 
 |                 case 'insideStart': | 
 |                 case 'end': | 
 |                 case 'insideEnd': | 
 |                     return position + arcOrAngle as SectorTextPosition; | 
 |                 default: | 
 |                     return position; | 
 |             } | 
 |         }; | 
 |     })(isRadial); | 
 | } | 
 |  | 
 | function updateStyle( | 
 |     el: BarPossiblePath, | 
 |     data: SeriesData, dataIndex: number, | 
 |     itemModel: Model<BarDataItemOption>, | 
 |     layout: RectLayout | SectorLayout, | 
 |     seriesModel: BarSeriesModel, | 
 |     isHorizontalOrRadial: boolean, | 
 |     isPolar: boolean | 
 | ) { | 
 |     const style = data.getItemVisual(dataIndex, 'style'); | 
 |  | 
 |     if (!isPolar) { | 
 |         (el as Rect).setShape('r', itemModel.get(['itemStyle', 'borderRadius']) || 0); | 
 |     } | 
 |  | 
 |     el.useStyle(style); | 
 |  | 
 |     const cursorStyle = itemModel.getShallow('cursor'); | 
 |     cursorStyle && (el as Path).attr('cursor', cursorStyle); | 
 |  | 
 |     const labelPositionOutside = isPolar | 
 |         ? (isHorizontalOrRadial | 
 |             ? ((layout as SectorLayout).r >= (layout as SectorLayout).r0 ? 'endArc' : 'startArc') | 
 |             : ((layout as SectorLayout).endAngle >= (layout as SectorLayout).startAngle | 
 |                 ? 'endAngle' | 
 |                 : 'startAngle' | 
 |             ) | 
 |         ) | 
 |         : (isHorizontalOrRadial | 
 |             ? ((layout as RectLayout).height >= 0 ? 'bottom' : 'top') | 
 |             : ((layout as RectLayout).width >= 0 ? 'right' : 'left')); | 
 |  | 
 |     const labelStatesModels = getLabelStatesModels(itemModel); | 
 |  | 
 |     setLabelStyle( | 
 |         el, labelStatesModels, | 
 |         { | 
 |             labelFetcher: seriesModel, | 
 |             labelDataIndex: dataIndex, | 
 |             defaultText: getDefaultLabel(seriesModel.getData(), dataIndex), | 
 |             inheritColor: style.fill as ColorString, | 
 |             defaultOpacity: style.opacity, | 
 |             defaultOutsidePosition: labelPositionOutside as BuiltinTextPosition | 
 |         } | 
 |     ); | 
 |  | 
 |     const label = el.getTextContent(); | 
 |     if (isPolar && label) { | 
 |         const position = itemModel.get(['label', 'position']); | 
 |         el.textConfig.inside = position === 'middle' ? true : null; | 
 |         setSectorTextRotation( | 
 |             el as Sector, | 
 |             position === 'outside' ? labelPositionOutside : position, | 
 |             createPolarPositionMapping(isHorizontalOrRadial), | 
 |             itemModel.get(['label', 'rotate']) | 
 |         ); | 
 |     } | 
 |  | 
 |     setLabelValueAnimation( | 
 |         label, | 
 |         labelStatesModels, | 
 |         seriesModel.getRawValue(dataIndex) as ParsedValue, | 
 |         (value: number) => getDefaultInterpolatedLabel(data, value) | 
 |     ); | 
 |  | 
 |     const emphasisModel = itemModel.getModel(['emphasis']); | 
 |     toggleHoverEmphasis(el, emphasisModel.get('focus'), emphasisModel.get('blurScope'), emphasisModel.get('disabled')); | 
 |     setStatesStylesFromModel(el, itemModel); | 
 |  | 
 |     if (isZeroOnPolar(layout as SectorLayout)) { | 
 |         el.style.fill = 'none'; | 
 |         el.style.stroke = 'none'; | 
 |         each(el.states, (state) => { | 
 |             if (state.style) { | 
 |                 state.style.fill = state.style.stroke = 'none'; | 
 |             } | 
 |         }); | 
 |     } | 
 | } | 
 |  | 
 | // In case width or height are too small. | 
 | function getLineWidth( | 
 |     itemModel: Model<BarDataItemOption>, | 
 |     rawLayout: RectLayout | 
 | ) { | 
 |     // Has no border. | 
 |     const borderColor = itemModel.get(['itemStyle', 'borderColor']); | 
 |     if (!borderColor || borderColor === 'none') { | 
 |         return 0; | 
 |     } | 
 |     const lineWidth = itemModel.get(['itemStyle', 'borderWidth']) || 0; | 
 |     // width or height may be NaN for empty data | 
 |     const width = isNaN(rawLayout.width) ? Number.MAX_VALUE : Math.abs(rawLayout.width); | 
 |     const height = isNaN(rawLayout.height) ? Number.MAX_VALUE : Math.abs(rawLayout.height); | 
 |     return Math.min(lineWidth, width, height); | 
 | } | 
 |  | 
 | class LagePathShape { | 
 |     points: ArrayLike<number>; | 
 | } | 
 | interface LargePathProps extends PathProps { | 
 |     shape?: LagePathShape | 
 | } | 
 | class LargePath extends Path<LargePathProps> { | 
 |     type = 'largeBar'; | 
 |  | 
 |     shape: LagePathShape; | 
 |  | 
 |     baseDimIdx: number; | 
 |     largeDataIndices: ArrayLike<number>; | 
 |     barWidth: number; | 
 |  | 
 |     constructor(opts?: LargePathProps) { | 
 |         super(opts); | 
 |     } | 
 |  | 
 |     getDefaultShape() { | 
 |         return new LagePathShape(); | 
 |     } | 
 |  | 
 |     buildPath(ctx: CanvasRenderingContext2D, shape: LagePathShape) { | 
 |         // Drawing lines is more efficient than drawing | 
 |         // a whole line or drawing rects. | 
 |         const points = shape.points; | 
 |         const baseDimIdx = this.baseDimIdx; | 
 |         const valueDimIdx = 1 - this.baseDimIdx; | 
 |         const startPoint = []; | 
 |         const size = []; | 
 |         const barWidth = this.barWidth; | 
 |  | 
 |         for (let i = 0; i < points.length; i += 3) { | 
 |             size[baseDimIdx] = barWidth; | 
 |             size[valueDimIdx] = points[i + 2]; | 
 |             startPoint[baseDimIdx] = points[i + baseDimIdx]; | 
 |             startPoint[valueDimIdx] = points[i + valueDimIdx]; | 
 |             ctx.rect(startPoint[0], startPoint[1], size[0], size[1]); | 
 |         } | 
 |     } | 
 | } | 
 |  | 
 | function createLarge( | 
 |     seriesModel: BarSeriesModel, | 
 |     group: Group, | 
 |     progressiveEls?: Element[], | 
 |     incremental?: boolean | 
 | ) { | 
 |     // TODO support polar | 
 |     const data = seriesModel.getData(); | 
 |     const baseDimIdx = data.getLayout('valueAxisHorizontal') ? 1 : 0; | 
 |  | 
 |     const largeDataIndices = data.getLayout('largeDataIndices'); | 
 |     const barWidth = data.getLayout('size'); | 
 |  | 
 |     const backgroundModel = seriesModel.getModel('backgroundStyle'); | 
 |     const bgPoints = data.getLayout('largeBackgroundPoints'); | 
 |  | 
 |     if (bgPoints) { | 
 |         const bgEl = new LargePath({ | 
 |             shape: { | 
 |                 points: bgPoints | 
 |             }, | 
 |             incremental: !!incremental, | 
 |             silent: true, | 
 |             z2: 0 | 
 |         }); | 
 |         bgEl.baseDimIdx = baseDimIdx; | 
 |         bgEl.largeDataIndices = largeDataIndices; | 
 |         bgEl.barWidth = barWidth; | 
 |         bgEl.useStyle(backgroundModel.getItemStyle()); | 
 |         group.add(bgEl); | 
 |  | 
 |         progressiveEls && progressiveEls.push(bgEl); | 
 |     } | 
 |  | 
 |     const el = new LargePath({ | 
 |         shape: {points: data.getLayout('largePoints')}, | 
 |         incremental: !!incremental, | 
 |         ignoreCoarsePointer: true, | 
 |         z2: 1 | 
 |     }); | 
 |     el.baseDimIdx = baseDimIdx; | 
 |     el.largeDataIndices = largeDataIndices; | 
 |     el.barWidth = barWidth; | 
 |     group.add(el); | 
 |     el.useStyle(data.getVisual('style')); | 
 |  | 
 |     // Enable tooltip and user mouse/touch event handlers. | 
 |     getECData(el).seriesIndex = seriesModel.seriesIndex; | 
 |  | 
 |     if (!seriesModel.get('silent')) { | 
 |         el.on('mousedown', largePathUpdateDataIndex); | 
 |         el.on('mousemove', largePathUpdateDataIndex); | 
 |     } | 
 |     progressiveEls && progressiveEls.push(el); | 
 | } | 
 |  | 
 | // Use throttle to avoid frequently traverse to find dataIndex. | 
 | const largePathUpdateDataIndex = throttle(function (this: LargePath, event: ZRElementEvent) { | 
 |     const largePath = this; | 
 |     const dataIndex = largePathFindDataIndex(largePath, event.offsetX, event.offsetY); | 
 |     getECData(largePath).dataIndex = dataIndex >= 0 ? dataIndex : null; | 
 | }, 30, false); | 
 |  | 
 | function largePathFindDataIndex(largePath: LargePath, x: number, y: number) { | 
 |     const baseDimIdx = largePath.baseDimIdx; | 
 |     const valueDimIdx = 1 - baseDimIdx; | 
 |     const points = largePath.shape.points; | 
 |     const largeDataIndices = largePath.largeDataIndices; | 
 |     const startPoint = []; | 
 |     const size = []; | 
 |     const barWidth = largePath.barWidth; | 
 |  | 
 |     for (let i = 0, len = points.length / 3; i < len; i++) { | 
 |         const ii = i * 3; | 
 |         size[baseDimIdx] = barWidth; | 
 |         size[valueDimIdx] = points[ii + 2]; | 
 |         startPoint[baseDimIdx] = points[ii + baseDimIdx]; | 
 |         startPoint[valueDimIdx] = points[ii + valueDimIdx]; | 
 |         if (size[valueDimIdx] < 0) { | 
 |             startPoint[valueDimIdx] += size[valueDimIdx]; | 
 |             size[valueDimIdx] = -size[valueDimIdx]; | 
 |         } | 
 |  | 
 |         if (x >= startPoint[0] && x <= startPoint[0] + size[0] | 
 |             && y >= startPoint[1] && y <= startPoint[1] + size[1] | 
 |         ) { | 
 |             return largeDataIndices[i]; | 
 |         } | 
 |     } | 
 |  | 
 |     return -1; | 
 | } | 
 |  | 
 | function createBackgroundShape( | 
 |     isHorizontalOrRadial: boolean, | 
 |     layout: SectorLayout | RectLayout, | 
 |     coord: CoordSysOfBar | 
 | ): SectorShape | RectShape { | 
 |     if (isCoordinateSystemType<Cartesian2D>(coord, 'cartesian2d')) { | 
 |         const rectShape = layout as RectShape; | 
 |         const coordLayout = coord.getArea(); | 
 |         return { | 
 |             x: isHorizontalOrRadial ? rectShape.x : coordLayout.x, | 
 |             y: isHorizontalOrRadial ? coordLayout.y : rectShape.y, | 
 |             width: isHorizontalOrRadial ? rectShape.width : coordLayout.width, | 
 |             height: isHorizontalOrRadial ? coordLayout.height : rectShape.height | 
 |         } as RectShape; | 
 |     } | 
 |     else { | 
 |         const coordLayout = coord.getArea(); | 
 |         const sectorShape = layout as SectorShape; | 
 |         return { | 
 |             cx: coordLayout.cx, | 
 |             cy: coordLayout.cy, | 
 |             r0: isHorizontalOrRadial ? coordLayout.r0 : sectorShape.r0, | 
 |             r: isHorizontalOrRadial ? coordLayout.r : sectorShape.r, | 
 |             startAngle: isHorizontalOrRadial ? sectorShape.startAngle : 0, | 
 |             endAngle: isHorizontalOrRadial ? sectorShape.endAngle : Math.PI * 2 | 
 |         } as SectorShape; | 
 |     } | 
 | } | 
 |  | 
 | function createBackgroundEl( | 
 |     coord: CoordSysOfBar, | 
 |     isHorizontalOrRadial: boolean, | 
 |     layout: SectorLayout | RectLayout | 
 | ): Rect | Sector { | 
 |     const ElementClz = coord.type === 'polar' ? Sector : Rect; | 
 |     return new ElementClz({ | 
 |         shape: createBackgroundShape(isHorizontalOrRadial, layout, coord) as any, | 
 |         silent: true, | 
 |         z2: 0 | 
 |     }); | 
 | } | 
 |  | 
 | export default BarView; |