| /* | 
 | * 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 {Group, Path} from '../../util/graphic'; | 
 | import { enterEmphasis, leaveEmphasis, toggleHoverEmphasis } from '../../util/states'; | 
 | import SymbolClz from './Symbol'; | 
 | import SeriesData from '../../data/SeriesData'; | 
 | import type { ZRColor, ECElement } from '../../util/types'; | 
 | import type Displayable from 'zrender/src/graphic/Displayable'; | 
 | import { SymbolDrawItemModelOption } from './SymbolDraw'; | 
 |  | 
 | interface RippleEffectCfg { | 
 |     showEffectOn?: 'emphasis' | 'render' | 
 |     rippleScale?: number | 
 |     brushType?: 'fill' | 'stroke' | 
 |     period?: number | 
 |     effectOffset?: number | 
 |     z?: number | 
 |     zlevel?: number | 
 |     symbolType?: string | 
 |     color?: ZRColor | 
 |     rippleEffectColor?: ZRColor, | 
 |     rippleNumber?: number | 
 | } | 
 |  | 
 | function updateRipplePath(rippleGroup: Group, effectCfg: RippleEffectCfg) { | 
 |     const color = effectCfg.rippleEffectColor || effectCfg.color; | 
 |     rippleGroup.eachChild(function (ripplePath: Displayable) { | 
 |         ripplePath.attr({ | 
 |             z: effectCfg.z, | 
 |             zlevel: effectCfg.zlevel, | 
 |             style: { | 
 |                 stroke: effectCfg.brushType === 'stroke' ? color : null, | 
 |                 fill: effectCfg.brushType === 'fill' ? color : null | 
 |             } | 
 |         }); | 
 |     }); | 
 | } | 
 |  | 
 | class EffectSymbol extends Group { | 
 |  | 
 |     private _effectCfg: RippleEffectCfg; | 
 |  | 
 |     constructor(data: SeriesData, idx: number) { | 
 |         super(); | 
 |  | 
 |         const symbol = new SymbolClz(data, idx); | 
 |         const rippleGroup = new Group(); | 
 |         this.add(symbol); | 
 |         this.add(rippleGroup); | 
 |  | 
 |         this.updateData(data, idx); | 
 |     } | 
 |  | 
 |  | 
 |     stopEffectAnimation() { | 
 |         (this.childAt(1) as Group).removeAll(); | 
 |     } | 
 |  | 
 |     startEffectAnimation(effectCfg: RippleEffectCfg) { | 
 |         const symbolType = effectCfg.symbolType; | 
 |         const color = effectCfg.color; | 
 |         const rippleNumber = effectCfg.rippleNumber; | 
 |         const rippleGroup = this.childAt(1) as Group; | 
 |  | 
 |         for (let i = 0; i < rippleNumber; i++) { | 
 |             // 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 #4136. | 
 |             const ripplePath = createSymbol( | 
 |                 symbolType, -1, -1, 2, 2, color | 
 |             ); | 
 |             ripplePath.attr({ | 
 |                 style: { | 
 |                     strokeNoScale: true | 
 |                 }, | 
 |                 z2: 99, | 
 |                 silent: true, | 
 |                 scaleX: 0.5, | 
 |                 scaleY: 0.5 | 
 |             }); | 
 |  | 
 |             const delay = -i / rippleNumber * effectCfg.period + effectCfg.effectOffset; | 
 |             ripplePath.animate('', true) | 
 |                 .when(effectCfg.period, { | 
 |                     scaleX: effectCfg.rippleScale / 2, | 
 |                     scaleY: effectCfg.rippleScale / 2 | 
 |                 }) | 
 |                 .delay(delay) | 
 |                 .start(); | 
 |             ripplePath.animateStyle(true) | 
 |                 .when(effectCfg.period, { | 
 |                     opacity: 0 | 
 |                 }) | 
 |                 .delay(delay) | 
 |                 .start(); | 
 |  | 
 |             rippleGroup.add(ripplePath); | 
 |         } | 
 |  | 
 |         updateRipplePath(rippleGroup, effectCfg); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Update effect symbol | 
 |      */ | 
 |     updateEffectAnimation(effectCfg: RippleEffectCfg) { | 
 |         const oldEffectCfg = this._effectCfg; | 
 |         const rippleGroup = this.childAt(1) as Group; | 
 |  | 
 |         // Must reinitialize effect if following configuration changed | 
 |         const DIFFICULT_PROPS = ['symbolType', 'period', 'rippleScale', 'rippleNumber'] as const; | 
 |         for (let i = 0; i < DIFFICULT_PROPS.length; i++) { | 
 |             const propName = DIFFICULT_PROPS[i]; | 
 |             if (oldEffectCfg[propName] !== effectCfg[propName]) { | 
 |                 this.stopEffectAnimation(); | 
 |                 this.startEffectAnimation(effectCfg); | 
 |                 return; | 
 |             } | 
 |         } | 
 |  | 
 |         updateRipplePath(rippleGroup, effectCfg); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Highlight symbol | 
 |      */ | 
 |     highlight() { | 
 |         enterEmphasis(this); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Downplay symbol | 
 |      */ | 
 |     downplay() { | 
 |         leaveEmphasis(this); | 
 |     } | 
 |  | 
 |     getSymbolType() { | 
 |         const symbol = this.childAt(0) as SymbolClz; | 
 |         return symbol && symbol.getSymbolType(); | 
 |     } | 
 |  | 
 |     /** | 
 |      * Update symbol properties | 
 |      */ | 
 |     updateData(data: SeriesData, idx: number) { | 
 |         const seriesModel = data.hostModel; | 
 |  | 
 |         (this.childAt(0) as SymbolClz).updateData(data, idx); | 
 |  | 
 |         const rippleGroup = this.childAt(1); | 
 |         const itemModel = data.getItemModel<SymbolDrawItemModelOption>(idx); | 
 |         const symbolType = data.getItemVisual(idx, 'symbol'); | 
 |         const symbolSize = normalizeSymbolSize(data.getItemVisual(idx, 'symbolSize')); | 
 |  | 
 |         const symbolStyle = data.getItemVisual(idx, 'style'); | 
 |         const color = symbolStyle && symbolStyle.fill; | 
 |         const emphasisModel = itemModel.getModel('emphasis'); | 
 |  | 
 |         rippleGroup.setScale(symbolSize); | 
 |  | 
 |         rippleGroup.traverse(function (ripplePath: Path) { | 
 |             ripplePath.setStyle('fill', color); | 
 |         }); | 
 |  | 
 |         const symbolOffset = normalizeSymbolOffset(data.getItemVisual(idx, 'symbolOffset'), symbolSize); | 
 |         if (symbolOffset) { | 
 |             rippleGroup.x = symbolOffset[0]; | 
 |             rippleGroup.y = symbolOffset[1]; | 
 |         } | 
 |  | 
 |         const symbolRotate = data.getItemVisual(idx, 'symbolRotate'); | 
 |         rippleGroup.rotation = (symbolRotate || 0) * Math.PI / 180 || 0; | 
 |  | 
 |         const effectCfg: RippleEffectCfg = {}; | 
 |  | 
 |         effectCfg.showEffectOn = seriesModel.get('showEffectOn'); | 
 |         effectCfg.rippleScale = itemModel.get(['rippleEffect', 'scale']); | 
 |         effectCfg.brushType = itemModel.get(['rippleEffect', 'brushType']); | 
 |         effectCfg.period = itemModel.get(['rippleEffect', 'period']) * 1000; | 
 |         effectCfg.effectOffset = idx / data.count(); | 
 |         effectCfg.z = seriesModel.getShallow('z') || 0; | 
 |         effectCfg.zlevel = seriesModel.getShallow('zlevel') || 0; | 
 |         effectCfg.symbolType = symbolType; | 
 |         effectCfg.color = color; | 
 |         effectCfg.rippleEffectColor = itemModel.get(['rippleEffect', 'color']); | 
 |         effectCfg.rippleNumber = itemModel.get(['rippleEffect', 'number']); | 
 |  | 
 |         if (effectCfg.showEffectOn === 'render') { | 
 |             this._effectCfg | 
 |                 ? this.updateEffectAnimation(effectCfg) | 
 |                 : this.startEffectAnimation(effectCfg); | 
 |  | 
 |             this._effectCfg = effectCfg; | 
 |         } | 
 |         else { | 
 |             // Not keep old effect config | 
 |             this._effectCfg = null; | 
 |  | 
 |             this.stopEffectAnimation(); | 
 |  | 
 |             (this as ECElement).onHoverStateChange = (toState) => { | 
 |                 if (toState === 'emphasis') { | 
 |                     if (effectCfg.showEffectOn !== 'render') { | 
 |                         this.startEffectAnimation(effectCfg); | 
 |                     } | 
 |                 } | 
 |                 else if (toState === 'normal') { | 
 |                     if (effectCfg.showEffectOn !== 'render') { | 
 |                         this.stopEffectAnimation(); | 
 |                     } | 
 |                 } | 
 |             }; | 
 |         } | 
 |  | 
 |         this._effectCfg = effectCfg; | 
 |  | 
 |         toggleHoverEmphasis( | 
 |             this, | 
 |             emphasisModel.get('focus'), | 
 |             emphasisModel.get('blurScope'), | 
 |             emphasisModel.get('disabled') | 
 |         ); | 
 |     }; | 
 |  | 
 |     fadeOut(cb: () => void) { | 
 |         cb && cb(); | 
 |     }; | 
 |  | 
 | } | 
 |  | 
 | export default EffectSymbol; |