LineView.ts 30.7 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 34 35 36 37 38
import LineSeriesModel, { LineSeriesOption } from './LineSeries';
import type GlobalModel from '../../model/Global';
import type ExtensionAPI from '../../ExtensionAPI';
// TODO
import Cartesian2D from '../../coord/cartesian/Cartesian2D';
import Polar from '../../coord/polar/Polar';
import type List from '../../data/List';
39
import type { Payload, Dictionary, ColorString, ECElement, DisplayState } from '../../util/types';
P
pissang 已提交
40 41
import type OrdinalScale from '../../scale/Ordinal';
import type Axis2D from '../../coord/cartesian/Axis2D';
42
import { CoordinateSystemClipArea } from '../../coord/CoordinateSystem';
43
import { setStatesStylesFromModel, setStatesFlag, enableHoverEmphasis } from '../../util/states';
44
import { getECData } from '../../util/ecData';
45
import { createFloat32Array } from '../../util/vendor';
P
pissang 已提交
46 47


1
100pah 已提交
48 49
type PolarArea = ReturnType<Polar['getArea']>;
type Cartesian2DArea = ReturnType<Cartesian2D['getArea']>;
P
pissang 已提交
50 51 52 53

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

55
function isPointsSame(points1: ArrayLike<number>, points2: ArrayLike<number>) {
S
sushuang 已提交
56 57 58
    if (points1.length !== points2.length) {
        return;
    }
59
    for (let i = 0; i < points1.length; i++) {
60
        if (points1[i] !== points2[i]) {
61 62 63
            return;
        }
    }
S
sushuang 已提交
64 65 66
    return true;
}

67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89
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]
    ];
}
90

91
function getBoundingDiff(points1: ArrayLike<number>, points2: ArrayLike<number>): number {
92

93 94
    const [min1, max1] = bboxFromPoints(points1);
    const [min2, max2] = bboxFromPoints(points2);
95 96 97 98 99 100 101 102 103 104 105

    // 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 已提交
106 107
function getSmooth(smooth: number | boolean) {
    return typeof smooth === 'number' ? smooth : (smooth ? 0.5 : 0);
S
sushuang 已提交
108 109
}

P
pissang 已提交
110 111 112 113 114
function getStackedOnPoints(
    coordSys: Cartesian2D | Polar,
    data: List,
    dataCoordInfo: ReturnType<typeof prepareDataCoordInfo>
) {
S
sushuang 已提交
115 116
    if (!dataCoordInfo.valueDim) {
        return [];
S
sushuang 已提交
117
    }
L
lang 已提交
118

119 120 121 122 123 124
    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 已提交
125
    }
S
sushuang 已提交
126

S
sushuang 已提交
127
    return points;
S
sushuang 已提交
128 129
}

P
pissang 已提交
130
function turnPointsIntoStep(
131
    points: ArrayLike<number>,
P
pissang 已提交
132 133
    coordSys: Cartesian2D | Polar,
    stepTurnAt: 'start' | 'end' | 'middle'
134
): number[] {
135 136
    const baseAxis = coordSys.getBaseAxis();
    const baseIndex = baseAxis.dim === 'x' || baseAxis.dim === 'radius' ? 0 : 1;
S
sushuang 已提交
137

138
    const stepPoints: number[] = [];
139
    let i = 0;
140 141 142 143 144 145 146 147 148
    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 已提交
149 150 151 152 153

        switch (stepTurnAt) {
            case 'end':
                stepPt[baseIndex] = nextPt[baseIndex];
                stepPt[1 - baseIndex] = pt[1 - baseIndex];
154
                stepPoints.push(stepPt[0], stepPt[1]);
S
sushuang 已提交
155 156
                break;
            case 'middle':
157 158
                const middle = (pt[baseIndex] + nextPt[baseIndex]) / 2;
                const stepPt2 = [];
S
sushuang 已提交
159 160 161
                stepPt[baseIndex] = stepPt2[baseIndex] = middle;
                stepPt[1 - baseIndex] = pt[1 - baseIndex];
                stepPt2[1 - baseIndex] = nextPt[1 - baseIndex];
162 163
                stepPoints.push(stepPt[0], stepPt[1]);
                stepPoints.push(stepPt2[0], stepPt[1]);
S
sushuang 已提交
164 165
                break;
            default:
166
                // default is start
S
sushuang 已提交
167 168
                stepPt[baseIndex] = pt[baseIndex];
                stepPt[1 - baseIndex] = nextPt[1 - baseIndex];
169
                stepPoints.push(stepPt[0], stepPt[1]);
170 171
        }
    }
S
sushuang 已提交
172
    // Last points
173
    stepPoints.push(points[i++], points[i++]);
S
sushuang 已提交
174 175 176
    return stepPoints;
}

P
pissang 已提交
177 178 179 180
function getVisualGradient(
    data: List,
    coordSys: Cartesian2D | Polar
) {
181
    const visualMetaList = data.getVisual('visualMeta');
S
sushuang 已提交
182 183 184
    if (!visualMetaList || !visualMetaList.length || !data.count()) {
        // When data.count() is 0, gradient range can not be calculated.
        return;
185 186
    }

S
sushuang 已提交
187 188 189 190 191 192 193
    if (coordSys.type !== 'cartesian2d') {
        if (__DEV__) {
            console.warn('Visual map on line style is only supported on cartesian2d.');
        }
        return;
    }

194 195
    let coordDim: 'x' | 'y';
    let visualMeta;
S
sushuang 已提交
196

197
    for (let i = visualMetaList.length - 1; i >= 0; i--) {
198 199 200
        const dimIndex = visualMetaList[i].dimension;
        const dimName = data.dimensions[dimIndex];
        const dimInfo = data.getDimensionInfo(dimName);
P
pissang 已提交
201
        coordDim = (dimInfo && dimInfo.coordDim) as 'x' | 'y';
S
sushuang 已提交
202
        // Can only be x or y
S
sushuang 已提交
203
        if (coordDim === 'x' || coordDim === 'y') {
S
sushuang 已提交
204 205
            visualMeta = visualMetaList[i];
            break;
L
lang 已提交
206 207
        }
    }
S
sushuang 已提交
208 209

    if (!visualMeta) {
S
sushuang 已提交
210 211
        if (__DEV__) {
            console.warn('Visual map on line style only support x or y dimension.');
212
        }
S
sushuang 已提交
213 214
        return;
    }
215

S
sushuang 已提交
216 217 218 219 220 221 222
    // 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`.

223
    const axis = coordSys.getAxis(coordDim);
S
sushuang 已提交
224

P
pissang 已提交
225 226 227 228 229
    interface ColorStop {
        offset: number
        coord?: number
        color: ColorString
    }
S
sushuang 已提交
230
    // dataToCoor mapping may not be linear, but must be monotonic.
231
    const colorStops: ColorStop[] = zrUtil.map(visualMeta.stops, function (stop) {
S
sushuang 已提交
232
        return {
P
pissang 已提交
233
            offset: 0,
S
sushuang 已提交
234 235 236 237
            coord: axis.toGlobalCoord(axis.dataToCoord(stop.value)),
            color: stop.color
        };
    });
238 239
    const stopLen = colorStops.length;
    const outerColors = visualMeta.outerColors.slice();
240

S
sushuang 已提交
241 242 243 244
    if (stopLen && colorStops[0].coord > colorStops[stopLen - 1].coord) {
        colorStops.reverse();
        outerColors.reverse();
    }
245

246 247 248 249
    const tinyExtent = 10; // Arbitrary value: 10px
    const minCoord = colorStops[0].coord - tinyExtent;
    const maxCoord = colorStops[stopLen - 1].coord + tinyExtent;
    const coordSpan = maxCoord - minCoord;
250

S
sushuang 已提交
251 252 253
    if (coordSpan < 1e-3) {
        return 'transparent';
    }
254

S
sushuang 已提交
255 256 257 258 259 260 261 262 263 264 265
    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'
    });
266

S
sushuang 已提交
267 268 269 270
    // 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);
    // });
271

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

S
sushuang 已提交
276 277
    return gradient;
}
278

P
pissang 已提交
279 280 281 282 283
function getIsIgnoreFunc(
    seriesModel: LineSeriesModel,
    data: List,
    coordSys: Cartesian2D
) {
284 285
    const showAllSymbol = seriesModel.get('showAllSymbol');
    const isAuto = showAllSymbol === 'auto';
286 287 288 289 290

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

291
    const categoryAxis = coordSys.getAxesByScale('ordinal')[0];
292 293 294 295 296 297 298 299 300 301 302 303 304 305 306
    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.
307 308
    const categoryDataDim = data.mapDimension(categoryAxis.dim);
    const labelMap: Dictionary<1> = {};
309 310 311 312 313

    zrUtil.each(categoryAxis.getViewLabels(), function (labelItem) {
        labelMap[labelItem.tickValue] = 1;
    });

P
pissang 已提交
314
    return function (dataIndex: number) {
315 316 317 318
        return !labelMap.hasOwnProperty(data.get(categoryDataDim, dataIndex));
    };
}

P
pissang 已提交
319 320 321 322
function canShowAllSymbolForCategory(
    categoryAxis: Axis2D,
    data: List
) {
323 324 325 326
    // 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.
327
    const axisExtent = categoryAxis.getExtent();
328
    let availSize = Math.abs(axisExtent[1] - axisExtent[0]) / (categoryAxis.scale as OrdinalScale).count();
329 330 331
    isNaN(availSize) && (availSize = 0); // 0/0 is NaN.

    // Sampling some points, max 5.
332 333
    const dataLen = data.count();
    const step = Math.max(1, Math.round(dataLen / 5));
334
    for (let dataIndex = 0; dataIndex < dataLen; dataIndex += step) {
335 336 337 338 339 340 341 342 343 344 345 346 347 348
        if (SymbolClz.getSymbolSize(
                data, dataIndex
            // Only for cartesian, where `isHorizontal` exists.
            )[categoryAxis.isHorizontal() ? 1 : 0]
            // Empirical number
            * 1.5 > availSize
        ) {
            return false;
        }
    }

    return true;
}

P
pissang 已提交
349 350 351 352 353
function createLineClipPath(
    coordSys: Cartesian2D | Polar,
    hasAnimation: boolean,
    seriesModel: LineSeriesModel
) {
354
    if (coordSys.type === 'cartesian2d') {
355 356
        const isHorizontal = coordSys.getBaseAxis().isHorizontal();
        const clipPath = createGridClipPath(coordSys, hasAnimation, seriesModel);
357
        // Expand clip shape to avoid clipping when line value exceeds axis
358
        if (!seriesModel.get('clip', true)) {
359 360
            const rectShape = clipPath.shape;
            const expandSize = Math.max(rectShape.width, rectShape.height);
361 362 363 364 365 366 367 368 369
            if (isHorizontal) {
                rectShape.y -= expandSize;
                rectShape.height += expandSize * 2;
            }
            else {
                rectShape.x -= expandSize;
                rectShape.width += expandSize * 2;
            }
        }
370
        return clipPath;
371 372 373 374 375 376 377
    }
    else {
        return createPolarClipPath(coordSys, hasAnimation, seriesModel);
    }

}

P
pissang 已提交
378 379
class LineView extends ChartView {

1
100pah 已提交
380
    static readonly type = 'line';
P
pissang 已提交
381

1
100pah 已提交
382
    _symbolDraw: SymbolDraw;
P
pissang 已提交
383

1
100pah 已提交
384 385
    _lineGroup: graphic.Group;
    _coordSys: Cartesian2D | Polar;
P
pissang 已提交
386

1
100pah 已提交
387 388
    _polyline: ECPolyline;
    _polygon: ECPolygon;
P
pissang 已提交
389

390 391
    _stackedOnPoints: ArrayLike<number>;
    _points: ArrayLike<number>;
392

1
100pah 已提交
393 394
    _step: LineSeriesOption['step'];
    _valueOrigin: LineSeriesOption['areaStyle']['origin'];
L
lang 已提交
395

1
100pah 已提交
396
    _clipShapeForSymbol: CoordinateSystemClipArea;
P
pissang 已提交
397

1
100pah 已提交
398
    _data: List;
P
pissang 已提交
399 400

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

403
        const symbolDraw = new SymbolDraw();
S
sushuang 已提交
404
        this.group.add(symbolDraw.group);
L
tweak  
lang 已提交
405

S
sushuang 已提交
406 407
        this._symbolDraw = symbolDraw;
        this._lineGroup = lineGroup;
P
pissang 已提交
408
    }
L
tweak  
lang 已提交
409

P
pissang 已提交
410
    render(seriesModel: LineSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) {
411 412 413 414 415
        const coordSys = seriesModel.coordinateSystem;
        const group = this.group;
        const data = seriesModel.getData();
        const lineStyleModel = seriesModel.getModel('lineStyle');
        const areaStyleModel = seriesModel.getModel('areaStyle');
L
lang 已提交
416

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

419 420
        const isCoordSysPolar = coordSys.type === 'polar';
        const prevCoordSys = this._coordSys;
421

422
        const symbolDraw = this._symbolDraw;
423 424
        let polyline = this._polyline;
        let polygon = this._polygon;
L
lang 已提交
425

426
        const lineGroup = this._lineGroup;
L
lang 已提交
427

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

430
        const isAreaChart = !areaStyleModel.isEmpty();
S
sushuang 已提交
431

432 433
        const valueOrigin = areaStyleModel.get('origin');
        const dataCoordInfo = prepareDataCoordInfo(coordSys, data, valueOrigin);
S
sushuang 已提交
434

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

437
        const showSymbol = seriesModel.get('showSymbol');
L
lang 已提交
438

439
        const isIgnoreFunc = showSymbol && !isCoordSysPolar
P
pissang 已提交
440
            && getIsIgnoreFunc(seriesModel, data, coordSys as Cartesian2D);
441

S
sushuang 已提交
442
        // Remove temporary symbols
443
        const oldData = this._data;
P
pissang 已提交
444
        oldData && oldData.eachItemGraphicEl(function (el: SymbolExtended, idx) {
S
sushuang 已提交
445 446 447 448 449
            if (el.__temp) {
                group.remove(el);
                oldData.setItemGraphicEl(idx, null);
            }
        });
L
lang 已提交
450

S
sushuang 已提交
451 452 453 454 455 456
        // Remove previous created symbols if showSymbol changed to false
        if (!showSymbol) {
            symbolDraw.remove();
        }

        group.add(lineGroup);
457

S
sushuang 已提交
458
        // FIXME step not support polar
459
        const step = !isCoordSysPolar ? seriesModel.get('step') : false;
460
        let clipShapeForSymbol: PolarArea | Cartesian2DArea;
H
hantianjiao 已提交
461
        if (coordSys && coordSys.getArea && seriesModel.get('clip', true)) {
462 463 464
            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 已提交
465 466 467 468 469
            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;
470
            }
P
pissang 已提交
471 472 473
            else if ((clipShapeForSymbol as PolarArea).r0) {
                (clipShapeForSymbol as PolarArea).r0 -= 0.5;
                (clipShapeForSymbol as PolarArea).r += 0.5;
474 475
            }
        }
476
        this._clipShapeForSymbol = clipShapeForSymbol;
S
sushuang 已提交
477 478 479 480
        // Initialization animation or coordinate system changed
        if (
            !(polyline && prevCoordSys.type === coordSys.type && step === this._step)
        ) {
S
sushuang 已提交
481
            showSymbol && symbolDraw.updateData(data, {
482
                isIgnore: isIgnoreFunc,
483 484 485 486
                clipShape: clipShapeForSymbol,
                getSymbolPoint(idx) {
                    return [points[idx * 2], points[idx * 2 + 1]];
                }
S
sushuang 已提交
487
            });
S
sushuang 已提交
488 489 490 491

            if (step) {
                // TODO If stacked series is not step
                points = turnPointsIntoStep(points, coordSys, step);
492 493 494 495

                if (stackedOnPoints) {
                    stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys, step);
                }
496 497
            }

P
pissang 已提交
498
            polyline = this._newPolyline(points);
S
sushuang 已提交
499 500
            if (isAreaChart) {
                polygon = this._newPolygon(
P
pissang 已提交
501
                    points, stackedOnPoints
S
sushuang 已提交
502 503
                );
            }
504
            lineGroup.setClipPath(createLineClipPath(coordSys, true, seriesModel));
S
sushuang 已提交
505 506 507 508 509
        }
        else {
            if (isAreaChart && !polygon) {
                // If areaStyle is added
                polygon = this._newPolygon(
P
pissang 已提交
510
                    points, stackedOnPoints
S
sushuang 已提交
511 512 513 514 515 516 517
                );
            }
            else if (polygon && !isAreaChart) {
                // If areaStyle is removed
                lineGroup.remove(polygon);
                polygon = this._polygon = null;
            }
518

S
sushuang 已提交
519
            // Update clipPath
520
            lineGroup.setClipPath(createLineClipPath(coordSys, false, seriesModel));
521

S
sushuang 已提交
522 523
            // Always update, or it is wrong in the case turning on legend
            // because points are not changed
S
sushuang 已提交
524
            showSymbol && symbolDraw.updateData(data, {
525
                isIgnore: isIgnoreFunc,
526 527 528 529
                clipShape: clipShapeForSymbol,
                getSymbolPoint(idx) {
                    return [points[idx * 2], points[idx * 2 + 1]];
                }
S
sushuang 已提交
530
            });
L
lang 已提交
531

S
sushuang 已提交
532 533 534
            // Stop symbol animation and sync with line points
            // FIXME performance?
            data.eachItemGraphicEl(function (el) {
535
                el && el.stopAnimation(null, true);
S
sushuang 已提交
536 537 538 539 540 541 542 543 544
            });

            // 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) {
                    this._updateAnimation(
S
sushuang 已提交
545
                        data, stackedOnPoints, coordSys, api, step, valueOrigin
L
lang 已提交
546 547
                    );
                }
S
sushuang 已提交
548 549 550 551 552
                else {
                    // Not do it in update with animation
                    if (step) {
                        // TODO If stacked series is not step
                        points = turnPointsIntoStep(points, coordSys, step);
553 554 555
                        if (stackedOnPoints) {
                            stackedOnPoints = turnPointsIntoStep(stackedOnPoints, coordSys, step);
                        }
L
lang 已提交
556
                    }
S
sushuang 已提交
557 558 559 560 561 562 563 564

                    polyline.setShape({
                        points: points
                    });
                    polygon && polygon.setShape({
                        points: points,
                        stackedOnPoints: stackedOnPoints
                    });
L
lang 已提交
565
                }
L
lang 已提交
566
            }
S
sushuang 已提交
567 568
        }

569
        const visualColor = getVisualGradient(data, coordSys)
570
            || data.getVisual('style')[data.getVisual('drawType')];
571 572
        const focus = seriesModel.get(['emphasis', 'focus']);
        const blurScope = seriesModel.get(['emphasis', 'blurScope']);
S
sushuang 已提交
573 574 575 576 577 578 579

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

584
        setStatesStylesFromModel(polyline, seriesModel, 'lineStyle');
585

586 587
        const shouldBolderOnEmphasis = seriesModel.get(['emphasis', 'lineStyle', 'width']) === 'bolder';
        if (shouldBolderOnEmphasis) {
588
            const emphasisLineStyle = polyline.getState('emphasis').style;
589
            emphasisLineStyle.lineWidth = polyline.style.lineWidth + 1;
590 591
        }

592
        // Needs seriesIndex for focus
593
        getECData(polyline).seriesIndex = seriesModel.seriesIndex;
594
        enableHoverEmphasis(polyline, focus, blurScope);
595

596
        const smooth = getSmooth(seriesModel.get('smooth'));
S
sushuang 已提交
597 598 599 600 601
        polyline.setShape({
            smooth: smooth,
            smoothMonotone: seriesModel.get('smoothMonotone'),
            connectNulls: seriesModel.get('connectNulls')
        });
L
lang 已提交
602

S
sushuang 已提交
603
        if (polygon) {
604
            const stackedOnSeries = data.getCalculationInfo('stackedOnSeries');
605
            let stackedOnSmooth = 0;
606

S
sushuang 已提交
607 608
            polygon.useStyle(zrUtil.defaults(
                areaStyleModel.getAreaStyle(),
L
lang 已提交
609
                {
S
sushuang 已提交
610 611
                    fill: visualColor,
                    opacity: 0.7,
612
                    lineJoin: 'bevel' as CanvasLineJoin
L
lang 已提交
613 614
                }
            ));
L
lang 已提交
615

S
sushuang 已提交
616
            if (stackedOnSeries) {
S
sushuang 已提交
617 618 619 620
                stackedOnSmooth = getSmooth(stackedOnSeries.get('smooth'));
            }

            polygon.setShape({
621
                smooth: smooth,
S
sushuang 已提交
622
                stackedOnSmooth: stackedOnSmooth,
L
lang 已提交
623 624
                smoothMonotone: seriesModel.get('smoothMonotone'),
                connectNulls: seriesModel.get('connectNulls')
625
            });
626 627

            setStatesStylesFromModel(polygon, seriesModel, 'areaStyle');
628
            // Needs seriesIndex for focus
629
            getECData(polygon).seriesIndex = seriesModel.seriesIndex;
630
            enableHoverEmphasis(polygon, focus, blurScope);
S
sushuang 已提交
631
        }
L
lang 已提交
632

633 634
        const changePolyState = (toState: DisplayState) => {
            this._changePolyState(toState);
635 636 637 638
        };

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

S
sushuang 已提交
642 643 644 645 646 647
        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 已提交
648
        this._valueOrigin = valueOrigin;
P
pissang 已提交
649
    }
S
sushuang 已提交
650

P
pissang 已提交
651
    dispose() {}
S
sushuang 已提交
652

P
pissang 已提交
653 654 655 656 657 658
    highlight(
        seriesModel: LineSeriesModel,
        ecModel: GlobalModel,
        api: ExtensionAPI,
        payload: Payload
    ) {
659 660
        const data = seriesModel.getData();
        const dataIndex = modelUtil.queryDataIndex(data, payload);
S
sushuang 已提交
661

662 663
        this._changePolyState('emphasis');

S
sushuang 已提交
664
        if (!(dataIndex instanceof Array) && dataIndex != null && dataIndex >= 0) {
665
            const points = data.getLayout('points');
666
            let symbol = data.getItemGraphicEl(dataIndex) as SymbolClz;
S
sushuang 已提交
667 668
            if (!symbol) {
                // Create a temporary symbol if it is not exists
669 670 671
                const x = points[dataIndex * 2];
                const y = points[dataIndex * 2 + 1];
                if (isNaN(x) || isNaN(y)) {
S
sushuang 已提交
672 673 674
                    // Null data
                    return;
                }
675
                // fix #11360: should't draw symbol outside clipShapeForSymbol
676
                if (this._clipShapeForSymbol && !this._clipShapeForSymbol.contain(x, y)) {
677 678
                    return;
                }
S
sushuang 已提交
679
                symbol = new SymbolClz(data, dataIndex);
680 681
                symbol.x = x;
                symbol.y = y;
S
sushuang 已提交
682 683 684 685
                symbol.setZ(
                    seriesModel.get('zlevel'),
                    seriesModel.get('z')
                );
P
pissang 已提交
686
                (symbol as SymbolExtended).__temp = true;
S
sushuang 已提交
687
                data.setItemGraphicEl(dataIndex, symbol);
L
lang 已提交
688

S
sushuang 已提交
689 690
                // Stop scale animation
                symbol.stopSymbolAnimation(true);
L
lang 已提交
691

S
sushuang 已提交
692 693 694 695 696 697 698 699 700 701
                this.group.add(symbol);
            }
            symbol.highlight();
        }
        else {
            // Highlight whole series
            ChartView.prototype.highlight.call(
                this, seriesModel, ecModel, api, payload
            );
        }
P
pissang 已提交
702
    }
S
sushuang 已提交
703

P
pissang 已提交
704 705 706 707 708 709
    downplay(
        seriesModel: LineSeriesModel,
        ecModel: GlobalModel,
        api: ExtensionAPI,
        payload: Payload
    ) {
710 711
        const data = seriesModel.getData();
        const dataIndex = modelUtil.queryDataIndex(data, payload) as number;
712 713 714

        this._changePolyState('normal');

S
sushuang 已提交
715
        if (dataIndex != null && dataIndex >= 0) {
716
            const symbol = data.getItemGraphicEl(dataIndex) as SymbolExtended;
S
sushuang 已提交
717 718 719 720 721 722 723
            if (symbol) {
                if (symbol.__temp) {
                    data.setItemGraphicEl(dataIndex, null);
                    this.group.remove(symbol);
                }
                else {
                    symbol.downplay();
L
lang 已提交
724
                }
L
lang 已提交
725
            }
S
sushuang 已提交
726 727 728 729 730 731 732 733 734
        }
        else {
            // FIXME
            // can not downplay completely.
            // Downplay whole series
            ChartView.prototype.downplay.call(
                this, seriesModel, ecModel, api, payload
            );
        }
P
pissang 已提交
735
    }
L
lang 已提交
736

737 738 739 740 741 742
    _changePolyState(toState: DisplayState) {
        const polygon = this._polygon;
        setStatesFlag(this._polyline, toState);
        polygon && setStatesFlag(polygon, toState);
    }

743
    _newPolyline(points: ArrayLike<number>) {
744
        let polyline = this._polyline;
S
sushuang 已提交
745 746 747 748
        // Remove previous created polyline
        if (polyline) {
            this._lineGroup.remove(polyline);
        }
L
lang 已提交
749

P
pissang 已提交
750
        polyline = new ECPolyline({
S
sushuang 已提交
751
            shape: {
752
                points
S
sushuang 已提交
753
            },
754
            segmentIgnoreThreshold: 2,
S
sushuang 已提交
755 756
            z2: 10
        });
757

S
sushuang 已提交
758
        this._lineGroup.add(polyline);
L
lang 已提交
759

S
sushuang 已提交
760
        this._polyline = polyline;
L
lang 已提交
761

S
sushuang 已提交
762
        return polyline;
P
pissang 已提交
763
    }
L
lang 已提交
764

765
    _newPolygon(points: ArrayLike<number>, stackedOnPoints: ArrayLike<number>) {
766
        let polygon = this._polygon;
S
sushuang 已提交
767 768 769 770
        // Remove previous created polygon
        if (polygon) {
            this._lineGroup.remove(polygon);
        }
L
lang 已提交
771

P
pissang 已提交
772
        polygon = new ECPolygon({
S
sushuang 已提交
773
            shape: {
774
                points,
S
sushuang 已提交
775 776
                stackedOnPoints: stackedOnPoints
            },
777
            segmentIgnoreThreshold: 2
S
sushuang 已提交
778
        });
L
lang 已提交
779

S
sushuang 已提交
780
        this._lineGroup.add(polygon);
L
lang 已提交
781

S
sushuang 已提交
782 783
        this._polygon = polygon;
        return polygon;
P
pissang 已提交
784
    }
785

S
sushuang 已提交
786 787 788 789
    /**
     * @private
     */
    // FIXME Two value axis
P
pissang 已提交
790 791
    _updateAnimation(
        data: List,
792
        stackedOnPoints: ArrayLike<number>,
P
pissang 已提交
793 794 795 796 797
        coordSys: Cartesian2D | Polar,
        api: ExtensionAPI,
        step: LineSeriesOption['step'],
        valueOrigin: LineSeriesOption['areaStyle']['origin']
    ) {
798 799 800
        const polyline = this._polyline;
        const polygon = this._polygon;
        const seriesModel = data.hostModel;
S
sushuang 已提交
801

802
        const diff = lineAnimationDiff(
S
sushuang 已提交
803 804
            this._data, data,
            this._stackedOnPoints, stackedOnPoints,
S
sushuang 已提交
805 806
            this._coordSys, coordSys,
            this._valueOrigin, valueOrigin
S
sushuang 已提交
807 808
        );

809 810 811 812
        let current = diff.current;
        let stackedOnCurrent = diff.stackedOnCurrent;
        let next = diff.next;
        let stackedOnNext = diff.stackedOnNext;
S
sushuang 已提交
813 814 815 816 817 818 819
        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);
        }
820

821 822 823
        // 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 已提交
824 825
        if (getBoundingDiff(current, next) > 3000
            || (polygon && getBoundingDiff(stackedOnCurrent, stackedOnNext) > 3000)
826 827 828 829 830 831 832 833 834 835 836 837
        ) {
            polyline.setShape({
                points: next
            });
            if (polygon) {
                polygon.setShape({
                    points: next,
                    stackedOnPoints: stackedOnNext
                });
            }
            return;
        }
838

S
sushuang 已提交
839 840 841
        // `diff.current` is subset of `current` (which should be ensured by
        // turnPointsIntoStep), so points in `__points` can be updated when
        // points in `current` are update during animation.
P
pissang 已提交
842
        (polyline.shape as any).__points = diff.current;
S
sushuang 已提交
843
        polyline.shape.points = current;
L
lang 已提交
844

845 846
        // Stop previous animation.
        polyline.stopAnimation();
S
sushuang 已提交
847 848 849
        graphic.updateProps(polyline, {
            shape: {
                points: next
L
lang 已提交
850
            }
S
sushuang 已提交
851
        }, seriesModel);
L
lang 已提交
852

S
sushuang 已提交
853 854
        if (polygon) {
            polygon.setShape({
855
                // Reuse the points with polyline.
S
sushuang 已提交
856 857 858
                points: current,
                stackedOnPoints: stackedOnCurrent
            });
859
            polygon.stopAnimation();
S
sushuang 已提交
860
            graphic.updateProps(polygon, {
L
lang 已提交
861
                shape: {
S
sushuang 已提交
862
                    stackedOnPoints: stackedOnNext
L
lang 已提交
863
                }
L
lang 已提交
864
            }, seriesModel);
865 866 867 868
            // If use attr directly in updateProps.
            if (polyline.shape.points !== polygon.shape.points) {
                polygon.shape.points = polyline.shape.points;
            }
S
sushuang 已提交
869
        }
L
lang 已提交
870

871

872
        const updatedDataInfo: {
P
pissang 已提交
873 874 875
            el: SymbolExtended,
            ptIdx: number
        }[] = [];
876
        const diffStatus = diff.status;
S
sushuang 已提交
877

878
        for (let i = 0; i < diffStatus.length; i++) {
879
            const cmd = diffStatus[i].cmd;
S
sushuang 已提交
880
            if (cmd === '=') {
881
                const el = data.getItemGraphicEl(diffStatus[i].idx1) as SymbolExtended;
S
sushuang 已提交
882 883 884 885 886
                if (el) {
                    updatedDataInfo.push({
                        el: el,
                        ptIdx: i    // Index of points
                    });
L
lang 已提交
887
                }
L
lang 已提交
888
            }
S
sushuang 已提交
889
        }
L
lang 已提交
890

S
sushuang 已提交
891 892
        if (polyline.animators && polyline.animators.length) {
            polyline.animators[0].during(function () {
893
                const points = (polyline.shape as any).__points;
894
                for (let i = 0; i < updatedDataInfo.length; i++) {
895
                    const el = updatedDataInfo[i].el;
896 897 898
                    const offset = updatedDataInfo[i].ptIdx * 2;
                    el.x = points[offset];
                    el.y = points[offset + 1];
899
                    el.markRedraw();
900 901
                }
            });
L
lang 已提交
902
        }
P
pissang 已提交
903
    }
S
sushuang 已提交
904

P
pissang 已提交
905
    remove(ecModel: GlobalModel) {
906 907
        const group = this.group;
        const oldData = this._data;
S
sushuang 已提交
908 909 910
        this._lineGroup.removeAll();
        this._symbolDraw.remove(true);
        // Remove temporary created elements when highlighting
P
pissang 已提交
911
        oldData && oldData.eachItemGraphicEl(function (el: SymbolExtended, idx) {
S
sushuang 已提交
912 913 914 915 916 917
            if (el.__temp) {
                group.remove(el);
                oldData.setItemGraphicEl(idx, null);
            }
        });

S
sushuang 已提交
918 919 920 921 922 923
        this._polyline =
            this._polygon =
            this._coordSys =
            this._points =
            this._stackedOnPoints =
            this._data = null;
S
sushuang 已提交
924
    }
P
pissang 已提交
925 926 927 928
}

ChartView.registerClass(LineView);

929
export default ChartView;