| /* | 
 | * 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 {createSymbol, normalizeSymbolOffset, normalizeSymbolSize} from '../../util/symbol'; | 
 | import * as graphic from '../../util/graphic'; | 
 | import {getECData} from '../../util/innerStore'; | 
 | import { enterEmphasis, leaveEmphasis, toggleHoverEmphasis } from '../../util/states'; | 
 | import {getDefaultLabel} from './labelHelper'; | 
 | import SeriesData from '../../data/SeriesData'; | 
 | import { ColorString, BlurScope, AnimationOption, ZRColor, AnimationOptionMixin } from '../../util/types'; | 
 | import SeriesModel from '../../model/Series'; | 
 | import { PathProps } from 'zrender/src/graphic/Path'; | 
 | import { SymbolDrawSeriesScope, SymbolDrawItemModelOption } from './SymbolDraw'; | 
 | import { extend } from 'zrender/src/core/util'; | 
 | import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle'; | 
 | import ZRImage from 'zrender/src/graphic/Image'; | 
 | import { saveOldStyle } from '../../animation/basicTransition'; | 
 | import Model from '../../model/Model'; | 
 |  | 
 | type ECSymbol = ReturnType<typeof createSymbol>; | 
 |  | 
 | interface SymbolOpts { | 
 |     disableAnimation?: boolean | 
 |  | 
 |     useNameLabel?: boolean | 
 |     symbolInnerColor?: ZRColor | 
 | } | 
 |  | 
 | class Symbol extends graphic.Group { | 
 |  | 
 |     private _symbolType: string; | 
 |  | 
 |     /** | 
 |      * Original scale | 
 |      */ | 
 |     private _sizeX: number; | 
 |     private _sizeY: number; | 
 |  | 
 |     private _z2: number; | 
 |  | 
 |     constructor(data: SeriesData, idx: number, seriesScope?: SymbolDrawSeriesScope, opts?: SymbolOpts) { | 
 |         super(); | 
 |         this.updateData(data, idx, seriesScope, opts); | 
 |     } | 
 |  | 
 |     _createSymbol( | 
 |         symbolType: string, | 
 |         data: SeriesData, | 
 |         idx: number, | 
 |         symbolSize: number[], | 
 |         keepAspect: boolean | 
 |     ) { | 
 |         // Remove paths created before | 
 |         this.removeAll(); | 
 |  | 
 |         // let symbolPath = createSymbol( | 
 |         //     symbolType, -0.5, -0.5, 1, 1, color | 
 |         // ); | 
 |         // If width/height are set too small (e.g., set to 1) on ios10 | 
 |         // and macOS Sierra, a circle stroke become a rect, no matter what | 
 |         // the scale is set. So we set width/height as 2. See #4150. | 
 |         const symbolPath = createSymbol( | 
 |             symbolType, -1, -1, 2, 2, null, keepAspect | 
 |         ); | 
 |  | 
 |         symbolPath.attr({ | 
 |             z2: 100, | 
 |             culling: true, | 
 |             scaleX: symbolSize[0] / 2, | 
 |             scaleY: symbolSize[1] / 2 | 
 |         }); | 
 |         // Rewrite drift method | 
 |         symbolPath.drift = driftSymbol; | 
 |  | 
 |         this._symbolType = symbolType; | 
 |  | 
 |         this.add(symbolPath); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Stop animation | 
 |      * @param {boolean} toLastFrame | 
 |      */ | 
 |     stopSymbolAnimation(toLastFrame: boolean) { | 
 |         this.childAt(0).stopAnimation(null, toLastFrame); | 
 |     } | 
 |  | 
 |     getSymbolType() { | 
 |         return this._symbolType; | 
 |     } | 
 |     /** | 
 |      * FIXME: | 
 |      * Caution: This method breaks the encapsulation of this module, | 
 |      * but it indeed brings convenience. So do not use the method | 
 |      * unless you detailedly know all the implements of `Symbol`, | 
 |      * especially animation. | 
 |      * | 
 |      * Get symbol path element. | 
 |      */ | 
 |     getSymbolPath() { | 
 |         return this.childAt(0) as ECSymbol; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Highlight symbol | 
 |      */ | 
 |     highlight() { | 
 |         enterEmphasis(this.childAt(0)); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Downplay symbol | 
 |      */ | 
 |     downplay() { | 
 |         leaveEmphasis(this.childAt(0)); | 
 |     } | 
 |  | 
 |     /** | 
 |      * @param {number} zlevel | 
 |      * @param {number} z | 
 |      */ | 
 |     setZ(zlevel: number, z: number) { | 
 |         const symbolPath = this.childAt(0) as ECSymbol; | 
 |         symbolPath.zlevel = zlevel; | 
 |         symbolPath.z = z; | 
 |     } | 
 |  | 
 |     setDraggable(draggable: boolean, hasCursorOption?: boolean) { | 
 |         const symbolPath = this.childAt(0) as ECSymbol; | 
 |         symbolPath.draggable = draggable; | 
 |         symbolPath.cursor = !hasCursorOption && draggable ? 'move' : symbolPath.cursor; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Update symbol properties | 
 |      */ | 
 |     updateData(data: SeriesData, idx: number, seriesScope?: SymbolDrawSeriesScope, opts?: SymbolOpts) { | 
 |         this.silent = false; | 
 |  | 
 |         const symbolType = data.getItemVisual(idx, 'symbol') || 'circle'; | 
 |         const seriesModel = data.hostModel as SeriesModel; | 
 |         const symbolSize = Symbol.getSymbolSize(data, idx); | 
 |         const isInit = symbolType !== this._symbolType; | 
 |         const disableAnimation = opts && opts.disableAnimation; | 
 |  | 
 |         if (isInit) { | 
 |             const keepAspect = data.getItemVisual(idx, 'symbolKeepAspect'); | 
 |             this._createSymbol(symbolType as string, data, idx, symbolSize, keepAspect); | 
 |         } | 
 |         else { | 
 |             const symbolPath = this.childAt(0) as ECSymbol; | 
 |             symbolPath.silent = false; | 
 |             const target = { | 
 |                 scaleX: symbolSize[0] / 2, | 
 |                 scaleY: symbolSize[1] / 2 | 
 |             }; | 
 |             disableAnimation ? symbolPath.attr(target) | 
 |                 : graphic.updateProps(symbolPath, target, seriesModel, idx); | 
 |  | 
 |             saveOldStyle(symbolPath); | 
 |         } | 
 |  | 
 |         this._updateCommon(data, idx, symbolSize, seriesScope, opts); | 
 |  | 
 |         if (isInit) { | 
 |             const symbolPath = this.childAt(0) as ECSymbol; | 
 |  | 
 |             if (!disableAnimation) { | 
 |                 const target: PathProps = { | 
 |                     scaleX: this._sizeX, | 
 |                     scaleY: this._sizeY, | 
 |                     style: { | 
 |                         // Always fadeIn. Because it has fadeOut animation when symbol is removed.. | 
 |                         opacity: symbolPath.style.opacity | 
 |                     } | 
 |                 }; | 
 |                 symbolPath.scaleX = symbolPath.scaleY = 0; | 
 |                 symbolPath.style.opacity = 0; | 
 |                 graphic.initProps(symbolPath, target, seriesModel, idx); | 
 |             } | 
 |         } | 
 |  | 
 |         if (disableAnimation) { | 
 |             // Must stop leave transition manually if don't call initProps or updateProps. | 
 |             this.childAt(0).stopAnimation('leave'); | 
 |         } | 
 |     } | 
 |  | 
 |     _updateCommon( | 
 |         data: SeriesData, | 
 |         idx: number, | 
 |         symbolSize: number[], | 
 |         seriesScope?: SymbolDrawSeriesScope, | 
 |         opts?: SymbolOpts | 
 |     ) { | 
 |         const symbolPath = this.childAt(0) as ECSymbol; | 
 |         const seriesModel = data.hostModel as SeriesModel; | 
 |  | 
 |         let emphasisItemStyle; | 
 |         let blurItemStyle; | 
 |         let selectItemStyle; | 
 |         let focus; | 
 |         let blurScope: BlurScope; | 
 |         let emphasisDisabled: boolean; | 
 |  | 
 |         let labelStatesModels; | 
 |  | 
 |         let hoverScale: SymbolDrawSeriesScope['hoverScale']; | 
 |         let cursorStyle: SymbolDrawSeriesScope['cursorStyle']; | 
 |  | 
 |         if (seriesScope) { | 
 |             emphasisItemStyle = seriesScope.emphasisItemStyle; | 
 |             blurItemStyle = seriesScope.blurItemStyle; | 
 |             selectItemStyle = seriesScope.selectItemStyle; | 
 |             focus = seriesScope.focus; | 
 |             blurScope = seriesScope.blurScope; | 
 |  | 
 |             labelStatesModels = seriesScope.labelStatesModels; | 
 |  | 
 |             hoverScale = seriesScope.hoverScale; | 
 |             cursorStyle = seriesScope.cursorStyle; | 
 |             emphasisDisabled = seriesScope.emphasisDisabled; | 
 |         } | 
 |  | 
 |         if (!seriesScope || data.hasItemOption) { | 
 |             const itemModel = (seriesScope && seriesScope.itemModel) | 
 |                 ? seriesScope.itemModel : data.getItemModel<SymbolDrawItemModelOption>(idx); | 
 |             const emphasisModel = itemModel.getModel('emphasis'); | 
 |  | 
 |             emphasisItemStyle = emphasisModel.getModel('itemStyle').getItemStyle(); | 
 |             selectItemStyle = itemModel.getModel(['select', 'itemStyle']).getItemStyle(); | 
 |             blurItemStyle = itemModel.getModel(['blur', 'itemStyle']).getItemStyle(); | 
 |  | 
 |             focus = emphasisModel.get('focus'); | 
 |             blurScope = emphasisModel.get('blurScope'); | 
 |             emphasisDisabled = emphasisModel.get('disabled'); | 
 |  | 
 |             labelStatesModels = getLabelStatesModels(itemModel); | 
 |  | 
 |             hoverScale = emphasisModel.getShallow('scale'); | 
 |             cursorStyle = itemModel.getShallow('cursor'); | 
 |         } | 
 |  | 
 |         const symbolRotate = data.getItemVisual(idx, 'symbolRotate'); | 
 |         symbolPath.attr('rotation', (symbolRotate || 0) * Math.PI / 180 || 0); | 
 |  | 
 |         const symbolOffset = normalizeSymbolOffset(data.getItemVisual(idx, 'symbolOffset'), symbolSize); | 
 |         if (symbolOffset) { | 
 |             symbolPath.x = symbolOffset[0]; | 
 |             symbolPath.y = symbolOffset[1]; | 
 |         } | 
 |  | 
 |         cursorStyle && symbolPath.attr('cursor', cursorStyle); | 
 |  | 
 |         const symbolStyle = data.getItemVisual(idx, 'style'); | 
 |         const visualColor = symbolStyle.fill; | 
 |  | 
 |         if (symbolPath instanceof ZRImage) { | 
 |             const pathStyle = symbolPath.style; | 
 |             symbolPath.useStyle(extend({ | 
 |                 // TODO other properties like x, y ? | 
 |                 image: pathStyle.image, | 
 |                 x: pathStyle.x, y: pathStyle.y, | 
 |                 width: pathStyle.width, height: pathStyle.height | 
 |             }, symbolStyle)); | 
 |         } | 
 |         else { | 
 |             if (symbolPath.__isEmptyBrush) { | 
 |                 // fill and stroke will be swapped if it's empty. | 
 |                 // So we cloned a new style to avoid it affecting the original style in visual storage. | 
 |                 // TODO Better implementation. No empty logic! | 
 |                 symbolPath.useStyle(extend({}, symbolStyle)); | 
 |             } | 
 |             else { | 
 |                 symbolPath.useStyle(symbolStyle); | 
 |             } | 
 |             // Disable decal because symbol scale will been applied on the decal. | 
 |             symbolPath.style.decal = null; | 
 |             symbolPath.setColor(visualColor, opts && opts.symbolInnerColor); | 
 |             symbolPath.style.strokeNoScale = true; | 
 |  | 
 |         } | 
 |         const liftZ = data.getItemVisual(idx, 'liftZ'); | 
 |         const z2Origin = this._z2; | 
 |         if (liftZ != null) { | 
 |             if (z2Origin == null) { | 
 |                 this._z2 = symbolPath.z2; | 
 |                 symbolPath.z2 += liftZ; | 
 |             } | 
 |         } | 
 |         else if (z2Origin != null) { | 
 |             symbolPath.z2 = z2Origin; | 
 |             this._z2 = null; | 
 |         } | 
 |  | 
 |         const useNameLabel = opts && opts.useNameLabel; | 
 |  | 
 |         setLabelStyle( | 
 |             symbolPath, labelStatesModels, | 
 |             { | 
 |                 labelFetcher: seriesModel, | 
 |                 labelDataIndex: idx, | 
 |                 defaultText: getLabelDefaultText, | 
 |                 inheritColor: visualColor as ColorString, | 
 |                 defaultOpacity: symbolStyle.opacity | 
 |             } | 
 |         ); | 
 |  | 
 |         // Do not execute util needed. | 
 |         function getLabelDefaultText(idx: number) { | 
 |             return useNameLabel ? data.getName(idx) : getDefaultLabel(data, idx); | 
 |         } | 
 |  | 
 |         this._sizeX = symbolSize[0] / 2; | 
 |         this._sizeY = symbolSize[1] / 2; | 
 |  | 
 |         const emphasisState = symbolPath.ensureState('emphasis'); | 
 |  | 
 |         emphasisState.style = emphasisItemStyle; | 
 |         symbolPath.ensureState('select').style = selectItemStyle; | 
 |         symbolPath.ensureState('blur').style = blurItemStyle; | 
 |  | 
 |         // null / undefined / true means to use default strategy. | 
 |         // 0 / false / negative number / NaN / Infinity means no scale. | 
 |         const scaleRatio = | 
 |             hoverScale == null || hoverScale === true | 
 |                 ? Math.max(1.1, 3 / this._sizeY) | 
 |                 // PENDING: restrict hoverScale > 1? It seems unreasonable to scale down | 
 |                 : isFinite(hoverScale as number) && hoverScale > 0 | 
 |                     ? +hoverScale | 
 |                     : 1; | 
 |         // always set scale to allow resetting | 
 |         emphasisState.scaleX = this._sizeX * scaleRatio; | 
 |         emphasisState.scaleY = this._sizeY * scaleRatio; | 
 |  | 
 |         this.setSymbolScale(1); | 
 |  | 
 |         toggleHoverEmphasis(this, focus, blurScope, emphasisDisabled); | 
 |     } | 
 |  | 
 |     setSymbolScale(scale: number) { | 
 |         this.scaleX = this.scaleY = scale; | 
 |     } | 
 |  | 
 |     fadeOut(cb: () => void, seriesModel: Model<AnimationOptionMixin>, opt?: { | 
 |         fadeLabel: boolean, | 
 |         animation?: AnimationOption | 
 |     }) { | 
 |         const symbolPath = this.childAt(0) as ECSymbol; | 
 |         const dataIndex = getECData(this).dataIndex; | 
 |         const animationOpt = opt && opt.animation; | 
 |         // Avoid mistaken hover when fading out | 
 |         this.silent = symbolPath.silent = true; | 
 |         // Not show text when animating | 
 |         if (opt && opt.fadeLabel) { | 
 |             const textContent = symbolPath.getTextContent(); | 
 |             if (textContent) { | 
 |                 graphic.removeElement(textContent, { | 
 |                     style: { | 
 |                         opacity: 0 | 
 |                     } | 
 |                 }, seriesModel, { | 
 |                     dataIndex, | 
 |                     removeOpt: animationOpt, | 
 |                     cb() { | 
 |                         symbolPath.removeTextContent(); | 
 |                     } | 
 |                 }); | 
 |             } | 
 |         } | 
 |         else { | 
 |             symbolPath.removeTextContent(); | 
 |         } | 
 |  | 
 |         graphic.removeElement( | 
 |             symbolPath, | 
 |             { | 
 |                 style: { | 
 |                     opacity: 0 | 
 |                 }, | 
 |                 scaleX: 0, | 
 |                 scaleY: 0 | 
 |             }, | 
 |             seriesModel, | 
 |             { dataIndex, cb, removeOpt: animationOpt} | 
 |         ); | 
 |     } | 
 |  | 
 |     static getSymbolSize(data: SeriesData, idx: number) { | 
 |         return normalizeSymbolSize(data.getItemVisual(idx, 'symbolSize')); | 
 |     } | 
 | } | 
 |  | 
 |  | 
 | function driftSymbol(this: ECSymbol, dx: number, dy: number) { | 
 |     this.parent.drift(dx, dy); | 
 | } | 
 |  | 
 | export default Symbol; |