|  | /* | 
|  | * 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. | 
|  | */ | 
|  |  | 
|  | /** | 
|  | * Provide effect for line | 
|  | */ | 
|  |  | 
|  | import * as graphic from '../../util/graphic'; | 
|  | import Line from './Line'; | 
|  | import * as zrUtil from 'zrender/src/core/util'; | 
|  | import {createSymbol} from '../../util/symbol'; | 
|  | import * as vec2 from 'zrender/src/core/vector'; | 
|  | import * as curveUtil from 'zrender/src/core/curve'; | 
|  | import type SeriesData from '../../data/SeriesData'; | 
|  | import { LineDrawSeriesScope, LineDrawModelOption } from './LineDraw'; | 
|  | import Model from '../../model/Model'; | 
|  | import { ColorString } from '../../util/types'; | 
|  |  | 
|  | export type ECSymbolOnEffectLine = ReturnType<typeof createSymbol> & { | 
|  | __t: number | 
|  | __lastT: number | 
|  | __p1: number[] | 
|  | __p2: number[] | 
|  | __cp1: number[] | 
|  | }; | 
|  | class EffectLine extends graphic.Group { | 
|  |  | 
|  | private _symbolType: string; | 
|  |  | 
|  | private _period: number; | 
|  |  | 
|  | private _loop: boolean; | 
|  |  | 
|  | private _roundTrip: boolean; | 
|  |  | 
|  | private _symbolScale: number[]; | 
|  |  | 
|  | constructor(lineData: SeriesData, idx: number, seriesScope: LineDrawSeriesScope) { | 
|  | super(); | 
|  | this.add(this.createLine(lineData, idx, seriesScope)); | 
|  |  | 
|  | this._updateEffectSymbol(lineData, idx); | 
|  | } | 
|  |  | 
|  | createLine(lineData: SeriesData, idx: number, seriesScope: LineDrawSeriesScope): graphic.Group { | 
|  | return new Line(lineData, idx, seriesScope); | 
|  | } | 
|  |  | 
|  | private _updateEffectSymbol(lineData: SeriesData, idx: number) { | 
|  | const itemModel = lineData.getItemModel<LineDrawModelOption>(idx); | 
|  | const effectModel = itemModel.getModel('effect'); | 
|  | let size = effectModel.get('symbolSize'); | 
|  | const symbolType = effectModel.get('symbol'); | 
|  | if (!zrUtil.isArray(size)) { | 
|  | size = [size, size]; | 
|  | } | 
|  |  | 
|  | const lineStyle = lineData.getItemVisual(idx, 'style'); | 
|  | const color = effectModel.get('color') || (lineStyle && lineStyle.stroke); | 
|  | let symbol = this.childAt(1) as ECSymbolOnEffectLine; | 
|  |  | 
|  | if (this._symbolType !== symbolType) { | 
|  | // Remove previous | 
|  | this.remove(symbol); | 
|  |  | 
|  | symbol = createSymbol( | 
|  | symbolType, -0.5, -0.5, 1, 1, color | 
|  | ) as ECSymbolOnEffectLine; | 
|  | symbol.z2 = 100; | 
|  | symbol.culling = true; | 
|  |  | 
|  | this.add(symbol); | 
|  | } | 
|  |  | 
|  | // Symbol may be removed if loop is false | 
|  | if (!symbol) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | // Shadow color is same with color in default | 
|  | symbol.setStyle('shadowColor', color as ColorString); | 
|  | symbol.setStyle(effectModel.getItemStyle(['color'])); | 
|  |  | 
|  | symbol.scaleX = size[0]; | 
|  | symbol.scaleY = size[1]; | 
|  |  | 
|  | symbol.setColor(color); | 
|  |  | 
|  | this._symbolType = symbolType; | 
|  | this._symbolScale = size; | 
|  |  | 
|  | this._updateEffectAnimation(lineData, effectModel, idx); | 
|  | } | 
|  |  | 
|  | private _updateEffectAnimation( | 
|  | lineData: SeriesData, | 
|  | effectModel: Model<LineDrawModelOption['effect']>, | 
|  | idx: number | 
|  | ) { | 
|  |  | 
|  | const symbol = this.childAt(1) as ECSymbolOnEffectLine; | 
|  | if (!symbol) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | const points = lineData.getItemLayout(idx); | 
|  |  | 
|  | let period = effectModel.get('period') * 1000; | 
|  | const loop = effectModel.get('loop'); | 
|  | const roundTrip = effectModel.get('roundTrip'); | 
|  | const constantSpeed = effectModel.get('constantSpeed'); | 
|  | const delayExpr = zrUtil.retrieve(effectModel.get('delay'), function (idx) { | 
|  | return idx / lineData.count() * period / 3; | 
|  | }); | 
|  |  | 
|  | // Ignore when updating | 
|  | symbol.ignore = true; | 
|  |  | 
|  | this._updateAnimationPoints(symbol, points); | 
|  |  | 
|  | if (constantSpeed > 0) { | 
|  | period = this._getLineLength(symbol) / constantSpeed * 1000; | 
|  | } | 
|  |  | 
|  | if (period !== this._period || loop !== this._loop || roundTrip !== this._roundTrip) { | 
|  | symbol.stopAnimation(); | 
|  | let delayNum: number; | 
|  | if (zrUtil.isFunction(delayExpr)) { | 
|  | delayNum = delayExpr(idx); | 
|  | } | 
|  | else { | 
|  | delayNum = delayExpr; | 
|  | } | 
|  | if (symbol.__t > 0) { | 
|  | delayNum = -period * symbol.__t; | 
|  | } | 
|  |  | 
|  | this._animateSymbol( | 
|  | symbol, period, delayNum, loop, roundTrip | 
|  | ); | 
|  | } | 
|  |  | 
|  | this._period = period; | 
|  | this._loop = loop; | 
|  | this._roundTrip = roundTrip; | 
|  | } | 
|  |  | 
|  | private _animateSymbol( | 
|  | symbol: ECSymbolOnEffectLine, period: number, delayNum: number, loop: boolean, roundTrip: boolean) { | 
|  | if (period > 0) { | 
|  | symbol.__t = 0; | 
|  | const self = this; | 
|  | const animator = symbol.animate('', loop) | 
|  | .when(roundTrip ? period * 2 : period, { | 
|  | __t: roundTrip ? 2 : 1 | 
|  | }) | 
|  | .delay(delayNum) | 
|  | .during(function () { | 
|  | self._updateSymbolPosition(symbol); | 
|  | }); | 
|  | if (!loop) { | 
|  | animator.done(function () { | 
|  | self.remove(symbol); | 
|  | }); | 
|  | } | 
|  | animator.start(); | 
|  | } | 
|  | } | 
|  |  | 
|  | protected _getLineLength(symbol: ECSymbolOnEffectLine) { | 
|  | // Not so accurate | 
|  | return (vec2.dist(symbol.__p1, symbol.__cp1) | 
|  | + vec2.dist(symbol.__cp1, symbol.__p2)); | 
|  | } | 
|  |  | 
|  | protected _updateAnimationPoints(symbol: ECSymbolOnEffectLine, points: number[][]) { | 
|  | symbol.__p1 = points[0]; | 
|  | symbol.__p2 = points[1]; | 
|  | symbol.__cp1 = points[2] || [ | 
|  | (points[0][0] + points[1][0]) / 2, | 
|  | (points[0][1] + points[1][1]) / 2 | 
|  | ]; | 
|  | } | 
|  |  | 
|  | updateData(lineData: SeriesData, idx: number, seriesScope: LineDrawSeriesScope) { | 
|  | (this.childAt(0) as Line).updateData(lineData, idx, seriesScope); | 
|  | this._updateEffectSymbol(lineData, idx); | 
|  | } | 
|  |  | 
|  | protected _updateSymbolPosition(symbol: ECSymbolOnEffectLine) { | 
|  | const p1 = symbol.__p1; | 
|  | const p2 = symbol.__p2; | 
|  | const cp1 = symbol.__cp1; | 
|  | const t = symbol.__t < 1 ? symbol.__t : 2 - symbol.__t; | 
|  | const pos = [symbol.x, symbol.y]; | 
|  | const lastPos = pos.slice(); | 
|  | const quadraticAt = curveUtil.quadraticAt; | 
|  | const quadraticDerivativeAt = curveUtil.quadraticDerivativeAt; | 
|  | pos[0] = quadraticAt(p1[0], cp1[0], p2[0], t); | 
|  | pos[1] = quadraticAt(p1[1], cp1[1], p2[1], t); | 
|  |  | 
|  | // Tangent | 
|  | const tx = symbol.__t < 1 ? quadraticDerivativeAt(p1[0], cp1[0], p2[0], t) | 
|  | : quadraticDerivativeAt(p2[0], cp1[0], p1[0], 1 - t); | 
|  | const ty = symbol.__t < 1 ? quadraticDerivativeAt(p1[1], cp1[1], p2[1], t) | 
|  | : quadraticDerivativeAt(p2[1], cp1[1], p1[1], 1 - t); | 
|  |  | 
|  |  | 
|  | symbol.rotation = -Math.atan2(ty, tx) - Math.PI / 2; | 
|  | // enable continuity trail for 'line', 'rect', 'roundRect' symbolType | 
|  | if (this._symbolType === 'line' || this._symbolType === 'rect' || this._symbolType === 'roundRect') { | 
|  | if (symbol.__lastT !== undefined && symbol.__lastT < symbol.__t) { | 
|  | symbol.scaleY = vec2.dist(lastPos, pos) * 1.05; | 
|  | // make sure the last segment render within endPoint | 
|  | if (t === 1) { | 
|  | pos[0] = lastPos[0] + (pos[0] - lastPos[0]) / 2; | 
|  | pos[1] = lastPos[1] + (pos[1] - lastPos[1]) / 2; | 
|  | } | 
|  | } | 
|  | else if (symbol.__lastT === 1) { | 
|  | // After first loop, symbol.__t does NOT start with 0, so connect p1 to pos directly. | 
|  | symbol.scaleY = 2 * vec2.dist(p1, pos); | 
|  | } | 
|  | else { | 
|  | symbol.scaleY = this._symbolScale[1]; | 
|  | } | 
|  | } | 
|  | symbol.__lastT = symbol.__t; | 
|  | symbol.ignore = false; | 
|  | symbol.x = pos[0]; | 
|  | symbol.y = pos[1]; | 
|  | } | 
|  |  | 
|  |  | 
|  | updateLayout(lineData: SeriesData, idx: number) { | 
|  | (this.childAt(0) as Line).updateLayout(lineData, idx); | 
|  |  | 
|  | const effectModel = lineData.getItemModel<LineDrawModelOption>(idx).getModel('effect'); | 
|  | this._updateEffectAnimation(lineData, effectModel, idx); | 
|  | } | 
|  | } | 
|  | export default EffectLine; |