|  | /* | 
|  | * 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; |