| /* | 
 | * 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 * as graphic from '../../util/graphic'; | 
 | import SymbolClz from './Symbol'; | 
 | import { isObject } from 'zrender/src/core/util'; | 
 | import SeriesData from '../../data/SeriesData'; | 
 | import type Displayable from 'zrender/src/graphic/Displayable'; | 
 | import { | 
 |     StageHandlerProgressParams, | 
 |     LabelOption, | 
 |     SymbolOptionMixin, | 
 |     ItemStyleOption, | 
 |     ZRColor, | 
 |     AnimationOptionMixin, | 
 |     ZRStyleProps, | 
 |     StatesOptionMixin, | 
 |     BlurScope, | 
 |     DisplayState, | 
 |     DefaultEmphasisFocus | 
 | } from '../../util/types'; | 
 | import { CoordinateSystemClipArea } from '../../coord/CoordinateSystem'; | 
 | import Model from '../../model/Model'; | 
 | import { ScatterSeriesOption } from '../scatter/ScatterSeries'; | 
 | import { getLabelStatesModels } from '../../label/labelStyle'; | 
 | import Element from 'zrender/src/Element'; | 
 | import SeriesModel from '../../model/Series'; | 
 |  | 
 | interface UpdateOpt { | 
 |     isIgnore?(idx: number): boolean | 
 |     clipShape?: CoordinateSystemClipArea, | 
 |     getSymbolPoint?(idx: number): number[] | 
 |  | 
 |     disableAnimation?: boolean | 
 | } | 
 |  | 
 | interface SymbolLike extends graphic.Group { | 
 |     updateData(data: SeriesData, idx: number, scope?: SymbolDrawSeriesScope, opt?: UpdateOpt): void | 
 |     fadeOut?(cb: () => void, seriesModel: SeriesModel): void | 
 | } | 
 |  | 
 | interface SymbolLikeCtor { | 
 |     new(data: SeriesData, idx: number, scope?: SymbolDrawSeriesScope, opt?: UpdateOpt): SymbolLike | 
 | } | 
 |  | 
 | function symbolNeedsDraw(data: SeriesData, point: number[], idx: number, opt: UpdateOpt) { | 
 |     return point && !isNaN(point[0]) && !isNaN(point[1]) | 
 |         && !(opt.isIgnore && opt.isIgnore(idx)) | 
 |         // We do not set clipShape on group, because it will cut part of | 
 |         // the symbol element shape. We use the same clip shape here as | 
 |         // the line clip. | 
 |         && !(opt.clipShape && !opt.clipShape.contain(point[0], point[1])) | 
 |         && data.getItemVisual(idx, 'symbol') !== 'none'; | 
 | } | 
 |  | 
 | function normalizeUpdateOpt(opt: UpdateOpt) { | 
 |     if (opt != null && !isObject(opt)) { | 
 |         opt = {isIgnore: opt}; | 
 |     } | 
 |     return opt || {}; | 
 | } | 
 |  | 
 | interface RippleEffectOption { | 
 |     period?: number | 
 |     /** | 
 |      * Scale of ripple | 
 |      */ | 
 |     scale?: number | 
 |  | 
 |     brushType?: 'fill' | 'stroke' | 
 |  | 
 |     color?: ZRColor, | 
 |  | 
 |     /** | 
 |      * ripple number | 
 |      */ | 
 |     number?: number | 
 | } | 
 |  | 
 | interface SymbolDrawStateOption { | 
 |     itemStyle?: ItemStyleOption | 
 |     label?: LabelOption | 
 | } | 
 |  | 
 | // TODO Separate series and item? | 
 | export interface SymbolDrawItemModelOption extends SymbolOptionMixin<object>, | 
 |     StatesOptionMixin<SymbolDrawStateOption, { | 
 |         emphasis?: { | 
 |             focus?: DefaultEmphasisFocus | 
 |             scale?: boolean | number | 
 |         } | 
 |     }>, | 
 |     SymbolDrawStateOption { | 
 |  | 
 |     cursor?: string | 
 |  | 
 |     // If has ripple effect | 
 |     rippleEffect?: RippleEffectOption | 
 | } | 
 |  | 
 | export interface SymbolDrawSeriesScope { | 
 |     emphasisItemStyle?: ZRStyleProps | 
 |     blurItemStyle?: ZRStyleProps | 
 |     selectItemStyle?: ZRStyleProps | 
 |  | 
 |     focus?: DefaultEmphasisFocus | 
 |     blurScope?: BlurScope | 
 |     emphasisDisabled?: boolean | 
 |  | 
 |     labelStatesModels: Record<DisplayState, Model<LabelOption>> | 
 |  | 
 |     itemModel?: Model<SymbolDrawItemModelOption> | 
 |  | 
 |     hoverScale?: boolean | number | 
 |  | 
 |     cursorStyle?: string | 
 |     fadeIn?: boolean | 
 | } | 
 |  | 
 | function makeSeriesScope(data: SeriesData): SymbolDrawSeriesScope { | 
 |     const seriesModel = data.hostModel as Model<ScatterSeriesOption>; | 
 |     const emphasisModel = seriesModel.getModel('emphasis'); | 
 |     return { | 
 |         emphasisItemStyle: emphasisModel.getModel('itemStyle').getItemStyle(), | 
 |         blurItemStyle: seriesModel.getModel(['blur', 'itemStyle']).getItemStyle(), | 
 |         selectItemStyle: seriesModel.getModel(['select', 'itemStyle']).getItemStyle(), | 
 |  | 
 |         focus: emphasisModel.get('focus'), | 
 |         blurScope: emphasisModel.get('blurScope'), | 
 |         emphasisDisabled: emphasisModel.get('disabled'), | 
 |  | 
 |         hoverScale: emphasisModel.get('scale'), | 
 |  | 
 |         labelStatesModels: getLabelStatesModels(seriesModel), | 
 |  | 
 |         cursorStyle: seriesModel.get('cursor') | 
 |     }; | 
 | } | 
 |  | 
 | export type ListForSymbolDraw = SeriesData<Model<SymbolDrawItemModelOption & AnimationOptionMixin>>; | 
 |  | 
 | class SymbolDraw { | 
 |     group = new graphic.Group(); | 
 |  | 
 |     private _data: ListForSymbolDraw; | 
 |  | 
 |     private _SymbolCtor: SymbolLikeCtor; | 
 |  | 
 |     private _seriesScope: SymbolDrawSeriesScope; | 
 |  | 
 |     private _getSymbolPoint: UpdateOpt['getSymbolPoint']; | 
 |  | 
 |     private _progressiveEls: SymbolLike[]; | 
 |  | 
 |     constructor(SymbolCtor?: SymbolLikeCtor) { | 
 |         this._SymbolCtor = SymbolCtor || SymbolClz as SymbolLikeCtor; | 
 |     } | 
 |  | 
 |     /** | 
 |      * Update symbols draw by new data | 
 |      */ | 
 |     updateData(data: ListForSymbolDraw, opt?: UpdateOpt) { | 
 |         // Remove progressive els. | 
 |         this._progressiveEls = null; | 
 |  | 
 |         opt = normalizeUpdateOpt(opt); | 
 |  | 
 |         const group = this.group; | 
 |         const seriesModel = data.hostModel; | 
 |         const oldData = this._data; | 
 |         const SymbolCtor = this._SymbolCtor; | 
 |         const disableAnimation = opt.disableAnimation; | 
 |  | 
 |         const seriesScope = makeSeriesScope(data); | 
 |  | 
 |         const symbolUpdateOpt = { disableAnimation }; | 
 |  | 
 |         const getSymbolPoint = opt.getSymbolPoint || function (idx: number) { | 
 |             return data.getItemLayout(idx); | 
 |         }; | 
 |  | 
 |  | 
 |         // There is no oldLineData only when first rendering or switching from | 
 |         // stream mode to normal mode, where previous elements should be removed. | 
 |         if (!oldData) { | 
 |             group.removeAll(); | 
 |         } | 
 |  | 
 |         data.diff(oldData) | 
 |             .add(function (newIdx) { | 
 |                 const point = getSymbolPoint(newIdx); | 
 |                 if (symbolNeedsDraw(data, point, newIdx, opt)) { | 
 |                     const symbolEl = new SymbolCtor(data, newIdx, seriesScope, symbolUpdateOpt); | 
 |                     symbolEl.setPosition(point); | 
 |                     data.setItemGraphicEl(newIdx, symbolEl); | 
 |                     group.add(symbolEl); | 
 |                 } | 
 |             }) | 
 |             .update(function (newIdx, oldIdx) { | 
 |                 let symbolEl = oldData.getItemGraphicEl(oldIdx) as SymbolLike; | 
 |  | 
 |                 const point = getSymbolPoint(newIdx) as number[]; | 
 |                 if (!symbolNeedsDraw(data, point, newIdx, opt)) { | 
 |                     group.remove(symbolEl); | 
 |                     return; | 
 |                 } | 
 |                 const newSymbolType = data.getItemVisual(newIdx, 'symbol') || 'circle'; | 
 |                 const oldSymbolType = symbolEl | 
 |                     && (symbolEl as SymbolClz).getSymbolType | 
 |                     && (symbolEl as SymbolClz).getSymbolType(); | 
 |  | 
 |                 if (!symbolEl | 
 |                     // Create a new if symbol type changed. | 
 |                     || (oldSymbolType && oldSymbolType !== newSymbolType) | 
 |                 ) { | 
 |                     group.remove(symbolEl); | 
 |                     symbolEl = new SymbolCtor(data, newIdx, seriesScope, symbolUpdateOpt); | 
 |                     symbolEl.setPosition(point); | 
 |                 } | 
 |                 else { | 
 |                     symbolEl.updateData(data, newIdx, seriesScope, symbolUpdateOpt); | 
 |                     const target = { | 
 |                         x: point[0], | 
 |                         y: point[1] | 
 |                     }; | 
 |                     disableAnimation | 
 |                         ? symbolEl.attr(target) | 
 |                         : graphic.updateProps(symbolEl, target, seriesModel); | 
 |                 } | 
 |  | 
 |                 // Add back | 
 |                 group.add(symbolEl); | 
 |  | 
 |                 data.setItemGraphicEl(newIdx, symbolEl); | 
 |             }) | 
 |             .remove(function (oldIdx) { | 
 |                 const el = oldData.getItemGraphicEl(oldIdx) as SymbolLike; | 
 |                 el && el.fadeOut(function () { | 
 |                     group.remove(el); | 
 |                 }, seriesModel as SeriesModel); | 
 |             }) | 
 |             .execute(); | 
 |  | 
 |         this._getSymbolPoint = getSymbolPoint; | 
 |         this._data = data; | 
 |     }; | 
 |  | 
 |     updateLayout() { | 
 |         const data = this._data; | 
 |         if (data) { | 
 |             // Not use animation | 
 |             data.eachItemGraphicEl((el, idx) => { | 
 |                 const point = this._getSymbolPoint(idx); | 
 |                 el.setPosition(point); | 
 |                 el.markRedraw(); | 
 |             }); | 
 |         } | 
 |     }; | 
 |  | 
 |     incrementalPrepareUpdate(data: ListForSymbolDraw) { | 
 |         this._seriesScope = makeSeriesScope(data); | 
 |         this._data = null; | 
 |         this.group.removeAll(); | 
 |     }; | 
 |  | 
 |     /** | 
 |      * Update symbols draw by new data | 
 |      */ | 
 |     incrementalUpdate(taskParams: StageHandlerProgressParams, data: ListForSymbolDraw, opt?: UpdateOpt) { | 
 |  | 
 |         // Clear | 
 |         this._progressiveEls = []; | 
 |  | 
 |         opt = normalizeUpdateOpt(opt); | 
 |  | 
 |         function updateIncrementalAndHover(el: Displayable) { | 
 |             if (!el.isGroup) { | 
 |                 el.incremental = true; | 
 |                 el.ensureState('emphasis').hoverLayer = true; | 
 |             } | 
 |         } | 
 |         for (let idx = taskParams.start; idx < taskParams.end; idx++) { | 
 |             const point = data.getItemLayout(idx) as number[]; | 
 |             if (symbolNeedsDraw(data, point, idx, opt)) { | 
 |                 const el = new this._SymbolCtor(data, idx, this._seriesScope); | 
 |                 el.traverse(updateIncrementalAndHover); | 
 |                 el.setPosition(point); | 
 |                 this.group.add(el); | 
 |                 data.setItemGraphicEl(idx, el); | 
 |                 this._progressiveEls.push(el); | 
 |             } | 
 |         } | 
 |     }; | 
 |  | 
 |     eachRendered(cb: (el: Element) => boolean | void) { | 
 |         graphic.traverseElements(this._progressiveEls || this.group, cb); | 
 |     } | 
 |  | 
 |     remove(enableAnimation?: boolean) { | 
 |         const group = this.group; | 
 |         const data = this._data; | 
 |         // Incremental model do not have this._data. | 
 |         if (data && enableAnimation) { | 
 |             data.eachItemGraphicEl(function (el: SymbolLike) { | 
 |                 el.fadeOut(function () { | 
 |                     group.remove(el); | 
 |                 }, data.hostModel as SeriesModel); | 
 |             }); | 
 |         } | 
 |         else { | 
 |             group.removeAll(); | 
 |         } | 
 |     }; | 
 |  | 
 | } | 
 |  | 
 | export default SymbolDraw; |