| /* | 
 | * 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 { bind, each, clone, trim, isString, isFunction, isArray, isObject, extend } from 'zrender/src/core/util'; | 
 | import env from 'zrender/src/core/env'; | 
 | import TooltipHTMLContent from './TooltipHTMLContent'; | 
 | import TooltipRichContent from './TooltipRichContent'; | 
 | import { convertToColorString, formatTpl, TooltipMarker } from '../../util/format'; | 
 | import { parsePercent } from '../../util/number'; | 
 | import { Rect } from '../../util/graphic'; | 
 | import findPointFromSeries from '../axisPointer/findPointFromSeries'; | 
 | import { getLayoutRect } from '../../util/layout'; | 
 | import Model from '../../model/Model'; | 
 | import * as globalListener from '../axisPointer/globalListener'; | 
 | import * as axisHelper from '../../coord/axisHelper'; | 
 | import * as axisPointerViewHelper from '../axisPointer/viewHelper'; | 
 | import { getTooltipRenderMode, preParseFinder, queryReferringComponents } from '../../util/model'; | 
 | import ComponentView from '../../view/Component'; | 
 | import { format as timeFormat } from '../../util/time'; | 
 | import { | 
 |     HorizontalAlign, | 
 |     VerticalAlign, | 
 |     ZRRectLike, | 
 |     BoxLayoutOptionMixin, | 
 |     CallbackDataParams, | 
 |     TooltipRenderMode, | 
 |     ECElement, | 
 |     CommonTooltipOption, | 
 |     ZRColor, | 
 |     ComponentMainType, | 
 |     ComponentItemTooltipOption | 
 | } from '../../util/types'; | 
 | import GlobalModel from '../../model/Global'; | 
 | import ExtensionAPI from '../../core/ExtensionAPI'; | 
 | import TooltipModel, { TooltipOption } from './TooltipModel'; | 
 | import Element from 'zrender/src/Element'; | 
 | import { AxisBaseModel } from '../../coord/AxisBaseModel'; | 
 | import { ECData, getECData } from '../../util/innerStore'; | 
 | import { shouldTooltipConfine } from './helper'; | 
 | import { DataByCoordSys, DataByAxis } from '../axisPointer/axisTrigger'; | 
 | import { normalizeTooltipFormatResult } from '../../model/mixin/dataFormat'; | 
 | import { createTooltipMarkup, buildTooltipMarkup, TooltipMarkupStyleCreator } from './tooltipMarkup'; | 
 | import { findEventDispatcher } from '../../util/event'; | 
 | import { clear, createOrUpdate } from '../../util/throttle'; | 
 |  | 
 | const proxyRect = new Rect({ | 
 |     shape: { x: -1, y: -1, width: 2, height: 2 } | 
 | }); | 
 |  | 
 | interface DataIndex { | 
 |     seriesIndex: number | 
 |     dataIndex: number | 
 |  | 
 |     dataIndexInside: number | 
 | } | 
 |  | 
 | interface ShowTipPayload { | 
 |     type?: 'showTip' | 
 |     from?: string | 
 |  | 
 |     // Type 1 | 
 |     tooltip?: ECData['tooltipConfig']['option'] | 
 |  | 
 |     // Type 2 | 
 |     dataByCoordSys?: DataByCoordSys[] | 
 |     tooltipOption?: CommonTooltipOption<TooltipCallbackDataParams | TooltipCallbackDataParams[]> | 
 |  | 
 |     // Type 3 | 
 |     seriesIndex?: number | 
 |     dataIndex?: number | 
 |  | 
 |     // Type 4 | 
 |     name?: string // target item name that enable tooltip. | 
 |     // legendIndex: 0, | 
 |     // toolboxId: 'some_id', | 
 |     // geoName: 'some_name', | 
 |  | 
 |     x?: number | 
 |     y?: number | 
 |     position?: TooltipOption['position'] | 
 |  | 
 |     dispatchAction?: ExtensionAPI['dispatchAction'] | 
 | } | 
 |  | 
 | interface HideTipPayload { | 
 |     type?: 'hideTip' | 
 |     from?: string | 
 |  | 
 |     dispatchAction?: ExtensionAPI['dispatchAction'] | 
 | } | 
 |  | 
 | interface TryShowParams { | 
 |     target?: ECElement, | 
 |  | 
 |     offsetX?: number | 
 |     offsetY?: number | 
 |  | 
 |     /** | 
 |      * Used for axis trigger. | 
 |      */ | 
 |     dataByCoordSys?: DataByCoordSys[] | 
 |  | 
 |     tooltipOption?: ComponentItemTooltipOption<TooltipCallbackDataParams | TooltipCallbackDataParams[]> | 
 |  | 
 |     position?: TooltipOption['position'] | 
 |     /** | 
 |      * If `position` is not set in payload nor option, use it. | 
 |      */ | 
 |     positionDefault?: TooltipOption['position'] | 
 | } | 
 |  | 
 | type TooltipCallbackDataParams = CallbackDataParams & { | 
 |     axisDim?: string | 
 |     axisIndex?: number | 
 |     axisType?: string | 
 |     axisId?: string | 
 |     // TODO: TYPE Value type | 
 |     axisValue?: string | number | 
 |     axisValueLabel?: string | 
 |     marker?: TooltipMarker | 
 | }; | 
 |  | 
 | class TooltipView extends ComponentView { | 
 |     static type = 'tooltip' as const; | 
 |     type = TooltipView.type; | 
 |  | 
 |     private _renderMode: TooltipRenderMode; | 
 |  | 
 |     private _tooltipModel: TooltipModel; | 
 |  | 
 |     private _ecModel: GlobalModel; | 
 |  | 
 |     private _api: ExtensionAPI; | 
 |  | 
 |     private _alwaysShowContent: boolean; | 
 |  | 
 |     private _tooltipContent: TooltipHTMLContent | TooltipRichContent; | 
 |  | 
 |     private _refreshUpdateTimeout: number; | 
 |  | 
 |     private _lastX: number; | 
 |     private _lastY: number; | 
 |  | 
 |     private _ticket: string; | 
 |  | 
 |     private _showTimout: number; | 
 |  | 
 |     private _lastDataByCoordSys: DataByCoordSys[]; | 
 |     private _cbParamsList: TooltipCallbackDataParams[]; | 
 |  | 
 |     init(ecModel: GlobalModel, api: ExtensionAPI) { | 
 |         if (env.node || !api.getDom()) { | 
 |             return; | 
 |         } | 
 |  | 
 |         const tooltipModel = ecModel.getComponent('tooltip') as TooltipModel; | 
 |         const renderMode = this._renderMode = getTooltipRenderMode(tooltipModel.get('renderMode')); | 
 |  | 
 |         this._tooltipContent = renderMode === 'richText' | 
 |             ? new TooltipRichContent(api) | 
 |             : new TooltipHTMLContent(api.getDom(), api, { | 
 |                 appendToBody: tooltipModel.get('appendToBody', true) | 
 |             }); | 
 |     } | 
 |  | 
 |     render( | 
 |         tooltipModel: TooltipModel, | 
 |         ecModel: GlobalModel, | 
 |         api: ExtensionAPI | 
 |     ) { | 
 |         if (env.node || !api.getDom()) { | 
 |             return; | 
 |         } | 
 |  | 
 |         // Reset | 
 |         this.group.removeAll(); | 
 |  | 
 |         this._tooltipModel = tooltipModel; | 
 |  | 
 |         this._ecModel = ecModel; | 
 |  | 
 |         this._api = api; | 
 |  | 
 |         /** | 
 |          * @private | 
 |          * @type {boolean} | 
 |          */ | 
 |         this._alwaysShowContent = tooltipModel.get('alwaysShowContent'); | 
 |  | 
 |         const tooltipContent = this._tooltipContent; | 
 |         tooltipContent.update(tooltipModel); | 
 |         tooltipContent.setEnterable(tooltipModel.get('enterable')); | 
 |  | 
 |         this._initGlobalListener(); | 
 |  | 
 |         this._keepShow(); | 
 |  | 
 |         // PENDING | 
 |         // `mousemove` event will be triggered very frequently when the mouse moves fast, | 
 |         // which causes that the `updatePosition` function was also called frequently. | 
 |         // In Chrome with devtools open and Firefox, tooltip looks laggy and shakes. See #14695 #16101 | 
 |         // To avoid frequent triggering, | 
 |         // consider throttling it in 50ms when transition is enabled | 
 |         if (this._renderMode !== 'richText' && tooltipModel.get('transitionDuration')) { | 
 |             createOrUpdate(this, '_updatePosition', 50, 'fixRate'); | 
 |         } | 
 |         else { | 
 |             clear(this, '_updatePosition'); | 
 |         } | 
 |     } | 
 |  | 
 |     private _initGlobalListener() { | 
 |         const tooltipModel = this._tooltipModel; | 
 |         const triggerOn = tooltipModel.get('triggerOn'); | 
 |  | 
 |         globalListener.register( | 
 |             'itemTooltip', | 
 |             this._api, | 
 |             bind(function (currTrigger, e, dispatchAction) { | 
 |                 // If 'none', it is not controlled by mouse totally. | 
 |                 if (triggerOn !== 'none') { | 
 |                     if (triggerOn.indexOf(currTrigger) >= 0) { | 
 |                         this._tryShow(e, dispatchAction); | 
 |                     } | 
 |                     else if (currTrigger === 'leave') { | 
 |                         this._hide(dispatchAction); | 
 |                     } | 
 |                 } | 
 |             }, this) | 
 |         ); | 
 |     } | 
 |  | 
 |     private _keepShow() { | 
 |         const tooltipModel = this._tooltipModel; | 
 |         const ecModel = this._ecModel; | 
 |         const api = this._api; | 
 |         const triggerOn = tooltipModel.get('triggerOn'); | 
 |  | 
 |         // Try to keep the tooltip show when refreshing | 
 |         if (this._lastX != null | 
 |             && this._lastY != null | 
 |             // When user is willing to control tooltip totally using API, | 
 |             // self.manuallyShowTip({x, y}) might cause tooltip hide, | 
 |             // which is not expected. | 
 |             && triggerOn !== 'none' | 
 |             && triggerOn !== 'click' | 
 |         ) { | 
 |             const self = this; | 
 |             clearTimeout(this._refreshUpdateTimeout); | 
 |             this._refreshUpdateTimeout = setTimeout(function () { | 
 |                 // Show tip next tick after other charts are rendered | 
 |                 // In case highlight action has wrong result | 
 |                 // FIXME | 
 |                 !api.isDisposed() && self.manuallyShowTip(tooltipModel, ecModel, api, { | 
 |                     x: self._lastX, | 
 |                     y: self._lastY, | 
 |                     dataByCoordSys: self._lastDataByCoordSys | 
 |                 }); | 
 |             }) as any; | 
 |         } | 
 |     } | 
 |  | 
 |     /** | 
 |      * Show tip manually by | 
 |      * dispatchAction({ | 
 |      *     type: 'showTip', | 
 |      *     x: 10, | 
 |      *     y: 10 | 
 |      * }); | 
 |      * Or | 
 |      * dispatchAction({ | 
 |      *      type: 'showTip', | 
 |      *      seriesIndex: 0, | 
 |      *      dataIndex or dataIndexInside or name | 
 |      * }); | 
 |      * | 
 |      *  TODO Batch | 
 |      */ | 
 |     manuallyShowTip( | 
 |         tooltipModel: TooltipModel, | 
 |         ecModel: GlobalModel, | 
 |         api: ExtensionAPI, | 
 |         payload: ShowTipPayload | 
 |     ) { | 
 |         if (payload.from === this.uid || env.node || !api.getDom()) { | 
 |             return; | 
 |         } | 
 |  | 
 |         const dispatchAction = makeDispatchAction(payload, api); | 
 |  | 
 |         // Reset ticket | 
 |         this._ticket = ''; | 
 |  | 
 |         // When triggered from axisPointer. | 
 |         const dataByCoordSys = payload.dataByCoordSys; | 
 |  | 
 |         const cmptRef = findComponentReference(payload, ecModel, api); | 
 |  | 
 |         if (cmptRef) { | 
 |             const rect = cmptRef.el.getBoundingRect().clone(); | 
 |             rect.applyTransform(cmptRef.el.transform); | 
 |             this._tryShow({ | 
 |                 offsetX: rect.x + rect.width / 2, | 
 |                 offsetY: rect.y + rect.height / 2, | 
 |                 target: cmptRef.el, | 
 |                 position: payload.position, | 
 |                 // When manully trigger, the mouse is not on the el, so we'd better to | 
 |                 // position tooltip on the bottom of the el and display arrow is possible. | 
 |                 positionDefault: 'bottom' | 
 |             }, dispatchAction); | 
 |         } | 
 |         else if (payload.tooltip && payload.x != null && payload.y != null) { | 
 |             const el = proxyRect as unknown as ECElement; | 
 |             el.x = payload.x; | 
 |             el.y = payload.y; | 
 |             el.update(); | 
 |             getECData(el).tooltipConfig = { | 
 |                 name: null, | 
 |                 option: payload.tooltip | 
 |             }; | 
 |             // Manually show tooltip while view is not using zrender elements. | 
 |             this._tryShow({ | 
 |                 offsetX: payload.x, | 
 |                 offsetY: payload.y, | 
 |                 target: el | 
 |             }, dispatchAction); | 
 |         } | 
 |         else if (dataByCoordSys) { | 
 |             this._tryShow({ | 
 |                 offsetX: payload.x, | 
 |                 offsetY: payload.y, | 
 |                 position: payload.position, | 
 |                 dataByCoordSys: dataByCoordSys, | 
 |                 tooltipOption: payload.tooltipOption | 
 |             }, dispatchAction); | 
 |         } | 
 |         else if (payload.seriesIndex != null) { | 
 |  | 
 |             if (this._manuallyAxisShowTip(tooltipModel, ecModel, api, payload)) { | 
 |                 return; | 
 |             } | 
 |  | 
 |             const pointInfo = findPointFromSeries(payload, ecModel); | 
 |             const cx = pointInfo.point[0]; | 
 |             const cy = pointInfo.point[1]; | 
 |             if (cx != null && cy != null) { | 
 |                 this._tryShow({ | 
 |                     offsetX: cx, | 
 |                     offsetY: cy, | 
 |                     target: pointInfo.el, | 
 |                     position: payload.position, | 
 |                     // When manully trigger, the mouse is not on the el, so we'd better to | 
 |                     // position tooltip on the bottom of the el and display arrow is possible. | 
 |                     positionDefault: 'bottom' | 
 |                 }, dispatchAction); | 
 |             } | 
 |         } | 
 |         else if (payload.x != null && payload.y != null) { | 
 |             // FIXME | 
 |             // should wrap dispatchAction like `axisPointer/globalListener` ? | 
 |             api.dispatchAction({ | 
 |                 type: 'updateAxisPointer', | 
 |                 x: payload.x, | 
 |                 y: payload.y | 
 |             }); | 
 |  | 
 |             this._tryShow({ | 
 |                 offsetX: payload.x, | 
 |                 offsetY: payload.y, | 
 |                 position: payload.position, | 
 |                 target: api.getZr().findHover(payload.x, payload.y).target | 
 |             }, dispatchAction); | 
 |         } | 
 |     } | 
 |  | 
 |     manuallyHideTip( | 
 |         tooltipModel: TooltipModel, | 
 |         ecModel: GlobalModel, | 
 |         api: ExtensionAPI, | 
 |         payload: HideTipPayload | 
 |     ) { | 
 |         const tooltipContent = this._tooltipContent; | 
 |  | 
 |         if (!this._alwaysShowContent && this._tooltipModel) { | 
 |             tooltipContent.hideLater(this._tooltipModel.get('hideDelay')); | 
 |         } | 
 |  | 
 |         this._lastX = this._lastY = this._lastDataByCoordSys = null; | 
 |  | 
 |         if (payload.from !== this.uid) { | 
 |             this._hide(makeDispatchAction(payload, api)); | 
 |         } | 
 |     } | 
 |  | 
 |     // Be compatible with previous design, that is, when tooltip.type is 'axis' and | 
 |     // dispatchAction 'showTip' with seriesIndex and dataIndex will trigger axis pointer | 
 |     // and tooltip. | 
 |     private _manuallyAxisShowTip( | 
 |         tooltipModel: TooltipModel, | 
 |         ecModel: GlobalModel, | 
 |         api: ExtensionAPI, | 
 |         payload: ShowTipPayload | 
 |     ) { | 
 |         const seriesIndex = payload.seriesIndex; | 
 |         const dataIndex = payload.dataIndex; | 
 |         // @ts-ignore | 
 |         const coordSysAxesInfo = ecModel.getComponent('axisPointer').coordSysAxesInfo; | 
 |  | 
 |         if (seriesIndex == null || dataIndex == null || coordSysAxesInfo == null) { | 
 |             return; | 
 |         } | 
 |  | 
 |         const seriesModel = ecModel.getSeriesByIndex(seriesIndex); | 
 |         if (!seriesModel) { | 
 |             return; | 
 |         } | 
 |  | 
 |         const data = seriesModel.getData(); | 
 |         const tooltipCascadedModel = buildTooltipModel([ | 
 |             data.getItemModel<TooltipableOption>(dataIndex), | 
 |             seriesModel as Model<TooltipableOption>, | 
 |             (seriesModel.coordinateSystem || {}).model as Model<TooltipableOption> | 
 |         ], this._tooltipModel); | 
 |  | 
 |         if (tooltipCascadedModel.get('trigger') !== 'axis') { | 
 |             return; | 
 |         } | 
 |  | 
 |         api.dispatchAction({ | 
 |             type: 'updateAxisPointer', | 
 |             seriesIndex: seriesIndex, | 
 |             dataIndex: dataIndex, | 
 |             position: payload.position | 
 |         }); | 
 |  | 
 |         return true; | 
 |     } | 
 |  | 
 |     private _tryShow( | 
 |         e: TryShowParams, | 
 |         dispatchAction: ExtensionAPI['dispatchAction'] | 
 |     ) { | 
 |         const el = e.target; | 
 |         const tooltipModel = this._tooltipModel; | 
 |  | 
 |         if (!tooltipModel) { | 
 |             return; | 
 |         } | 
 |  | 
 |         // Save mouse x, mouse y. So we can try to keep showing the tip if chart is refreshed | 
 |         this._lastX = e.offsetX; | 
 |         this._lastY = e.offsetY; | 
 |  | 
 |         const dataByCoordSys = e.dataByCoordSys; | 
 |         if (dataByCoordSys && dataByCoordSys.length) { | 
 |             this._showAxisTooltip(dataByCoordSys, e); | 
 |         } | 
 |         else if (el) { | 
 |             this._lastDataByCoordSys = null; | 
 |  | 
 |             let seriesDispatcher: Element; | 
 |             let cmptDispatcher: Element; | 
 |             findEventDispatcher(el, (target) => { | 
 |                 // Always show item tooltip if mouse is on the element with dataIndex | 
 |                 if (getECData(target).dataIndex != null) { | 
 |                     seriesDispatcher = target; | 
 |                     return true; | 
 |                 } | 
 |                 // Tooltip provided directly. Like legend. | 
 |                 if (getECData(target).tooltipConfig != null) { | 
 |                     cmptDispatcher = target; | 
 |                     return true; | 
 |                 } | 
 |             }, true); | 
 |  | 
 |             if (seriesDispatcher) { | 
 |                 this._showSeriesItemTooltip(e, seriesDispatcher, dispatchAction); | 
 |             } | 
 |             else if (cmptDispatcher) { | 
 |                 this._showComponentItemTooltip(e, cmptDispatcher, dispatchAction); | 
 |             } | 
 |             else { | 
 |                 this._hide(dispatchAction); | 
 |             } | 
 |         } | 
 |         else { | 
 |             this._lastDataByCoordSys = null; | 
 |             this._hide(dispatchAction); | 
 |         } | 
 |     } | 
 |  | 
 |     private _showOrMove( | 
 |         tooltipModel: Model<TooltipOption>, | 
 |         cb: () => void | 
 |     ) { | 
 |         // showDelay is used in this case: tooltip.enterable is set | 
 |         // as true. User intent to move mouse into tooltip and click | 
 |         // something. `showDelay` makes it easier to enter the content | 
 |         // but tooltip do not move immediately. | 
 |         const delay = tooltipModel.get('showDelay'); | 
 |         cb = bind(cb, this); | 
 |         clearTimeout(this._showTimout); | 
 |         delay > 0 | 
 |             ? (this._showTimout = setTimeout(cb, delay) as any) | 
 |             : cb(); | 
 |     } | 
 |  | 
 |     private _showAxisTooltip( | 
 |         dataByCoordSys: DataByCoordSys[], | 
 |         e: TryShowParams | 
 |     ) { | 
 |         const ecModel = this._ecModel; | 
 |         const globalTooltipModel = this._tooltipModel; | 
 |         const point = [e.offsetX, e.offsetY]; | 
 |         const singleTooltipModel = buildTooltipModel( | 
 |             [e.tooltipOption], | 
 |             globalTooltipModel | 
 |         ); | 
 |         const renderMode = this._renderMode; | 
 |         const cbParamsList: TooltipCallbackDataParams[] = []; | 
 |         const articleMarkup = createTooltipMarkup('section', { | 
 |             blocks: [], | 
 |             noHeader: true | 
 |         }); | 
 |         // Only for legacy: `Serise['formatTooltip']` returns a string. | 
 |         const markupTextArrLegacy: string[] = []; | 
 |         const markupStyleCreator = new TooltipMarkupStyleCreator(); | 
 |  | 
 |         each(dataByCoordSys, function (itemCoordSys) { | 
 |             each(itemCoordSys.dataByAxis, function (axisItem) { | 
 |                 const axisModel = ecModel.getComponent(axisItem.axisDim + 'Axis', axisItem.axisIndex) as AxisBaseModel; | 
 |                 const axisValue = axisItem.value; | 
 |                 if (!axisModel || axisValue == null) { | 
 |                     return; | 
 |                 } | 
 |                 const axisValueLabel = axisPointerViewHelper.getValueLabel( | 
 |                     axisValue, axisModel.axis, ecModel, | 
 |                     axisItem.seriesDataIndices, | 
 |                     axisItem.valueLabelOpt | 
 |                 ); | 
 |                 const axisSectionMarkup = createTooltipMarkup('section', { | 
 |                     header: axisValueLabel, | 
 |                     noHeader: !trim(axisValueLabel), | 
 |                     sortBlocks: true, | 
 |                     blocks: [] | 
 |                 }); | 
 |                 articleMarkup.blocks.push(axisSectionMarkup); | 
 |  | 
 |                 each(axisItem.seriesDataIndices, function (idxItem) { | 
 |                     const series = ecModel.getSeriesByIndex(idxItem.seriesIndex); | 
 |                     const dataIndex = idxItem.dataIndexInside; | 
 |                     const cbParams = series.getDataParams(dataIndex) as TooltipCallbackDataParams; | 
 |                     // Can't find data. | 
 |                     if (cbParams.dataIndex < 0) { | 
 |                         return; | 
 |                     } | 
 |  | 
 |                     cbParams.axisDim = axisItem.axisDim; | 
 |                     cbParams.axisIndex = axisItem.axisIndex; | 
 |                     cbParams.axisType = axisItem.axisType; | 
 |                     cbParams.axisId = axisItem.axisId; | 
 |                     cbParams.axisValue = axisHelper.getAxisRawValue( | 
 |                         axisModel.axis, { value: axisValue as number } | 
 |                     ); | 
 |                     cbParams.axisValueLabel = axisValueLabel; | 
 |                     // Pre-create marker style for makers. Users can assemble richText | 
 |                     // text in `formatter` callback and use those markers style. | 
 |                     cbParams.marker = markupStyleCreator.makeTooltipMarker( | 
 |                         'item', convertToColorString(cbParams.color), renderMode | 
 |                     ); | 
 |  | 
 |                     const seriesTooltipResult = normalizeTooltipFormatResult( | 
 |                         series.formatTooltip(dataIndex, true, null) | 
 |                     ); | 
 |                     const frag = seriesTooltipResult.frag; | 
 |                     if (frag) { | 
 |                         const valueFormatter = buildTooltipModel( | 
 |                             [series as Model<TooltipableOption>], | 
 |                             globalTooltipModel | 
 |                         ).get('valueFormatter'); | 
 |                         axisSectionMarkup.blocks.push(valueFormatter ? extend({ valueFormatter }, frag) : frag); | 
 |                     } | 
 |                     if (seriesTooltipResult.text) { | 
 |                         markupTextArrLegacy.push(seriesTooltipResult.text); | 
 |                     } | 
 |                     cbParamsList.push(cbParams); | 
 |                 }); | 
 |             }); | 
 |         }); | 
 |  | 
 |         // In most cases, the second axis is displays upper on the first one. | 
 |         // So we reverse it to look better. | 
 |         articleMarkup.blocks.reverse(); | 
 |         markupTextArrLegacy.reverse(); | 
 |  | 
 |         const positionExpr = e.position; | 
 |         const orderMode = singleTooltipModel.get('order'); | 
 |  | 
 |         const builtMarkupText = buildTooltipMarkup( | 
 |             articleMarkup, markupStyleCreator, renderMode, orderMode, ecModel.get('useUTC'), | 
 |             singleTooltipModel.get('textStyle') | 
 |         ); | 
 |         builtMarkupText && markupTextArrLegacy.unshift(builtMarkupText); | 
 |         const blockBreak = renderMode === 'richText' ? '\n\n' : '<br/>'; | 
 |         const allMarkupText = markupTextArrLegacy.join(blockBreak); | 
 |  | 
 |         this._showOrMove(singleTooltipModel, function (this: TooltipView) { | 
 |             if (this._updateContentNotChangedOnAxis(dataByCoordSys, cbParamsList)) { | 
 |                 this._updatePosition( | 
 |                     singleTooltipModel, | 
 |                     positionExpr, | 
 |                     point[0], point[1], | 
 |                     this._tooltipContent, | 
 |                     cbParamsList | 
 |                 ); | 
 |             } | 
 |             else { | 
 |                 this._showTooltipContent( | 
 |                     singleTooltipModel, allMarkupText, cbParamsList, Math.random() + '', | 
 |                     point[0], point[1], positionExpr, null, markupStyleCreator | 
 |                 ); | 
 |             } | 
 |         }); | 
 |  | 
 |         // Do not trigger events here, because this branch only be entered | 
 |         // from dispatchAction. | 
 |     } | 
 |  | 
 |     private _showSeriesItemTooltip( | 
 |         e: TryShowParams, | 
 |         dispatcher: ECElement, | 
 |         dispatchAction: ExtensionAPI['dispatchAction'] | 
 |     ) { | 
 |         const ecModel = this._ecModel; | 
 |         const ecData = getECData(dispatcher); | 
 |         // Use dataModel in element if possible | 
 |         // Used when mouseover on a element like markPoint or edge | 
 |         // In which case, the data is not main data in series. | 
 |         const seriesIndex = ecData.seriesIndex; | 
 |         const seriesModel = ecModel.getSeriesByIndex(seriesIndex); | 
 |  | 
 |         // For example, graph link. | 
 |         const dataModel = ecData.dataModel || seriesModel; | 
 |         const dataIndex = ecData.dataIndex; | 
 |         const dataType = ecData.dataType; | 
 |         const data = dataModel.getData(dataType); | 
 |         const renderMode = this._renderMode; | 
 |  | 
 |         const positionDefault = e.positionDefault; | 
 |         const tooltipModel = buildTooltipModel( | 
 |             [ | 
 |                 data.getItemModel<TooltipableOption>(dataIndex), | 
 |                 dataModel, | 
 |                 seriesModel && (seriesModel.coordinateSystem || {}).model as Model<TooltipableOption> | 
 |             ], | 
 |             this._tooltipModel, | 
 |             positionDefault ? { position: positionDefault } : null | 
 |         ); | 
 |  | 
 |         const tooltipTrigger = tooltipModel.get('trigger'); | 
 |         if (tooltipTrigger != null && tooltipTrigger !== 'item') { | 
 |             return; | 
 |         } | 
 |  | 
 |         const params = dataModel.getDataParams(dataIndex, dataType); | 
 |         const markupStyleCreator = new TooltipMarkupStyleCreator(); | 
 |         // Pre-create marker style for makers. Users can assemble richText | 
 |         // text in `formatter` callback and use those markers style. | 
 |         params.marker = markupStyleCreator.makeTooltipMarker( | 
 |             'item', convertToColorString(params.color), renderMode | 
 |         ); | 
 |  | 
 |         const seriesTooltipResult = normalizeTooltipFormatResult( | 
 |             dataModel.formatTooltip(dataIndex, false, dataType) | 
 |         ); | 
 |         const orderMode = tooltipModel.get('order'); | 
 |         const valueFormatter = tooltipModel.get('valueFormatter'); | 
 |         const frag = seriesTooltipResult.frag; | 
 |         const markupText = frag ? buildTooltipMarkup( | 
 |                 valueFormatter ? extend({ valueFormatter }, frag) : frag, | 
 |                 markupStyleCreator, | 
 |                 renderMode, | 
 |                 orderMode, | 
 |                 ecModel.get('useUTC'), | 
 |                 tooltipModel.get('textStyle') | 
 |             ) | 
 |             : seriesTooltipResult.text; | 
 |  | 
 |         const asyncTicket = 'item_' + dataModel.name + '_' + dataIndex; | 
 |  | 
 |         this._showOrMove(tooltipModel, function (this: TooltipView) { | 
 |             this._showTooltipContent( | 
 |                 tooltipModel, markupText, params, asyncTicket, | 
 |                 e.offsetX, e.offsetY, e.position, e.target, | 
 |                 markupStyleCreator | 
 |             ); | 
 |         }); | 
 |  | 
 |         // FIXME | 
 |         // duplicated showtip if manuallyShowTip is called from dispatchAction. | 
 |         dispatchAction({ | 
 |             type: 'showTip', | 
 |             dataIndexInside: dataIndex, | 
 |             dataIndex: data.getRawIndex(dataIndex), | 
 |             seriesIndex: seriesIndex, | 
 |             from: this.uid | 
 |         }); | 
 |     } | 
 |  | 
 |     private _showComponentItemTooltip( | 
 |         e: TryShowParams, | 
 |         el: ECElement, | 
 |         dispatchAction: ExtensionAPI['dispatchAction'] | 
 |     ) { | 
 |         const ecData = getECData(el); | 
 |         const tooltipConfig = ecData.tooltipConfig; | 
 |         let tooltipOpt = tooltipConfig.option || {}; | 
 |         if (isString(tooltipOpt)) { | 
 |             const content = tooltipOpt; | 
 |             tooltipOpt = { | 
 |                 content: content, | 
 |                 // Fixed formatter | 
 |                 formatter: content | 
 |             }; | 
 |         } | 
 |  | 
 |         const tooltipModelCascade = [tooltipOpt] as TooltipModelOptionCascade[]; | 
 |         const cmpt = this._ecModel.getComponent(ecData.componentMainType, ecData.componentIndex); | 
 |         if (cmpt) { | 
 |             tooltipModelCascade.push(cmpt as Model<TooltipableOption>); | 
 |         } | 
 |         // In most cases, component tooltip formatter has different params with series tooltip formatter, | 
 |         // so that they can not share the same formatter. Since the global tooltip formatter is used for series | 
 |         // by convension, we do not use it as the default formatter for component. | 
 |         tooltipModelCascade.push({ formatter: tooltipOpt.content }); | 
 |  | 
 |         const positionDefault = e.positionDefault; | 
 |         const subTooltipModel = buildTooltipModel( | 
 |             tooltipModelCascade, | 
 |             this._tooltipModel, | 
 |             positionDefault ? { position: positionDefault } : null | 
 |         ); | 
 |  | 
 |         const defaultHtml = subTooltipModel.get('content'); | 
 |         const asyncTicket = Math.random() + ''; | 
 |         // PENDING: this case do not support richText style yet. | 
 |         const markupStyleCreator = new TooltipMarkupStyleCreator(); | 
 |  | 
 |         // Do not check whether `trigger` is 'none' here, because `trigger` | 
 |         // only works on coordinate system. In fact, we have not found case | 
 |         // that requires setting `trigger` nothing on component yet. | 
 |  | 
 |         this._showOrMove(subTooltipModel, function (this: TooltipView) { | 
 |             // Use formatterParams from element defined in component | 
 |             // Avoid users modify it. | 
 |             const formatterParams = clone(subTooltipModel.get('formatterParams') as any || {}); | 
 |             this._showTooltipContent( | 
 |                 subTooltipModel, defaultHtml, formatterParams, | 
 |                 asyncTicket, e.offsetX, e.offsetY, e.position, el, markupStyleCreator | 
 |             ); | 
 |         }); | 
 |  | 
 |         // If not dispatch showTip, tip may be hide triggered by axis. | 
 |         dispatchAction({ | 
 |             type: 'showTip', | 
 |             from: this.uid | 
 |         }); | 
 |     } | 
 |  | 
 |     private _showTooltipContent( | 
 |         // Use Model<TooltipOption> insteadof TooltipModel because this model may be from series or other options. | 
 |         // Instead of top level tooltip. | 
 |         tooltipModel: Model<TooltipOption>, | 
 |         defaultHtml: string, | 
 |         params: TooltipCallbackDataParams | TooltipCallbackDataParams[], | 
 |         asyncTicket: string, | 
 |         x: number, | 
 |         y: number, | 
 |         positionExpr: TooltipOption['position'], | 
 |         el: ECElement, | 
 |         markupStyleCreator: TooltipMarkupStyleCreator | 
 |     ) { | 
 |         // Reset ticket | 
 |         this._ticket = ''; | 
 |  | 
 |         if (!tooltipModel.get('showContent') || !tooltipModel.get('show')) { | 
 |             return; | 
 |         } | 
 |  | 
 |         const tooltipContent = this._tooltipContent; | 
 |         tooltipContent.setEnterable(tooltipModel.get('enterable')); | 
 |  | 
 |         const formatter = tooltipModel.get('formatter'); | 
 |         positionExpr = positionExpr || tooltipModel.get('position'); | 
 |         let html: string | HTMLElement | HTMLElement[] = defaultHtml; | 
 |         const nearPoint = this._getNearestPoint( | 
 |             [x, y], | 
 |             params, | 
 |             tooltipModel.get('trigger'), | 
 |             tooltipModel.get('borderColor') | 
 |         ); | 
 |         const nearPointColor = nearPoint.color; | 
 |  | 
 |         if (formatter) { | 
 |             if (isString(formatter)) { | 
 |                 const useUTC = tooltipModel.ecModel.get('useUTC'); | 
 |                 const params0 = isArray(params) ? params[0] : params; | 
 |                 const isTimeAxis = params0 && params0.axisType && params0.axisType.indexOf('time') >= 0; | 
 |                 html = formatter; | 
 |                 if (isTimeAxis) { | 
 |                     html = timeFormat(params0.axisValue, html, useUTC); | 
 |                 } | 
 |                 html = formatTpl(html, params, true); | 
 |             } | 
 |             else if (isFunction(formatter)) { | 
 |                 const callback = bind(function (cbTicket: string, html: string | HTMLElement | HTMLElement[]) { | 
 |                     if (cbTicket === this._ticket) { | 
 |                         tooltipContent.setContent(html, markupStyleCreator, tooltipModel, nearPointColor, positionExpr); | 
 |                         this._updatePosition( | 
 |                             tooltipModel, positionExpr, x, y, tooltipContent, params, el | 
 |                         ); | 
 |                     } | 
 |                 }, this); | 
 |                 this._ticket = asyncTicket; | 
 |                 html = formatter(params, asyncTicket, callback); | 
 |             } | 
 |             else { | 
 |                 html = formatter; | 
 |             } | 
 |         } | 
 |  | 
 |         tooltipContent.setContent(html, markupStyleCreator, tooltipModel, nearPointColor, positionExpr); | 
 |         tooltipContent.show(tooltipModel, nearPointColor); | 
 |         this._updatePosition( | 
 |             tooltipModel, positionExpr, x, y, tooltipContent, params, el | 
 |         ); | 
 |  | 
 |     } | 
 |  | 
 |     private _getNearestPoint( | 
 |         point: number[], | 
 |         tooltipDataParams: TooltipCallbackDataParams | TooltipCallbackDataParams[], | 
 |         trigger: TooltipOption['trigger'], | 
 |         borderColor: ZRColor | 
 |     ): { | 
 |         color: ZRColor; | 
 |     } { | 
 |         if (trigger === 'axis' || isArray(tooltipDataParams)) { | 
 |             return { | 
 |                 color: borderColor || (this._renderMode === 'html' ? '#fff' : 'none') | 
 |             }; | 
 |         } | 
 |  | 
 |         if (!isArray(tooltipDataParams)) { | 
 |             return { | 
 |                 color: borderColor || tooltipDataParams.color || tooltipDataParams.borderColor | 
 |             }; | 
 |         } | 
 |     } | 
 |  | 
 |     _updatePosition( | 
 |         tooltipModel: Model<TooltipOption>, | 
 |         positionExpr: TooltipOption['position'], | 
 |         x: number,  // Mouse x | 
 |         y: number,  // Mouse y | 
 |         content: TooltipHTMLContent | TooltipRichContent, | 
 |         params: TooltipCallbackDataParams | TooltipCallbackDataParams[], | 
 |         el?: Element | 
 |     ) { | 
 |         const viewWidth = this._api.getWidth(); | 
 |         const viewHeight = this._api.getHeight(); | 
 |  | 
 |         positionExpr = positionExpr || tooltipModel.get('position'); | 
 |  | 
 |         const contentSize = content.getSize(); | 
 |         let align = tooltipModel.get('align'); | 
 |         let vAlign = tooltipModel.get('verticalAlign'); | 
 |         const rect = el && el.getBoundingRect().clone(); | 
 |         el && rect.applyTransform(el.transform); | 
 |  | 
 |         if (isFunction(positionExpr)) { | 
 |             // Callback of position can be an array or a string specify the position | 
 |             positionExpr = positionExpr([x, y], params, content.el, rect, { | 
 |                 viewSize: [viewWidth, viewHeight], | 
 |                 contentSize: contentSize.slice() as [number, number] | 
 |             }); | 
 |         } | 
 |  | 
 |         if (isArray(positionExpr)) { | 
 |             x = parsePercent(positionExpr[0], viewWidth); | 
 |             y = parsePercent(positionExpr[1], viewHeight); | 
 |         } | 
 |         else if (isObject(positionExpr)) { | 
 |             const boxLayoutPosition = positionExpr as BoxLayoutOptionMixin; | 
 |             boxLayoutPosition.width = contentSize[0]; | 
 |             boxLayoutPosition.height = contentSize[1]; | 
 |             const layoutRect = getLayoutRect( | 
 |                 boxLayoutPosition, { width: viewWidth, height: viewHeight } | 
 |             ); | 
 |             x = layoutRect.x; | 
 |             y = layoutRect.y; | 
 |             align = null; | 
 |             // When positionExpr is left/top/right/bottom, | 
 |             // align and verticalAlign will not work. | 
 |             vAlign = null; | 
 |         } | 
 |         // Specify tooltip position by string 'top' 'bottom' 'left' 'right' around graphic element | 
 |         else if (isString(positionExpr) && el) { | 
 |             const pos = calcTooltipPosition( | 
 |                 positionExpr, rect, contentSize, tooltipModel.get('borderWidth') | 
 |             ); | 
 |             x = pos[0]; | 
 |             y = pos[1]; | 
 |         } | 
 |         else { | 
 |             const pos = refixTooltipPosition( | 
 |                 x, y, content, viewWidth, viewHeight, align ? null : 20, vAlign ? null : 20 | 
 |             ); | 
 |             x = pos[0]; | 
 |             y = pos[1]; | 
 |         } | 
 |  | 
 |         align && (x -= isCenterAlign(align) ? contentSize[0] / 2 : align === 'right' ? contentSize[0] : 0); | 
 |         vAlign && (y -= isCenterAlign(vAlign) ? contentSize[1] / 2 : vAlign === 'bottom' ? contentSize[1] : 0); | 
 |  | 
 |         if (shouldTooltipConfine(tooltipModel)) { | 
 |             const pos = confineTooltipPosition( | 
 |                 x, y, content, viewWidth, viewHeight | 
 |             ); | 
 |             x = pos[0]; | 
 |             y = pos[1]; | 
 |         } | 
 |  | 
 |         content.moveTo(x, y); | 
 |     } | 
 |  | 
 |     // FIXME | 
 |     // Should we remove this but leave this to user? | 
 |     private _updateContentNotChangedOnAxis( | 
 |         dataByCoordSys: DataByCoordSys[], | 
 |         cbParamsList: TooltipCallbackDataParams[] | 
 |     ) { | 
 |         const lastCoordSys = this._lastDataByCoordSys; | 
 |         const lastCbParamsList = this._cbParamsList; | 
 |         let contentNotChanged = !!lastCoordSys | 
 |             && lastCoordSys.length === dataByCoordSys.length; | 
 |  | 
 |         contentNotChanged && each(lastCoordSys, (lastItemCoordSys, indexCoordSys) => { | 
 |             const lastDataByAxis = lastItemCoordSys.dataByAxis || [] as DataByAxis[]; | 
 |             const thisItemCoordSys = dataByCoordSys[indexCoordSys] || {} as DataByCoordSys; | 
 |             const thisDataByAxis = thisItemCoordSys.dataByAxis || [] as DataByAxis[]; | 
 |             contentNotChanged = contentNotChanged && lastDataByAxis.length === thisDataByAxis.length; | 
 |  | 
 |             contentNotChanged && each(lastDataByAxis, (lastItem, indexAxis) => { | 
 |                 const thisItem = thisDataByAxis[indexAxis] || {} as DataByAxis; | 
 |                 const lastIndices = lastItem.seriesDataIndices || [] as DataIndex[]; | 
 |                 const newIndices = thisItem.seriesDataIndices || [] as DataIndex[]; | 
 |  | 
 |                 contentNotChanged = contentNotChanged | 
 |                     && lastItem.value === thisItem.value | 
 |                     && lastItem.axisType === thisItem.axisType | 
 |                     && lastItem.axisId === thisItem.axisId | 
 |                     && lastIndices.length === newIndices.length; | 
 |  | 
 |                 contentNotChanged && each(lastIndices, (lastIdxItem, j) => { | 
 |                     const newIdxItem = newIndices[j]; | 
 |                     contentNotChanged = contentNotChanged | 
 |                         && lastIdxItem.seriesIndex === newIdxItem.seriesIndex | 
 |                         && lastIdxItem.dataIndex === newIdxItem.dataIndex; | 
 |                 }); | 
 |  | 
 |                 // check is cbParams data value changed | 
 |                 lastCbParamsList && each(lastItem.seriesDataIndices, (idxItem) => { | 
 |                     const seriesIdx = idxItem.seriesIndex; | 
 |                     const cbParams = cbParamsList[seriesIdx]; | 
 |                     const lastCbParams = lastCbParamsList[seriesIdx]; | 
 |                     if (cbParams && lastCbParams && lastCbParams.data !== cbParams.data) { | 
 |                         contentNotChanged = false; | 
 |                     } | 
 |                 }); | 
 |             }); | 
 |         }); | 
 |  | 
 |         this._lastDataByCoordSys = dataByCoordSys; | 
 |         this._cbParamsList = cbParamsList; | 
 |  | 
 |         return !!contentNotChanged; | 
 |     } | 
 |  | 
 |     private _hide(dispatchAction: ExtensionAPI['dispatchAction']) { | 
 |         // Do not directly hideLater here, because this behavior may be prevented | 
 |         // in dispatchAction when showTip is dispatched. | 
 |  | 
 |         // FIXME | 
 |         // duplicated hideTip if manuallyHideTip is called from dispatchAction. | 
 |         this._lastDataByCoordSys = null; | 
 |         dispatchAction({ | 
 |             type: 'hideTip', | 
 |             from: this.uid | 
 |         }); | 
 |     } | 
 |  | 
 |     dispose(ecModel: GlobalModel, api: ExtensionAPI) { | 
 |         if (env.node || !api.getDom()) { | 
 |             return; | 
 |         } | 
 |         clear(this, '_updatePosition'); | 
 |         this._tooltipContent.dispose(); | 
 |         globalListener.unregister('itemTooltip', api); | 
 |     } | 
 | } | 
 |  | 
 | type TooltipableOption = { | 
 |     tooltip?: CommonTooltipOption<unknown>; | 
 | }; | 
 | type TooltipModelOptionCascade = | 
 |     Model<TooltipableOption> | CommonTooltipOption<unknown> | string; | 
 | /** | 
 |  * From top to bottom. (the last one should be globalTooltipModel); | 
 |  */ | 
 | function buildTooltipModel( | 
 |     modelCascade: TooltipModelOptionCascade[], | 
 |     globalTooltipModel: TooltipModel, | 
 |     defaultTooltipOption?: CommonTooltipOption<unknown> | 
 | ): Model<TooltipOption & ComponentItemTooltipOption<unknown>> { | 
 |     // Last is always tooltip model. | 
 |     const ecModel = globalTooltipModel.ecModel; | 
 |     let resultModel: Model<TooltipOption & ComponentItemTooltipOption<unknown>>; | 
 |  | 
 |     if (defaultTooltipOption) { | 
 |         resultModel = new Model(defaultTooltipOption, ecModel, ecModel); | 
 |         resultModel = new Model(globalTooltipModel.option, resultModel, ecModel); | 
 |     } | 
 |     else { | 
 |         resultModel = globalTooltipModel as Model<TooltipOption & ComponentItemTooltipOption<unknown>>; | 
 |     } | 
 |  | 
 |     for (let i = modelCascade.length - 1; i >= 0; i--) { | 
 |         let tooltipOpt = modelCascade[i]; | 
 |         if (tooltipOpt) { | 
 |             if (tooltipOpt instanceof Model) { | 
 |                 tooltipOpt = (tooltipOpt as Model<TooltipableOption>).get('tooltip', true); | 
 |             } | 
 |             // In each data item tooltip can be simply write: | 
 |             // { | 
 |             //  value: 10, | 
 |             //  tooltip: 'Something you need to know' | 
 |             // } | 
 |             if (isString(tooltipOpt)) { | 
 |                 tooltipOpt = { | 
 |                     formatter: tooltipOpt | 
 |                 }; | 
 |             } | 
 |             if (tooltipOpt) { | 
 |                 resultModel = new Model(tooltipOpt, resultModel, ecModel); | 
 |             } | 
 |         } | 
 |     } | 
 |  | 
 |     return resultModel as Model<TooltipOption & ComponentItemTooltipOption<unknown>>; | 
 | } | 
 |  | 
 | function makeDispatchAction(payload: ShowTipPayload | HideTipPayload, api: ExtensionAPI) { | 
 |     return payload.dispatchAction || bind(api.dispatchAction, api); | 
 | } | 
 |  | 
 | function refixTooltipPosition( | 
 |     x: number, y: number, | 
 |     content: TooltipHTMLContent | TooltipRichContent, | 
 |     viewWidth: number, viewHeight: number, | 
 |     gapH: number, gapV: number | 
 | ) { | 
 |     const size = content.getSize(); | 
 |     const width = size[0]; | 
 |     const height = size[1]; | 
 |  | 
 |     if (gapH != null) { | 
 |         // Add extra 2 pixels for this case: | 
 |         // At present the "values" in defaut tooltip are using CSS `float: right`. | 
 |         // When the right edge of the tooltip box is on the right side of the | 
 |         // viewport, the `float` layout might push the "values" to the second line. | 
 |         if (x + width + gapH + 2 > viewWidth) { | 
 |             x -= width + gapH; | 
 |         } | 
 |         else { | 
 |             x += gapH; | 
 |         } | 
 |     } | 
 |     if (gapV != null) { | 
 |         if (y + height + gapV > viewHeight) { | 
 |             y -= height + gapV; | 
 |         } | 
 |         else { | 
 |             y += gapV; | 
 |         } | 
 |     } | 
 |     return [x, y]; | 
 | } | 
 |  | 
 | function confineTooltipPosition( | 
 |     x: number, y: number, | 
 |     content: TooltipHTMLContent | TooltipRichContent, | 
 |     viewWidth: number, | 
 |     viewHeight: number | 
 | ): [number, number] { | 
 |     const size = content.getSize(); | 
 |     const width = size[0]; | 
 |     const height = size[1]; | 
 |  | 
 |     x = Math.min(x + width, viewWidth) - width; | 
 |     y = Math.min(y + height, viewHeight) - height; | 
 |     x = Math.max(x, 0); | 
 |     y = Math.max(y, 0); | 
 |  | 
 |     return [x, y]; | 
 | } | 
 |  | 
 | function calcTooltipPosition( | 
 |     position: TooltipOption['position'], | 
 |     rect: ZRRectLike, | 
 |     contentSize: number[], | 
 |     borderWidth: number | 
 | ): [number, number] { | 
 |     const domWidth = contentSize[0]; | 
 |     const domHeight = contentSize[1]; | 
 |     const offset = Math.ceil(Math.SQRT2 * borderWidth) + 8; | 
 |     let x = 0; | 
 |     let y = 0; | 
 |     const rectWidth = rect.width; | 
 |     const rectHeight = rect.height; | 
 |     switch (position) { | 
 |         case 'inside': | 
 |             x = rect.x + rectWidth / 2 - domWidth / 2; | 
 |             y = rect.y + rectHeight / 2 - domHeight / 2; | 
 |             break; | 
 |         case 'top': | 
 |             x = rect.x + rectWidth / 2 - domWidth / 2; | 
 |             y = rect.y - domHeight - offset; | 
 |             break; | 
 |         case 'bottom': | 
 |             x = rect.x + rectWidth / 2 - domWidth / 2; | 
 |             y = rect.y + rectHeight + offset; | 
 |             break; | 
 |         case 'left': | 
 |             x = rect.x - domWidth - offset; | 
 |             y = rect.y + rectHeight / 2 - domHeight / 2; | 
 |             break; | 
 |         case 'right': | 
 |             x = rect.x + rectWidth + offset; | 
 |             y = rect.y + rectHeight / 2 - domHeight / 2; | 
 |     } | 
 |     return [x, y]; | 
 | } | 
 |  | 
 | function isCenterAlign(align: HorizontalAlign | VerticalAlign) { | 
 |     return align === 'center' || align === 'middle'; | 
 | } | 
 |  | 
 | /** | 
 |  * Find target component by payload like: | 
 |  * ```js | 
 |  * { legendId: 'some_id', name: 'xxx' } | 
 |  * { toolboxIndex: 1, name: 'xxx' } | 
 |  * { geoName: 'some_name', name: 'xxx' } | 
 |  * ``` | 
 |  * PENDING: at present only | 
 |  * | 
 |  * If not found, return null/undefined. | 
 |  */ | 
 | function findComponentReference( | 
 |     payload: ShowTipPayload, | 
 |     ecModel: GlobalModel, | 
 |     api: ExtensionAPI | 
 | ): { | 
 |     componentMainType: ComponentMainType; | 
 |     componentIndex: number; | 
 |     el: ECElement; | 
 | } { | 
 |     const { queryOptionMap } = preParseFinder(payload); | 
 |     const componentMainType = queryOptionMap.keys()[0]; | 
 |     if (!componentMainType || componentMainType === 'series') { | 
 |         return; | 
 |     } | 
 |  | 
 |     const queryResult = queryReferringComponents( | 
 |         ecModel, | 
 |         componentMainType, | 
 |         queryOptionMap.get(componentMainType), | 
 |         { useDefault: false, enableAll: false, enableNone: false } | 
 |     ); | 
 |     const model = queryResult.models[0]; | 
 |     if (!model) { | 
 |         return; | 
 |     } | 
 |  | 
 |     const view = api.getViewOfComponentModel(model); | 
 |     let el: ECElement; | 
 |     view.group.traverse((subEl: ECElement) => { | 
 |         const tooltipConfig = getECData(subEl).tooltipConfig; | 
 |         if (tooltipConfig && tooltipConfig.name === payload.name) { | 
 |             el = subEl; | 
 |             return true; // stop | 
 |         } | 
 |     }); | 
 |  | 
 |     if (el) { | 
 |         return { | 
 |             componentMainType, | 
 |             componentIndex: model.componentIndex, | 
 |             el | 
 |         }; | 
 |     } | 
 | } | 
 |  | 
 | export default TooltipView; |