You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by su...@apache.org on 2020/04/29 19:54:00 UTC

[incubator-echarts] branch next updated (2708396 -> 95e9b27)

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

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


    from 2708396  fix: tweak emphasis text position.
     new 0802ef0  fix: (1) revert increamental render broken brought by a96d97191b28500d2a3dc4f44e53221d54fbe218 (2) add stream test tools.
     new 95e9b27  fix: (1) fix bar incremental render (brought by a61fd5ab7f434c7e967e107d6435c1e3f4f6d008) (2) add stream test case.

The 2 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.


Summary of changes:
 src/chart/bar/BarView.ts                           |   5 +-
 src/echarts.ts                                     |   4 +-
 test/-cases.js                                     |   2 +
 test/bar-stream-large.html                         |  15 +-
 test/lib/reset.css                                 |  14 ++
 test/lib/testHelper.js                             | 224 +++++++++++++++++----
 .../removeDEV/src/code.src.js => stream-basic.css} |  47 ++---
 test/stream-basic.js                               |  99 +++++++++
 test/stream-basic1.html                            | 106 ++++++++++
 test/stream-basic2.html                            | 114 +++++++++++
 10 files changed, 556 insertions(+), 74 deletions(-)
 copy test/{build/removeDEV/src/code.src.js => stream-basic.css} (66%)
 create mode 100644 test/stream-basic.js
 create mode 100644 test/stream-basic1.html
 create mode 100644 test/stream-basic2.html


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


[incubator-echarts] 02/02: fix: (1) fix bar incremental render (brought by a61fd5ab7f434c7e967e107d6435c1e3f4f6d008) (2) add stream test case.

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

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

commit 95e9b2717cf28c81709933ec8ea8edbabb46cc61
Author: 100pah <su...@gmail.com>
AuthorDate: Thu Apr 30 03:52:59 2020 +0800

    fix:
    (1) fix bar incremental render (brought by a61fd5ab7f434c7e967e107d6435c1e3f4f6d008)
    (2) add stream test case.
---
 src/chart/bar/BarView.ts   |   5 +-
 test/-cases.js             |   1 +
 test/bar-stream-large.html |  15 +++++-
 test/lib/testHelper.js     |  19 +++++---
 test/stream-basic.css      |  39 ++++++++++++++++
 test/stream-basic.js       |  99 +++++++++++++++++++++++++++++++++++++++
 test/stream-basic1.html    |  69 +++------------------------
 test/stream-basic2.html    | 114 +++++++++++++++++++++++++++++++++++++++++++++
 8 files changed, 288 insertions(+), 73 deletions(-)

diff --git a/src/chart/bar/BarView.ts b/src/chart/bar/BarView.ts
index 4565a2b..8bc6103 100644
--- a/src/chart/bar/BarView.ts
+++ b/src/chart/bar/BarView.ts
@@ -119,6 +119,9 @@ class BarView extends ChartView {
     incrementalPrepareRender(seriesModel: BarSeriesModel): void {
         this._clear();
         this._updateDrawMode(seriesModel);
+        // incremental also need to clip, otherwise might be overlow.
+        // But must not set clip in each frame, otherwise all of the children will be marked redraw.
+        this._updateLargeClip(seriesModel);
     }
 
     incrementalRender(params: StageHandlerProgressParams, seriesModel: BarSeriesModel): void {
@@ -288,8 +291,6 @@ class BarView extends ChartView {
     private _incrementalRenderLarge(params: StageHandlerProgressParams, seriesModel: BarSeriesModel): void {
         this._removeBackground();
         createLarge(seriesModel, this.group, true);
-        // incremental also need to clip, otherwise might be overlow.
-        this._updateLargeClip(seriesModel);
     }
 
     private _updateLargeClip(seriesModel: BarSeriesModel): void {
diff --git a/test/-cases.js b/test/-cases.js
index 9f5434f..e4c81b8 100644
--- a/test/-cases.js
+++ b/test/-cases.js
@@ -54,6 +54,7 @@
         name: 'stream-cases',
         whiteList: [
             'stream-basic1.html',
+            'stream-basic2.html',
             'lines-ny-appendData.html',
             'scatter-stream-large.html',
             'scatter-stream-not-large.html',
diff --git a/test/bar-stream-large.html b/test/bar-stream-large.html
index c2ad31e..d3fcf7b 100644
--- a/test/bar-stream-large.html
+++ b/test/bar-stream-large.html
@@ -25,8 +25,11 @@ under the License.
         <script src='lib/config.js'></script>
         <script src='lib/jquery.min.js'></script>
         <script src='lib/testHelper.js'></script>
+        <!-- <script src="lib/canteen.js"></script> -->
         <link rel="stylesheet" href="lib/reset.css" />
         <meta name='viewport' content='width=device-width, initial-scale=1' />
+        <link rel="stylesheet" href="./stream-basic.css" />
+        <script src="./stream-basic.js"></script>
     </head>
     <body>
         <style>
@@ -38,9 +41,19 @@ under the License.
             }
         </style>
         <div id='main0'></div>
+        <div id='record'></div>
         <img id="snapshot"/>
         <script>
 
+        var chart;
+        var recordContainer = document.getElementById('record');
+        // testHelper.controlFrame({
+        //     pauseAt: 20,
+        //     onFrame: function (frameNumber) {
+        //         window.printIncrementalOnFrame(chart, frameNumber, recordContainer);
+        //     }
+        // });
+
         require(['echarts'], function (echarts) {
 
             var count = 2e5;
@@ -77,7 +90,7 @@ under the License.
                 }]
             };
 
-            var chart = testHelper.create(echarts, 'main0', {
+            chart = testHelper.create(echarts, 'main0', {
                 title: [
                     count + ' points should be rendered from left to right.',
                     '(1) Check all bars rendered (full of xAxis)',
diff --git a/test/lib/testHelper.js b/test/lib/testHelper.js
index efbe103..5e6c6fe 100644
--- a/test/lib/testHelper.js
+++ b/test/lib/testHelper.js
@@ -285,12 +285,16 @@
         var _frameNumber = 0;
         var _mounted = false;
 
+        function getRunBtnText() {
+            return _running ? 'pause' : 'run';
+        }
+
         var buttons = [{
-            text: 'run',
-            onclick: run
-        }, {
-            text: 'pause',
-            onclick: pause
+            text: getRunBtnText(),
+            onclick: function () {
+                buttons[0].el.innerHTML = getRunBtnText();
+                _running ? pause() : run();
+            }
         }, {
             text: 'next frame',
             onclick: nextFrame
@@ -304,7 +308,7 @@
         document.body.appendChild(btnPanel);
         for (var i = 0; i < buttons.length; i++) {
             var button = buttons[i];
-            var btnEl = document.createElement('button');
+            var btnEl = button.el = document.createElement('button');
             btnEl.innerHTML = button.text;
             btnEl.addEventListener('click', button.onclick);
             btnPanel.appendChild(btnEl);
@@ -335,11 +339,12 @@
         function nextFrame() {
             opt.onFrame && opt.onFrame(_frameNumber);
 
-            infoEl.innerHTML = 'current frame: ' + _frameNumber;
             if (pauseAt != null && _frameNumber === pauseAt) {
                 _running = false;
                 pauseAt = null;
             }
+            infoEl.innerHTML = 'Frame: ' + _frameNumber + ' ( ' + (_running ? 'Running' : 'Paused') + ' )';
+            buttons[0].el.innerHTML = getRunBtnText();
 
             _mounted = false;
             var pending = _pendingCbList;
diff --git a/test/stream-basic.css b/test/stream-basic.css
new file mode 100644
index 0000000..55c2311
--- /dev/null
+++ b/test/stream-basic.css
@@ -0,0 +1,39 @@
+
+/*
+* 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.
+*/
+
+.print-incremental-record-title {
+    margin: 10px;
+    font-size: 18px;
+    font-weight: 700;
+}
+.print-incremental-record-title .print-incremental-cmd-count {
+    color: red;
+}
+.print-incremental-record {
+    margin: 5px 20px;
+}
+.print-incremental-record-line {
+    margin: 10px 10px;
+    font-size: 10px;
+}
+.print-incremental-record-line .print-incremental-cmd-count {
+    color: red;
+    font-size: 12px;
+}
diff --git a/test/stream-basic.js b/test/stream-basic.js
new file mode 100644
index 0000000..87360a1
--- /dev/null
+++ b/test/stream-basic.js
@@ -0,0 +1,99 @@
+/*
+* 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.
+*/
+
+(function () {
+
+    var _layersInfoMap = {};
+    var _recordContainer;
+    var CELL_MAX = 160;
+
+    if (window.Canteen) {
+        window.Canteen.globals.STACK_SIZE = 100000000;
+    }
+
+    window.printIncrementalOnFrame = function (chart, frameNumber, recordContainer) {
+        if (!_recordContainer) {
+            _recordContainer = recordContainer;
+            initContainer();
+        }
+        if (!chart) {
+            return;
+        }
+        var layers = chart.getZr().painter.getLayers();
+        for (var zlevel in layers) {
+            if (layers.hasOwnProperty(zlevel)) {
+                printIncremental(zlevel, layers[zlevel], frameNumber);
+            }
+        }
+    }
+
+    function initContainer() {
+        _recordContainer.innerHTML = [
+            '<div class="print-incremental-record-title">',
+                'In the "incremental layer", each frame: <br>',
+                'canvas instruction count (<span class="print-incremental-cmd-count">red number</span>) should be the same:',
+            '</div>'
+        ].join('');
+        _recordContainer.className = 'print-incremental-record';
+    }
+
+    function printIncremental(zlevel, layer, frameNumber) {
+        var layerInfo = _layersInfoMap[zlevel];
+        if (!layerInfo) {
+            layerInfo = _layersInfoMap[zlevel] = {
+                recordLineCellCount: 0,
+                recordLineTitle: document.createElement('div'),
+                recordLineContainer: document.createElement('div')
+            };
+            var incrementalText = layer.incremental ? ' (incremental)' : '';
+            layerInfo.recordLineTitle.innerHTML = 'layer ' + zlevel + incrementalText + ': <br>';
+            layerInfo.recordLineContainer.className = 'print-incremental-record-line';
+            _recordContainer.appendChild(layerInfo.recordLineTitle);
+            _recordContainer.appendChild(layerInfo.recordLineContainer);
+        }
+
+        var canvas = layer.dom;
+        var ctx = canvas.getContext('2d');
+        var stackLength = getStackLength(ctx);
+        var thisStackLength = stackLength;
+
+        var cell;
+        if (layerInfo.recordLineCellCount > CELL_MAX) {
+            cell = layerInfo.recordLineContainer.firstChild;
+        }
+        else {
+            cell = document.createElement('span');
+            layerInfo.recordLineCellCount++;
+        }
+        cell.innerHTML = frameNumber + ':<span class="print-incremental-cmd-count">' + thisStackLength + '</span> ';
+        layerInfo.recordLineContainer.appendChild(cell);
+
+        clearStack(ctx);
+    }
+
+    function getStackLength(ctx) {
+        return ctx.stack().length;
+    }
+
+    function clearStack(ctx) {
+        window.printIncrementalLastStack = ctx.stack().slice();
+        ctx.clear();
+    }
+
+})();
diff --git a/test/stream-basic1.html b/test/stream-basic1.html
index bb74ee9..c22321a 100644
--- a/test/stream-basic1.html
+++ b/test/stream-basic1.html
@@ -30,83 +30,26 @@ under the License.
         <script src="lib/testHelper.js"></script>
         <script src="lib/canteen.js"></script>
         <link rel="stylesheet" href="lib/reset.css" />
+        <link rel="stylesheet" href="./stream-basic.css" />
+        <script src="./stream-basic.js"></script>
     </head>
     <body>
-        <style>
-            .record-title {
-                margin: 10px;
-                font-size: 18px;
-                font-weight: 700;
-            }
-            .record {
-                margin: 5px 20px;
-            }
-            .record-unit {
-                margin: 10px 10px;
-                font-size: 10px;
-            }
-            .record-unit .cmd-count {
-                color: red;
-                font-size: 12px;
-            }
-            .record-title .cmd-count {
-                color: red;
-            }
-        </style>
-
-
 
         <div id="main0"></div>
-        <div class="record-title">
-            In incremental layer, each frame, <br>
-            new canvas command count (<span class="cmd-count">red number</span>) should be the same:</div>
         <div id="record"></div>
 
-
         <script>
 
-        window.Canteen.globals.STACK_SIZE = 100000000;
-
         var recordContainer = document.getElementById('record');
         var chart;
-        var layersInfoMap = {};
 
         testHelper.controlFrame({
             pauseAt: 120,
             onFrame: function (frameNumber) {
-                if (chart) {
-                    var layers = chart.getZr().painter.getLayers();
-                    for (var zlevel in layers) {
-                        if (layers.hasOwnProperty(zlevel)) {
-                            printNewCmds(zlevel, layers[zlevel], frameNumber)
-                        }
-                    }
-                }
+                window.printIncrementalOnFrame(chart, frameNumber, recordContainer);
             }
         });
 
-        function printNewCmds(zlevel, layer, frameNumber) {
-            var layerInfo = layersInfoMap[zlevel];
-            if (!layerInfo) {
-                layerInfo = layersInfoMap[zlevel] = {
-                    lastStackLength: 0,
-                    recordDom: document.createElement('div')
-                };
-                layerInfo.recordDom.className = 'record-unit';
-                layerInfo.recordDom.innerHTML = 'layer ' + zlevel + ': <br>';
-                recordContainer.appendChild(layerInfo.recordDom);
-            }
-
-            var canvas = layer.dom;
-            var ctx = canvas.getContext('2d');
-            var stackLength = ctx.stack().length;
-            var span = document.createElement('span');
-            span.innerHTML = frameNumber + ':<span class="cmd-count">' + (stackLength - layerInfo.lastStackLength) + '</span> ';
-            layerInfo.recordDom.appendChild(span);
-            layerInfo.lastStackLength = stackLength;
-        }
-
-
         require(['echarts'], function (echarts) {
             var option;
 
@@ -136,12 +79,12 @@ under the License.
                     axisLabel: {fontSize: 9, interval: 0},
                     boundaryGap: false
                 },
-                progressive: progressive,
-                progressiveThreshold: 1,
-                largeThreshold: 1,
                 series: {
                     type: 'scatter',
                     symbolSize: 2,
+                    progressive: progressive,
+                    progressiveThreshold: 1,
+                    largeThreshold: 1,
                     data: data
                 }
             };
diff --git a/test/stream-basic2.html b/test/stream-basic2.html
new file mode 100644
index 0000000..a98449f
--- /dev/null
+++ b/test/stream-basic2.html
@@ -0,0 +1,114 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+
+
+<html>
+    <head>
+        <meta charset="utf-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        <script src="lib/esl.js"></script>
+        <script src="lib/config.js"></script>
+        <script src="lib/jquery.min.js"></script>
+        <script src="lib/facePrint.js"></script>
+        <script src="lib/testHelper.js"></script>
+        <script src="lib/canteen.js"></script>
+        <link rel="stylesheet" href="lib/reset.css" />
+        <link rel="stylesheet" href="./stream-basic.css" />
+        <script src="./stream-basic.js"></script>
+    </head>
+    <body>
+
+        <div id="main0"></div>
+        <div id="record"></div>
+
+        <script>
+
+        var recordContainer = document.getElementById('record');
+        var chart;
+
+        testHelper.controlFrame({
+            pauseAt: 120,
+            onFrame: function (frameNumber) {
+                window.printIncrementalOnFrame(chart, frameNumber, recordContainer);
+            }
+        });
+
+
+        require(['echarts'], function (echarts) {
+            var option;
+
+            var count = 5000;
+            var lineCount = 100;
+            var progressive = 50;
+            var data = [];
+            var yCurr = 5;
+            for (var i = 0; i < count; i++) {
+                // var mod = i % lineCount;
+                // if (mod === 0) {
+                //     yCurr += 5;
+                // }
+                // data.push([mod, yCurr]);
+                data.push([i, 5]);
+            }
+
+            option = {
+                color: ['red'],
+                grid: {top: 20, bottom: 80},
+                xAxis: {
+                    type: 'category',
+                    // axisLabel: {fontSize: 9, interval: 0, rotate: 90, margin: 20},
+                    boundaryGap: false
+                },
+                yAxis: {
+                    // type: 'category',
+                    axisLabel: {fontSize: 9, interval: 0},
+                    boundaryGap: false
+                },
+                dataZoom: [{
+                    type: 'slider'
+                }, {
+                    type: 'inside'
+                }],
+                series: {
+                    type: 'bar',
+                    progressiveChunkMode: 'sequential',
+                    large: true,
+                    progressive: progressive,
+                    progressiveThreshold: 1,
+                    largeThreshold: 1,
+                    data: data
+                }
+            };
+
+            chart = testHelper.create(echarts, 'main0', {
+                title: [
+                    'Click **run** and check the printed records:',
+                ],
+                option: option,
+                height: 450,
+                recordCanvas: true
+            });
+        });
+        </script>
+
+
+    </body>
+</html>
+


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


[incubator-echarts] 01/02: fix: (1) revert increamental render broken brought by a96d97191b28500d2a3dc4f44e53221d54fbe218 (2) add stream test tools.

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

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

commit 0802ef0a8ba2f7944fc1f1eca843bad3ecf328ca
Author: 100pah <su...@gmail.com>
AuthorDate: Wed Apr 29 23:26:04 2020 +0800

    fix:
    (1) revert increamental render broken brought by a96d97191b28500d2a3dc4f44e53221d54fbe218
    (2) add stream test tools.
---
 src/echarts.ts          |   4 +-
 test/-cases.js          |   1 +
 test/lib/reset.css      |  14 ++++
 test/lib/testHelper.js  | 219 +++++++++++++++++++++++++++++++++++++++---------
 test/stream-basic1.html | 163 +++++++++++++++++++++++++++++++++++
 5 files changed, 360 insertions(+), 41 deletions(-)

diff --git a/src/echarts.ts b/src/echarts.ts
index d60e366..bb85b18 100644
--- a/src/echarts.ts
+++ b/src/echarts.ts
@@ -1654,7 +1654,9 @@ class ECharts extends Eventful {
                 unfinished |= +renderTask.perform(scheduler.getPerformArgs(renderTask));
 
                 chartView.group.silent = !!seriesModel.get('silent');
-                chartView.group.markRedraw();
+                // Should not call markRedraw on group, because it will disable zrender
+                // increamental render (alway render from the __startIndex each frame)
+                // chartView.group.markRedraw();
 
                 updateZ(seriesModel, chartView);
 
diff --git a/test/-cases.js b/test/-cases.js
index 676b67f..9f5434f 100644
--- a/test/-cases.js
+++ b/test/-cases.js
@@ -53,6 +53,7 @@
     }, {
         name: 'stream-cases',
         whiteList: [
+            'stream-basic1.html',
             'lines-ny-appendData.html',
             'scatter-stream-large.html',
             'scatter-stream-not-large.html',
diff --git a/test/lib/reset.css b/test/lib/reset.css
index 67e7b0d..fd34ca6 100644
--- a/test/lib/reset.css
+++ b/test/lib/reset.css
@@ -122,3 +122,17 @@ td.test-data-table-key {
     width: 300px;
     height: 500px;
 }
+
+.control-frame-btn-panel {
+    position: fixed;
+    top: 10px;
+    left: 10px;
+    box-shadow: 0 0 3px #000;
+    background: green;
+    padding: 5px;
+}
+.control-frame-btn-panel .control-frame-info {
+    display: block;
+    color: #fff;
+    font-size: 10px;
+}
diff --git a/test/lib/testHelper.js b/test/lib/testHelper.js
index d6685d4..efbe103 100644
--- a/test/lib/testHelper.js
+++ b/test/lib/testHelper.js
@@ -147,44 +147,51 @@
             infoContainer.innerHTML = createObjectHTML(opt.info, opt.infoKey || 'option');
         }
 
-        if (opt.recordCanvas) {
-            recordCanvasContainer.innerHTML = ''
-                + '<button>Show Canvas Record</button>'
-                + '<button>Clear Canvas Record</button>'
-                + '<div class="content-area"><textarea></textarea><br><button>Close</button></div>';
-            var buttons = recordCanvasContainer.getElementsByTagName('button');
-            var canvasRecordButton = buttons[0];
-            var clearButton = buttons[1];
-            var closeButton = buttons[2];
-            var recordArea = recordCanvasContainer.getElementsByTagName('textarea')[0];
-            var contentAraa = recordArea.parentNode;
-            canvasRecordButton.addEventListener('click', function () {
-                var content = [];
-                eachCtx(function (zlevel, ctx) {
-                    content.push('Layer zlevel: ' + zlevel, '\n\n');
-                    if (typeof ctx.stack !== 'function') {
-                        alert('Missing: <script src="test/lib/canteen.js"></script>');
-                        return;
-                    }
-                    var stack = ctx.stack();
-                    for (var i = 0; i < stack.length; i++) {
-                        var line = stack[i];
-                        content.push(JSON.stringify(line), ',\n');
-                    }
-                });
-                contentAraa.style.display = 'block';
-                recordArea.value = content.join('');
-            });
-            clearButton.addEventListener('click', function () {
-                eachCtx(function (zlevel, ctx) {
-                    ctx.clear();
-                });
-                recordArea.value = 'Cleared.';
+        initRecordCanvas(opt, chart, recordCanvasContainer);
+
+        return chart;
+    };
+
+    function initRecordCanvas(opt, chart, recordCanvasContainer) {
+        if (!opt.recordCanvas) {
+            return;
+        }
+        recordCanvasContainer.innerHTML = ''
+            + '<button>Show Canvas Record</button>'
+            + '<button>Clear Canvas Record</button>'
+            + '<div class="content-area"><textarea></textarea><br><button>Close</button></div>';
+        var buttons = recordCanvasContainer.getElementsByTagName('button');
+        var canvasRecordButton = buttons[0];
+        var clearButton = buttons[1];
+        var closeButton = buttons[2];
+        var recordArea = recordCanvasContainer.getElementsByTagName('textarea')[0];
+        var contentAraa = recordArea.parentNode;
+        canvasRecordButton.addEventListener('click', function () {
+            var content = [];
+            eachCtx(function (zlevel, ctx) {
+                content.push('\nLayer zlevel: ' + zlevel, '\n\n');
+                if (typeof ctx.stack !== 'function') {
+                    alert('Missing: <script src="test/lib/canteen.js"></script>');
+                    return;
+                }
+                var stack = ctx.stack();
+                for (var i = 0; i < stack.length; i++) {
+                    var line = stack[i];
+                    content.push(JSON.stringify(line), ',\n');
+                }
             });
-            closeButton.addEventListener('click', function () {
-                contentAraa.style.display = 'none';
+            contentAraa.style.display = 'block';
+            recordArea.value = content.join('');
+        });
+        clearButton.addEventListener('click', function () {
+            eachCtx(function (zlevel, ctx) {
+                ctx.clear();
             });
-        }
+            recordArea.value = 'Cleared.';
+        });
+        closeButton.addEventListener('click', function () {
+            contentAraa.style.display = 'none';
+        });
 
         function eachCtx(cb) {
             var layers = chart.getZr().painter.getLayers();
@@ -197,9 +204,7 @@
                 }
             }
         }
-
-        return chart;
-    };
+    }
 
     /**
      * @param {ECharts} echarts
@@ -257,6 +262,95 @@
     };
 
 
+    var _dummyRequestAnimationFrameMounted = false;
+
+    /**
+     * Usage:
+     * ```js
+     * testHelper.controlFrame({pauseAt: 60});
+     * // Then load echarts.js (must after controlFrame called)
+     * ```
+     *
+     * @param {Object} [opt]
+     * @param {number} [opt.puaseAt] If specified `pauseAt`, auto pause at the frame.
+     * @param {Function} [opt.onFrame]
+     */
+    testHelper.controlFrame = function (opt) {
+        opt = opt || {};
+        var pauseAt = opt.pauseAt;
+        pauseAt == null && (pauseAt = 0);
+
+        var _running = true;
+        var _pendingCbList = [];
+        var _frameNumber = 0;
+        var _mounted = false;
+
+        var buttons = [{
+            text: 'run',
+            onclick: run
+        }, {
+            text: 'pause',
+            onclick: pause
+        }, {
+            text: 'next frame',
+            onclick: nextFrame
+        }];
+
+        var btnPanel = document.createElement('div');
+        btnPanel.className = 'control-frame-btn-panel'
+        var infoEl = document.createElement('div');
+        infoEl.className = 'control-frame-info';
+        btnPanel.appendChild(infoEl);
+        document.body.appendChild(btnPanel);
+        for (var i = 0; i < buttons.length; i++) {
+            var button = buttons[i];
+            var btnEl = document.createElement('button');
+            btnEl.innerHTML = button.text;
+            btnEl.addEventListener('click', button.onclick);
+            btnPanel.appendChild(btnEl);
+        }
+
+        if (_dummyRequestAnimationFrameMounted) {
+            throw new Error('Do not support `controlFrame` twice');
+        }
+        _dummyRequestAnimationFrameMounted = true;
+        var raf = window.requestAnimationFrame;
+        window.requestAnimationFrame = function (cb) {
+            _pendingCbList.push(cb);
+            if (_running && !_mounted) {
+                _mounted = true;
+                raf(nextFrame);
+            }
+        };
+
+        function run() {
+            _running = true;
+            nextFrame();
+        }
+
+        function pause() {
+            _running = false;
+        }
+
+        function nextFrame() {
+            opt.onFrame && opt.onFrame(_frameNumber);
+
+            infoEl.innerHTML = 'current frame: ' + _frameNumber;
+            if (pauseAt != null && _frameNumber === pauseAt) {
+                _running = false;
+                pauseAt = null;
+            }
+
+            _mounted = false;
+            var pending = _pendingCbList;
+            _pendingCbList = [];
+            for (var i = 0; i < pending.length; i++) {
+                pending[i]();
+            }
+            _frameNumber++;
+        }
+    }
+
     testHelper.resizable = function (chart) {
         var dom = chart.getDom();
         var width = dom.clientWidth;
@@ -505,7 +599,6 @@
      *        For example: 'z2' or ['z2', 'style.fill', 'style.stroke']
      * @param {function} [opt.filter] print a subtree only if any satisfied node exists.
      *        param: el, return: boolean
-     * @param {boolean} [opt.preventPrint]
      */
     testHelper.stringifyElements = function (chart, opt) {
         if (!chart) {
@@ -602,6 +695,52 @@
         console.log(elsStr);
     };
 
+    /**
+     * Usage:
+     * ```js
+     * // Print all elements that has `style.text`:
+     * testHelper.retrieveElements(chart, {
+     *     filter: el => el.style && el.style.text
+     * });
+     * ```
+     *
+     * @param {EChart} chart
+     * @param {Object} [opt]
+     * @param {function} [opt.filter] print a subtree only if any satisfied node exists.
+     *        param: el, return: boolean
+     * @return {Array.<Element>}
+     */
+    testHelper.retrieveElements = function (chart, opt) {
+        if (!chart) {
+            return;
+        }
+        opt = opt || {};
+        var attrNameList = opt.attr;
+        if (getType(attrNameList) !== 'array') {
+            attrNameList = attrNameList ? [attrNameList] : [];
+        }
+
+        var zr = chart.getZr();
+        var roots = zr.storage.getRoots();
+        var result = [];
+
+        retrieve(roots);
+
+        function retrieve(elList) {
+            for (var i = 0; i < elList.length; i++) {
+                var el = elList[i];
+                if (!opt.filter || opt.filter(el)) {
+                    result.push(el);
+                }
+                if (el.isGroup) {
+                    retrieve(el.childrenRef());
+                }
+            }
+        }
+
+        return result;
+    };
+
     // opt: {record: JSON, width: number, height: number}
     testHelper.reproduceCanteen = function (opt) {
         var canvas = document.createElement('canvas');
diff --git a/test/stream-basic1.html b/test/stream-basic1.html
new file mode 100644
index 0000000..bb74ee9
--- /dev/null
+++ b/test/stream-basic1.html
@@ -0,0 +1,163 @@
+<!DOCTYPE html>
+<!--
+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.
+-->
+
+
+<html>
+    <head>
+        <meta charset="utf-8">
+        <meta name="viewport" content="width=device-width, initial-scale=1" />
+        <script src="lib/esl.js"></script>
+        <script src="lib/config.js"></script>
+        <script src="lib/jquery.min.js"></script>
+        <script src="lib/facePrint.js"></script>
+        <script src="lib/testHelper.js"></script>
+        <script src="lib/canteen.js"></script>
+        <link rel="stylesheet" href="lib/reset.css" />
+    </head>
+    <body>
+        <style>
+            .record-title {
+                margin: 10px;
+                font-size: 18px;
+                font-weight: 700;
+            }
+            .record {
+                margin: 5px 20px;
+            }
+            .record-unit {
+                margin: 10px 10px;
+                font-size: 10px;
+            }
+            .record-unit .cmd-count {
+                color: red;
+                font-size: 12px;
+            }
+            .record-title .cmd-count {
+                color: red;
+            }
+        </style>
+
+
+
+        <div id="main0"></div>
+        <div class="record-title">
+            In incremental layer, each frame, <br>
+            new canvas command count (<span class="cmd-count">red number</span>) should be the same:</div>
+        <div id="record"></div>
+
+
+        <script>
+
+        window.Canteen.globals.STACK_SIZE = 100000000;
+
+        var recordContainer = document.getElementById('record');
+        var chart;
+        var layersInfoMap = {};
+
+        testHelper.controlFrame({
+            pauseAt: 120,
+            onFrame: function (frameNumber) {
+                if (chart) {
+                    var layers = chart.getZr().painter.getLayers();
+                    for (var zlevel in layers) {
+                        if (layers.hasOwnProperty(zlevel)) {
+                            printNewCmds(zlevel, layers[zlevel], frameNumber)
+                        }
+                    }
+                }
+            }
+        });
+
+        function printNewCmds(zlevel, layer, frameNumber) {
+            var layerInfo = layersInfoMap[zlevel];
+            if (!layerInfo) {
+                layerInfo = layersInfoMap[zlevel] = {
+                    lastStackLength: 0,
+                    recordDom: document.createElement('div')
+                };
+                layerInfo.recordDom.className = 'record-unit';
+                layerInfo.recordDom.innerHTML = 'layer ' + zlevel + ': <br>';
+                recordContainer.appendChild(layerInfo.recordDom);
+            }
+
+            var canvas = layer.dom;
+            var ctx = canvas.getContext('2d');
+            var stackLength = ctx.stack().length;
+            var span = document.createElement('span');
+            span.innerHTML = frameNumber + ':<span class="cmd-count">' + (stackLength - layerInfo.lastStackLength) + '</span> ';
+            layerInfo.recordDom.appendChild(span);
+            layerInfo.lastStackLength = stackLength;
+        }
+
+
+        require(['echarts'], function (echarts) {
+            var option;
+
+            var count = 5000;
+            var lineCount = 100;
+            var progressive = 50;
+            var data = [];
+            var yCurr = 5;
+            for (var i = 0; i < count; i++) {
+                var mod = i % lineCount;
+                if (mod === 0) {
+                    yCurr += 5;
+                }
+                data.push([mod, yCurr]);
+            }
+
+            option = {
+                color: ['red'],
+                grid: {top: 20, bottom: 30},
+                xAxis: {
+                    type: 'category',
+                    axisLabel: {fontSize: 9, interval: 0, rotate: 90, margin: 20},
+                    boundaryGap: false
+                },
+                yAxis: {
+                    type: 'category',
+                    axisLabel: {fontSize: 9, interval: 0},
+                    boundaryGap: false
+                },
+                progressive: progressive,
+                progressiveThreshold: 1,
+                largeThreshold: 1,
+                series: {
+                    type: 'scatter',
+                    symbolSize: 2,
+                    data: data
+                }
+            };
+
+            chart = testHelper.create(echarts, 'main0', {
+                title: [
+                    'Click **run** and check the printed records:',
+                ],
+                option: option,
+                height: 450,
+                recordCanvas: true
+            });
+        });
+        </script>
+
+
+    </body>
+</html>
+


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