LineView.ts 44.2 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
/*
* 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.
*/

L
lang 已提交
20
// FIXME step not support polar
L
lang 已提交
21

S
sushuang 已提交
22
import * as zrUtil from 'zrender/src/core/util';
S
sushuang 已提交
23 24 25
import SymbolDraw from '../helper/SymbolDraw';
import SymbolClz from '../helper/Symbol';
import lineAnimationDiff from './lineAnimationDiff';
S
sushuang 已提交
26 27
import * as graphic from '../../util/graphic';
import * as modelUtil from '../../util/model';
P
pissang 已提交
28
import {ECPolyline, ECPolygon} from './poly';
S
sushuang 已提交
29
import ChartView from '../../view/Chart';
S
sushuang 已提交
30
import {prepareDataCoordInfo, getStackedOnPoint} from './helper';
31
import {createGridClipPath, createPolarClipPath} from '../helper/createClipPathFromCoordSys';
P
pissang 已提交
32 33
import LineSeriesModel, { LineSeriesOption } from './LineSeries';
import type GlobalModel from '../../model/Global';
34
import type ExtensionAPI from '../../core/ExtensionAPI';
P
pissang 已提交
35 36 37 38
// TODO
import Cartesian2D from '../../coord/cartesian/Cartesian2D';
import Polar from '../../coord/polar/Polar';
import type List from '../../data/List';
39 40 41 42 43 44 45
import type {
    Payload,
    Dictionary,
    ColorString,
    ECElement,
    DisplayState,
    LabelOption,
S
susiwen8 已提交
46
    ParsedValue
47
} from '../../util/types';
P
pissang 已提交
48 49
import type OrdinalScale from '../../scale/Ordinal';
import type Axis2D from '../../coord/cartesian/Axis2D';
50
import { CoordinateSystemClipArea, isCoordinateSystemType } from '../../coord/CoordinateSystem';
51
import { setStatesStylesFromModel, setStatesFlag, enableHoverEmphasis } from '../../util/states';
O
Ovilia 已提交
52
import Model from '../../model/Model';
53
import {setLabelStyle, getLabelStatesModels, labelInner} from '../../label/labelStyle';
54
import {getDefaultLabel, getDefaultInterpolatedLabel} from '../helper/labelHelper';
55

56
import { getECData } from '../../util/innerStore';
57
import { createFloat32Array } from '../../util/vendor';
S
susiwen8 已提交
58
import { convertToColorString } from '../../util/format';
P
pissang 已提交
59

1
100pah 已提交
60 61
type PolarArea = ReturnType<Polar['getArea']>;
type Cartesian2DArea = ReturnType<Cartesian2D['getArea']>;
P
pissang 已提交
62 63 64 65

interface SymbolExtended extends SymbolClz {
    __temp: boolean
}
S
sushuang 已提交
66

67
function isPointsSame(points1: ArrayLike<number>, points2: ArrayLike<number>) {
S
sushuang 已提交
68 69 70
    if (points1.length !== points2.length) {
        return;
    }
71
    for (let i = 0; i < points1.length; i++) {
72
        if (points1[i] !== points2[i]) {
73 74 75
            return;
        }
    }
S
sushuang 已提交
76 77 78
    return true;
}

79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101
function bboxFromPoints(points: ArrayLike<number>) {
    let minX = Infinity;
    let minY = Infinity;
    let maxX = -Infinity;
    let maxY = -Infinity;

    for (let i = 0; i < points.length;) {
        const x = points[i++];
        const y = points[i++];
        if (!isNaN(x)) {
            minX = Math.min(x, minX);
            maxX = Math.max(x, maxX);
        }
        if (!isNaN(y)) {
            minY = Math.min(y, minY);
            maxY = Math.max(y, maxY);
        }
    }
    return [
        [minX, minY],
        [maxX, maxY]
    ];
}
102

103
function getBoundingDiff(points1: ArrayLike<number>, points2: ArrayLike<number>): number {
104

105 106
    const [min1, max1] = bboxFromPoints(points1);
    const [min2, max2] = bboxFromPoints(points2);
107 108 109 110 111 112 113 114 115 116 117

    // Get a max value from each corner of two boundings.
    return Math.max(
        Math.abs(min1[0] - min2[0]),
        Math.abs(min1[1] - min2[1]),

        Math.abs(max1[0] - max2[0]),
        Math.abs(max1[1] - max2[1])
    );
}

P
pissang 已提交
118 119
function getSmooth(smooth: number | boolean) {
    return typeof smooth === 'number' ? smooth : (smooth ? 0.5 : 0);
S
sushuang 已提交
120 121
}

P
pissang 已提交
122 123 124 125 126
function getStackedOnPoints(
    coordSys: Cartesian2D | Polar,
    data: List,
    dataCoordInfo: ReturnType<typeof prepareDataCoordInfo>
) {
S
sushuang 已提交
127 128
    if (!dataCoordInfo.valueDim) {
        return [];
S
sushuang 已提交
129
    }
L
lang 已提交
130

131 132 133 134 135 136
    const len = data.count();
    const points = createFloat32Array(len * 2);
    for (let idx = 0; idx < len; idx++) {
        const pt = getStackedOnPoint(dataCoordInfo, coordSys, data, idx);
        points[idx * 2] = pt[0];
        points[idx * 2 + 1] = pt[1];
S
sushuang 已提交
137
    }
S
sushuang 已提交
138

S
sushuang 已提交
139
    return points;
S
sushuang 已提交
140 141
}

P
pissang 已提交
142
function turnPointsIntoStep(
143
    points: ArrayLike<number>,
P
pissang 已提交
144 145
    coordSys: Cartesian2D | Polar,
    stepTurnAt: 'start' | 'end' | 'middle'
146
): number[] {
147 148
    const baseAxis = coordSys.getBaseAxis();
    const baseIndex = baseAxis.dim === 'x' || baseAxis.dim === 'radius' ? 0 : 1;
S
sushuang 已提交
149

150
    const stepPoints: number[] = [];
151
    let i = 0;
152 153 154 155 156 157 158 159 160
    const stepPt: number[] = [];
    const pt: number[] = [];
    const nextPt: number[] = [];
    for (; i < points.length - 2; i += 2) {
        nextPt[0] = points[i + 2];
        nextPt[1] = points[i + 3];
        pt[0] = points[i];
        pt[1] = points[i + 1];
        stepPoints.push(pt[0], pt[1]);
S
sushuang 已提交
161 162 163 164 165

        switch (stepTurnAt) {
            case 'end':
                stepPt[baseIndex] = nextPt[baseIndex];
                stepPt[1 - baseIndex] = pt[1 - baseIndex];
166
                stepPoints.push(stepPt[0], stepPt[1]);
S
sushuang 已提交
167 168
                break;
            case 'middle':
169 170
                const middle = (pt[baseIndex] + nextPt[baseIndex]) / 2;
                const stepPt2 = [];
S
sushuang 已提交
171 172 173
                stepPt[baseIndex] = stepPt2[baseIndex] = middle;
                stepPt[1 - baseIndex] = pt[1 - baseIndex];
                stepPt2[1 - baseIndex] = nextPt[1 - baseIndex];
174
                stepPoints.push(stepPt[0], stepPt[1]);
P
pissang 已提交
175
                stepPoints.push(stepPt2[0], stepPt2[1]);
S
sushuang 已提交
176 177
                break;
            default:
178
                // default is start
S
sushuang 已提交
179 180
                stepPt[baseIndex] = pt[baseIndex];
                stepPt[1 - baseIndex] = nextPt[1 - baseIndex];
181
                stepPoints.push(stepPt[0], stepPt[1]);
182 183
        }
    }
S
sushuang 已提交
184
    // Last points
185
    stepPoints.push(points[i++], points[i++]);
S
sushuang 已提交
186 187 188
    return stepPoints;
}

P
pissang 已提交
189 190 191 192
function getVisualGradient(
    data: List,
    coordSys: Cartesian2D | Polar
) {
193
    const visualMetaList = data.getVisual('visualMeta');
S
sushuang 已提交
194 195 196
    if (!visualMetaList || !visualMetaList.length || !data.count()) {
        // When data.count() is 0, gradient range can not be calculated.
        return;
197 198
    }

S
sushuang 已提交
199 200 201 202 203 204 205
    if (coordSys.type !== 'cartesian2d') {
        if (__DEV__) {
            console.warn('Visual map on line style is only supported on cartesian2d.');
        }
        return;
    }

206 207
    let coordDim: 'x' | 'y';
    let visualMeta;
S
sushuang 已提交
208

209
    for (let i = visualMetaList.length - 1; i >= 0; i--) {
210 211 212
        const dimIndex = visualMetaList[i].dimension;
        const dimName = data.dimensions[dimIndex];
        const dimInfo = data.getDimensionInfo(dimName);
P
pissang 已提交
213
        coordDim = (dimInfo && dimInfo.coordDim) as 'x' | 'y';
S
sushuang 已提交
214
        // Can only be x or y
S
sushuang 已提交
215
        if (coordDim === 'x' || coordDim === 'y') {
S
sushuang 已提交
216 217
            visualMeta = visualMetaList[i];
            break;
L
lang 已提交
218 219
        }
    }
S
sushuang 已提交
220 221

    if (!visualMeta) {
S
sushuang 已提交
222 223
        if (__DEV__) {
            console.warn('Visual map on line style only support x or y dimension.');
224
        }
S
sushuang 已提交
225 226
        return;
    }
227

S
sushuang 已提交
228 229 230 231 232 233 234
    // If the area to be rendered is bigger than area defined by LinearGradient,
    // the canvas spec prescribes that the color of the first stop and the last
    // stop should be used. But if two stops are added at offset 0, in effect
    // browsers use the color of the second stop to render area outside
    // LinearGradient. So we can only infinitesimally extend area defined in
    // LinearGradient to render `outerColors`.

235
    const axis = coordSys.getAxis(coordDim);
S
sushuang 已提交
236

P
pissang 已提交
237 238 239 240 241
    interface ColorStop {
        offset: number
        coord?: number
        color: ColorString
    }
S
sushuang 已提交
242
    // dataToCoor mapping may not be linear, but must be monotonic.
243
    const colorStops: ColorStop[] = zrUtil.map(visualMeta.stops, function (stop) {
S
sushuang 已提交
244
        return {
P
pissang 已提交
245
            offset: 0,
S
sushuang 已提交
246 247 248 249
            coord: axis.toGlobalCoord(axis.dataToCoord(stop.value)),
            color: stop.color
        };
    });
250 251
    const stopLen = colorStops.length;
    const outerColors = visualMeta.outerColors.slice();
252

S
sushuang 已提交
253 254 255 256
    if (stopLen && colorStops[0].coord > colorStops[stopLen - 1].coord) {
        colorStops.reverse();
        outerColors.reverse();
    }
257

258 259 260 261
    const tinyExtent = 10; // Arbitrary value: 10px
    const minCoord = colorStops[0].coord - tinyExtent;
    const maxCoord = colorStops[stopLen - 1].coord + tinyExtent;
    const coordSpan = maxCoord - minCoord;
262

S
sushuang 已提交
263 264 265
    if (coordSpan < 1e-3) {
        return 'transparent';
    }
266

S
sushuang 已提交
267 268 269 270 271 272 273 274 275 276 277
    zrUtil.each(colorStops, function (stop) {
        stop.offset = (stop.coord - minCoord) / coordSpan;
    });
    colorStops.push({
        offset: stopLen ? colorStops[stopLen - 1].offset : 0.5,
        color: outerColors[1] || 'transparent'
    });
    colorStops.unshift({ // notice colorStops.length have been changed.
        offset: stopLen ? colorStops[0].offset : 0.5,
        color: outerColors[0] || 'transparent'
    });
278

S
sushuang 已提交
279 280 281 282
    // zrUtil.each(colorStops, function (colorStop) {
    //     // Make sure each offset has rounded px to avoid not sharp edge
    //     colorStop.offset = (Math.round(colorStop.offset * (end - start) + start) - start) / (end - start);
    // });
283

284
    const gradient = new graphic.LinearGradient(0, 0, 0, 0, colorStops, true);
S
sushuang 已提交
285
    gradient[coordDim] = minCoord;
P
pissang 已提交
286
    gradient[coordDim + '2' as 'x2' | 'y2'] = maxCoord;
L
Tweak  
lang 已提交
287

S
sushuang 已提交
288 289
    return gradient;
}
290

P
pissang 已提交
291 292 293 294 295
function getIsIgnoreFunc(
    seriesModel: LineSeriesModel,
    data: List,
    coordSys: Cartesian2D
) {
296 297
    const showAllSymbol = seriesModel.get('showAllSymbol');
    const isAuto = showAllSymbol === 'auto';
298 299 300 301 302

    if (showAllSymbol && !isAuto) {
        return;
    }

303
    const categoryAxis = coordSys.getAxesByScale('ordinal')[0];
304 305 306 307 308 309 310 311 312 313 314 315 316 317 318
    if (!categoryAxis) {
        return;
    }

    // Note that category label interval strategy might bring some weird effect
    // in some scenario: users may wonder why some of the symbols are not
    // displayed. So we show all symbols as possible as we can.
    if (isAuto
        // Simplify the logic, do not determine label overlap here.
        && canShowAllSymbolForCategory(categoryAxis, data)
    ) {
        return;
    }

    // Otherwise follow the label interval strategy on category axis.
319 320
    const categoryDataDim = data.mapDimension(categoryAxis.dim);
    const labelMap: Dictionary<1> = {};
321 322

    zrUtil.each(categoryAxis.getViewLabels(), function (labelItem) {
1
100pah 已提交
323 324 325
        const ordinalNumber = (categoryAxis.scale as OrdinalScale)
            .getRawOrdinalNumber(labelItem.tickValue);
        labelMap[ordinalNumber] = 1;
326 327
    });

P
pissang 已提交
328
    return function (dataIndex: number) {
329 330 331 332
        return !labelMap.hasOwnProperty(data.get(categoryDataDim, dataIndex));
    };
}

P
pissang 已提交
333 334 335 336
function canShowAllSymbolForCategory(
    categoryAxis: Axis2D,
    data: List
) {
337 338 339 340
    // In mose cases, line is monotonous on category axis, and the label size
    // is close with each other. So we check the symbol size and some of the
    // label size alone with the category axis to estimate whether all symbol
    // can be shown without overlap.
341
    const axisExtent = categoryAxis.getExtent();
342
    let availSize = Math.abs(axisExtent[1] - axisExtent[0]) / (categoryAxis.scale as OrdinalScale).count();
343 344 345
    isNaN(availSize) && (availSize = 0); // 0/0 is NaN.

    // Sampling some points, max 5.
346 347
    const dataLen = data.count();
    const step = Math.max(1, Math.round(dataLen / 5));
348
    for (let dataIndex = 0; dataIndex < dataLen; dataIndex += step) {
349 350 351 352 353 354 355 356 357 358 359 360 361 362
        if (SymbolClz.getSymbolSize(
                data, dataIndex
            // Only for cartesian, where `isHorizontal` exists.
            )[categoryAxis.isHorizontal() ? 1 : 0]
            // Empirical number
            * 1.5 > availSize
        ) {
            return false;
        }
    }

    return true;
}

363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415

function isPointNull(x: number, y: number) {
    return isNaN(x) || isNaN(y);
}

function getLastIndexNotNull(points: ArrayLike<number>) {
    let len = points.length / 2;
    for (; len > 0; len--) {
        if (!isPointNull(points[len * 2 - 2], points[len * 2 - 1])) {
            break;
        }
    }

    return len - 1;
}

function getPointAtIndex(points: ArrayLike<number>, idx: number) {
    return [points[idx * 2], points[idx * 2 + 1]];
}

function getIndexRange(points: ArrayLike<number>, xOrY: number, dim: 'x' | 'y') {
    const len = points.length / 2;

    const dimIdx = dim === 'x' ? 0 : 1;
    let a;
    let b;
    let prevIndex = 0;
    let nextIndex = -1;
    for (let i = 0; i < len; i++) {
        b = points[i * 2 + dimIdx];
        if (isNaN(b) || isNaN(points[i * 2 + 1 - dimIdx])) {
            continue;
        }
        if (i === 0) {
            a = b;
            continue;
        }
        if (a <= xOrY && b >= xOrY || a >= xOrY && b <= xOrY) {
            nextIndex = i;
            break;
        }

        prevIndex = i;
        a = b;
    }

    return {
        range: [prevIndex, nextIndex],
        t: (xOrY - a) / (b - a)
    };
}


416 417 418 419 420 421
interface EndLabelAnimationRecord {
    lastFrameIndex: number
    originalX?: number
    originalY?: number
}

P
pissang 已提交
422
function createLineClipPath(
O
Ovilia 已提交
423
    lineView: LineView,
P
pissang 已提交
424 425 426 427
    coordSys: Cartesian2D | Polar,
    hasAnimation: boolean,
    seriesModel: LineSeriesModel
) {
428
    if (isCoordinateSystemType<Cartesian2D>(coordSys, 'cartesian2d')) {
429
        const endLabelModel = seriesModel.getModel('endLabel');
430
        const showEndLabel = endLabelModel.get('show');
431
        const valueAnimation = endLabelModel.get('valueAnimation');
432
        const data = seriesModel.getData();
O
Ovilia 已提交
433

434
        const labelAnimationRecord: EndLabelAnimationRecord = { lastFrameIndex: 0 };
435 436

        const during = showEndLabel
O
Ovilia 已提交
437
            ? (percent: number, clipRect: graphic.Rect) => {
438 439 440
                lineView._endLabelOnDuring(
                    percent,
                    clipRect,
441
                    data,
442 443
                    labelAnimationRecord,
                    valueAnimation,
444 445
                    endLabelModel,
                    coordSys
446
                );
O
Ovilia 已提交
447 448 449
            }
            : null;

450
        const isHorizontal = coordSys.getBaseAxis().isHorizontal();
451
        const clipPath = createGridClipPath(coordSys, hasAnimation, seriesModel, () => {
452 453 454 455 456 457 458 459
            const endLabel = lineView._endLabel;
            if (endLabel && hasAnimation) {
                if (labelAnimationRecord.originalX != null) {
                    endLabel.attr({
                        x: labelAnimationRecord.originalX,
                        y: labelAnimationRecord.originalY
                    });
                }
460 461
            }
        }, during);
462
        // Expand clip shape to avoid clipping when line value exceeds axis
463
        if (!seriesModel.get('clip', true)) {
464 465
            const rectShape = clipPath.shape;
            const expandSize = Math.max(rectShape.width, rectShape.height);
466 467 468 469 470 471 472 473 474
            if (isHorizontal) {
                rectShape.y -= expandSize;
                rectShape.height += expandSize * 2;
            }
            else {
                rectShape.x -= expandSize;
                rectShape.width += expandSize * 2;
            }
        }
475 476 477 478 479

        // Set to the final frame. To make sure label layout is right.
        if (during) {
            during(1, clipPath);
        }
480
        return clipPath;
481 482
    }
    else {
483
        if (__DEV__) {
484 485
            if (seriesModel.get(['endLabel', 'show'])) {
                console.warn('endLabel is not supported for lines in polar systems.');
486 487
            }
        }
488 489 490 491 492
        return createPolarClipPath(coordSys, hasAnimation, seriesModel);
    }

}

O
Ovilia 已提交
493
function getEndLabelStateSpecified(endLabelModel: Model, coordSys: Cartesian2D) {
O
Ovilia 已提交
494 495 496 497
    const baseAxis = coordSys.getBaseAxis();
    const isHorizontal = baseAxis.isHorizontal();
    const isBaseInversed = baseAxis.inverse;
    const align = isHorizontal
O
Ovilia 已提交
498
        ? (isBaseInversed ? 'right' : 'left')
O
Ovilia 已提交
499 500 501 502 503
        : 'center';
    const verticalAlign = isHorizontal
        ? 'middle'
        : (isBaseInversed ? 'top' : 'bottom');

O
Ovilia 已提交
504 505 506
    return {
        normal: {
            align: endLabelModel.get('align') || align,
507
            verticalAlign: endLabelModel.get('verticalAlign') || verticalAlign
O
Ovilia 已提交
508 509
        }
    };
O
Ovilia 已提交
510 511
}

P
pissang 已提交
512 513
class LineView extends ChartView {

1
100pah 已提交
514
    static readonly type = 'line';
P
pissang 已提交
515

1
100pah 已提交
516
    _symbolDraw: SymbolDraw;
P
pissang 已提交
517

1
100pah 已提交
518 519
    _lineGroup: graphic.Group;
    _coordSys: Cartesian2D | Polar;
P
pissang 已提交
520

521
    _endLabel: graphic.Text;
O
Ovilia 已提交
522

1
100pah 已提交
523 524
    _polyline: ECPolyline;
    _polygon: ECPolygon;
P
pissang 已提交
525

526 527
    _stackedOnPoints: ArrayLike<number>;
    _points: ArrayLike<number>;
528

1
100pah 已提交
529 530
    _step: LineSeriesOption['step'];
    _valueOrigin: LineSeriesOption['areaStyle']['origin'];
L
lang 已提交
531

1
100pah 已提交
532
    _clipShapeForSymbol: CoordinateSystemClipArea;
P
pissang 已提交
533

1
100pah 已提交
534
    _data: List;
P
pissang 已提交
535 536

    init() {
537
        const lineGroup = new graphic.Group();
L
lang 已提交
538

539
        const symbolDraw = new SymbolDraw();
S
sushuang 已提交
540
        this.group.add(symbolDraw.group);
L
tweak  
lang 已提交
541

S
sushuang 已提交
542 543
        this._symbolDraw = symbolDraw;
        this._lineGroup = lineGroup;
P
pissang 已提交
544
    }
L
tweak  
lang 已提交
545

P
pissang 已提交
546
    render(seriesModel: LineSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) {
547 548 549 550 551
        const coordSys = seriesModel.coordinateSystem;
        const group = this.group;
        const data = seriesModel.getData();
        const lineStyleModel = seriesModel.getModel('lineStyle');
        const areaStyleModel = seriesModel.getModel('areaStyle');
L
lang 已提交
552

553
        let points = data.getLayout('points') as number[] || [];
L
lang 已提交
554

555 556
        const isCoordSysPolar = coordSys.type === 'polar';
        const prevCoordSys = this._coordSys;
557

558
        const symbolDraw = this._symbolDraw;
559 560
        let polyline = this._polyline;
        let polygon = this._polygon;
L
lang 已提交
561

562
        const lineGroup = this._lineGroup;
L
lang 已提交
563

564
        const hasAnimation = seriesModel.get('animation');
L
tweak  
lang 已提交
565

566
        const isAreaChart = !areaStyleModel.isEmpty();
S
sushuang 已提交
567

568 569
        const valueOrigin = areaStyleModel.get('origin');
        const dataCoordInfo = prepareDataCoordInfo(coordSys, data, valueOrigin);
S
sushuang 已提交
570

571
        let stackedOnPoints = isAreaChart && getStackedOnPoints(coordSys, data, dataCoordInfo);
L
lang 已提交
572

573
        const showSymbol = seriesModel.get('showSymbol');
L
lang 已提交
574

575
        const isIgnoreFunc = showSymbol && !isCoordSysPolar
P
pissang 已提交
576
            && getIsIgnoreFunc(seriesModel, data, coordSys as Cartesian2D);
577

S
sushuang 已提交
578
        // Remove temporary symbols
579
        const oldData = this._data;
P
pissang 已提交
580
        oldData && oldData.eachItemGraphicEl(function (el: SymbolExtended, idx) {
S
sushuang 已提交
581 582 583 584 585
            if (el.__temp) {
                group.remove(el);
                oldData.setItemGraphicEl(idx, null);
            }
        });
L
lang 已提交
586

S
sushuang 已提交
587 588 589 590 591 592
        // Remove previous created symbols if showSymbol changed to false
        if (!showSymbol) {
            symbolDraw.remove();
        }

        group.add(lineGroup);
593

S
sushuang 已提交
594
        // FIXME step not support polar
595
        const step = !isCoordSysPolar ? seriesModel.get('step') : false;
596
        let clipShapeForSymbol: PolarArea | Cartesian2DArea;
H
hantianjiao 已提交
597
        if (coordSys && coordSys.getArea && seriesModel.get('clip', true)) {
598 599 600
            clipShapeForSymbol = coordSys.getArea();
            // Avoid float number rounding error for symbol on the edge of axis extent.
            // See #7913 and `test/dataZoom-clip.html`.
P
pissang 已提交
601 602 603 604 605
            if ((clipShapeForSymbol as Cartesian2DArea).width != null) {
                (clipShapeForSymbol as Cartesian2DArea).x -= 0.1;
                (clipShapeForSymbol as Cartesian2DArea).y -= 0.1;
                (clipShapeForSymbol as Cartesian2DArea).width += 0.2;
                (clipShapeForSymbol as Cartesian2DArea).height += 0.2;
606
            }
P
pissang 已提交
607 608 609
            else if ((clipShapeForSymbol as PolarArea).r0) {
                (clipShapeForSymbol as PolarArea).r0 -= 0.5;
                (clipShapeForSymbol as PolarArea).r += 0.5;
610 611
            }
        }
612
        this._clipShapeForSymbol = clipShapeForSymbol;
613 614
        const visualColor = getVisualGradient(data, coordSys)
            || data.getVisual('style')[data.getVisual('drawType')];
S
sushuang 已提交
615 616 617 618
        // Initialization animation or coordinate system changed
        if (
            !(polyline && prevCoordSys.type === coordSys.type && step === this._step)
        ) {
S
sushuang 已提交
619
            showSymbol && symbolDraw.updateData(data, {
620
                isIgnore: isIgnoreFunc,
621
                clipShape: clipShapeForSymbol,
622
                disableAnimation: true,
623 624 625
                getSymbolPoint(idx) {
                    return [points[idx * 2], points[idx * 2 + 1]];
                }
S
sushuang 已提交
626
            });
627

628
            hasAnimation && this._initSymbolLabelAnimation(
629 630 631 632
                data,
                coordSys,
                clipShapeForSymbol
            );
S
sushuang 已提交
633 634 635 636

            if (step) {
                // TODO If stacked series is not step
                points = turnPointsIntoStep(points, coordSys, step);
637 638 639 640

                if (stackedOnPoints) {
                    stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys, step);
                }
641 642
            }

P
pissang 已提交
643
            polyline = this._newPolyline(points);
S
sushuang 已提交
644 645
            if (isAreaChart) {
                polygon = this._newPolygon(
P
pissang 已提交
646
                    points, stackedOnPoints
S
sushuang 已提交
647 648
                );
            }
649 650 651

            // NOTE: Must update _endLabel before setClipPath.
            if (!isCoordSysPolar) {
S
susiwen8 已提交
652
                this._initOrUpdateEndLabel(seriesModel, coordSys as Cartesian2D, convertToColorString(visualColor));
653 654
            }

O
Ovilia 已提交
655 656 657
            lineGroup.setClipPath(
                createLineClipPath(this, coordSys, true, seriesModel)
            );
S
sushuang 已提交
658 659 660 661 662
        }
        else {
            if (isAreaChart && !polygon) {
                // If areaStyle is added
                polygon = this._newPolygon(
P
pissang 已提交
663
                    points, stackedOnPoints
S
sushuang 已提交
664 665 666 667 668 669 670
                );
            }
            else if (polygon && !isAreaChart) {
                // If areaStyle is removed
                lineGroup.remove(polygon);
                polygon = this._polygon = null;
            }
671

672 673
            // NOTE: Must update _endLabel before setClipPath.
            if (!isCoordSysPolar) {
S
susiwen8 已提交
674
                this._initOrUpdateEndLabel(seriesModel, coordSys as Cartesian2D, convertToColorString(visualColor));
675 676
            }

S
sushuang 已提交
677
            // Update clipPath
O
Ovilia 已提交
678 679 680
            lineGroup.setClipPath(
                createLineClipPath(this, coordSys, false, seriesModel)
            );
681

S
sushuang 已提交
682 683
            // Always update, or it is wrong in the case turning on legend
            // because points are not changed
S
sushuang 已提交
684
            showSymbol && symbolDraw.updateData(data, {
685
                isIgnore: isIgnoreFunc,
O
Ovilia 已提交
686
                clipShape: clipShapeForSymbol,
687
                disableAnimation: true,
688 689 690
                getSymbolPoint(idx) {
                    return [points[idx * 2], points[idx * 2 + 1]];
                }
S
sushuang 已提交
691
            });
L
lang 已提交
692

S
sushuang 已提交
693 694 695 696 697 698
            // In the case data zoom triggerred refreshing frequently
            // Data may not change if line has a category axis. So it should animate nothing
            if (!isPointsSame(this._stackedOnPoints, stackedOnPoints)
                || !isPointsSame(this._points, points)
            ) {
                if (hasAnimation) {
699
                    this._doUpdateAnimation(
S
sushuang 已提交
700
                        data, stackedOnPoints, coordSys, api, step, valueOrigin
L
lang 已提交
701 702
                    );
                }
S
sushuang 已提交
703 704 705 706 707
                else {
                    // Not do it in update with animation
                    if (step) {
                        // TODO If stacked series is not step
                        points = turnPointsIntoStep(points, coordSys, step);
708 709 710
                        if (stackedOnPoints) {
                            stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys, step);
                        }
L
lang 已提交
711
                    }
S
sushuang 已提交
712 713 714 715 716 717 718 719

                    polyline.setShape({
                        points: points
                    });
                    polygon && polygon.setShape({
                        points: points,
                        stackedOnPoints: stackedOnPoints
                    });
L
lang 已提交
720
                }
L
lang 已提交
721
            }
S
sushuang 已提交
722 723
        }

724 725
        const focus = seriesModel.get(['emphasis', 'focus']);
        const blurScope = seriesModel.get(['emphasis', 'blurScope']);
S
sushuang 已提交
726 727 728 729 730 731 732

        polyline.useStyle(zrUtil.defaults(
            // Use color in lineStyle first
            lineStyleModel.getLineStyle(),
            {
                fill: 'none',
                stroke: visualColor,
733
                lineJoin: 'bevel' as CanvasLineJoin
S
sushuang 已提交
734 735 736
            }
        ));

737
        setStatesStylesFromModel(polyline, seriesModel, 'lineStyle');
738

739
        if (polyline.style.lineWidth > 0 && seriesModel.get(['emphasis', 'lineStyle', 'width']) === 'bolder') {
740
            const emphasisLineStyle = polyline.getState('emphasis').style;
741
            emphasisLineStyle.lineWidth = +polyline.style.lineWidth + 1;
742 743
        }

744
        // Needs seriesIndex for focus
745
        getECData(polyline).seriesIndex = seriesModel.seriesIndex;
746
        enableHoverEmphasis(polyline, focus, blurScope);
747

748
        const smooth = getSmooth(seriesModel.get('smooth'));
749 750
        const smoothMonotone = seriesModel.get('smoothMonotone');
        const connectNulls = seriesModel.get('connectNulls');
S
sushuang 已提交
751
        polyline.setShape({
752 753 754
            smooth,
            smoothMonotone,
            connectNulls
S
sushuang 已提交
755
        });
L
lang 已提交
756

S
sushuang 已提交
757
        if (polygon) {
758
            const stackedOnSeries = data.getCalculationInfo('stackedOnSeries');
759
            let stackedOnSmooth = 0;
760

S
sushuang 已提交
761 762
            polygon.useStyle(zrUtil.defaults(
                areaStyleModel.getAreaStyle(),
L
lang 已提交
763
                {
S
sushuang 已提交
764 765
                    fill: visualColor,
                    opacity: 0.7,
O
Ovilia 已提交
766 767
                    lineJoin: 'bevel' as CanvasLineJoin,
                    decal: data.getVisual('style').decal
L
lang 已提交
768 769
                }
            ));
L
lang 已提交
770

S
sushuang 已提交
771
            if (stackedOnSeries) {
S
sushuang 已提交
772 773 774 775
                stackedOnSmooth = getSmooth(stackedOnSeries.get('smooth'));
            }

            polygon.setShape({
776 777 778 779
                smooth,
                stackedOnSmooth,
                smoothMonotone,
                connectNulls
780
            });
781 782

            setStatesStylesFromModel(polygon, seriesModel, 'areaStyle');
783
            // Needs seriesIndex for focus
784
            getECData(polygon).seriesIndex = seriesModel.seriesIndex;
785
            enableHoverEmphasis(polygon, focus, blurScope);
S
sushuang 已提交
786
        }
L
lang 已提交
787

788 789
        const changePolyState = (toState: DisplayState) => {
            this._changePolyState(toState);
790 791 792 793
        };

        data.eachItemGraphicEl(function (el) {
            // Switch polyline / polygon state if element changed its state.
794
            el && ((el as ECElement).onHoverStateChange = changePolyState);
795 796
        });

797 798
        (this._polyline as ECElement).onHoverStateChange = changePolyState;

S
sushuang 已提交
799 800 801 802 803 804
        this._data = data;
        // Save the coordinate system for transition animation when data changed
        this._coordSys = coordSys;
        this._stackedOnPoints = stackedOnPoints;
        this._points = points;
        this._step = step;
S
sushuang 已提交
805
        this._valueOrigin = valueOrigin;
P
pissang 已提交
806
    }
S
sushuang 已提交
807

P
pissang 已提交
808
    dispose() {}
S
sushuang 已提交
809

P
pissang 已提交
810 811 812 813 814 815
    highlight(
        seriesModel: LineSeriesModel,
        ecModel: GlobalModel,
        api: ExtensionAPI,
        payload: Payload
    ) {
816 817
        const data = seriesModel.getData();
        const dataIndex = modelUtil.queryDataIndex(data, payload);
S
sushuang 已提交
818

819 820
        this._changePolyState('emphasis');

S
sushuang 已提交
821
        if (!(dataIndex instanceof Array) && dataIndex != null && dataIndex >= 0) {
822
            const points = data.getLayout('points');
823
            let symbol = data.getItemGraphicEl(dataIndex) as SymbolClz;
S
sushuang 已提交
824 825
            if (!symbol) {
                // Create a temporary symbol if it is not exists
826 827 828
                const x = points[dataIndex * 2];
                const y = points[dataIndex * 2 + 1];
                if (isNaN(x) || isNaN(y)) {
S
sushuang 已提交
829 830 831
                    // Null data
                    return;
                }
832
                // fix #11360: should't draw symbol outside clipShapeForSymbol
833
                if (this._clipShapeForSymbol && !this._clipShapeForSymbol.contain(x, y)) {
834 835
                    return;
                }
S
sushuang 已提交
836
                symbol = new SymbolClz(data, dataIndex);
837 838
                symbol.x = x;
                symbol.y = y;
S
sushuang 已提交
839 840 841 842
                symbol.setZ(
                    seriesModel.get('zlevel'),
                    seriesModel.get('z')
                );
P
pissang 已提交
843
                (symbol as SymbolExtended).__temp = true;
S
sushuang 已提交
844
                data.setItemGraphicEl(dataIndex, symbol);
L
lang 已提交
845

S
sushuang 已提交
846 847
                // Stop scale animation
                symbol.stopSymbolAnimation(true);
L
lang 已提交
848

S
sushuang 已提交
849 850 851 852 853 854 855 856 857 858
                this.group.add(symbol);
            }
            symbol.highlight();
        }
        else {
            // Highlight whole series
            ChartView.prototype.highlight.call(
                this, seriesModel, ecModel, api, payload
            );
        }
P
pissang 已提交
859
    }
S
sushuang 已提交
860

P
pissang 已提交
861 862 863 864 865 866
    downplay(
        seriesModel: LineSeriesModel,
        ecModel: GlobalModel,
        api: ExtensionAPI,
        payload: Payload
    ) {
867 868
        const data = seriesModel.getData();
        const dataIndex = modelUtil.queryDataIndex(data, payload) as number;
869 870 871

        this._changePolyState('normal');

S
sushuang 已提交
872
        if (dataIndex != null && dataIndex >= 0) {
873
            const symbol = data.getItemGraphicEl(dataIndex) as SymbolExtended;
S
sushuang 已提交
874 875 876 877 878 879 880
            if (symbol) {
                if (symbol.__temp) {
                    data.setItemGraphicEl(dataIndex, null);
                    this.group.remove(symbol);
                }
                else {
                    symbol.downplay();
L
lang 已提交
881
                }
L
lang 已提交
882
            }
S
sushuang 已提交
883 884 885 886 887 888 889 890 891
        }
        else {
            // FIXME
            // can not downplay completely.
            // Downplay whole series
            ChartView.prototype.downplay.call(
                this, seriesModel, ecModel, api, payload
            );
        }
P
pissang 已提交
892
    }
L
lang 已提交
893

894 895 896 897 898 899
    _changePolyState(toState: DisplayState) {
        const polygon = this._polygon;
        setStatesFlag(this._polyline, toState);
        polygon && setStatesFlag(polygon, toState);
    }

900
    _newPolyline(points: ArrayLike<number>) {
901
        let polyline = this._polyline;
S
sushuang 已提交
902 903 904 905
        // Remove previous created polyline
        if (polyline) {
            this._lineGroup.remove(polyline);
        }
L
lang 已提交
906

P
pissang 已提交
907
        polyline = new ECPolyline({
S
sushuang 已提交
908
            shape: {
909
                points
S
sushuang 已提交
910
            },
911
            segmentIgnoreThreshold: 2,
S
sushuang 已提交
912 913
            z2: 10
        });
914

S
sushuang 已提交
915
        this._lineGroup.add(polyline);
L
lang 已提交
916

S
sushuang 已提交
917
        this._polyline = polyline;
L
lang 已提交
918

S
sushuang 已提交
919
        return polyline;
P
pissang 已提交
920
    }
L
lang 已提交
921

922
    _newPolygon(points: ArrayLike<number>, stackedOnPoints: ArrayLike<number>) {
923
        let polygon = this._polygon;
S
sushuang 已提交
924 925 926 927
        // Remove previous created polygon
        if (polygon) {
            this._lineGroup.remove(polygon);
        }
L
lang 已提交
928

P
pissang 已提交
929
        polygon = new ECPolygon({
S
sushuang 已提交
930
            shape: {
931
                points,
S
sushuang 已提交
932 933
                stackedOnPoints: stackedOnPoints
            },
934
            segmentIgnoreThreshold: 2
S
sushuang 已提交
935
        });
L
lang 已提交
936

S
sushuang 已提交
937
        this._lineGroup.add(polygon);
L
lang 已提交
938

S
sushuang 已提交
939 940
        this._polygon = polygon;
        return polygon;
P
pissang 已提交
941
    }
942

943
    _initSymbolLabelAnimation(
944
        data: List,
945
        coordSys: Polar | Cartesian2D,
946 947
        clipShape: PolarArea | Cartesian2DArea
    ) {
948 949 950
        let isHorizontalOrRadial: boolean;
        let isCoordSysPolar: boolean;
        const baseAxis = coordSys.getBaseAxis();
951
        const isAxisInverse = baseAxis.inverse;
952 953 954 955 956 957 958 959 960
        if (coordSys.type === 'cartesian2d') {
            isHorizontalOrRadial = (baseAxis as Axis2D).isHorizontal();
            isCoordSysPolar = false;
        }
        else if (coordSys.type === 'polar') {
            isHorizontalOrRadial = baseAxis.dim === 'angle';
            isCoordSysPolar = true;
        }

961 962 963 964 965
        const seriesModel = data.hostModel;
        let seriesDuration = seriesModel.get('animationDuration');
        if (typeof seriesDuration === 'function') {
            seriesDuration = seriesDuration(null);
        }
966 967 968 969
        const seriesDalay = seriesModel.get('animationDelay') || 0;
        const seriesDalayValue = typeof seriesDalay === 'function'
            ? seriesDalay(null)
            : seriesDalay;
970

971 972
        data.eachItemGraphicEl(function (symbol: SymbolExtended, idx) {
            const el = symbol;
973
            if (el) {
974
                const point = [symbol.x, symbol.y];
975 976 977
                let start;
                let end;
                let current;
978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995
                if (isCoordSysPolar) {
                    const polarClip = clipShape as PolarArea;
                    const coord = (coordSys as Polar).pointToCoord(point);
                    if (isHorizontalOrRadial) {
                        start = polarClip.startAngle;
                        end = polarClip.endAngle;
                        current = -coord[1] / 180 * Math.PI;
                    }
                    else {
                        start = polarClip.r0;
                        end = polarClip.r;
                        current = coord[0];
                    }
                }
                else {
                    const gridClip = clipShape as Cartesian2DArea;
                    if (isHorizontalOrRadial) {
                        start = gridClip.x;
996
                        end = gridClip.x + gridClip.width;
997 998 999 1000 1001 1002 1003 1004
                        current = symbol.x;
                    }
                    else {
                        start = gridClip.y + gridClip.height;
                        end = gridClip.y;
                        current = symbol.y;
                    }
                }
1005 1006 1007 1008
                let ratio = end === start ? 0 : (current - start) / (end - start);
                if (isAxisInverse) {
                    ratio = 1 - ratio;
                }
1009

1010 1011 1012
                const delay = typeof seriesDalay === 'function' ? seriesDalay(idx)
                    : (seriesDuration * ratio) + seriesDalayValue;

P
pissang 已提交
1013 1014 1015
                const symbolPath = el.getSymbolPath();
                const text = symbolPath.getTextContent();

1016
                el.attr({ scaleX: 0, scaleY: 0});
1017
                el.animateTo({
1018 1019
                    scaleX: 1,
                    scaleY: 1
1020 1021 1022 1023 1024 1025
                }, {
                    duration: 200,
                    delay: delay
                });

                if (text) {
1026
                    text.animateFrom({
1027 1028 1029 1030 1031 1032 1033 1034 1035
                        style: {
                            opacity: 0
                        }
                    }, {
                        duration: 300,
                        delay: delay
                    });
                }

1036
                (symbolPath as ECElement).disableLabelAnimation = true;
1037 1038 1039 1040
            }
        });
    }

O
Ovilia 已提交
1041
    _initOrUpdateEndLabel(
O
Ovilia 已提交
1042
        seriesModel: LineSeriesModel,
1043
        coordSys: Cartesian2D,
S
susiwen8 已提交
1044
        inheritColor: string
O
Ovilia 已提交
1045
    ) {
1046
        const endLabelModel = seriesModel.getModel('endLabel');
O
Ovilia 已提交
1047

1048
        if (endLabelModel.get('show')) {
1049
            const data = seriesModel.getData();
1050
            const polyline = this._polyline;
1051 1052 1053
            let endLabel = this._endLabel;
            if (!endLabel) {
                endLabel = this._endLabel = new graphic.Text({
1054
                    z2: 200 // should be higher than item symbol
O
Ovilia 已提交
1055
                });
1056 1057 1058
                endLabel.ignoreClip = true;
                polyline.setTextContent(this._endLabel);
                (polyline as ECElement).disableLabelAnimation = true;
O
Ovilia 已提交
1059 1060
            }

1061
            // Find last non-NaN data to display data
1062
            const dataIndex = getLastIndexNotNull(data.getLayout('points'));
1063 1064
            if (dataIndex >= 0) {
                setLabelStyle(
O
Ovilia 已提交
1065
                    polyline,
1066 1067
                    getLabelStatesModels(seriesModel, 'endLabel'),
                    {
S
susiwen8 已提交
1068
                        inheritColor,
1069 1070
                        labelFetcher: seriesModel,
                        labelDataIndex: dataIndex,
1
fix:  
100pah 已提交
1071 1072 1073
                        defaultText(dataIndex, opt, interpolatedValue) {
                            return interpolatedValue != null
                                ? getDefaultInterpolatedLabel(data, interpolatedValue)
1074 1075
                                : getDefaultLabel(data, dataIndex);
                        },
1076 1077 1078 1079
                        enableTextSetter: true
                    },
                    getEndLabelStateSpecified(endLabelModel, coordSys)
                );
O
Ovilia 已提交
1080
                polyline.textConfig.position = null;
1081
            }
O
Ovilia 已提交
1082
        }
O
Ovilia 已提交
1083
        else if (this._endLabel) {
1084
            this._polyline.removeTextContent();
O
Ovilia 已提交
1085 1086
            this._endLabel = null;
        }
O
Ovilia 已提交
1087 1088
    }

1089
    _endLabelOnDuring(
O
Ovilia 已提交
1090 1091
        percent: number,
        clipRect: graphic.Rect,
1092
        data: List,
1093
        animationRecord: EndLabelAnimationRecord,
1094
        valueAnimation: boolean,
1095 1096
        endLabelModel: Model<LabelOption>,
        coordSys: Cartesian2D
O
Ovilia 已提交
1097
    ) {
1098 1099
        const endLabel = this._endLabel;
        const polyline = this._polyline;
1100

1101
        if (endLabel) {
1102 1103 1104 1105 1106 1107 1108
            // NOTE: Don't remove percent < 1. percent === 1 means the first frame during render.
            // The label is not prepared at this time.
            if (percent < 1 && animationRecord.originalX == null) {
                animationRecord.originalX = endLabel.x;
                animationRecord.originalY = endLabel.y;
            }

1109
            const points = data.getLayout('points');
1110
            const seriesModel = data.hostModel as LineSeriesModel;
1111
            const connectNulls = seriesModel.get('connectNulls');
1112
            const precision = endLabelModel.get('precision');
O
Ovilia 已提交
1113
            const distance = endLabelModel.get('distance') || 0;
O
Ovilia 已提交
1114

1115 1116 1117
            const baseAxis = coordSys.getBaseAxis();
            const isHorizontal = baseAxis.isHorizontal();
            const isBaseInversed = baseAxis.inverse;
1118 1119 1120 1121 1122
            const clipShape = clipRect.shape;

            const xOrY = isBaseInversed
                ? isHorizontal ? clipShape.x : (clipShape.y + clipShape.height)
                : isHorizontal ? (clipShape.x + clipShape.width) : clipShape.y;
O
Ovilia 已提交
1123 1124
            const distanceX = (isHorizontal ? distance : 0) * (isBaseInversed ? -1 : 1);
            const distanceY = (isHorizontal ? 0 : -distance) * (isBaseInversed ? -1 : 1);
1125 1126
            const dim = isHorizontal ? 'x' : 'y';

1127
            const dataIndexRange = getIndexRange(points, xOrY, dim);
1128 1129 1130 1131 1132 1133 1134
            const indices = dataIndexRange.range;

            const diff = indices[1] - indices[0];
            let value: ParsedValue;
            if (diff >= 1) {
                // diff > 1 && connectNulls, which is on the null data.
                if (diff > 1 && !connectNulls) {
1135
                    const pt = getPointAtIndex(points, indices[0]);
O
Ovilia 已提交
1136 1137 1138 1139
                    endLabel.attr({
                        x: pt[0] + distanceX,
                        y: pt[1] + distanceY
                    });
1140
                    valueAnimation && (value = seriesModel.getRawValue(indices[0]) as ParsedValue);
1141
                }
1142 1143
                else {
                    const pt = polyline.getPointOn(xOrY, dim);
O
Ovilia 已提交
1144 1145 1146 1147
                    pt && endLabel.attr({
                        x: pt[0] + distanceX,
                        y: pt[1] + distanceY
                    });
1148 1149 1150 1151 1152 1153

                    const startValue = seriesModel.getRawValue(indices[0]) as ParsedValue;
                    const endValue = seriesModel.getRawValue(indices[1]) as ParsedValue;
                    valueAnimation && (value = modelUtil.interpolateRawValues(
                        data, precision, startValue, endValue, dataIndexRange.t
                    ) as ParsedValue);
1154
                }
1155 1156 1157 1158 1159
                animationRecord.lastFrameIndex = indices[0];
            }
            else {
                // If diff <= 0, which is the range is not found(Include NaN)
                // Choose the first point or last point.
1160
                const idx = (percent === 1 || animationRecord.lastFrameIndex > 0) ? indices[0] : 0;
1161
                const pt = getPointAtIndex(points, idx);
1162
                valueAnimation && (value = seriesModel.getRawValue(idx) as ParsedValue);
O
Ovilia 已提交
1163 1164 1165 1166
                endLabel.attr({
                    x: pt[0] + distanceX,
                    y: pt[1] + distanceY
                });
1167 1168
            }
            if (valueAnimation) {
P
pissang 已提交
1169
                labelInner(endLabel).setLabelText(value);
1170
            }
O
Ovilia 已提交
1171 1172 1173
        }
    }

S
sushuang 已提交
1174 1175 1176 1177
    /**
     * @private
     */
    // FIXME Two value axis
1178
    _doUpdateAnimation(
P
pissang 已提交
1179
        data: List,
1180
        stackedOnPoints: ArrayLike<number>,
P
pissang 已提交
1181 1182 1183 1184 1185
        coordSys: Cartesian2D | Polar,
        api: ExtensionAPI,
        step: LineSeriesOption['step'],
        valueOrigin: LineSeriesOption['areaStyle']['origin']
    ) {
1186 1187 1188
        const polyline = this._polyline;
        const polygon = this._polygon;
        const seriesModel = data.hostModel;
S
sushuang 已提交
1189

1190
        const diff = lineAnimationDiff(
S
sushuang 已提交
1191 1192
            this._data, data,
            this._stackedOnPoints, stackedOnPoints,
S
sushuang 已提交
1193 1194
            this._coordSys, coordSys,
            this._valueOrigin, valueOrigin
S
sushuang 已提交
1195 1196
        );

1197 1198 1199 1200
        let current = diff.current;
        let stackedOnCurrent = diff.stackedOnCurrent;
        let next = diff.next;
        let stackedOnNext = diff.stackedOnNext;
S
sushuang 已提交
1201 1202 1203 1204 1205 1206 1207
        if (step) {
            // TODO If stacked series is not step
            current = turnPointsIntoStep(diff.current, coordSys, step);
            stackedOnCurrent = turnPointsIntoStep(diff.stackedOnCurrent, coordSys, step);
            next = turnPointsIntoStep(diff.next, coordSys, step);
            stackedOnNext = turnPointsIntoStep(diff.stackedOnNext, coordSys, step);
        }
1208

1209 1210 1211
        // Don't apply animation if diff is large.
        // For better result and avoid memory explosion problems like
        // https://github.com/apache/incubator-echarts/issues/12229
P
pissang 已提交
1212 1213
        if (getBoundingDiff(current, next) > 3000
            || (polygon && getBoundingDiff(stackedOnCurrent, stackedOnNext) > 3000)
1214 1215 1216 1217 1218 1219 1220 1221 1222 1223 1224 1225
        ) {
            polyline.setShape({
                points: next
            });
            if (polygon) {
                polygon.setShape({
                    points: next,
                    stackedOnPoints: stackedOnNext
                });
            }
            return;
        }
1226

P
pissang 已提交
1227
        (polyline.shape as any).__points = diff.current;
S
sushuang 已提交
1228
        polyline.shape.points = current;
L
lang 已提交
1229

1230
        const target = {
S
sushuang 已提交
1231 1232
            shape: {
                points: next
L
lang 已提交
1233
            }
1234 1235 1236 1237 1238 1239 1240 1241 1242 1243
        };
        // Also animate the original points.
        // If points reference is changed when turning into step line.
        if (diff.current !== current) {
            (target.shape as any).__points = diff.next;
        }

        // Stop previous animation.
        polyline.stopAnimation();
        graphic.updateProps(polyline, target, seriesModel);
L
lang 已提交
1244

S
sushuang 已提交
1245 1246
        if (polygon) {
            polygon.setShape({
1247
                // Reuse the points with polyline.
S
sushuang 已提交
1248 1249 1250
                points: current,
                stackedOnPoints: stackedOnCurrent
            });
1251
            polygon.stopAnimation();
S
sushuang 已提交
1252
            graphic.updateProps(polygon, {
L
lang 已提交
1253
                shape: {
S
sushuang 已提交
1254
                    stackedOnPoints: stackedOnNext
L
lang 已提交
1255
                }
L
lang 已提交
1256
            }, seriesModel);
1257 1258 1259 1260
            // If use attr directly in updateProps.
            if (polyline.shape.points !== polygon.shape.points) {
                polygon.shape.points = polyline.shape.points;
            }
S
sushuang 已提交
1261
        }
L
lang 已提交
1262

1263

1264
        const updatedDataInfo: {
P
pissang 已提交
1265 1266 1267
            el: SymbolExtended,
            ptIdx: number
        }[] = [];
1268
        const diffStatus = diff.status;
S
sushuang 已提交
1269

1270
        for (let i = 0; i < diffStatus.length; i++) {
1271
            const cmd = diffStatus[i].cmd;
S
sushuang 已提交
1272
            if (cmd === '=') {
1273
                const el = data.getItemGraphicEl(diffStatus[i].idx1) as SymbolExtended;
S
sushuang 已提交
1274 1275 1276 1277 1278
                if (el) {
                    updatedDataInfo.push({
                        el: el,
                        ptIdx: i    // Index of points
                    });
L
lang 已提交
1279
                }
L
lang 已提交
1280
            }
S
sushuang 已提交
1281
        }
L
lang 已提交
1282

S
sushuang 已提交
1283 1284
        if (polyline.animators && polyline.animators.length) {
            polyline.animators[0].during(function () {
1285
                polygon && polygon.dirtyShape();
1286
                const points = (polyline.shape as any).__points;
1287
                for (let i = 0; i < updatedDataInfo.length; i++) {
1288
                    const el = updatedDataInfo[i].el;
1289 1290 1291
                    const offset = updatedDataInfo[i].ptIdx * 2;
                    el.x = points[offset];
                    el.y = points[offset + 1];
1292
                    el.markRedraw();
1293 1294
                }
            });
L
lang 已提交
1295
        }
P
pissang 已提交
1296
    }
S
sushuang 已提交
1297

P
pissang 已提交
1298
    remove(ecModel: GlobalModel) {
1299 1300
        const group = this.group;
        const oldData = this._data;
S
sushuang 已提交
1301 1302 1303
        this._lineGroup.removeAll();
        this._symbolDraw.remove(true);
        // Remove temporary created elements when highlighting
P
pissang 已提交
1304
        oldData && oldData.eachItemGraphicEl(function (el: SymbolExtended, idx) {
S
sushuang 已提交
1305 1306 1307 1308 1309 1310
            if (el.__temp) {
                group.remove(el);
                oldData.setItemGraphicEl(idx, null);
            }
        });

S
sushuang 已提交
1311 1312 1313 1314 1315
        this._polyline =
            this._polygon =
            this._coordSys =
            this._points =
            this._stackedOnPoints =
1316
            this._endLabel =
S
sushuang 已提交
1317
            this._data = null;
S
sushuang 已提交
1318
    }
P
pissang 已提交
1319 1320
}

P
pissang 已提交
1321
export default LineView;