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 2021/10/11 10:12:43 UTC

[echarts] branch optimize-progressive created (now 3b191a1)

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

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


      at 3b191a1  fix(progressive): only update status on the new rendered elements

This branch includes the following new commits:

     new 3b191a1  fix(progressive): only update status on the new rendered elements

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


[echarts] 01/01: fix(progressive): only update status on the new rendered elements

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

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

commit 3b191a136f1bd7e89a0009074dc74ae433b97511
Author: pissang <bm...@gmail.com>
AuthorDate: Mon Oct 11 18:11:36 2021 +0800

    fix(progressive): only update status on the new rendered elements
---
 src/chart/heatmap/HeatmapView.ts    |  19 +++--
 src/chart/helper/LargeLineDraw.ts   | 134 +++++++++++++++++-------------------
 src/chart/helper/LargeSymbolDraw.ts | 133 ++++++++++++++++-------------------
 src/chart/helper/LineDraw.ts        |  19 +++--
 src/chart/helper/SymbolDraw.ts      |  19 +++--
 src/chart/lines/LinesView.ts        |   5 ++
 src/chart/scatter/ScatterView.ts    |   7 +-
 src/component/graphic/install.ts    |  62 +++++++----------
 src/core/echarts.ts                 |  53 +++-----------
 src/util/format.ts                  |  16 ++---
 src/util/graphic.ts                 |  28 +++++++-
 src/view/Chart.ts                   |  11 +++
 src/view/Component.ts               |  13 ++++
 13 files changed, 275 insertions(+), 244 deletions(-)

diff --git a/src/chart/heatmap/HeatmapView.ts b/src/chart/heatmap/HeatmapView.ts
index a90a4e4..f4061ba 100644
--- a/src/chart/heatmap/HeatmapView.ts
+++ b/src/chart/heatmap/HeatmapView.ts
@@ -33,6 +33,7 @@ import { StageHandlerProgressParams, Dictionary, OptionDataValue } from '../../u
 import type Cartesian2D from '../../coord/cartesian/Cartesian2D';
 import type Calendar from '../../coord/calendar/Calendar';
 import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle';
+import Element from 'zrender/src/Element';
 
 // Coord can be 'geo' 'bmap' 'amap' 'leaflet'...
 interface GeoLikeCoordSys extends CoordinateSystem {
@@ -102,10 +103,10 @@ class HeatmapView extends ChartView {
     static readonly type = 'heatmap';
     readonly type = HeatmapView.type;
 
-    private _incrementalDisplayable: boolean;
-
     private _hmLayer: HeatmapLayer;
 
+    private _progressiveEls: Element[];
+
     render(seriesModel: HeatmapSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) {
         let visualMapOfThisSeries;
         ecModel.eachComponent('visualMap', function (visualMap: VisualMapModel) {
@@ -122,9 +123,10 @@ class HeatmapView extends ChartView {
             }
         }
 
-        this.group.removeAll();
+        // Clear previously rendered progressive elements.
+        this._progressiveEls = null;
 
-        this._incrementalDisplayable = null;
+        this.group.removeAll();
 
         const coordSys = seriesModel.coordinateSystem;
         if (coordSys.type === 'cartesian2d' || coordSys.type === 'calendar') {
@@ -154,11 +156,16 @@ class HeatmapView extends ChartView {
                 this.render(seriesModel, ecModel, api);
             }
             else {
+                this._progressiveEls = [];
                 this._renderOnCartesianAndCalendar(seriesModel, api, params.start, params.end, true);
             }
         }
     }
 
+    eachRendered(cb: (el: Element) => boolean | void) {
+        graphic.traverseElements(this._progressiveEls || this.group, cb);
+    }
+
     _renderOnCartesianAndCalendar(
         seriesModel: HeatmapSeriesModel,
         api: ExtensionAPI,
@@ -305,6 +312,10 @@ class HeatmapView extends ChartView {
 
             group.add(rect);
             data.setItemGraphicEl(idx, rect);
+
+            if (this._progressiveEls) {
+                this._progressiveEls.push(rect);
+            }
         }
     }
 
diff --git a/src/chart/helper/LargeLineDraw.ts b/src/chart/helper/LargeLineDraw.ts
index fca6979..0e7c55f 100644
--- a/src/chart/helper/LargeLineDraw.ts
+++ b/src/chart/helper/LargeLineDraw.ts
@@ -20,7 +20,6 @@
 // TODO Batch by color
 
 import * as graphic from '../../util/graphic';
-import IncrementalDisplayable from 'zrender/src/graphic/IncrementalDisplayable';
 import * as lineContain from 'zrender/src/contain/line';
 import * as quadraticContain from 'zrender/src/contain/quadratic';
 import { PathProps } from 'zrender/src/graphic/Path';
@@ -28,6 +27,7 @@ import SeriesData from '../../data/SeriesData';
 import { StageHandlerProgressParams, LineStyleOption, ColorString } from '../../util/types';
 import Model from '../../model/Model';
 import { getECData } from '../../util/innerStore';
+import Element from 'zrender/src/Element';
 
 class LargeLinesPathShape {
     polyline = false;
@@ -168,33 +168,19 @@ class LargeLinesPath extends graphic.Path {
 
 class LargeLineDraw {
     group = new graphic.Group();
-
-    _incremental?: IncrementalDisplayable;
-
-    isPersistent() {
-        return !this._incremental;
-    };
-
+    private _newAdded: LargeLinesPath[];
     /**
      * Update symbols draw by new data
      */
     updateData(data: LargeLinesData) {
-        this.group.removeAll();
+        this._clear();
 
-        const lineEl = new LargeLinesPath({
-            rectHover: true,
-            cursor: 'default'
-        });
+        const lineEl = this._create();
         lineEl.setShape({
             segs: data.getLayout('linesPoints')
         });
 
         this._setCommon(lineEl, data);
-
-        // Add back
-        this.group.add(lineEl);
-
-        this._incremental = null;
     };
 
     /**
@@ -202,54 +188,67 @@ class LargeLineDraw {
      */
     incrementalPrepareUpdate(data: LargeLinesData) {
         this.group.removeAll();
-
-        this._clearIncremental();
-
-        if (data.count() > 5e5) {
-            if (!this._incremental) {
-                this._incremental = new IncrementalDisplayable({
-                    silent: true
-                });
-            }
-            this.group.add(this._incremental);
-        }
-        else {
-            this._incremental = null;
-        }
+        this._clear();
     };
 
     /**
      * @override
      */
     incrementalUpdate(taskParams: StageHandlerProgressParams, data: LargeLinesData) {
-        const lineEl = new LargeLinesPath();
-        lineEl.setShape({
-            segs: data.getLayout('linesPoints')
-        });
-
-        this._setCommon(lineEl, data, !!this._incremental);
-
-        if (!this._incremental) {
-            lineEl.rectHover = true;
-            lineEl.cursor = 'default';
-            lineEl.__startIndex = taskParams.start;
-            this.group.add(lineEl);
+        const lastAdded = this._newAdded[0];
+        const linePoints = data.getLayout('linesPoints');
+        // Clear
+        this._newAdded = [];
+
+
+        const oldSegs = lastAdded && lastAdded.shape.segs;
+
+        // Merging the exists. Each element has 1e4 points.
+        // Consider the performance balance between too much elements and too much points in one shape(may affect hover optimization)
+        if (oldSegs && oldSegs.length < 2e4) {
+            const oldLen = oldSegs.length;
+            const newSegs = new Float32Array(oldLen + linePoints.length);
+            // Concat two array
+            newSegs.set(oldSegs);
+            newSegs.set(linePoints, oldLen);
+            lastAdded.setShape({
+                segs: newSegs
+            });
         }
         else {
-            this._incremental.addDisplayable(lineEl, true);
+            const lineEl = this._create();
+            lineEl.incremental = true;
+            lineEl.setShape({
+                segs: linePoints
+            });
+            this._setCommon(lineEl, data);
+            lineEl.__startIndex = taskParams.start;
         }
-    };
+    }
 
     /**
      * @override
      */
     remove() {
-        this._clearIncremental();
-        this._incremental = null;
-        this.group.removeAll();
-    };
+        this._clear();
+    }
+
+    eachRendered(cb: (el: Element) => boolean | void) {
+        this._newAdded[0] && cb(this._newAdded[0]);
+    }
+
+    private _create() {
+        const lineEl = new LargeLinesPath({
+            rectHover: true,
+            cursor: 'default'
+        });
+        this._newAdded.push(lineEl);
+        this.group.add(lineEl);
+        return lineEl;
+    }
+
 
-    _setCommon(lineEl: LargeLinesPath, data: LargeLinesData, isIncremental?: boolean) {
+    private _setCommon(lineEl: LargeLinesPath, data: LargeLinesData, isIncremental?: boolean) {
         const hostModel = data.hostModel;
 
         lineEl.setShape({
@@ -268,27 +267,22 @@ class LargeLineDraw {
         }
         lineEl.setStyle('fill', null);
 
-        if (!isIncremental) {
-            const ecData = getECData(lineEl);
-            // Enable tooltip
-            // PENDING May have performance issue when path is extremely large
-            ecData.seriesIndex = hostModel.seriesIndex;
-            lineEl.on('mousemove', function (e) {
-                ecData.dataIndex = null;
-                const dataIndex = lineEl.findDataIndex(e.offsetX, e.offsetY);
-                if (dataIndex > 0) {
-                    // Provide dataIndex for tooltip
-                    ecData.dataIndex = dataIndex + lineEl.__startIndex;
-                }
-            });
-        }
+        const ecData = getECData(lineEl);
+        // Enable tooltip
+        // PENDING May have performance issue when path is extremely large
+        ecData.seriesIndex = hostModel.seriesIndex;
+        lineEl.on('mousemove', function (e) {
+            ecData.dataIndex = null;
+            const dataIndex = lineEl.findDataIndex(e.offsetX, e.offsetY);
+            if (dataIndex > 0) {
+                // Provide dataIndex for tooltip
+                ecData.dataIndex = dataIndex + lineEl.__startIndex;
+            }
+        });
     };
 
-    _clearIncremental() {
-        const incremental = this._incremental;
-        if (incremental) {
-            incremental.clearDisplaybles();
-        }
+    private _clear() {
+        this.group.removeAll();
     };
 
 
diff --git a/src/chart/helper/LargeSymbolDraw.ts b/src/chart/helper/LargeSymbolDraw.ts
index 3f3bcbb..788de72 100644
--- a/src/chart/helper/LargeSymbolDraw.ts
+++ b/src/chart/helper/LargeSymbolDraw.ts
@@ -23,7 +23,6 @@
 
 import * as graphic from '../../util/graphic';
 import {createSymbol} from '../../util/symbol';
-import IncrementalDisplayable from 'zrender/src/graphic/IncrementalDisplayable';
 import SeriesData from '../../data/SeriesData';
 import { PathProps } from 'zrender/src/graphic/Path';
 import PathProxy from 'zrender/src/core/PathProxy';
@@ -31,6 +30,7 @@ import SeriesModel from '../../model/Series';
 import { StageHandlerProgressParams } from '../../util/types';
 import { CoordinateSystemClipArea } from '../../coord/CoordinateSystem';
 import { getECData } from '../../util/innerStore';
+import Element from 'zrender/src/Element';
 
 const BOOST_SIZE_THRESHOLD = 4;
 
@@ -173,36 +173,23 @@ class LargeSymbolDraw {
 
     group = new graphic.Group();
 
-    _incremental: IncrementalDisplayable;
-
-    isPersistent() {
-        return !this._incremental;
-    };
+    // New add element in this frame of progressive render.
+    private _newAdded: LargeSymbolPath[];
 
     /**
      * Update symbols draw by new data
      */
     updateData(data: SeriesData, opt?: UpdateOpt) {
-        this.group.removeAll();
-        const symbolEl = new LargeSymbolPath({
-            rectHover: true,
-            cursor: 'default'
-        });
+        this._clear();
 
+        const symbolEl = this._create();
         symbolEl.setShape({
             points: data.getLayout('points')
         });
-        this._setCommon(symbolEl, data, false, opt);
-        this.group.add(symbolEl);
-
-        this._incremental = null;
+        this._setCommon(symbolEl, data, opt);
     }
 
     updateLayout(data: SeriesData) {
-        if (this._incremental) {
-            return;
-        }
-
         let points = data.getLayout('points');
         this.group.eachChild(function (child: LargeSymbolPath) {
             if (child.startIndex != null) {
@@ -215,51 +202,57 @@ class LargeSymbolDraw {
     }
 
     incrementalPrepareUpdate(data: SeriesData) {
-        this.group.removeAll();
-
-        this._clearIncremental();
-        // Only use incremental displayables when data amount is larger than 2 million.
-        // PENDING Incremental data?
-        if (data.count() > 2e6) {
-            if (!this._incremental) {
-                this._incremental = new IncrementalDisplayable({
-                    silent: true
-                });
-            }
-            this.group.add(this._incremental);
-        }
-        else {
-            this._incremental = null;
-        }
+        this._clear();
     }
 
     incrementalUpdate(taskParams: StageHandlerProgressParams, data: SeriesData, opt: UpdateOpt) {
-        let symbolEl;
-        if (this._incremental) {
-            symbolEl = new LargeSymbolPath();
-            this._incremental.addDisplayable(symbolEl, true);
+        const lastAdded = this._newAdded[0];
+        const points = data.getLayout('points');
+        // Clear
+        this._newAdded = [];
+
+        const oldPoints = lastAdded && lastAdded.shape.points;
+        // Merging the exists. Each element has 1e4 points.
+        // Consider the performance balance between too much elements and too much points in one shape(may affect hover optimization)
+        if (oldPoints && oldPoints.length < 2e4) {
+            const oldLen = oldPoints.length;
+            const newPoints = new Float32Array(oldLen + points.length);
+            // Concat two array
+            newPoints.set(oldPoints);
+            newPoints.set(points, oldLen);
+            // Update endIndex
+            lastAdded.endIndex = taskParams.end;
+            lastAdded.setShape({ points: newPoints });
         }
         else {
-            symbolEl = new LargeSymbolPath({
-                rectHover: true,
-                cursor: 'default',
-                startIndex: taskParams.start,
-                endIndex: taskParams.end
-            });
+            const symbolEl = this._create();
+            symbolEl.startIndex = taskParams.start;
+            symbolEl.endIndex = taskParams.end;
             symbolEl.incremental = true;
-            this.group.add(symbolEl);
+            symbolEl.setShape({
+                points
+            });
+            this._setCommon(symbolEl, data, opt);
         }
+    }
 
-        symbolEl.setShape({
-            points: data.getLayout('points')
+    eachRendered(cb: (el: Element) => boolean | void) {
+        this._newAdded[0] && cb(this._newAdded[0]);
+    }
+
+    private _create() {
+        const symbolEl = new LargeSymbolPath({
+            rectHover: true,
+            cursor: 'default'
         });
-        this._setCommon(symbolEl, data, !!this._incremental, opt);
+        this.group.add(symbolEl);
+        this._newAdded.push(symbolEl);
+        return symbolEl;
     }
 
-    _setCommon(
+    private _setCommon(
         symbolEl: LargeSymbolPath,
         data: SeriesData,
-        isIncremental: boolean,
         opt: UpdateOpt
     ) {
         const hostModel = data.hostModel;
@@ -291,33 +284,27 @@ class LargeSymbolDraw {
             symbolEl.setColor(visualColor);
         }
 
-        if (!isIncremental) {
-            const ecData = getECData(symbolEl);
-            // Enable tooltip
-            // PENDING May have performance issue when path is extremely large
-            ecData.seriesIndex = (hostModel as SeriesModel).seriesIndex;
-            symbolEl.on('mousemove', function (e) {
-                ecData.dataIndex = null;
-                const dataIndex = symbolEl.findDataIndex(e.offsetX, e.offsetY);
-                if (dataIndex >= 0) {
-                    // Provide dataIndex for tooltip
-                    ecData.dataIndex = dataIndex + (symbolEl.startIndex || 0);
-                }
-            });
-        }
+        const ecData = getECData(symbolEl);
+        // Enable tooltip
+        // PENDING May have performance issue when path is extremely large
+        ecData.seriesIndex = (hostModel as SeriesModel).seriesIndex;
+        symbolEl.on('mousemove', function (e) {
+            ecData.dataIndex = null;
+            const dataIndex = symbolEl.findDataIndex(e.offsetX, e.offsetY);
+            if (dataIndex >= 0) {
+                // Provide dataIndex for tooltip
+                ecData.dataIndex = dataIndex + (symbolEl.startIndex || 0);
+            }
+        });
     }
 
     remove() {
-        this._clearIncremental();
-        this._incremental = null;
-        this.group.removeAll();
+        this._clear();
     }
 
-    _clearIncremental() {
-        const incremental = this._incremental;
-        if (incremental) {
-            incremental.clearDisplaybles();
-        }
+    private _clear() {
+        this._newAdded = [];
+        this.group.removeAll();
     }
 }
 
diff --git a/src/chart/helper/LineDraw.ts b/src/chart/helper/LineDraw.ts
index 5f5ff61..2bb7d01 100644
--- a/src/chart/helper/LineDraw.ts
+++ b/src/chart/helper/LineDraw.ts
@@ -35,6 +35,7 @@ import {
 import Displayable from 'zrender/src/graphic/Displayable';
 import Model from '../../model/Model';
 import { getLabelStatesModels } from '../../label/labelStyle';
+import Element from 'zrender/src/Element';
 
 interface LineLike extends graphic.Group {
     updateData(data: SeriesData, idx: number, scope?: LineDrawSeriesScope): void
@@ -98,15 +99,16 @@ class LineDraw {
 
     private _seriesScope: LineDrawSeriesScope;
 
+    private _progressiveEls: LineLike[];
+
     constructor(LineCtor?: LineLikeCtor) {
         this._LineCtor = LineCtor || LineGroup;
     }
 
-    isPersistent() {
-        return true;
-    };
-
     updateData(lineData: ListForLineDraw) {
+        // Remove progressive els.
+        this._progressiveEls = null;
+
         const lineDraw = this;
         const group = lineDraw.group;
 
@@ -154,6 +156,9 @@ class LineDraw {
     };
 
     incrementalUpdate(taskParams: StageHandlerProgressParams, lineData: ListForLineDraw) {
+
+        this._progressiveEls = [];
+
         function updateIncrementalAndHover(el: Displayable) {
             if (!el.isGroup && !isEffectObject(el)) {
                 el.incremental = true;
@@ -170,6 +175,8 @@ class LineDraw {
 
                 this.group.add(el);
                 lineData.setItemGraphicEl(idx, el);
+
+                this._progressiveEls.push(el);
             }
         }
     };
@@ -178,6 +185,10 @@ class LineDraw {
         this.group.removeAll();
     };
 
+    eachRendered(cb: (el: Element) => boolean | void) {
+        graphic.traverseElements(this._progressiveEls || this.group, cb);
+    }
+
     private _doAdd(
         lineData: ListForLineDraw,
         idx: number,
diff --git a/src/chart/helper/SymbolDraw.ts b/src/chart/helper/SymbolDraw.ts
index 43b1249..1f27f1a 100644
--- a/src/chart/helper/SymbolDraw.ts
+++ b/src/chart/helper/SymbolDraw.ts
@@ -39,6 +39,7 @@ import { CoordinateSystemClipArea } from '../../coord/CoordinateSystem';
 import Model from '../../model/Model';
 import { ScatterSeriesOption } from '../scatter/ScatterSeries';
 import { getLabelStatesModels } from '../../label/labelStyle';
+import Element from 'zrender/src/Element';
 
 interface UpdateOpt {
     isIgnore?(idx: number): boolean
@@ -162,6 +163,8 @@ class SymbolDraw {
 
     private _getSymbolPoint: UpdateOpt['getSymbolPoint'];
 
+    private _progressiveEls: SymbolLike[];
+
     constructor(SymbolCtor?: SymbolLikeCtor) {
         this._SymbolCtor = SymbolCtor || SymbolClz as SymbolLikeCtor;
     }
@@ -170,6 +173,9 @@ class SymbolDraw {
      * Update symbols draw by new data
      */
     updateData(data: ListForSymbolDraw, opt?: UpdateOpt) {
+        // Remove progressive els.
+        this._progressiveEls = null;
+
         opt = normalizeUpdateOpt(opt);
 
         const group = this.group;
@@ -252,10 +258,6 @@ class SymbolDraw {
         this._data = data;
     };
 
-    isPersistent() {
-        return true;
-    };
-
     updateLayout() {
         const data = this._data;
         if (data) {
@@ -278,6 +280,10 @@ class SymbolDraw {
      * Update symbols draw by new data
      */
     incrementalUpdate(taskParams: StageHandlerProgressParams, data: ListForSymbolDraw, opt?: UpdateOpt) {
+
+        // Clear
+        this._progressiveEls = [];
+
         opt = normalizeUpdateOpt(opt);
 
         function updateIncrementalAndHover(el: Displayable) {
@@ -294,10 +300,15 @@ class SymbolDraw {
                 el.setPosition(point);
                 this.group.add(el);
                 data.setItemGraphicEl(idx, el);
+                this._progressiveEls.push(el);
             }
         }
     };
 
+    eachRendered(cb: (el: Element) => boolean | void) {
+        graphic.traverseElements(this._progressiveEls || this.group, cb);
+    }
+
     remove(enableAnimation?: boolean) {
         const group = this.group;
         const data = this._data;
diff --git a/src/chart/lines/LinesView.ts b/src/chart/lines/LinesView.ts
index 6c4479d..36de7d2 100644
--- a/src/chart/lines/LinesView.ts
+++ b/src/chart/lines/LinesView.ts
@@ -34,6 +34,7 @@ import { StageHandlerProgressParams, StageHandlerProgressExecutor } from '../../
 import SeriesData from '../../data/SeriesData';
 import type Polar from '../../coord/polar/Polar';
 import type Cartesian2D from '../../coord/cartesian/Cartesian2D';
+import Element from 'zrender/src/Element';
 
 class LinesView extends ChartView {
 
@@ -129,6 +130,10 @@ class LinesView extends ChartView {
         this._finished = taskParams.end === seriesModel.getData().count();
     }
 
+    eachRendered(cb: (el: Element) => boolean | void) {
+        this._lineDraw && this._lineDraw.eachRendered(cb);
+    }
+
     updateTransform(seriesModel: LinesSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) {
         const data = seriesModel.getData();
         const pipelineContext = seriesModel.pipelineContext;
diff --git a/src/chart/scatter/ScatterView.ts b/src/chart/scatter/ScatterView.ts
index 1bf6e3c..849d96a 100644
--- a/src/chart/scatter/ScatterView.ts
+++ b/src/chart/scatter/ScatterView.ts
@@ -28,6 +28,7 @@ import ExtensionAPI from '../../core/ExtensionAPI';
 import SeriesData from '../../data/SeriesData';
 import { TaskProgressParams } from '../../core/task';
 import type { StageHandlerProgressExecutor } from '../../util/types';
+import Element from 'zrender/src/Element';
 
 class ScatterView extends ChartView {
     static readonly type = 'scatter';
@@ -78,7 +79,7 @@ class ScatterView extends ChartView {
         // PENDING
         this.group.dirty();
 
-        if (!this._finished || data.count() > 1e4 || !this._symbolDraw.isPersistent()) {
+        if (!this._finished || data.count() > 1e4) {
             return {
                 update: true
             };
@@ -93,6 +94,10 @@ class ScatterView extends ChartView {
         }
     }
 
+    eachRendered(cb: (el: Element) => boolean | void) {
+        this._symbolDraw && this._symbolDraw.eachRendered(cb);
+    }
+
     _getClipShape(seriesModel: ScatterSeriesModel) {
         const coordSys = seriesModel.coordinateSystem;
         const clipArea = coordSys && coordSys.getArea && coordSys.getArea();
diff --git a/src/component/graphic/install.ts b/src/component/graphic/install.ts
index 61ed438..fb06330 100644
--- a/src/component/graphic/install.ts
+++ b/src/component/graphic/install.ts
@@ -46,16 +46,7 @@ import { TextStyleProps } from 'zrender/src/graphic/Text';
 import { isEC4CompatibleStyle, convertFromEC4CompatibleStyle } from '../../util/styleCompat';
 import { EChartsExtensionInstallRegisters } from '../../extension';
 
-const TRANSFORM_PROPS = {
-    x: 1,
-    y: 1,
-    scaleX: 1,
-    scaleY: 1,
-    originX: 1,
-    originY: 1,
-    rotation: 1
-} as const;
-type TransformProp = keyof typeof TRANSFORM_PROPS;
+type TransformProp = 'x' | 'y' | 'scaleX' | 'scaleY' | 'originX' | 'originY' | 'skewX' | 'skewY';
 
 interface GraphicComponentBaseElementOption extends
         Partial<Pick<
@@ -133,6 +124,7 @@ interface GraphicComponentBaseElementOption extends
 
     tooltip?: CommonTooltipOption<unknown>;
 };
+
 interface GraphicComponentDisplayableOption extends
         GraphicComponentBaseElementOption,
         Partial<Pick<Displayable, 'zlevel' | 'z' | 'z2' | 'invisible' | 'cursor'>> {
@@ -200,11 +192,11 @@ type GraphicExtraElementInfo = Dictionary<unknown>;
 type ElementMap = zrUtil.HashMap<Element, string>;
 
 const inner = modelUtil.makeInner<{
-    __ecGraphicWidthOption: number;
-    __ecGraphicHeightOption: number;
-    __ecGraphicWidth: number;
-    __ecGraphicHeight: number;
-    __ecGraphicId: string;
+    widthOption: number;
+    heightOption: number;
+    width: number;
+    height: number;
+    id: string;
 }, Element>();
 
 
@@ -330,16 +322,12 @@ class GraphicComponentModel extends ComponentModel<GraphicComponentOption> {
         }, this);
 
         // Clean
-        for (let i = existList.length - 1; i >= 0; i--) {
-            if (existList[i] == null) {
-                existList.splice(i, 1);
-            }
-            else {
-                // $action should be volatile, otherwise option gotten from
-                // `getOption` will contain unexpected $action.
-                delete existList[i].$action;
-            }
-        }
+        thisOption.elements = zrUtil.filter(existList, (item) => {
+            // $action should be volatile, otherwise option gotten from
+            // `getOption` will contain unexpected $action.
+            item && delete item.$action;
+            return item != null;
+        });
     }
 
     /**
@@ -517,8 +505,8 @@ class GraphicComponentView extends ComponentView {
 
             if (el) {
                 const elInner = inner(el);
-                elInner.__ecGraphicWidthOption = (elOption as GraphicComponentGroupOption).width;
-                elInner.__ecGraphicHeightOption = (elOption as GraphicComponentGroupOption).height;
+                elInner.widthOption = (elOption as GraphicComponentGroupOption).width;
+                elInner.heightOption = (elOption as GraphicComponentGroupOption).height;
                 setEventData(el, graphicModel, elOption);
 
                 graphicUtil.setTooltipConfig({
@@ -555,13 +543,13 @@ class GraphicComponentView extends ComponentView {
             // Like 'position:absolut' in css, default 0.
             const elInner = inner(el);
             const parentElInner = inner(parentEl);
-            elInner.__ecGraphicWidth = parsePercent(
-                elInner.__ecGraphicWidthOption,
-                isParentRoot ? apiWidth : parentElInner.__ecGraphicWidth
+            elInner.width = parsePercent(
+                elInner.widthOption,
+                isParentRoot ? apiWidth : parentElInner.width
             ) || 0;
-            elInner.__ecGraphicHeight = parsePercent(
-                elInner.__ecGraphicHeightOption,
-                isParentRoot ? apiHeight : parentElInner.__ecGraphicHeight
+            elInner.height = parsePercent(
+                elInner.heightOption,
+                isParentRoot ? apiHeight : parentElInner.height
             ) || 0;
         }
 
@@ -583,8 +571,8 @@ class GraphicComponentView extends ComponentView {
                     height: apiHeight
                 }
                 : {
-                    width: parentElInner.__ecGraphicWidth,
-                    height: parentElInner.__ecGraphicHeight
+                    width: parentElInner.width,
+                    height: parentElInner.height
                 };
 
             // PENDING
@@ -641,7 +629,7 @@ function createEl(
     const el = new Clz(elOption);
     targetElParent.add(el);
     elMap.set(id, el);
-    inner(el).__ecGraphicId = id;
+    inner(el).id = id;
 }
 
 function removeEl(elExisting: Element, elMap: ElementMap): void {
@@ -650,7 +638,7 @@ function removeEl(elExisting: Element, elMap: ElementMap): void {
         elExisting.type === 'group' && elExisting.traverse(function (el) {
             removeEl(el, elMap);
         });
-        elMap.removeKey(inner(elExisting).__ecGraphicId);
+        elMap.removeKey(inner(elExisting).id);
         existElParent.remove(elExisting);
     }
 }
diff --git a/src/core/echarts.ts b/src/core/echarts.ts
index 8a61c98..52cc6b6 100644
--- a/src/core/echarts.ts
+++ b/src/core/echarts.ts
@@ -225,7 +225,6 @@ export interface SetOptionOpts {
     transition?: SetOptionTransitionOpt
 };
 
-
 export interface ResizeOpts {
     width?: number | 'auto', // Can be 'auto' (the same as null/undefined)
     height?: number | 'auto', // Can be 'auto' (the same as null/undefined)
@@ -2184,7 +2183,7 @@ class ECharts extends Eventful<ECEventDefinition> {
                     }
                     const chartView = ecIns._chartsMap[seriesModel.__viewId];
                     if (chartView.__alive) {
-                        chartView.group.traverse(function (el: ECElement) {
+                        chartView.eachRendered((el: ECElement) => {
                             if (el.states.emphasis) {
                                 el.states.emphasis.hoverLayer = true;
                             }
@@ -2204,17 +2203,12 @@ class ECharts extends Eventful<ECEventDefinition> {
                     console.warn('Only canvas support blendMode');
                 }
             }
-            chartView.group.traverse(function (el: Displayable) {
+            chartView.eachRendered((el: Displayable) => {
                 // FIXME marker and other components
                 if (!el.isGroup) {
                     // DONT mark the element dirty. In case element is incremental and don't wan't to rerender.
                     el.style.blend = blendMode;
                 }
-                if ((el as IncrementalDisplayable).eachPendingDisplayable) {
-                    (el as IncrementalDisplayable).eachPendingDisplayable(function (displayable) {
-                        displayable.style.blend = blendMode;
-                    });
-                }
             });
         };
 
@@ -2222,13 +2216,14 @@ class ECharts extends Eventful<ECEventDefinition> {
             if (model.preventAutoZ) {
                 return;
             }
+            const z = model.get('z') || 0;
+            const zlevel = model.get('zlevel') || 0;
             // Set z and zlevel
-            _updateZ(
-                view.group,
-                model.get('z') || 0,
-                model.get('zlevel') || 0,
-                -Infinity
-            );
+            view.eachRendered((el) => {
+                _updateZ(el, z, zlevel, -Infinity);
+                // Don't traverse the children because it has been traversed in _updateZ.
+                return true;
+            });
         };
 
         function _updateZ(el: Element, z: number, zlevel: number, maxZ2: number): number {
@@ -2239,7 +2234,6 @@ class ECharts extends Eventful<ECEventDefinition> {
 
             if (isGroup) {
                 // set z & zlevel of children elements of Group
-                // el.traverse((childEl: Element) => _updateZ(childEl, z, zlevel));
                 const children = (el as graphic.Group).childrenRef();
                 for (let i = 0; i < children.length; i++) {
                     maxZ2 = Math.max(_updateZ(children[i], z, zlevel, maxZ2), maxZ2);
@@ -2274,7 +2268,7 @@ class ECharts extends Eventful<ECEventDefinition> {
         // Clear states without animation.
         // TODO States on component.
         function clearStates(model: ComponentModel, view: ComponentView | ChartView): void {
-            view.group.traverse(function (el: Displayable) {
+            view.eachRendered(function (el: Displayable) {
                 // Not applied on removed elements, it may still in fading.
                 if (graphic.isElementRemoved(el)) {
                     return;
@@ -2313,7 +2307,7 @@ class ECharts extends Eventful<ECEventDefinition> {
                 easing: stateAnimationModel.get('easing')
                 // additive: stateAnimationModel.get('additive')
             } : null;
-            view.group.traverse(function (el: Displayable) {
+            view.eachRendered(function (el: Displayable) {
                 if (el.states && el.states.emphasis) {
                     // Not applied on removed elements, it may still in fading.
                     if (graphic.isElementRemoved(el)) {
@@ -2445,7 +2439,6 @@ class ECharts extends Eventful<ECEventDefinition> {
     })();
 }
 
-
 const echartsProto = ECharts.prototype;
 echartsProto.on = createRegisterEventWithLowercaseECharts('on');
 echartsProto.off = createRegisterEventWithLowercaseECharts('off');
@@ -2465,30 +2458,6 @@ echartsProto.one = function (eventName: string, cb: Function, ctx?: any) {
     this.on.call(this, eventName, wrapped, ctx);
 };
 
-// /**
-//  * Encode visual infomation from data after data processing
-//  *
-//  * @param {module:echarts/model/Global} ecModel
-//  * @param {object} layout
-//  * @param {boolean} [layoutFilter] `true`: only layout,
-//  *                                 `false`: only not layout,
-//  *                                 `null`/`undefined`: all.
-//  * @param {string} taskBaseTag
-//  * @private
-//  */
-// function startVisualEncoding(ecIns, ecModel, api, payload, layoutFilter) {
-//     each(visualFuncs, function (visual, index) {
-//         let isLayout = visual.isLayout;
-//         if (layoutFilter == null
-//             || (layoutFilter === false && !isLayout)
-//             || (layoutFilter === true && isLayout)
-//         ) {
-//             visual.func(ecModel, api, payload);
-//         }
-//     });
-// }
-
-
 const MOUSE_EVENT_NAMES: ZRElementEventName[] = [
     'click', 'dblclick', 'mouseover', 'mouseout', 'mousemove',
     'mousedown', 'mouseup', 'globalout', 'contextmenu'
diff --git a/src/util/format.ts b/src/util/format.ts
index 1f9749e..65ad3cd 100644
--- a/src/util/format.ts
+++ b/src/util/format.ts
@@ -275,14 +275,14 @@ export function formatTime(tpl: string, value: unknown, isUTC?: boolean) {
     }
 
     const date = parseDate(value);
-    const utc = isUTC ? 'UTC' : '';
-    const y = (date as any)['get' + utc + 'FullYear']();
-    const M = (date as any)['get' + utc + 'Month']() + 1;
-    const d = (date as any)['get' + utc + 'Date']();
-    const h = (date as any)['get' + utc + 'Hours']();
-    const m = (date as any)['get' + utc + 'Minutes']();
-    const s = (date as any)['get' + utc + 'Seconds']();
-    const S = (date as any)['get' + utc + 'Milliseconds']();
+    const getUTC = isUTC ? 'getUTC' : 'get';
+    const y = (date as any)[getUTC + 'FullYear']();
+    const M = (date as any)[getUTC + 'Month']() + 1;
+    const d = (date as any)[getUTC + 'Date']();
+    const h = (date as any)[getUTC + 'Hours']();
+    const m = (date as any)[getUTC + 'Minutes']();
+    const s = (date as any)[getUTC + 'Seconds']();
+    const S = (date as any)[getUTC + 'Milliseconds']();
 
     tpl = tpl.replace('MM', pad(M, 2))
         .replace('M', M)
diff --git a/src/util/graphic.ts b/src/util/graphic.ts
index fb40300..2f66a79 100644
--- a/src/util/graphic.ts
+++ b/src/util/graphic.ts
@@ -62,7 +62,8 @@ import {
     isString,
     keys,
     each,
-    hasOwn
+    hasOwn,
+    isArray
 } from 'zrender/src/core/util';
 import { getECData } from './innerStore';
 import ComponentModel from '../model/Component';
@@ -610,6 +611,31 @@ export function setTooltipConfig(opt: {
     };
 }
 
+function traverseElement(el: Element, cb: (el: Element) => boolean | void) {
+    let stopped;
+    // TODO
+    // Polyfill for fixing zrender group traverse don't visit it's root issue.
+    if (el.isGroup) {
+        stopped = cb(el);
+    }
+    if (!stopped) {
+        el.traverse(cb);
+    }
+}
+
+export function traverseElements(els: Element | Element[] | undefined | null, cb: (el: Element) => boolean | void) {
+    if (els) {
+        if (isArray(els)) {
+            for (let i = 0; i < els.length; i++) {
+                traverseElement(els[i], cb);
+            }
+        }
+        else {
+            traverseElement(els, cb);
+        }
+    }
+}
+
 // Register built-in shapes. These shapes might be overwirtten
 // by users, although we do not recommend that.
 registerShape('circle', Circle);
diff --git a/src/view/Chart.ts b/src/view/Chart.ts
index 8f32838..7f01a75 100644
--- a/src/view/Chart.ts
+++ b/src/view/Chart.ts
@@ -35,6 +35,7 @@ import {
 } from '../util/types';
 import { SeriesTaskContext, SeriesTask } from '../core/Scheduler';
 import SeriesData from '../data/SeriesData';
+import { traverseElements } from '../util/graphic';
 
 const inner = modelUtil.makeInner<{
     updateMethod: keyof ChartView
@@ -189,6 +190,16 @@ class ChartView {
         this.render(seriesModel, ecModel, api, payload);
     }
 
+    /**
+     * Traverse the new rendered elements.
+     *
+     * It will traverse the new added element in progressive rendering.
+     * And traverse all in normal rendering.
+     */
+    eachRendered(cb: (el: Element) => boolean | void) {
+        traverseElements(this.group, cb);
+    }
+
     static markUpdateMethod(payload: Payload, methodName: keyof ChartView): void {
         inner(payload).updateMethod = methodName;
     }
diff --git a/src/view/Component.ts b/src/view/Component.ts
index cfefd04..1c729a1 100644
--- a/src/view/Component.ts
+++ b/src/view/Component.ts
@@ -114,6 +114,19 @@ class ComponentView {
          // Do nothing;
     }
 
+    /**
+     * Traverse the new rendered elements.
+     *
+     * It will traverse the new added element in progressive rendering.
+     * And traverse all in normal rendering.
+     */
+    eachRendered(cb: (el: Element) => boolean | void) {
+        const group = this.group;
+        if (group) {
+            group.traverse(cb);
+        }
+    }
+
     static registerClass: clazzUtil.ClassManager['registerClass'];
 };
 

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