You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by sh...@apache.org on 2020/09/16 15:35:58 UTC

[incubator-echarts] branch line-optimize created (now 71a590b)

This is an automated email from the ASF dual-hosted git repository.

shenyi pushed a change to branch line-optimize
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git.


      at 71a590b  line: optimize memory cost and initialize time for the large line.

This branch includes the following new commits:

     new 71a590b  line: optimize memory cost and initialize time for the large line.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.



---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org


[incubator-echarts] 01/01: line: optimize memory cost and initialize time for the large line.

Posted by sh...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

shenyi pushed a commit to branch line-optimize
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git

commit 71a590b4b84d9dd34163f5af3cd4b3848ee78168
Author: pissang <bm...@gmail.com>
AuthorDate: Wed Sep 16 23:35:10 2020 +0800

    line: optimize memory cost and initialize time for the large line.
---
 src/chart/helper/LargeSymbolDraw.ts   |   6 +-
 src/chart/helper/SymbolDraw.ts        |  18 +-
 src/chart/line.ts                     |   2 +-
 src/chart/line/LineView.ts            | 128 ++++++++------
 src/chart/line/helper.ts              |   2 +-
 src/chart/line/lineAnimationDiff.ts   | 126 ++++++++------
 src/chart/line/poly.ts                | 302 +++++++++++-----------------------
 src/layout/points.ts                  |  12 +-
 src/{chart/line.ts => util/vendor.ts} |  27 ++-
 test/largeLine.html                   |  30 ++--
 10 files changed, 304 insertions(+), 349 deletions(-)

diff --git a/src/chart/helper/LargeSymbolDraw.ts b/src/chart/helper/LargeSymbolDraw.ts
index 381beea..432df1f 100644
--- a/src/chart/helper/LargeSymbolDraw.ts
+++ b/src/chart/helper/LargeSymbolDraw.ts
@@ -190,7 +190,7 @@ class LargeSymbolDraw {
         });
 
         symbolEl.setShape({
-            points: data.getLayout('symbolPoints')
+            points: data.getLayout('points')
         });
         this._setCommon(symbolEl, data, false, opt);
         this.group.add(symbolEl);
@@ -203,7 +203,7 @@ class LargeSymbolDraw {
             return;
         }
 
-        let points = data.getLayout('symbolPoints');
+        let points = data.getLayout('points');
         this.group.eachChild(function (child: LargeSymbolPath) {
             if (child.startIndex != null) {
                 const len = (child.endIndex - child.startIndex) * 2;
@@ -251,7 +251,7 @@ class LargeSymbolDraw {
         }
 
         symbolEl.setShape({
-            points: data.getLayout('symbolPoints')
+            points: data.getLayout('points')
         });
         this._setCommon(symbolEl, data, !!this._incremental, opt);
     }
diff --git a/src/chart/helper/SymbolDraw.ts b/src/chart/helper/SymbolDraw.ts
index fd864e6..94c6b3d 100644
--- a/src/chart/helper/SymbolDraw.ts
+++ b/src/chart/helper/SymbolDraw.ts
@@ -25,7 +25,6 @@ import type Displayable from 'zrender/src/graphic/Displayable';
 import {
     StageHandlerProgressParams,
     LabelOption,
-    ColorString,
     SymbolOptionMixin,
     ItemStyleOption,
     ZRColor,
@@ -42,6 +41,7 @@ import { getLabelStatesModels } from '../../label/labelStyle';
 
 interface UpdateOpt {
     isIgnore?(idx: number): boolean
+    getSymbolPoint?(idx: number): number[]
     clipShape?: CoordinateSystemClipArea
 }
 
@@ -157,6 +157,8 @@ class SymbolDraw {
 
     private _seriesScope: SymbolDrawSeriesScope;
 
+    private _getSymbolPoint: UpdateOpt['getSymbolPoint'];
+
     constructor(SymbolCtor?: SymbolLikeCtor) {
         this._SymbolCtor = SymbolCtor || SymbolClz;
     }
@@ -174,6 +176,11 @@ class SymbolDraw {
 
         const seriesScope = makeSeriesScope(data);
 
+        const getSymbolPoint = opt.getSymbolPoint || function (idx: number) {
+            return data.getItemLayout(idx);
+        };
+
+
         // There is no oldLineData only when first rendering or switching from
         // stream mode to normal mode, where previous elements should be removed.
         if (!oldData) {
@@ -182,7 +189,7 @@ class SymbolDraw {
 
         data.diff(oldData)
             .add(function (newIdx) {
-                const point = data.getItemLayout(newIdx) as number[];
+                const point = getSymbolPoint(newIdx);
                 if (symbolNeedsDraw(data, point, newIdx, opt)) {
                     const symbolEl = new SymbolCtor(data, newIdx, seriesScope);
                     symbolEl.setPosition(point);
@@ -193,7 +200,7 @@ class SymbolDraw {
             .update(function (newIdx, oldIdx) {
                 let symbolEl = oldData.getItemGraphicEl(oldIdx) as SymbolLike;
 
-                const point = data.getItemLayout(newIdx) as number[];
+                const point = getSymbolPoint(newIdx) as number[];
                 if (!symbolNeedsDraw(data, point, newIdx, opt)) {
                     group.remove(symbolEl);
                     return;
@@ -223,6 +230,7 @@ class SymbolDraw {
             })
             .execute();
 
+        this._getSymbolPoint = getSymbolPoint;
         this._data = data;
     };
 
@@ -234,8 +242,8 @@ class SymbolDraw {
         const data = this._data;
         if (data) {
             // Not use animation
-            data.eachItemGraphicEl(function (el, idx) {
-                const point = data.getItemLayout(idx);
+            data.eachItemGraphicEl((el, idx) => {
+                const point = this._getSymbolPoint(idx);
                 el.setPosition(point);
                 el.markRedraw();
             });
diff --git a/src/chart/line.ts b/src/chart/line.ts
index 541b481..53be4a6 100644
--- a/src/chart/line.ts
+++ b/src/chart/line.ts
@@ -28,7 +28,7 @@ import dataSample from '../processor/dataSample';
 // In case developer forget to include grid component
 import '../component/gridSimple';
 
-echarts.registerLayout(layoutPoints('line'));
+echarts.registerLayout(layoutPoints('line', true));
 
 // Down sample after filter
 echarts.registerProcessor(
diff --git a/src/chart/line/LineView.ts b/src/chart/line/LineView.ts
index 2c77752..4616e2f 100644
--- a/src/chart/line/LineView.ts
+++ b/src/chart/line/LineView.ts
@@ -20,7 +20,6 @@
 // FIXME step not support polar
 
 import * as zrUtil from 'zrender/src/core/util';
-import {fromPoints} from 'zrender/src/core/bbox';
 import SymbolDraw from '../helper/SymbolDraw';
 import SymbolClz from '../helper/Symbol';
 import lineAnimationDiff from './lineAnimationDiff';
@@ -43,6 +42,8 @@ import type Axis2D from '../../coord/cartesian/Axis2D';
 import { CoordinateSystemClipArea } from '../../coord/CoordinateSystem';
 import { setStatesStylesFromModel, setStatesFlag, enableHoverEmphasis } from '../../util/states';
 import { getECData } from '../../util/ecData';
+import { createFloat32Array } from '../../util/vendor';
+import { createSymbol } from '../../util/symbol';
 
 
 type PolarArea = ReturnType<Polar['getArea']>;
@@ -52,29 +53,46 @@ interface SymbolExtended extends SymbolClz {
     __temp: boolean
 }
 
-function isPointsSame(points1: number[][], points2: number[][]) {
+function isPointsSame(points1: ArrayLike<number>, points2: ArrayLike<number>) {
     if (points1.length !== points2.length) {
         return;
     }
     for (let i = 0; i < points1.length; i++) {
-        const p1 = points1[i];
-        const p2 = points2[i];
-        if (p1[0] !== p2[0] || p1[1] !== p2[1]) {
+        if (points1[i] !== points2[i]) {
             return;
         }
     }
     return true;
 }
 
-function getBoundingDiff(points1: number[][], points2: number[][]): number {
-    const min1 = [] as number[];
-    const max1 = [] as number[];
+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]
+    ];
+}
 
-    const min2 = [] as number[];
-    const max2 = [] as number[];
+function getBoundingDiff(points1: ArrayLike<number>, points2: ArrayLike<number>): number {
 
-    fromPoints(points1, min1, max1);
-    fromPoints(points2, min2, max2);
+    const [min1, max1] = bboxFromPoints(points1);
+    const [min2, max2] = bboxFromPoints(points2);
 
     // Get a max value from each corner of two boundings.
     return Math.max(
@@ -99,56 +117,61 @@ function getStackedOnPoints(
         return [];
     }
 
-    const points = [];
-    for (let idx = 0, len = data.count(); idx < len; idx++) {
-        points.push(getStackedOnPoint(dataCoordInfo, coordSys, data, idx));
+    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];
     }
 
     return points;
 }
 
 function turnPointsIntoStep(
-    points: number[][],
+    points: ArrayLike<number>,
     coordSys: Cartesian2D | Polar,
     stepTurnAt: 'start' | 'end' | 'middle'
-) {
+): number[] {
     const baseAxis = coordSys.getBaseAxis();
     const baseIndex = baseAxis.dim === 'x' || baseAxis.dim === 'radius' ? 0 : 1;
 
-    const stepPoints = [];
+    const stepPoints: number[] = [];
     let i = 0;
-    for (; i < points.length - 1; i++) {
-        const nextPt = points[i + 1];
-        const pt = points[i];
-        stepPoints.push(pt);
+    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]);
 
-        const stepPt = [];
         switch (stepTurnAt) {
             case 'end':
                 stepPt[baseIndex] = nextPt[baseIndex];
                 stepPt[1 - baseIndex] = pt[1 - baseIndex];
-                // default is start
-                stepPoints.push(stepPt);
+                stepPoints.push(stepPt[0], stepPt[1]);
                 break;
             case 'middle':
-                // default is start
                 const middle = (pt[baseIndex] + nextPt[baseIndex]) / 2;
                 const stepPt2 = [];
                 stepPt[baseIndex] = stepPt2[baseIndex] = middle;
                 stepPt[1 - baseIndex] = pt[1 - baseIndex];
                 stepPt2[1 - baseIndex] = nextPt[1 - baseIndex];
-                stepPoints.push(stepPt);
-                stepPoints.push(stepPt2);
+                stepPoints.push(stepPt[0], stepPt[1]);
+                stepPoints.push(stepPt2[0], stepPt[1]);
                 break;
             default:
+                // default is start
                 stepPt[baseIndex] = pt[baseIndex];
                 stepPt[1 - baseIndex] = nextPt[1 - baseIndex];
-                // default is start
-                stepPoints.push(stepPt);
+                stepPoints.push(stepPt[0], stepPt[1]);
         }
     }
     // Last points
-    points[i] && stepPoints.push(points[i]);
+    stepPoints.push(points[i++], points[i++]);
     return stepPoints;
 }
 
@@ -365,8 +388,8 @@ class LineView extends ChartView {
     _polyline: ECPolyline;
     _polygon: ECPolygon;
 
-    _stackedOnPoints: number[][];
-    _points: number[][];
+    _stackedOnPoints: ArrayLike<number>;
+    _points: ArrayLike<number>;
 
     _step: LineSeriesOption['step'];
     _valueOrigin: LineSeriesOption['areaStyle']['origin'];
@@ -392,7 +415,7 @@ class LineView extends ChartView {
         const lineStyleModel = seriesModel.getModel('lineStyle');
         const areaStyleModel = seriesModel.getModel('areaStyle');
 
-        let points = data.mapArray(data.getItemLayout);
+        let points = data.getLayout('points') as number[] || [];
 
         const isCoordSysPolar = coordSys.type === 'polar';
         const prevCoordSys = this._coordSys;
@@ -458,7 +481,10 @@ class LineView extends ChartView {
         ) {
             showSymbol && symbolDraw.updateData(data, {
                 isIgnore: isIgnoreFunc,
-                clipShape: clipShapeForSymbol
+                clipShape: clipShapeForSymbol,
+                getSymbolPoint(idx) {
+                    return [points[idx * 2], points[idx * 2 + 1]];
+                }
             });
 
             if (step) {
@@ -495,7 +521,10 @@ class LineView extends ChartView {
             // because points are not changed
             showSymbol && symbolDraw.updateData(data, {
                 isIgnore: isIgnoreFunc,
-                clipShape: clipShapeForSymbol
+                clipShape: clipShapeForSymbol,
+                getSymbolPoint(idx) {
+                    return [points[idx * 2], points[idx * 2 + 1]];
+                }
             });
 
             // Stop symbol animation and sync with line points
@@ -629,25 +658,27 @@ class LineView extends ChartView {
         this._changePolyState('emphasis');
 
         if (!(dataIndex instanceof Array) && dataIndex != null && dataIndex >= 0) {
+            const points = data.getLayout('points');
             let symbol = data.getItemGraphicEl(dataIndex) as SymbolClz;
             if (!symbol) {
                 // Create a temporary symbol if it is not exists
-                const pt = data.getItemLayout(dataIndex) as number[];
-                if (!pt) {
+                const x = points[dataIndex * 2];
+                const y = points[dataIndex * 2 + 1];
+                if (isNaN(x) || isNaN(y)) {
                     // Null data
                     return;
                 }
                 // fix #11360: should't draw symbol outside clipShapeForSymbol
-                if (this._clipShapeForSymbol && !this._clipShapeForSymbol.contain(pt[0], pt[1])) {
+                if (this._clipShapeForSymbol && !this._clipShapeForSymbol.contain(x, y)) {
                     return;
                 }
                 symbol = new SymbolClz(data, dataIndex);
-                symbol.setPosition(pt);
+                symbol.x = x;
+                symbol.y = y;
                 symbol.setZ(
                     seriesModel.get('zlevel'),
                     seriesModel.get('z')
                 );
-                symbol.ignore = isNaN(pt[0]) || isNaN(pt[1]);
                 (symbol as SymbolExtended).__temp = true;
                 data.setItemGraphicEl(dataIndex, symbol);
 
@@ -705,7 +736,7 @@ class LineView extends ChartView {
         polygon && setStatesFlag(polygon, toState);
     }
 
-    _newPolyline(points: number[][]) {
+    _newPolyline(points: ArrayLike<number>) {
         let polyline = this._polyline;
         // Remove previous created polyline
         if (polyline) {
@@ -714,7 +745,7 @@ class LineView extends ChartView {
 
         polyline = new ECPolyline({
             shape: {
-                points: points
+                points
             },
             segmentIgnoreThreshold: 2,
             z2: 10
@@ -727,7 +758,7 @@ class LineView extends ChartView {
         return polyline;
     }
 
-    _newPolygon(points: number[][], stackedOnPoints: number[][]) {
+    _newPolygon(points: ArrayLike<number>, stackedOnPoints: ArrayLike<number>) {
         let polygon = this._polygon;
         // Remove previous created polygon
         if (polygon) {
@@ -736,7 +767,7 @@ class LineView extends ChartView {
 
         polygon = new ECPolygon({
             shape: {
-                points: points,
+                points,
                 stackedOnPoints: stackedOnPoints
             },
             segmentIgnoreThreshold: 2
@@ -754,7 +785,7 @@ class LineView extends ChartView {
     // FIXME Two value axis
     _updateAnimation(
         data: List,
-        stackedOnPoints: number[][],
+        stackedOnPoints: ArrayLike<number>,
         coordSys: Cartesian2D | Polar,
         api: ExtensionAPI,
         step: LineSeriesOption['step'],
@@ -855,9 +886,12 @@ class LineView extends ChartView {
 
         if (polyline.animators && polyline.animators.length) {
             polyline.animators[0].during(function () {
+                const points = (polyline.shape as any).__points;
                 for (let i = 0; i < updatedDataInfo.length; i++) {
                     const el = updatedDataInfo[i].el;
-                    el.setPosition((polyline.shape as any).__points[updatedDataInfo[i].ptIdx]);
+                    const offset = updatedDataInfo[i].ptIdx * 2;
+                    el.x = points[offset];
+                    el.y = points[offset + 1];
                     el.markRedraw();
                 }
             });
diff --git a/src/chart/line/helper.ts b/src/chart/line/helper.ts
index edaccc5..200824f 100644
--- a/src/chart/line/helper.ts
+++ b/src/chart/line/helper.ts
@@ -111,7 +111,7 @@ export function getStackedOnPoint(
     coordSys: Cartesian2D | Polar,
     data: List,
     idx: number
-    ) {
+) {
     let value = NaN;
     if (dataCoordInfo.stacked) {
         value = data.get(data.getCalculationInfo('stackedOverDimension'), idx) as number;
diff --git a/src/chart/line/lineAnimationDiff.ts b/src/chart/line/lineAnimationDiff.ts
index 9e164ef..84cc409 100644
--- a/src/chart/line/lineAnimationDiff.ts
+++ b/src/chart/line/lineAnimationDiff.ts
@@ -22,6 +22,7 @@ import List from '../../data/List';
 import type Cartesian2D from '../../coord/cartesian/Cartesian2D';
 import type Polar from '../../coord/polar/Polar';
 import { LineSeriesOption } from './LineSeries';
+import { createFloat32Array } from '../../util/vendor';
 
 interface DiffItem {
     cmd: '+' | '=' | '-'
@@ -49,7 +50,7 @@ function diffData(oldData: List, newData: List) {
 
 export default function (
     oldData: List, newData: List,
-    oldStackedOnPoints: number[][], newStackedOnPoints: number[][],
+    oldStackedOnPoints: ArrayLike<number>, newStackedOnPoints: ArrayLike<number>,
     oldCoordSys: Cartesian2D | Polar, newCoordSys: Cartesian2D | Polar,
     oldValueOrigin: LineSeriesOption['areaStyle']['origin'],
     newValueOrigin: LineSeriesOption['areaStyle']['origin']
@@ -64,11 +65,11 @@ export default function (
     // // FIXME One data ?
     // diff = arrayDiff(oldIdList, newIdList);
 
-    const currPoints: number[][] = [];
-    const nextPoints: number[][] = [];
+    const currPoints: number[] = [];
+    const nextPoints: number[] = [];
     // Points for stacking base line
-    const currStackedPoints: number[][] = [];
-    const nextStackedPoints: number[][] = [];
+    const currStackedPoints: number[] = [];
+    const nextStackedPoints: number[] = [];
 
     const status = [];
     const sortedIndices: number[] = [];
@@ -77,62 +78,78 @@ export default function (
     const newDataOldCoordInfo = prepareDataCoordInfo(oldCoordSys, newData, oldValueOrigin);
     const oldDataNewCoordInfo = prepareDataCoordInfo(newCoordSys, oldData, newValueOrigin);
 
+    const oldPoints = oldData.getLayout('points') as number[] || [];
+    const newPoints = newData.getLayout('points') as number[] || [];
+
     for (let i = 0; i < diff.length; i++) {
         const diffItem = diff[i];
         let pointAdded = true;
 
+        let oldIdx2: number;
+        let newIdx2: number;
+
         // FIXME, animation is not so perfect when dataZoom window moves fast
         // Which is in case remvoing or add more than one data in the tail or head
         switch (diffItem.cmd) {
             case '=':
-                let currentPt = oldData.getItemLayout(diffItem.idx) as number[];
-                const nextPt = newData.getItemLayout(diffItem.idx1) as number[];
+                oldIdx2 = diffItem.idx * 2;
+                newIdx2 = diffItem.idx1 * 2;
+                let currentX = oldPoints[oldIdx2];
+                let currentY = oldPoints[oldIdx2 + 1];
+                const nextX = newPoints[newIdx2];
+                const nextY = newPoints[newIdx2 + 1];
+
                 // If previous data is NaN, use next point directly
-                if (isNaN(currentPt[0]) || isNaN(currentPt[1])) {
-                    currentPt = nextPt.slice();
+                if (isNaN(currentX) || isNaN(currentY)) {
+                    currentX = nextX;
+                    currentY = nextY;
                 }
-                currPoints.push(currentPt);
-                nextPoints.push(nextPt);
+                currPoints.push(currentX, currentY);
+                nextPoints.push(nextX, nextY);
 
-                currStackedPoints.push(oldStackedOnPoints[diffItem.idx]);
-                nextStackedPoints.push(newStackedOnPoints[diffItem.idx1]);
+                currStackedPoints.push(oldStackedOnPoints[oldIdx2], oldStackedOnPoints[oldIdx2 + 1]);
+                nextStackedPoints.push(newStackedOnPoints[newIdx2], newStackedOnPoints[newIdx2 + 1]);
 
                 rawIndices.push(newData.getRawIndex(diffItem.idx1));
                 break;
             case '+':
-                const idxAdd = diffItem.idx;
-                currPoints.push(
-                    oldCoordSys.dataToPoint([
-                        newData.get(newDataOldCoordInfo.dataDimsForPoint[0], idxAdd),
-                        newData.get(newDataOldCoordInfo.dataDimsForPoint[1], idxAdd)
-                    ])
-                );
-
-                nextPoints.push((newData.getItemLayout(idxAdd) as number[]).slice());
-
-                currStackedPoints.push(
-                    getStackedOnPoint(newDataOldCoordInfo, oldCoordSys, newData, idxAdd)
-                );
-                nextStackedPoints.push(newStackedOnPoints[idxAdd]);
-
-                rawIndices.push(newData.getRawIndex(idxAdd));
+                const newIdx = diffItem.idx;
+                const newDataDimsForPoint = newDataOldCoordInfo.dataDimsForPoint;
+                const oldPt = oldCoordSys.dataToPoint([
+                    newData.get(newDataDimsForPoint[0], newIdx),
+                    newData.get(newDataDimsForPoint[1], newIdx)
+                ]);
+                newIdx2 = newIdx * 2;
+                currPoints.push(oldPt[0], oldPt[1]);
+
+                nextPoints.push(newPoints[newIdx2], newPoints[newIdx2 + 1]);
+
+                const stackedOnPoint = getStackedOnPoint(newDataOldCoordInfo, oldCoordSys, newData, newIdx);
+
+                currStackedPoints.push(stackedOnPoint[0], stackedOnPoint[1]);
+                nextStackedPoints.push(newStackedOnPoints[newIdx2], newStackedOnPoints[newIdx2 + 1]);
+
+                rawIndices.push(newData.getRawIndex(newIdx));
                 break;
             case '-':
-                const idxMinus = diffItem.idx;
-                const rawIndex = oldData.getRawIndex(idxMinus);
+                const oldIdx = diffItem.idx;
+                const rawIndex = oldData.getRawIndex(oldIdx);
+                const oldDataDimsForPoint = oldDataNewCoordInfo.dataDimsForPoint;
+                oldIdx2 = oldIdx * 2;
                 // Data is replaced. In the case of dynamic data queue
                 // FIXME FIXME FIXME
-                if (rawIndex !== idxMinus) {
-                    currPoints.push(oldData.getItemLayout(idxMinus) as number[]);
-                    nextPoints.push(newCoordSys.dataToPoint([
-                        oldData.get(oldDataNewCoordInfo.dataDimsForPoint[0], idxMinus),
-                        oldData.get(oldDataNewCoordInfo.dataDimsForPoint[1], idxMinus)
-                    ]));
-
-                    currStackedPoints.push(oldStackedOnPoints[idxMinus]);
-                    nextStackedPoints.push(
-                        getStackedOnPoint(oldDataNewCoordInfo, newCoordSys, oldData, idxMinus)
-                    );
+                if (rawIndex !== oldIdx) {
+                    const newPt = newCoordSys.dataToPoint([
+                        oldData.get(oldDataDimsForPoint[0], oldIdx),
+                        oldData.get(oldDataDimsForPoint[1], oldIdx)
+                    ]);
+                    const newStackedOnPt = getStackedOnPoint(oldDataNewCoordInfo, newCoordSys, oldData, oldIdx);
+
+                    currPoints.push(oldPoints[oldIdx2], oldPoints[oldIdx2 + 1]);
+                    nextPoints.push(newPt[0], newPt[1]);
+
+                    currStackedPoints.push(oldStackedOnPoints[oldIdx2], oldStackedOnPoints[oldIdx2 + 1]);
+                    nextStackedPoints.push(newStackedOnPt[0], newStackedOnPt[1]);
 
                     rawIndices.push(rawIndex);
                 }
@@ -154,20 +171,27 @@ export default function (
         return rawIndices[a] - rawIndices[b];
     });
 
-    const sortedCurrPoints = [];
-    const sortedNextPoints = [];
+    const len = currPoints.length;
+    const sortedCurrPoints = createFloat32Array(len);
+    const sortedNextPoints = createFloat32Array(len);
 
-    const sortedCurrStackedPoints = [];
-    const sortedNextStackedPoints = [];
+    const sortedCurrStackedPoints = createFloat32Array(len);
+    const sortedNextStackedPoints = createFloat32Array(len);
 
     const sortedStatus = [];
     for (let i = 0; i < sortedIndices.length; i++) {
         const idx = sortedIndices[i];
-        sortedCurrPoints[i] = currPoints[idx];
-        sortedNextPoints[i] = nextPoints[idx];
-
-        sortedCurrStackedPoints[i] = currStackedPoints[idx];
-        sortedNextStackedPoints[i] = nextStackedPoints[idx];
+        const i2 = i * 2;
+        const idx2 = idx * 2;
+        sortedCurrPoints[i2] = currPoints[idx2];
+        sortedCurrPoints[i2 + 1] = currPoints[idx2 + 1];
+        sortedNextPoints[i2] = nextPoints[idx2];
+        sortedNextPoints[i2 + 1] = nextPoints[idx2 + 1];
+
+        sortedCurrStackedPoints[i2] = currStackedPoints[idx2];
+        sortedCurrStackedPoints[i2 + 1] = currStackedPoints[idx2 + 1];
+        sortedNextStackedPoints[i2] = nextStackedPoints[idx2];
+        sortedNextStackedPoints[i2 + 1] = nextStackedPoints[idx2 + 1];
 
         sortedStatus[i] = status[idx];
     }
diff --git a/src/chart/line/poly.ts b/src/chart/line/poly.ts
index adb61eb..1c9a4ce 100644
--- a/src/chart/line/poly.ts
+++ b/src/chart/line/poly.ts
@@ -20,94 +20,21 @@
 // Poly path support NaN point
 
 import Path, { PathProps } from 'zrender/src/graphic/Path';
-import * as vec2 from 'zrender/src/core/vector';
 
-const vec2Min = vec2.min;
-const vec2Max = vec2.max;
+const mathMin = Math.min;
+const mathMax = Math.max;
 
-const scaleAndAdd = vec2.scaleAndAdd;
-const v2Copy = vec2.copy;
-
-// Temporary variable
-const v: number[] = [];
-const cp0: number[] = [];
-const cp1: number[] = [];
-
-function isPointNull(p: number[]) {
-    return isNaN(p[0]) || isNaN(p[1]);
+function isPointNull(x: number, y: number) {
+    return isNaN(x) || isNaN(y);
 }
-
-function drawSegment(
-    ctx: CanvasRenderingContext2D,
-    points: number[][],
-    start: number,
-    segLen: number,
-    allLen: number,
-    dir: number,
-    smoothMin: number[],
-    smoothMax: number[],
-    smooth: number,
-    smoothMonotone: 'x' | 'y' | 'none',
-    connectNulls: boolean
-) {
-    return ((smoothMonotone === 'none' || !smoothMonotone) ? drawNonMono : drawMono)(
-        ctx,
-        points,
-        start,
-        segLen,
-        allLen,
-        dir,
-        smoothMin,
-        smoothMax,
-        smooth,
-        smoothMonotone,
-        connectNulls
-    );
-}
-
-/**
- * Check if points is in monotone.
- *
- * @param {number[][]} points         Array of points which is in [x, y] form
- * @param {string}     smoothMonotone 'x', 'y', or 'none', stating for which
- *                                    dimension that is checking.
- *                                    If is 'none', `drawNonMono` should be
- *                                    called.
- *                                    If is undefined, either being monotone
- *                                    in 'x' or 'y' will call `drawMono`.
- */
-// function isMono(points, smoothMonotone) {
-//     if (points.length <= 1) {
-//         return true;
-//     }
-
-//     let dim = smoothMonotone === 'x' ? 0 : 1;
-//     let last = points[0][dim];
-//     let lastDiff = 0;
-//     for (let i = 1; i < points.length; ++i) {
-//         let diff = points[i][dim] - last;
-//         if (!isNaN(diff) && !isNaN(lastDiff)
-//             && diff !== 0 && lastDiff !== 0
-//             && ((diff >= 0) !== (lastDiff >= 0))
-//         ) {
-//             return false;
-//         }
-//         if (!isNaN(diff) && diff !== 0) {
-//             lastDiff = diff;
-//             last = points[i][dim];
-//         }
-//     }
-//     return true;
-// }
-
 /**
- * Draw smoothed line in monotone, in which only vertical or horizontal bezier
- * control points will be used. This should be used when points are monotone
- * either in x or y dimension.
+ * Draw smoothed line in non-monotone, in may cause undesired curve in extreme
+ * situations. This should be used when points are non-monotone neither in x or
+ * y dimension.
  */
-function drawMono(
+function drawSegment(
     ctx: CanvasRenderingContext2D,
-    points: number[][],
+    points: ArrayLike<number>,
     start: number,
     segLen: number,
     allLen: number,
@@ -118,15 +45,23 @@ function drawMono(
     smoothMonotone: 'x' | 'y' | 'none',
     connectNulls: boolean
 ) {
-    let prevIdx = 0;
+    let px: number;
+    let py: number;
+    let cpx0: number;
+    let cpy0: number;
+    let cpx1: number;
+    let cpy1: number;
     let idx = start;
     let k = 0;
     for (; k < segLen; k++) {
-        const p = points[idx];
+
+        const x = points[idx * 2];
+        const y = points[idx * 2 + dir];
+
         if (idx >= allLen || idx < 0) {
             break;
         }
-        if (isPointNull(p)) {
+        if (isPointNull(x, y)) {
             if (connectNulls) {
                 idx += dir;
                 continue;
@@ -135,165 +70,118 @@ function drawMono(
         }
 
         if (idx === start) {
-            ctx[dir > 0 ? 'moveTo' : 'lineTo'](p[0], p[1]);
+            ctx[dir > 0 ? 'moveTo' : 'lineTo'](x, y);
+            cpx0 = x;
+            cpy0 = y;
         }
         else {
-            if (smooth > 0) {
-                const prevP = points[prevIdx];
-                const dim = smoothMonotone === 'y' ? 1 : 0;
-
-                // Length of control point to p, either in x or y, but not both
-                const ctrlLen = (p[dim] - prevP[dim]) * smooth;
-
-                v2Copy(cp0, prevP);
-                cp0[dim] = prevP[dim] + ctrlLen;
-
-                v2Copy(cp1, p);
-                cp1[dim] = p[dim] - ctrlLen;
-
-                ctx.bezierCurveTo(
-                    cp0[0], cp0[1],
-                    cp1[0], cp1[1],
-                    p[0], p[1]
-                );
-            }
-            else {
-                ctx.lineTo(p[0], p[1]);
-            }
-        }
-
-        prevIdx = idx;
-        idx += dir;
-    }
+            const dx = x - px;
+            const dy = y - py;
 
-    return k;
-}
-
-/**
- * Draw smoothed line in non-monotone, in may cause undesired curve in extreme
- * situations. This should be used when points are non-monotone neither in x or
- * y dimension.
- */
-function drawNonMono(
-    ctx: CanvasRenderingContext2D,
-    points: number[][],
-    start: number,
-    segLen: number,
-    allLen: number,
-    dir: number,
-    smoothMin: number[],
-    smoothMax: number[],
-    smooth: number,
-    smoothMonotone: 'x' | 'y' | 'none',
-    connectNulls: boolean
-) {
-    let prevIdx = 0;
-    let idx = start;
-    let k = 0;
-    for (; k < segLen; k++) {
-        const p = points[idx];
-        if (idx >= allLen || idx < 0) {
-            break;
-        }
-        if (isPointNull(p)) {
-            if (connectNulls) {
+            // Ignore tiny segment.
+            if ((dx * dx + dy * dy) < 1) {
                 idx += dir;
                 continue;
             }
-            break;
-        }
 
-        if (idx === start) {
-            ctx[dir > 0 ? 'moveTo' : 'lineTo'](p[0], p[1]);
-            v2Copy(cp0, p);
-        }
-        else {
             if (smooth > 0) {
                 let nextIdx = idx + dir;
-                let nextP = points[nextIdx];
+                let nextX = points[nextIdx * 2];
+                let nextY = points[nextIdx * 2 + 1];
                 if (connectNulls) {
                     // Find next point not null
-                    while (nextP && isPointNull(points[nextIdx])) {
+                    while (isPointNull(nextX, nextY) && (nextIdx < (segLen + start)) || (dir < 0 && nextIdx >= start)) {
                         nextIdx += dir;
-                        nextP = points[nextIdx];
+                        nextX = points[nextIdx * 2];
+                        nextY = points[nextIdx * 2 + 1];
                     }
                 }
 
                 let ratioNextSeg = 0.5;
-                const prevP = points[prevIdx];
-                nextP = points[nextIdx];
-                // Last point
-                if (!nextP || isPointNull(nextP)) {
-                    v2Copy(cp1, p);
+                let vx: number = 0;
+                let vy: number = 0;
+                // Is last point
+                if ((dir > 0 && nextIdx >= (segLen + start)) || (dir < 0 && nextIdx < start)) {
+                    cpx1 = x;
+                    cpy1 = y;
                 }
                 else {
-                    // If next data is null in not connect case
-                    if (isPointNull(nextP) && !connectNulls) {
-                        nextP = p;
-                    }
-
-                    vec2.sub(v, nextP, prevP);
+                    vx = nextX - px;
+                    vy = nextY - py;
 
+                    const dx0 = x - px;
+                    const dx1 = nextX - x;
+                    const dy0 = y - py;
+                    const dy1 = nextY - y;
                     let lenPrevSeg;
                     let lenNextSeg;
-                    if (smoothMonotone === 'x' || smoothMonotone === 'y') {
-                        const dim = smoothMonotone === 'x' ? 0 : 1;
-                        lenPrevSeg = Math.abs(p[dim] - prevP[dim]);
-                        lenNextSeg = Math.abs(p[dim] - nextP[dim]);
+                    if (smoothMonotone === 'x') {
+                        lenPrevSeg = Math.abs(dx0);
+                        lenNextSeg = Math.abs(dx1);
+                    }
+                    else if (smoothMonotone === 'y') {
+                        lenPrevSeg = Math.abs(dy0);
+                        lenNextSeg = Math.abs(dy1);
                     }
                     else {
-                        lenPrevSeg = vec2.dist(p, prevP);
-                        lenNextSeg = vec2.dist(p, nextP);
+                        lenPrevSeg = Math.sqrt(dx0 * dx0 + dy0 * dy0);
+                        lenNextSeg = Math.sqrt(dx1 * dx1 + dy1 * dy1);
                     }
 
                     // Use ratio of seg length
                     ratioNextSeg = lenNextSeg / (lenNextSeg + lenPrevSeg);
 
-                    scaleAndAdd(cp1, p, v, -smooth * (1 - ratioNextSeg));
+                    cpx1 = x - vx * smooth * (1 - ratioNextSeg);
+                    cpy1 = y - vy * smooth * (1 - ratioNextSeg);
                 }
                 // Smooth constraint
-                vec2Min(cp0, cp0, smoothMax);
-                vec2Max(cp0, cp0, smoothMin);
-                vec2Min(cp1, cp1, smoothMax);
-                vec2Max(cp1, cp1, smoothMin);
-
-                ctx.bezierCurveTo(
-                    cp0[0], cp0[1],
-                    cp1[0], cp1[1],
-                    p[0], p[1]
-                );
+                cpx0 = mathMin(cpx0, smoothMax[0]);
+                cpy0 = mathMin(cpy0, smoothMax[1]);
+                cpx0 = mathMax(cpx0, smoothMin[0]);
+                cpy0 = mathMax(cpy0, smoothMin[1]);
+
+                cpx1 = mathMin(cpx1, smoothMax[0]);
+                cpy1 = mathMin(cpy1, smoothMax[1]);
+                cpx1 = mathMax(cpx1, smoothMin[0]);
+                cpy1 = mathMax(cpy1, smoothMin[1]);
+
+                ctx.bezierCurveTo(cpx0, cpy0, cpx1, cpy1, x, y);
+
                 // cp0 of next segment
-                scaleAndAdd(cp0, p, v, smooth * ratioNextSeg);
+                cpx0 = x + vx * smooth * ratioNextSeg;
+                cpy0 = y + vy * smooth * ratioNextSeg;
             }
             else {
-                ctx.lineTo(p[0], p[1]);
+                ctx.lineTo(x, y);
             }
         }
 
-        prevIdx = idx;
+        px = x;
+        py = y;
         idx += dir;
     }
 
     return k;
 }
 
-function getBoundingBox(points: number[][], smoothConstraint?: boolean) {
+function getBoundingBox(points: ArrayLike<number>, smoothConstraint?: boolean) {
     const ptMin = [Infinity, Infinity];
     const ptMax = [-Infinity, -Infinity];
     if (smoothConstraint) {
-        for (let i = 0; i < points.length; i++) {
-            const pt = points[i];
-            if (pt[0] < ptMin[0]) {
-                ptMin[0] = pt[0];
+        for (let i = 0; i < points.length;) {
+            const x = points[i++];
+            const y = points[i++];
+            if (x < ptMin[0]) {
+                ptMin[0] = x;
             }
-            if (pt[1] < ptMin[1]) {
-                ptMin[1] = pt[1];
+            if (y < ptMin[1]) {
+                ptMin[1] = y;
             }
-            if (pt[0] > ptMax[0]) {
-                ptMax[0] = pt[0];
+            if (x > ptMax[0]) {
+                ptMax[0] = x;
             }
-            if (pt[1] > ptMax[1]) {
-                ptMax[1] = pt[1];
+            if (y > ptMax[1]) {
+                ptMax[1] = y;
             }
         }
     }
@@ -304,7 +192,7 @@ function getBoundingBox(points: number[][], smoothConstraint?: boolean) {
 }
 
 class ECPolylineShape {
-    points: number[][];
+    points: ArrayLike<number>;
     smooth = 0;
     smoothConstraint = true;
     smoothMonotone: 'x' | 'y' | 'none';
@@ -346,13 +234,13 @@ export class ECPolyline extends Path<ECPolylineProps> {
 
         if (shape.connectNulls) {
             // Must remove first and last null values avoid draw error in polygon
-            for (; len > 0; len--) {
-                if (!isPointNull(points[len - 1])) {
+            for (; len > 0; len -= 2) {
+                if (!isPointNull(points[len - 2], points[len - 1])) {
                     break;
                 }
             }
-            for (; i < len; i++) {
-                if (!isPointNull(points[i])) {
+            for (; i < len; i += 2) {
+                if (!isPointNull(points[i], points[i + 1])) {
                     break;
                 }
             }
@@ -368,7 +256,7 @@ export class ECPolyline extends Path<ECPolylineProps> {
 }
 class ECPolygonShape extends ECPolylineShape {
     // Offset between stacked base points and points
-    stackedOnPoints: number[][];
+    stackedOnPoints: ArrayLike<number>;
     stackedOnSmooth: number;
 }
 
@@ -401,13 +289,13 @@ export class ECPolygon extends Path {
 
         if (shape.connectNulls) {
             // Must remove first and last null values avoid draw error in polygon
-            for (; len > 0; len--) {
-                if (!isPointNull(points[len - 1])) {
+            for (; len > 0; len -= 2) {
+                if (!isPointNull(points[len - 2], points[len - 1])) {
                     break;
                 }
             }
-            for (; i < len; i++) {
-                if (!isPointNull(points[i])) {
+            for (; i < len; i += 2) {
+                if (!isPointNull(points[i], points[i + 1])) {
                     break;
                 }
             }
@@ -425,7 +313,7 @@ export class ECPolygon extends Path {
             );
             i += k + 1;
 
-            ctx.closePath();
+            // ctx.closePath();
         }
     }
 }
\ No newline at end of file
diff --git a/src/layout/points.ts b/src/layout/points.ts
index 0808e10..e819581 100644
--- a/src/layout/points.ts
+++ b/src/layout/points.ts
@@ -24,8 +24,10 @@ import createRenderPlanner from '../chart/helper/createRenderPlanner';
 import {isDimensionStacked} from '../data/helper/dataStackHelper';
 import SeriesModel from '../model/Series';
 import { StageHandler, ParsedValueNumeric } from '../util/types';
+import { createFloat32Array } from '../util/vendor';
 
-export default function (seriesType?: string): StageHandler {
+
+export default function (seriesType: string, forceStoreInTypedArray?: boolean): StageHandler {
     return {
         seriesType: seriesType,
 
@@ -35,7 +37,7 @@ export default function (seriesType?: string): StageHandler {
             const data = seriesModel.getData();
             const coordSys = seriesModel.coordinateSystem;
             const pipelineContext = seriesModel.pipelineContext;
-            const isLargeRender = pipelineContext.large;
+            const useTypedArray = forceStoreInTypedArray || pipelineContext.large;
 
             if (!coordSys) {
                 return;
@@ -58,7 +60,7 @@ export default function (seriesType?: string): StageHandler {
             return dimLen && {
                 progress(params, data) {
                     const segCount = params.end - params.start;
-                    const points = isLargeRender && new Float32Array(segCount * dimLen);
+                    const points = useTypedArray && createFloat32Array(segCount * dimLen);
 
                     const tmpIn: ParsedValueNumeric[] = [];
                     const tmpOut: number[] = [];
@@ -76,7 +78,7 @@ export default function (seriesType?: string): StageHandler {
                             point = !isNaN(x) && !isNaN(y) && coordSys.dataToPoint(tmpIn, null, tmpOut);
                         }
 
-                        if (isLargeRender) {
+                        if (useTypedArray) {
                             points[offset++] = point ? point[0] : NaN;
                             points[offset++] = point ? point[1] : NaN;
                         }
@@ -85,7 +87,7 @@ export default function (seriesType?: string): StageHandler {
                         }
                     }
 
-                    isLargeRender && data.setLayout('symbolPoints', points);
+                    useTypedArray && data.setLayout('points', points);
                 }
             };
         }
diff --git a/src/chart/line.ts b/src/util/vendor.ts
similarity index 60%
copy from src/chart/line.ts
copy to src/util/vendor.ts
index 541b481..ab4db32 100644
--- a/src/chart/line.ts
+++ b/src/util/vendor.ts
@@ -17,21 +17,18 @@
 * under the License.
 */
 
-import * as echarts from '../echarts';
+import { isArray } from 'zrender/src/core/util';
 
-import './line/LineSeries';
-import './line/LineView';
+/* global Float32Array */
+const supportFloat32Array = typeof Float32Array !== 'undefined';
 
-import layoutPoints from '../layout/points';
-import dataSample from '../processor/dataSample';
+const Float32ArrayCtor = !supportFloat32Array ? Array : Float32Array;
 
-// In case developer forget to include grid component
-import '../component/gridSimple';
-
-echarts.registerLayout(layoutPoints('line'));
-
-// Down sample after filter
-echarts.registerProcessor(
-    echarts.PRIORITY.PROCESSOR.STATISTIC,
-    dataSample('line')
-);
+export function createFloat32Array(arg: number | number[]): number[] | Float32Array {
+    if (isArray(arg)) {
+        // Return self directly if don't support TypedArray.
+        return supportFloat32Array ? new Float32Array(arg) : arg;
+    }
+    // Else is number
+    return new Float32ArrayCtor(arg);
+}
\ No newline at end of file
diff --git a/test/largeLine.html b/test/largeLine.html
index dba5cd3..9a8cd9b 100644
--- a/test/largeLine.html
+++ b/test/largeLine.html
@@ -47,8 +47,8 @@ under the License.
             ], function (echarts) {
                 var myChart;
                 var lineCount = 20;
-                var pointCount = 1000;
-                var chartCount = 50;
+                var pointCount = 10000;
+                var chartCount = 20;
 
                 var option = {
                     tooltip : {
@@ -92,8 +92,7 @@ under the License.
                 var oneDay = 24 * 3600 * 1000;
                 var base = +new Date(1897, 9, 3);
                 for (var j = 0; j < pointCount; j++) {
-                    var now = new Date(base += oneDay);
-                    date.push([now.getFullYear(), now.getMonth() + 1, now.getDate()].join('-'));
+                    date.push(base += oneDay);
                 }
                 for (var i = 0; i < lineCount; i++) {
                     var y = Math.random() * 1000;
@@ -101,11 +100,9 @@ under the License.
                     for (var j = 0; j < pointCount; j++) {
                         y += Math.round(10 + Math.random() * (-10 - 10));
                         values.push(
-                            [
-                                date[j],
-                                // Math.random() < 0.1 ? '-' : y
-                                y
-                            ]
+                            date[j],
+                            // Math.random() < 0.1 ? '-' : y
+                            y
                         );
                     }
 
@@ -113,10 +110,14 @@ under the License.
                   option.series.push({
                     name: 'line' + i,
                     type: 'line',
-                    sampling: 'average',
                     hoverAnimation: false,
                     showSymbol: false,
-                    data: values,
+                    dimensions: ['date', 'value'],
+                    encode: {
+                        x: 'date',
+                        y: 'value'
+                    },
+                    data: new Float64Array(values),
                     lineStyle: lineStyle
                   });
                 }
@@ -131,9 +132,10 @@ under the License.
                       myChart = echarts.init(document.getElementById('chart'+n));
                       myChart.setOption(option, true);
                     }
-                    var end = new Date();
-
-                    document.getElementById('timing').innerHTML = 'Graphs loaded in ' + ( end - start ) + ' ms.';
+                    setTimeout(function () {
+                        var end = new Date();
+                        document.getElementById('timing').innerHTML = 'Graphs loaded in ' + ( end - start ) + ' ms.';
+                    });
                 };
 
                 refresh();


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org