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/06/02 17:54:30 UTC

[incubator-echarts] branch custom-series-enhance created (now 1dffebc)

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

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


      at 1dffebc  feature: support clipPath and clipPath animation in custom series.

This branch includes the following new commits:

     new d0e5f43  feat: custom series compat.
     new b65758c  Merge branch 'next' into custom-series-enhance
     new eab38da  Merge branch 'next' into custom-series-enhance
     new 88c51a0  fix: fix custom series api.size in polor.
     new c56e2dc  feature: add duration animation for custom series.
     new 1309317  feature: support text animation on custom series (via in `during`)
     new a631dba  feature: support axis label/tick animation on polar radius axis, the same as cartesian axis.
     new d0315f2  chore: tweak and make better example for tutorial.
     new 389d643  Merge branch 'next' into custom-series-enhance
     new 1dffebc  feature: support clipPath and clipPath animation in custom series.

The 10 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] 09/10: Merge branch 'next' into custom-series-enhance

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

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

commit 389d643c3915297691576334a293fd31c05b6a2c
Merge: d0315f2 75d9cd8
Author: 100pah <su...@gmail.com>
AuthorDate: Wed May 27 16:40:01 2020 +0800

    Merge branch 'next' into custom-series-enhance

 build/dev-fast.js                              |  74 +++
 dist/echarts-en.common.js                      | 520 ++++++++++++-----
 dist/echarts-en.common.min.js                  |   2 +-
 dist/echarts-en.js                             | 737 +++++++++++++++++--------
 dist/echarts-en.js.map                         |   2 +-
 dist/echarts-en.min.js                         |   2 +-
 dist/echarts-en.simple.js                      | 435 ++++++++++-----
 dist/echarts-en.simple.min.js                  |   2 +-
 dist/echarts.common.js                         | 494 ++++++++++++-----
 dist/echarts.common.min.js                     |   2 +-
 dist/echarts.js                                | 711 ++++++++++++++++--------
 dist/echarts.js.map                            |   2 +-
 dist/echarts.min.js                            |   2 +-
 dist/echarts.simple.js                         | 409 +++++++++-----
 dist/echarts.simple.min.js                     |   2 +-
 dist/extension/bmap.js                         | 338 +++++++++++-
 dist/extension/bmap.js.map                     |   2 +-
 dist/extension/bmap.min.js                     |   2 +-
 extension-src/bmap/BMapView.ts                 |   5 +-
 extension/bmap/BMapView.js                     |   8 +-
 map/js/province/gansu.js                       |   4 +-
 package-lock.json                              | 151 ++++-
 package.json                                   |   8 +-
 src/chart/bar/BarSeries.ts                     |   6 +-
 src/chart/bar/BarView.ts                       |  32 +-
 src/chart/heatmap/HeatmapView.ts               |   4 +-
 src/chart/helper/EffectSymbol.ts               |   3 +-
 src/chart/helper/Line.ts                       |   2 +-
 src/chart/helper/LineDraw.ts                   |   6 +-
 src/chart/helper/Symbol.ts                     |   4 +-
 src/chart/helper/createClipPathFromCoordSys.ts |   4 +
 src/chart/line/LineView.ts                     |  41 +-
 src/chart/map/MapSeries.ts                     |   8 +-
 src/chart/pie/PieView.ts                       |  39 +-
 src/chart/sankey/sankeyLayout.ts               |  22 +-
 src/chart/sunburst/SunburstPiece.ts            |   2 +
 src/chart/sunburst/SunburstSeries.ts           |   5 +-
 src/chart/sunburst/SunburstView.ts             |   4 +-
 src/chart/tree/TreeSeries.ts                   |   7 +-
 src/chart/treemap/TreemapSeries.ts             |  21 +-
 src/chart/treemap/TreemapView.ts               |  43 +-
 src/component/helper/MapDraw.ts                |  36 +-
 src/component/title.ts                         |   6 +-
 src/component/toolbox/ToolboxView.ts           |   2 +-
 src/component/toolbox/feature/MagicType.ts     |   3 +-
 src/component/toolbox/feature/SaveAsImage.ts   |   3 +-
 src/component/tooltip/TooltipView.ts           |   2 +-
 src/component/visualMap/PiecewiseModel.ts      |  14 +-
 src/coord/axisHelper.ts                        |  44 +-
 src/coord/calendar/Calendar.ts                 |  17 +-
 src/coord/geo/GeoModel.ts                      |   5 +-
 src/coord/geo/geoJSONLoader.ts                 |   4 +-
 src/coord/geo/geoSourceManager.ts              |   6 +-
 src/coord/geo/parseGeoJson.ts                  |   4 +-
 src/coord/radar/Radar.ts                       |   2 +-
 src/data/List.ts                               |  44 +-
 src/data/Tree.ts                               |  40 +-
 src/data/helper/dataProvider.ts                |   6 +-
 src/echarts.ts                                 | 187 ++++---
 src/langEN.ts                                  |  26 +
 src/loading/default.ts                         |  97 ++--
 src/model/Global.ts                            |  17 +-
 src/model/Series.ts                            |   2 +-
 src/model/mixin/itemStyle.ts                   |   2 +-
 src/util/format.ts                             |  18 +-
 src/util/graphic.ts                            |  14 +-
 src/util/types.ts                              |   4 +-
 src/visual/symbol.ts                           |  18 +-
 test/axis-extrema.html                         |  39 ++
 test/bar-background.html                       |  68 ++-
 test/calendar-timezone.html                    | 173 ++++++
 test/effectScatter2.html                       | 106 ++++
 test/heatmap-gap-bug.html                      | 188 +++++++
 test/line-crash.html                           | 131 +++++
 test/lines-bus.html                            |  17 +-
 test/loading.html                              |  55 +-
 test/map-nameProperty.html                     | 252 +++++++++
 test/map.html                                  |  72 +++
 test/min-max-function.html                     | 117 +++-
 test/pie-animation.html                        |  47 +-
 test/runTest/actions/__meta__.json             |   4 +
 test/runTest/actions/bar-background.json       |   1 +
 test/runTest/actions/line-crash.json           |   1 +
 test/runTest/actions/pie-animation.json        |   1 +
 test/runTest/actions/treemap-action.json       |   1 +
 test/sankey-depth.html                         |  14 +-
 test/symbol3.html                              | 105 ++++
 test/toolbox-saveImage-background-svg.html     | 208 +++++++
 88 files changed, 5005 insertions(+), 1385 deletions(-)



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


[incubator-echarts] 08/10: chore: tweak and make better example for tutorial.

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

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

commit d0315f25e9a4e6b8bcf86b76b2d5eb3e8a47c61d
Author: 100pah <su...@gmail.com>
AuthorDate: Thu May 21 22:43:10 2020 +0800

    chore: tweak and make better example for tutorial.
---
 src/chart/custom.ts         |   9 +-
 test/custom-feature.html    | 483 ----------------------------------
 test/custom-transition.html | 627 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 633 insertions(+), 486 deletions(-)

diff --git a/src/chart/custom.ts b/src/chart/custom.ts
index cfb9587..7af3ec0 100644
--- a/src/chart/custom.ts
+++ b/src/chart/custom.ts
@@ -286,7 +286,8 @@ const Z2_SPECIFIED_BIT = {
     emphasis: 1
 } as const;
 
-const tmpDuringElProps = { style: {} } as CustomDuringElProps;
+const tmpDuringStyle = {} as CustomDuringElProps['style'];
+const tmpDuringElProps = {} as CustomDuringElProps;
 
 export type PrepareCustomInfo = (coordSys: CoordinateSystem) => {
     coordSys: CustomSeriesRenderItemParamsCoordSys;
@@ -696,7 +697,9 @@ function elUpdateDuringAnimation(this: Element, key: string): void {
     // PENDING:
     // Do not expose other style in case that is not stable.
     const isText = this.type === 'text';
-    const textCurr = tmpDuringElProps.style.text = isText ? thisText.style.text : null;
+    // Always assign in case that user modify `.style`.
+    tmpDuringElProps.style = tmpDuringStyle;
+    const textCurr = tmpDuringStyle.text = isText ? thisText.style.text : null;
 
     customDuring(tmpDuringElProps);
 
@@ -711,7 +714,7 @@ function elUpdateDuringAnimation(this: Element, key: string): void {
     tmpDuringElProps.rotation !== rotationCurr && (this.rotation = tmpDuringElProps.rotation);
 
     if (isText) {
-        const currTmpStl = tmpDuringElProps.style;
+        const currTmpStl = tmpDuringElProps.style; // Allow user modify `.style`.
         currTmpStl && currTmpStl.text !== textCurr && (thisText.style.text = currTmpStl.text, dirtyStyle = true);
     }
 
diff --git a/test/custom-feature.html b/test/custom-feature.html
index dabf8b1..cee7c53 100644
--- a/test/custom-feature.html
+++ b/test/custom-feature.html
@@ -38,10 +38,6 @@ under the License.
         <div id="main0"></div>
         <div id="main2"></div>
         <div id="main3"></div>
-        <div id="init-animation-additive"></div>
-        <!-- <div id="spiral"></div> -->
-        <div id="spiral2"></div>
-        <div id="texture-bar"></div>
 
 
         <script>
@@ -368,485 +364,6 @@ under the License.
 
 
 
-        <script>
-
-            require(['echarts'], function (echarts) {
-
-                var animationDuration = 5000;
-                var animationDurationUpdate = 4000;
-                var option = {
-                    xAxis: {},
-                    yAxis: {},
-                    dataZoom: [
-                        { type: 'slider' },
-                        { type: 'inside' }
-                    ],
-                    animationDuration: animationDuration,
-                    animationDurationUpdate: animationDurationUpdate,
-                    series: [{
-                        type: 'custom',
-                        renderItem: function (params, api) {
-                            return {
-                                type: 'group',
-                                position: api.coord([api.value(0), api.value(1)]),
-                                children: [{
-                                    type: 'rect',
-                                    shape: {
-                                        x: -50,
-                                        y: 50,
-                                        width: 100,
-                                        height: 150,
-                                        r: 10
-                                    },
-                                    style: {
-                                        fill: 'rgba(102,241,98,0.9)'
-                                    }
-                                }, {
-                                    type: 'circle',
-                                    shape: {
-                                        cx: -50,
-                                        cy: 50,
-                                        r: 30
-                                    },
-                                    style: {
-                                        fill: 'blue'
-                                    },
-                                    textContent: {
-                                        text: 'data',
-                                        style: {
-                                            fill: '#fff'
-                                        }
-                                    }
-                                }]
-                            };
-                        },
-                        data: [[121, 333], [29, 312]]
-                    }]
-                };
-
-                var chart = testHelper.create(echarts, 'init-animation-additive', {
-                    title: [
-                        'Style merge:',
-                        '(1) dataZoom hide a data item, and then show it, ensure the fade in animation normal.',
-                        '(2) click button to setOption merge.'
-                    ],
-                    option: option,
-                    info: {
-                        animationDuration: animationDuration,
-                        animationDurationUpdate: animationDurationUpdate
-                    },
-                    buttons: [{
-                        text: 'merge style: border become blue, but background not changed',
-                        onclick: function () {
-                            chart.setOption({
-                                type: 'custom',
-                                renderItem: function (params, api) {
-                                    return {
-                                        type: 'group',
-                                        children: [{
-                                            type: 'rect',
-                                            style: {
-                                                stroke: 'red',
-                                                lineWidth: 5
-                                            }
-                                        }]
-                                    };
-                                }
-                            });
-                        }
-                    }]
-                });
-            });
-
-        </script>
-
-
-
-
-<!--
-
-        <script>
-            require([
-                'echarts'/*, 'map/js/china' */
-            ], function (echarts) {
-                var animationDuration = 5000;
-                var animationDurationUpdate = 4000;
-                var angleLabel = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo', 'Libra', 'Scorpius', 'Sagittarius', 'Capricornus', 'Aquarius', 'Pisces'];
-                var angleRoundValue = angleLabel.length;
-                var radiusOffset = 10;
-                var angleStep = angleRoundValue / 90;
-                var barWidthValue = 0.4;
-                var radiusStep = 4;
-                var colors = {
-                    'A': { stroke: 'green', fill: 'rgba(0,152,0,0.6)' },
-                    'B': { stroke: 'red', fill: 'rgba(152,0,0,0.6)' },
-                    'C': { stroke: 'blue', fill: 'rgba(0,0, 152,0.6)' },
-                };
-                var allData = [[
-                    [[1, 3, 'A']],
-                    [[2, 6, 'B']],
-                    [[3, 9, 'C']],
-                ], [
-                    [[1, 12, 'A']],
-                    [[2, 16, 'B']],
-                    [[3, 14, 'C']],
-                ], [
-                    [[1, 17, 'A']],
-                    [[2, 22, 'B']],
-                    [[3, 19, 'C']],
-                ]];
-                var currentDataIndex = 0;
-
-                function getMaxRadius() {
-                    var radius = 0;
-                    for (var j = 0; j < allData.length; j++) {
-                        var data = allData[j];
-                        for (var i = 0; i < data.length; i++) {
-                            radius = Math.max(radius, getSpiralValueRadius(data[i][0][0], data[i][0][1]));
-                        }
-                    }
-                    return Math.ceil(radius * 1.2);
-                }
-
-                function getSpiralValueRadius(valRadius, valAngle) {
-                    return valRadius + radiusStep * (valAngle / angleRoundValue);
-                }
-
-                function renderItem(params, api) {
-                    var valueRadius = api.value(0);
-                    var valueAngle = api.value(1);
-                    var points = [];
-                    for (var iAngleVal = 0, end = valueAngle + angleStep; iAngleVal < end; iAngleVal += angleStep) {
-                        iAngleVal > valueAngle && (iAngleVal = valueAngle);
-                        var iRadiusVal = getSpiralValueRadius(valueRadius - barWidthValue, iAngleVal);
-                        var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2);
-                        points.push(point);
-                    }
-                    for (var iAngleVal = valueAngle; iAngleVal > -angleStep; iAngleVal -= angleStep) {
-                        iAngleVal < 0 && (iAngleVal = 0);
-                        var iRadiusVal = getSpiralValueRadius(valueRadius + barWidthValue, iAngleVal);
-                        var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2);
-                        points.push(point);
-                    }
-                    var name = api.value(2);
-                    return {
-                        type: 'polygon',
-                        shape: { points: points },
-                        style: {
-                            lineWidth: 1,
-                            fill: colors[name].fill,
-                            stroke: colors[name].stroke
-                        }
-                    };
-                }
-
-                var option = {
-                    animationDuration: animationDuration,
-                    animationDurationUpdate: animationDurationUpdate,
-                    angleAxis: {
-                        type: 'value',
-                        // splitLine: { show: false },
-                        splitArea: {show: true},
-                        axisLabel: {
-                            formatter: function(val) {
-                                return angleLabel[val];
-                            },
-                            color: 'rgba(0,0,0,0.2)'
-                        },
-                        axisLine: { lineStyle: { color: 'rgba(0,0,0,0.2)' } },
-                        min: 0,
-                        max: angleRoundValue
-                    },
-                    radiusAxis: {
-                        type: 'value',
-                        splitLine: { show: false },
-                        axisLabel: { color: 'rgba(0,0,0,0.2)' },
-                        axisLine: { lineStyle: { color: 'rgba(0,0,0,0.2)' } },
-                        min: 0,
-                        max: getMaxRadius()
-                    },
-                    polar: {
-                    },
-                    tooltip: {},
-                    series: [{
-                        type: 'custom',
-                        name: 'A',
-                        coordinateSystem: 'polar',
-                        renderItem: renderItem,
-                        data: allData[currentDataIndex][0]
-                    }, {
-                        type: 'custom',
-                        name: 'B',
-                        coordinateSystem: 'polar',
-                        renderItem: renderItem,
-                        data: allData[currentDataIndex][1]
-                    }, {
-                        type: 'custom',
-                        name: 'C',
-                        coordinateSystem: 'polar',
-                        renderItem: renderItem,
-                        data: allData[currentDataIndex][2]
-                    }]
-                };
-
-                var chart = testHelper.create(echarts, 'spiral', {
-                    title: [
-                        'animation: ',
-                    ],
-                    option: option,
-                    buttons: [{
-                        text: 'next',
-                        onclick: function () {
-                            currentDataIndex++;
-                            currentDataIndex >= allData.length && (currentDataIndex = 0);
-                            chart.setOption({
-                                series: [{
-                                    data: allData[currentDataIndex][0]
-                                }, {
-                                    data: allData[currentDataIndex][1]
-                                }, {
-                                    data: allData[currentDataIndex][2]
-                                }]
-                            })
-                        }
-                    }, {
-                        text: 'enable animation',
-                        onclick: function () {
-                            chart.setOption({ animation: true });
-                        }
-                    }, {
-                        text: 'disable animation',
-                        onclick: function () {
-                            chart.setOption({ animation: false });
-                        }
-                    }]
-                });
-            });
-        </script>
- -->
-
-
-
-
-
-
-
-        <script>
-            require([
-                'echarts'/*, 'map/js/china' */
-            ], function (echarts) {
-                var animationDuration = 5000;
-                var animationDurationUpdate = 7000;
-                var animationEasingUpdate = 'elasticOut';
-                var angleLabel = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo', 'Libra', 'Scorpius', 'Sagittarius', 'Capricornus', 'Aquarius', 'Pisces'];
-                var angleRoundValue = angleLabel.length;
-                var radiusOffset = 10;
-                var angleStep = angleRoundValue / 90;
-                var barWidthValue = 0.4;
-                var radiusStep = 4;
-
-                var colors = [
-                    { border: 'green', inner: 'rgba(0,152,0,0.6)' },
-                    { border: 'red', inner: 'rgba(152,0,0,0.6)' },
-                    { border: 'blue', inner: 'rgba(0,0, 152,0.6)' },
-                ];
-                var currentDataIndex = 0;
-                var datasourceList = [
-                    [[3, 6, 9]],
-                    [[12, 16, 14]],
-                    [[17, 22, 19]],
-                ];
-                var barValOnRadiusList = [1, 2, 3];
-
-                // PENDING:
-                // The radius max should be fixed rather than change dynamically.
-                // If need to support dynamic coord sys while animation:
-                // (A) The `api.coord` should be able to accept a customized extent and
-                // return value on the middle state.
-                // or (B) Use "data interpolate".
-                function getMaxRadius() {
-                    var radius = 0;
-                    for (var k = 0; k < barValOnRadiusList.length; k++) {
-                        for (var i = 0; i < datasourceList.length; i++) {
-                            var row = datasourceList[i][0];
-                            for (var j = 0; j < row.length; j++) {
-                                var valOnAngle = row[j];
-                                radius = Math.max(
-                                    radius,
-                                    getSpiralValueRadius(barValOnRadiusList[k], valOnAngle)
-                                );
-                            }
-                        }
-                    }
-                    return Math.ceil(radius * 1.2);
-                }
-
-                function getSpiralValueRadius(valOnRadius, valOnAngle) {
-                    return valOnRadius + radiusStep * (valOnAngle / angleRoundValue);
-                }
-
-                function addShapes(api, children, valOnRadius, valOnAngle, color) {
-                    addPolygon(api, children, valOnRadius, valOnAngle, color);
-                    addLabel(api, children, valOnRadius, valOnAngle, color);
-                }
-
-                function addPolygon(api, children, valOnRadius, valOnAngle, color) {
-                    children.push({
-                        type: 'polygon',
-                        shape: {
-                            points: makeShapePoints(api, valOnRadius, valOnAngle),
-                            valOnAngle: valOnAngle
-                        },
-                        style: {
-                            lineWidth: 1,
-                            fill: color.inner,
-                            stroke: color.border
-                        },
-                        during: function (elProps) {
-                            elProps.shape.points = makeShapePoints(
-                                api, valOnRadius, elProps.shape.valOnAngle
-                            );
-                        }
-                    });
-                }
-
-                function makeShapePoints(api, valOnRadius, valOnAngle) {
-                    var points = [];
-                    for (var iAngleVal = 0, end = valOnAngle + angleStep; iAngleVal < end; iAngleVal += angleStep) {
-                        iAngleVal > valOnAngle && (iAngleVal = valOnAngle);
-                        var iRadiusVal = getSpiralValueRadius(valOnRadius - barWidthValue, iAngleVal);
-                        var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2);
-                        points.push(point);
-                    }
-                    for (var iAngleVal = valOnAngle; iAngleVal > -angleStep; iAngleVal -= angleStep) {
-                        iAngleVal < 0 && (iAngleVal = 0);
-                        var iRadiusVal = getSpiralValueRadius(valOnRadius + barWidthValue, iAngleVal);
-                        var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2);
-                        points.push(point);
-                    }
-                    return points;
-                }
-
-                function addLabel(api, children, valOnRadius, valOnAngle, color) {
-                    var point = makeLabelPosition(api, valOnRadius, valOnAngle);
-                    children.push({
-                        type: 'text',
-                        x: point[0],
-                        y: point[1],
-                        shape: {
-                            valOnAngle: valOnAngle
-                        },
-                        style: {
-                            text: getText(valOnAngle),
-                            fill: color.inner,
-                            stroke: '#fff',
-                            lineWidth: 3,
-                            fontSize: 16,
-                            align: 'center',
-                            verticalAlign: 'middle'
-                        },
-                        z2: 50,
-                        during: function (elProps) {
-                            var iValOnAngle = elProps.shape.valOnAngle;
-                            var point = makeLabelPosition(api, valOnRadius, iValOnAngle);
-                            elProps.x = point[0];
-                            elProps.y = point[1];
-                            elProps.style.text = getText(iValOnAngle);
-                        }
-                    });
-
-                    function getText(iValOnAngle) {
-                        return (iValOnAngle / angleRoundValue * 100).toFixed(0) + '%'
-                    }
-                }
-
-                function makeLabelPosition(api, valOnRadius, valOnAngle) {
-                    var iRadiusVal = getSpiralValueRadius(valOnRadius, valOnAngle);
-                    return api.coord([iRadiusVal, valOnAngle + 1 / iRadiusVal / (2 * Math.PI) * angleRoundValue]);
-                }
-
-                function renderItem(params, api) {
-                    var children = [];
-
-                    addShapes(api, children, barValOnRadiusList[0], api.value(0), colors[0]);
-                    addShapes(api, children, barValOnRadiusList[1], api.value(1), colors[1]);
-                    addShapes(api, children, barValOnRadiusList[2], api.value(2), colors[2]);
-
-                    return {
-                        type: 'group',
-                        children: children
-                    };
-                }
-
-                var option = {
-                    // animation: false,
-                    animationDuration: animationDuration,
-                    animationDurationUpdate: animationDurationUpdate,
-                    animationEasingUpdate: animationEasingUpdate,
-                    dataset: {
-                        source: datasourceList[currentDataIndex]
-                    },
-                    angleAxis: {
-                        type: 'value',
-                        // splitLine: { show: false },
-                        splitArea: {show: true},
-                        axisLabel: {
-                            formatter: function(val) {
-                                return angleLabel[val];
-                            },
-                            color: 'rgba(0,0,0,0.2)'
-                        },
-                        axisLine: { lineStyle: { color: 'rgba(0,0,0,0.2)' } },
-                        min: 0,
-                        max: angleRoundValue
-                    },
-                    radiusAxis: {
-                        type: 'value',
-                        splitLine: { show: false },
-                        axisLabel: { color: 'rgba(0,0,0,0.2)' },
-                        axisLine: { lineStyle: { color: 'rgba(0,0,0,0.2)' } },
-                        min: 0,
-                        max: getMaxRadius()
-                    },
-                    polar: {},
-                    tooltip: {},
-                    series: [{
-                        type: 'custom',
-                        coordinateSystem: 'polar',
-                        renderItem: renderItem
-                    }]
-                };
-
-                var chart = testHelper.create(echarts, 'spiral2', {
-                    title: [
-                        'polygon animation should be corrent. (coordSys extent is fixed)',
-                    ],
-                    option: option,
-                    buttons: [{
-                        text: 'next',
-                        onclick: function () {
-                            currentDataIndex++;
-                            currentDataIndex >= datasourceList.length && (currentDataIndex = 0);
-                            chart.setOption({
-                                dataset: {
-                                    source: datasourceList[currentDataIndex]
-                                }
-                            });
-                        }
-                    }, {
-                        text: 'enable animation',
-                        onclick: function () {
-                            chart.setOption({ animation: true });
-                        }
-                    }, {
-                        text: 'disable animation',
-                        onclick: function () {
-                            chart.setOption({ animation: false });
-                        }
-                    }]
-                });
-            });
-        </script>
 
 
 
diff --git a/test/custom-transition.html b/test/custom-transition.html
new file mode 100644
index 0000000..77a6aab
--- /dev/null
+++ b/test/custom-transition.html
@@ -0,0 +1,627 @@
+<!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>
+        <link rel="stylesheet" href="lib/reset.css" />
+    </head>
+    <body>
+        <style>
+        </style>
+
+
+        <div id="init-animation-additive"></div>
+        <div id="spiral-fixed-extent"></div>
+        <div id="spiral-dynamic-extent"></div>
+        <div id="texture-bar"></div>
+
+
+
+
+        <script>
+
+            require(['echarts'], function (echarts) {
+
+                var animationDuration = 5000;
+                var animationDurationUpdate = 4000;
+                var option = {
+                    xAxis: {},
+                    yAxis: {},
+                    dataZoom: [
+                        { type: 'slider' },
+                        { type: 'inside' }
+                    ],
+                    animationDuration: animationDuration,
+                    animationDurationUpdate: animationDurationUpdate,
+                    series: [{
+                        type: 'custom',
+                        renderItem: function (params, api) {
+                            return {
+                                type: 'group',
+                                position: api.coord([api.value(0), api.value(1)]),
+                                children: [{
+                                    type: 'rect',
+                                    shape: {
+                                        x: -50,
+                                        y: 50,
+                                        width: 100,
+                                        height: 150,
+                                        r: 10
+                                    },
+                                    style: {
+                                        fill: 'rgba(102,241,98,0.9)'
+                                    }
+                                }, {
+                                    type: 'circle',
+                                    shape: {
+                                        cx: -50,
+                                        cy: 50,
+                                        r: 30
+                                    },
+                                    style: {
+                                        fill: 'blue'
+                                    },
+                                    textContent: {
+                                        text: 'data',
+                                        style: {
+                                            fill: '#fff'
+                                        }
+                                    }
+                                }]
+                            };
+                        },
+                        data: [[121, 333], [29, 312]]
+                    }]
+                };
+
+                var chart = testHelper.create(echarts, 'init-animation-additive', {
+                    title: [
+                        'Style merge:',
+                        '(1) dataZoom hide a data item, and then show it, ensure the fade in animation normal.',
+                        '(2) click button to setOption merge.'
+                    ],
+                    option: option,
+                    info: {
+                        animationDuration: animationDuration,
+                        animationDurationUpdate: animationDurationUpdate
+                    },
+                    buttons: [{
+                        text: 'merge style: border become blue, but background not changed',
+                        onclick: function () {
+                            chart.setOption({
+                                type: 'custom',
+                                renderItem: function (params, api) {
+                                    return {
+                                        type: 'group',
+                                        children: [{
+                                            type: 'rect',
+                                            style: {
+                                                stroke: 'red',
+                                                lineWidth: 5
+                                            }
+                                        }]
+                                    };
+                                }
+                            });
+                        }
+                    }]
+                });
+            });
+
+        </script>
+
+
+
+
+
+
+
+        <script>
+            require([
+                'echarts'
+            ], function (echarts) {
+                var _animationDuration = 5000;
+                var _animationDurationUpdate = 7000;
+                var _animationEasingUpdate = 'elasticOut';
+                var _angleLabel = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo', 'Libra', 'Scorpius', 'Sagittarius', 'Capricornus', 'Aquarius', 'Pisces'];
+                var _valOnRoundAngle = _angleLabel.length;
+                var _valOnAngleStep = _valOnRoundAngle / 90;
+                var _barWidthValue = 0.4;
+                var _valOnRadiusStep = 4;
+
+                var _colors = [
+                    { border: 'green', inner: 'rgba(0,152,0,0.6)' },
+                    { border: 'red', inner: 'rgba(152,0,0,0.6)' },
+                    { border: 'blue', inner: 'rgba(0,0, 152,0.6)' },
+                ];
+                var _currentDataIndex = 0;
+                var _datasourceList = [
+                    [[3, 6, 9]],
+                    [[12, 16, 14]],
+                    [[17, 22, 19]],
+                ];
+                var _barValOnRadiusList = [1, 2, 3];
+
+                function getMaxRadius() {
+                    var radius = 0;
+                    for (var k = 0; k < _barValOnRadiusList.length; k++) {
+                        for (var i = 0; i < _datasourceList.length; i++) {
+                            var row = _datasourceList[i][0];
+                            for (var j = 0; j < row.length; j++) {
+                                var valOnAngle = row[j];
+                                radius = Math.max(
+                                    radius,
+                                    getSpiralValueOnRadius(_barValOnRadiusList[k], valOnAngle)
+                                );
+                            }
+                        }
+                    }
+                    return Math.ceil(radius * 1.2);
+                }
+
+                function getSpiralValueOnRadius(valOnRadius, valOnAngle) {
+                    return valOnRadius + _valOnRadiusStep * (valOnAngle / _valOnRoundAngle);
+                }
+
+                function renderItem(params, api) {
+                    var children = [];
+
+                    addShapes(api, children, _barValOnRadiusList[0], api.value(0), _colors[0]);
+                    addShapes(api, children, _barValOnRadiusList[1], api.value(1), _colors[1]);
+                    addShapes(api, children, _barValOnRadiusList[2], api.value(2), _colors[2]);
+
+                    return {
+                        type: 'group',
+                        children: children
+                    };
+                }
+
+                function addShapes(api, children, valOnRadius, valOnAngle, color) {
+                    addPolygon(api, children, valOnRadius, valOnAngle, color);
+                    addLabel(api, children, valOnRadius, valOnAngle, color);
+                }
+
+                function addPolygon(api, children, valOnRadius, valOnAngle, color) {
+                    children.push({
+                        type: 'polygon',
+                        shape: {
+                            points: makeShapePoints(api, valOnRadius, valOnAngle),
+                            valOnAngle: valOnAngle
+                        },
+                        style: {
+                            lineWidth: 1,
+                            fill: color.inner,
+                            stroke: color.border
+                        },
+                        during: function (elProps) {
+                            elProps.shape.points = makeShapePoints(
+                                api, valOnRadius, elProps.shape.valOnAngle
+                            );
+                        }
+                    });
+                }
+
+                function makeShapePoints(api, valOnRadius, valOnAngle) {
+                    var points = [];
+                    for (var iAngleVal = 0, end = valOnAngle + _valOnAngleStep; iAngleVal < end; iAngleVal += _valOnAngleStep) {
+                        iAngleVal > valOnAngle && (iAngleVal = valOnAngle);
+                        var iRadiusVal = getSpiralValueOnRadius(valOnRadius - _barWidthValue, iAngleVal);
+                        var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2);
+                        points.push(point);
+                    }
+                    for (var iAngleVal = valOnAngle; iAngleVal > -_valOnAngleStep; iAngleVal -= _valOnAngleStep) {
+                        iAngleVal < 0 && (iAngleVal = 0);
+                        var iRadiusVal = getSpiralValueOnRadius(valOnRadius + _barWidthValue, iAngleVal);
+                        var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2);
+                        points.push(point);
+                    }
+                    return points;
+                }
+
+                function addLabel(api, children, valOnRadius, valOnAngle, color) {
+                    var point = makeLabelPosition(api, valOnRadius, valOnAngle);
+                    children.push({
+                        type: 'text',
+                        x: point[0],
+                        y: point[1],
+                        shape: {
+                            valOnAngle: valOnAngle
+                        },
+                        style: {
+                            text: getText(valOnAngle),
+                            fill: color.inner,
+                            stroke: '#fff',
+                            lineWidth: 3,
+                            fontSize: 16,
+                            align: 'center',
+                            verticalAlign: 'middle'
+                        },
+                        z2: 50,
+                        during: function (elProps) {
+                            var iValOnAngle = elProps.shape.valOnAngle;
+                            var point = makeLabelPosition(api, valOnRadius, iValOnAngle);
+                            elProps.x = point[0];
+                            elProps.y = point[1];
+                            elProps.style.text = getText(iValOnAngle);
+                        }
+                    });
+
+                    function getText(iValOnAngle) {
+                        return (iValOnAngle / _valOnRoundAngle * 100).toFixed(0) + '%'
+                    }
+                }
+
+                function makeLabelPosition(api, valOnRadius, valOnAngle) {
+                    var iRadiusVal = getSpiralValueOnRadius(valOnRadius, valOnAngle);
+                    return api.coord([iRadiusVal, valOnAngle + 1 / iRadiusVal / (2 * Math.PI) * _valOnRoundAngle]);
+                }
+
+                var option = {
+                    // animation: false,
+                    animationDuration: _animationDuration,
+                    animationDurationUpdate: _animationDurationUpdate,
+                    animationEasingUpdate: _animationEasingUpdate,
+                    dataset: {
+                        source: _datasourceList[_currentDataIndex]
+                    },
+                    angleAxis: {
+                        type: 'value',
+                        // splitLine: { show: false },
+                        splitArea: {show: true},
+                        axisLabel: {
+                            formatter: function(val) {
+                                return _angleLabel[val];
+                            },
+                            color: 'rgba(0,0,0,0.2)'
+                        },
+                        axisLine: { lineStyle: { color: 'rgba(0,0,0,0.2)' } },
+                        min: 0,
+                        max: _valOnRoundAngle
+                    },
+                    radiusAxis: {
+                        type: 'value',
+                        splitLine: { show: false },
+                        axisLabel: { color: 'rgba(0,0,0,0.2)' },
+                        axisLine: { lineStyle: { color: 'rgba(0,0,0,0.2)' } },
+                        min: 0,
+                        max: getMaxRadius()
+                    },
+                    polar: {},
+                    series: [{
+                        type: 'custom',
+                        coordinateSystem: 'polar',
+                        renderItem: renderItem
+                    }]
+                };
+
+                var chart = testHelper.create(echarts, 'spiral-fixed-extent', {
+                    title: [
+                        'Spiral race with fixed radius extent.',
+                        'Click **next**, polygon animation should be corrent.',
+                    ],
+                    option: option,
+                    buttons: [{
+                        text: 'next',
+                        onclick: function () {
+                            _currentDataIndex++;
+                            _currentDataIndex >= _datasourceList.length && (_currentDataIndex = 0);
+                            chart.setOption({
+                                dataset: {
+                                    source: _datasourceList[_currentDataIndex]
+                                }
+                            });
+                        }
+                    }, {
+                        text: 'enable animation',
+                        onclick: function () {
+                            chart.setOption({ animation: true });
+                        }
+                    }, {
+                        text: 'disable animation',
+                        onclick: function () {
+                            chart.setOption({ animation: false });
+                        }
+                    }]
+                });
+            });
+        </script>
+
+
+
+
+
+
+
+
+
+        <script>
+            require([
+                'echarts'
+            ], function (echarts) {
+                var _animationDuration = 5000;
+                var _animationDurationUpdate = 7000;
+                // var _animationEasingUpdate = 'elasticOut';
+                var _animationEasingUpdate = 'quadraticOut';
+                var _radianLabels = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo', 'Libra', 'Scorpius', 'Sagittarius', 'Capricornus', 'Aquarius', 'Pisces'];
+                var _valOnRoundRadian = _radianLabels.length;
+                var _radianStep = Math.PI / 45;
+                var _barWidthValue = 0.4;
+                var _valOnRadiusStep = 4;
+                // angleAxis.startAngle is 90 by default.
+                var _startRadian = Math.PI / 2;
+
+                var _colors = [
+                    { border: 'green', inner: 'rgba(0,152,0,0.6)' },
+                    { border: 'red', inner: 'rgba(152,0,0,0.6)' },
+                    { border: 'blue', inner: 'rgba(0,0, 152,0.6)' },
+                ];
+                var _currentDataIndex = 0;
+                var _datasourceList = [
+                    [ [1, 3], [2, 6], [3, 9] ], // datasource 0
+                    [ [1, 12], [2, 16], [3, 14] ], // datasource 1
+                    [ [1, 17], [2, 22], [3, 19] ],  // datasource 2
+                    [ [1, 19], [2, 33], [3, 24] ],
+                    [ [1, 24], [2, 42], [3, 29] ],
+                    [ [1, 27], [2, 47], [3, 41] ],
+                    [ [1, 36], [2, 52], [3, 52] ],
+                    [ [1, 46], [2, 59], [3, 63] ],
+                    [ [1, 60], [2, 63], [3, 69] ],
+                ];
+                var _barNamesByOrdinal = {1: 'A', 2: 'B', 3: 'C'};
+
+                function getMaxRadius() {
+                    var radius = 0;
+                    var datasource = _datasourceList[_currentDataIndex];
+                    for (var j = 0; j < datasource.length; j++) {
+                        var dataItem = datasource[j];
+                        radius = Math.max(radius, getSpiralValueOnRadius(dataItem[0], dataItem[1]));
+                    }
+                    return Math.ceil(radius * 1.2);
+                }
+
+                function getSpiralValueOnRadius(valOnStartRadius, valOnEndAngle) {
+                    return valOnStartRadius + _valOnRadiusStep * (valOnEndAngle / _valOnRoundRadian);
+                }
+                function getSpiralRadius(startRadius, endRadian, radiusStep) {
+                    return startRadius + radiusStep * ((_startRadian - endRadian) / (Math.PI * 2));
+                }
+
+                function renderItem(params, api) {
+                    var children = [];
+                    var dataIdx = params.dataIndex;
+                    addShapes(params, api, children, api.value(0), api.value(1), _colors[dataIdx]);
+
+                    return {
+                        type: 'group',
+                        children: children
+                    };
+                }
+
+                function addShapes(params, api, children, valOnStartRadius, valOnEndRadian, color) {
+                    var coords = api.coord([valOnStartRadius, valOnEndRadian]);
+                    var startRadius = coords[2];
+                    var endRadian = coords[3];
+                    var widthRadius = api.coord([_barWidthValue, 0])[2];
+                    addPolygon(params, children, widthRadius, startRadius, endRadian, color);
+                    addLabel(params, children, widthRadius, startRadius, endRadian, color);
+                }
+
+                function addPolygon(params, children, widthRadius, startRadius, endRadian, color) {
+                    children.push({
+                        type: 'polygon',
+                        shape: {
+                            points: makeShapePoints(params, widthRadius, startRadius, endRadian),
+                            widthRadius: widthRadius,
+                            startRadius: startRadius,
+                            endRadian: endRadian
+                        },
+                        style: {
+                            lineWidth: 1,
+                            fill: color.inner,
+                            stroke: color.border
+                        },
+                        during: function (elProps) {
+                            var shp = elProps.shape;
+                            shp.points = makeShapePoints(params, shp.widthRadius, shp.startRadius, shp.endRadian);
+                        }
+                    });
+                }
+
+                function makeShapePoints(params, widthRadius, startRadius, endRadian) {
+                    var points = [];
+                    var radiusStep = getRadiusStepByWidth(widthRadius);
+                    // angleAxis.clockwise is true by default. So when rotate clickwisely, radian decreases.
+                    for (
+                        var iRadian = _startRadian, end = endRadian - _radianStep;
+                        iRadian > end;
+                        iRadian -= _radianStep
+                    ) {
+                        iRadian < endRadian && (iRadian = endRadian);
+                        var iRadius = getSpiralRadius(startRadius - widthRadius, iRadian, radiusStep);
+                        points.push(convertToPolarPoint(params, iRadius, iRadian));
+                    }
+                    for (
+                        var iRadian = endRadian;
+                        iRadian < _startRadian + _radianStep;
+                        iRadian += _radianStep
+                    ) {
+                        iRadian > _startRadian && (iRadian = _startRadian);
+                        var iRadius = getSpiralRadius(startRadius + widthRadius, iRadian, radiusStep);
+                        points.push(convertToPolarPoint(params, iRadius, iRadian));
+                    }
+                    return points;
+                }
+
+                function getRadiusStepByWidth(widthRadius) {
+                    return widthRadius / _barWidthValue * _valOnRadiusStep;
+                }
+
+                function addLabel(params, children, widthRadius, startRadius, endRadian, color) {
+                    var point = makeLabelPosition(params, widthRadius, startRadius, endRadian);
+                    children.push({
+                        type: 'text',
+                        x: point[0],
+                        y: point[1],
+                        shape: {
+                            startRadius: startRadius,
+                            endRadian: endRadian,
+                            widthRadius: widthRadius
+                        },
+                        style: {
+                            text: makeText(endRadian),
+                            fill: color.inner,
+                            stroke: '#fff',
+                            lineWidth: 3,
+                            fontSize: 12,
+                            align: 'center',
+                            verticalAlign: 'middle',
+                            rich: {
+                                round: { fontSize: 16 },
+                                percent: { fontSize: 14 }
+                            }
+                        },
+                        z2: 50,
+                        during: function (elProps) {
+                            var shp = elProps.shape;
+                            var point = makeLabelPosition(params, shp.widthRadius, shp.startRadius, shp.endRadian);
+                            elProps.x = point[0];
+                            elProps.y = point[1];
+                            elProps.style.text = makeText(shp.endRadian);
+                        }
+                    });
+
+                    function makeText(endRadian) {
+                        var radian = _startRadian - endRadian;
+                        var PI2 = Math.PI * 2;
+                        var round = Math.floor(radian / PI2);
+                        var percent = (((radian / PI2) % 1) * 100).toFixed(1) + '%';
+                        return 'Round {round|' + round + '}\n{percent|' + percent + '}';
+                    }
+                }
+
+                function makeLabelPosition(params, widthRadius, startRadius, endRadian) {
+                    var radiusStep = getRadiusStepByWidth(widthRadius);
+                    var iRadius = getSpiralRadius(startRadius, endRadian, radiusStep);
+                    return convertToPolarPoint(params, iRadius, endRadian - 10 / iRadius);
+                }
+
+                function convertToPolarPoint(renderItemParams, radius, radian) {
+                    return [
+                        Math.cos(radian) * radius + renderItemParams.coordSys.cx,
+                        -Math.sin(radian) * radius + renderItemParams.coordSys.cy
+                    ];
+                }
+
+                var option = {
+                    animationDuration: _animationDuration,
+                    animationDurationUpdate: _animationDurationUpdate,
+                    animationEasingUpdate: _animationEasingUpdate,
+                    dataset: {
+                        source: _datasourceList[_currentDataIndex]
+                    },
+                    tooltip: {},
+                    angleAxis: {
+                        type: 'value',
+                        splitArea: { show: true },
+                        axisLabel: {
+                            formatter: function(val) {
+                                return _radianLabels[val];
+                            },
+                            color: 'rgba(0,0,0,0.2)'
+                        },
+                        axisLine: { lineStyle: { color: 'rgba(0,0,0,0.2)' } },
+                        min: 0,
+                        max: _valOnRoundRadian
+                    },
+                    radiusAxis: {
+                        type: 'value',
+                        interval: 1,
+                        splitLine: { show: false },
+                        axisLabel: {
+                            color: 'rgba(0,0,0,0.6)',
+                            formatter: function (value) {
+                                return _barNamesByOrdinal[value] || '';
+                            }
+                        },
+                        axisTick: { show: false },
+                        axisLine: { lineStyle: { color: 'rgba(0,0,0,0.2)' } },
+                        min: 0,
+                        max: getMaxRadius()
+                    },
+                    polar: {},
+                    series: [{
+                        type: 'custom',
+                        coordinateSystem: 'polar',
+                        renderItem: renderItem
+                    }]
+                };
+
+                var chart = testHelper.create(echarts, 'spiral-dynamic-extent', {
+                    title: [
+                        'Spiral race with dynamic radius extent.',
+                        'Click **next**. Polygon animation should be corrent.',
+                    ],
+                    option: option,
+                    buttons: [{
+                        text: 'next',
+                        onclick: function () {
+                            _currentDataIndex++;
+                            _currentDataIndex >= _datasourceList.length && (_currentDataIndex = 0);
+                            chart.setOption({
+                                dataset: {
+                                    source: _datasourceList[_currentDataIndex]
+                                },
+                                radiusAxis: {
+                                    max: getMaxRadius()
+                                }
+                            });
+                        }
+                    }, {
+                        text: 'enable animation',
+                        onclick: function () {
+                            chart.setOption({ animation: true });
+                        }
+                    }, {
+                        text: 'disable animation',
+                        onclick: function () {
+                            chart.setOption({ animation: false });
+                        }
+                    }]
+                });
+            });
+        </script>
+
+
+
+
+
+
+
+
+    </body>
+</html>
\ No newline at end of file


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


[incubator-echarts] 10/10: feature: support clipPath and clipPath animation in custom series.

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

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

commit 1dffebc9bf846f8ea0d75fe97407e8d12a261eab
Author: 100pah <su...@gmail.com>
AuthorDate: Thu May 28 17:26:56 2020 +0800

    feature: support clipPath and clipPath animation in custom series.
---
 src/chart/custom.ts         | 124 +++++++++++++++--------
 test/custom-transition.html | 241 +++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 323 insertions(+), 42 deletions(-)

diff --git a/src/chart/custom.ts b/src/chart/custom.ts
index 7af3ec0..2bb85c9 100644
--- a/src/chart/custom.ts
+++ b/src/chart/custom.ts
@@ -105,6 +105,8 @@ interface CustomBaseElementOption extends Partial<Pick<
     info?: CustomExtraElementInfo;
     // `false` means remove the textContent.
     textContent?: CustomTextOption | false;
+    // `false` means remove the clipPath
+    clipPath?: CustomZRPathOption | false;
     // updateDuringAnimation
     during?(elProps: CustomDuringElProps): void;
 };
@@ -1180,7 +1182,7 @@ function createOrUpdate(
     group: ViewRootGroup,
     data: List<CustomSeriesModel>
 ): Element {
-    el = doCreateOrUpdate(el, dataIndex, elOption, seriesModel, group, data, true);
+    el = doCreateOrUpdate(el, dataIndex, elOption, seriesModel, group, true);
     el && data.setItemGraphicEl(dataIndex, el);
 
     return el;
@@ -1192,7 +1194,6 @@ function doCreateOrUpdate(
     elOption: CustomElementOption,
     seriesModel: CustomSeriesModel,
     group: ViewRootGroup,
-    data: List<CustomSeriesModel>,
     isRoot: boolean
 ): Element {
 
@@ -1213,37 +1214,12 @@ function doCreateOrUpdate(
     }
 
     elOption = elOption || {} as CustomElementOption;
-    const elOptionType = elOption.type;
-    const elOptionShape = (elOption as CustomZRPathOption).shape;
-    const elOptionStyle = elOption.style;
     let toBeReplacedIdx = -1;
 
-    if (el) {
-        const elInner = inner(el);
-        if (
-            // || elOption.$merge === false
-            // If `elOptionType` is `null`, follow the merge principle.
-            (elOptionType != null
-                && elOptionType !== elInner.customGraphicType
-            )
-            || (elOptionType === 'path'
-                && hasOwnPathData(elOptionShape)
-                && getPathData(elOptionShape) !== elInner.customPathData
-            )
-            || (elOptionType === 'image'
-                && zrUtil.hasOwn(elOptionStyle, 'image')
-                && (elOptionStyle as CustomImageOption['style']).image !== elInner.customImagePath
-            )
-            // // FIXME test and remove this restriction?
-            // || (elOptionType === 'text'
-            //     && zrUtil.hasOwn(elOptionStyle, 'text')
-            //     && (elOptionStyle as TextStyleProps).text !== elInner.customText
-            // )
-        ) {
-            // Should keep at the original index, otherwise "merge by index" will be incorrect.
-            toBeReplacedIdx = group.childrenRef().indexOf(el);
-            el = null;
-        }
+    if (el && doesElNeedRecreate(el, elOption)) {
+        // Should keep at the original index, otherwise "merge by index" will be incorrect.
+        toBeReplacedIdx = group.childrenRef().indexOf(el);
+        el = null;
     }
 
     const isInit = !el;
@@ -1252,6 +1228,7 @@ function doCreateOrUpdate(
         el = createEl(elOption);
     }
     else {
+        // FIMXE:NEXT unified clearState?
         // If in some case the performance issue arised, consider
         // do not clearState but update cached normal state directly.
         el.clearStates();
@@ -1265,6 +1242,10 @@ function doCreateOrUpdate(
         el, dataIndex, elOption, seriesModel, isInit, attachedTxInfoTmp
     );
 
+    doCreateOrUpdateClipPath(
+        el, dataIndex, elOption, seriesModel, isInit
+    );
+
     const stateOptEmphasis = retrieveStateOption(elOption, EMPHASIS);
     const styleOptEmphasis = retrieveStyleOptionOnState(elOption, stateOptEmphasis, EMPHASIS);
 
@@ -1273,9 +1254,9 @@ function doCreateOrUpdate(
 
     updateZ(el, elOption, seriesModel, attachedTxInfoTmp);
 
-    if (elOptionType === 'group') {
+    if (elOption.type === 'group') {
         mergeChildren(
-            el as graphicUtil.Group, dataIndex, elOption as CustomGroupOption, seriesModel, data
+            el as graphicUtil.Group, dataIndex, elOption as CustomGroupOption, seriesModel
         );
     }
 
@@ -1289,6 +1270,72 @@ function doCreateOrUpdate(
     return el;
 }
 
+// `el` must not be null/undefined.
+function doesElNeedRecreate(el: Element, elOption: CustomElementOption): boolean {
+    const elInner = inner(el);
+    const elOptionType = elOption.type;
+    const elOptionShape = (elOption as CustomZRPathOption).shape;
+    const elOptionStyle = elOption.style;
+    return (
+        // || elOption.$merge === false
+        // If `elOptionType` is `null`, follow the merge principle.
+        (elOptionType != null
+            && elOptionType !== elInner.customGraphicType
+        )
+        || (elOptionType === 'path'
+            && hasOwnPathData(elOptionShape)
+            && getPathData(elOptionShape) !== elInner.customPathData
+        )
+        || (elOptionType === 'image'
+            && zrUtil.hasOwn(elOptionStyle, 'image')
+            && (elOptionStyle as CustomImageOption['style']).image !== elInner.customImagePath
+        )
+        // // FIXME test and remove this restriction?
+        // || (elOptionType === 'text'
+        //     && zrUtil.hasOwn(elOptionStyle, 'text')
+        //     && (elOptionStyle as TextStyleProps).text !== elInner.customText
+        // )
+    );
+}
+
+function doCreateOrUpdateClipPath(
+    el: Element,
+    dataIndex: number,
+    elOption: CustomElementOption,
+    seriesModel: CustomSeriesModel,
+    isInit: boolean
+): void {
+    // Based on the "merge" principle, if no clipPath provided,
+    // do nothing. The exists clip will be totally removed only if
+    // `el.clipPath` is `false`. Otherwise it will be merged/replaced.
+    const clipPathOpt = elOption.clipPath;
+    if (clipPathOpt === false) {
+        if (el && el.getClipPath()) {
+            el.removeClipPath();
+        }
+    }
+    else if (clipPathOpt) {
+        let clipPath = el.getClipPath();
+        if (clipPath && doesElNeedRecreate(clipPath, clipPathOpt)) {
+            clipPath = null;
+        }
+        if (!clipPath) {
+            clipPath = createEl(clipPathOpt) as graphicUtil.Path;
+            if (__DEV__) {
+                zrUtil.assert(
+                    clipPath instanceof graphicUtil.Path,
+                    'Only any type of `path` can be used in `clipPath`, rather than ' + clipPath.type + '.'
+                );
+            }
+            el.setClipPath(clipPath);
+        }
+        updateElNormal(
+            clipPath, dataIndex, clipPathOpt, null, null, seriesModel, isInit, false
+        );
+    }
+    // If not define `clipPath` in option, do nothing unnecessary.
+}
+
 function doCreateOrUpdateAttachedTx(
     el: Element,
     dataIndex: number,
@@ -1440,8 +1487,7 @@ function mergeChildren(
     el: graphicUtil.Group,
     dataIndex: number,
     elOption: CustomGroupOption,
-    seriesModel: CustomSeriesModel,
-    data: List<CustomSeriesModel>
+    seriesModel: CustomSeriesModel
 ): void {
 
     const newChildren = elOption.children;
@@ -1462,8 +1508,7 @@ function mergeChildren(
             newChildren: newChildren || [],
             dataIndex: dataIndex,
             seriesModel: seriesModel,
-            group: el,
-            data: data
+            group: el
         });
         return;
     }
@@ -1480,7 +1525,6 @@ function mergeChildren(
             newChildren[index],
             seriesModel,
             el,
-            data,
             false
         );
     }
@@ -1497,8 +1541,7 @@ type DiffGroupContext = {
     newChildren: CustomElementOption[],
     dataIndex: number,
     seriesModel: CustomSeriesModel,
-    group: graphicUtil.Group,
-    data: List<CustomSeriesModel>
+    group: graphicUtil.Group
 };
 function diffGroupChildren(context: DiffGroupContext) {
     (new DataDiffer(
@@ -1534,7 +1577,6 @@ function processAddUpdate(
         childOption,
         context.seriesModel,
         context.group,
-        context.data,
         false
     );
 }
diff --git a/test/custom-transition.html b/test/custom-transition.html
index 77a6aab..2d8c718 100644
--- a/test/custom-transition.html
+++ b/test/custom-transition.html
@@ -28,6 +28,7 @@ under the License.
         <script src="lib/jquery.min.js"></script>
         <script src="lib/facePrint.js"></script>
         <script src="lib/testHelper.js"></script>
+        <script src="./custom-transition-texture.js"></script>
         <link rel="stylesheet" href="lib/reset.css" />
     </head>
     <body>
@@ -38,7 +39,8 @@ under the License.
         <div id="init-animation-additive"></div>
         <div id="spiral-fixed-extent"></div>
         <div id="spiral-dynamic-extent"></div>
-        <div id="texture-bar"></div>
+        <div id="texture-bar-by-clipPath"></div>
+        <!-- <div id="texture-bar-texture-maker"></div> -->
 
 
 
@@ -623,5 +625,242 @@ under the License.
 
 
 
+
+
+
+
+
+        <script>
+            require(['echarts'], function (echarts) {
+                var _animationDuration = 1000;
+                var _animationDurationUpdate = 1000;
+                var _animationEasingUpdate = 'elasticOut';
+                var _datasourceList = [
+                    [[1, 156]],
+                    [[1, 54]],
+                    [[1, 131]],
+                    [[1, 32]],
+                    [[1, 103]],
+                    [[1, 66]],
+                ];
+                var _valOnRadianMax = 200;
+                var _outerRadius = 100;
+                var _innerRadius = 85;
+                var _pointerInnerRadius = 40;
+                var _insidePanelRadius = 65;
+                var _currentDataIndex = 0;
+
+                function renderItem(params, api) {
+                    var children = [];
+                    var dataIdx = params.dataIndex;
+                    var valOnRadian = api.value(1);
+                    var coords = api.coord([api.value(0), valOnRadian]);
+                    var polarEndRadian = coords[3];
+                    var imageStyle = {
+                        image: window.BAR_ROUND_GRADIENT_TEXTURE,
+                        x: params.coordSys.cx - _outerRadius,
+                        y: params.coordSys.cy - _outerRadius,
+                        width: _outerRadius * 2,
+                        height: _outerRadius * 2
+                    };
+
+                    return {
+                        type: 'group',
+                        children: [{
+                            type: 'image',
+                            style: imageStyle,
+                            clipPath: {
+                                type: 'sector',
+                                shape: {
+                                    cx: params.coordSys.cx,
+                                    cy: params.coordSys.cy,
+                                    r: _outerRadius,
+                                    r0: _innerRadius,
+                                    startAngle: 0,
+                                    // polor: anticlockwise-positive radian
+                                    // sector: clockwise-positive radian
+                                    endAngle: -polarEndRadian
+                                },
+                            }
+                        }, {
+                            type: 'image',
+                            style: imageStyle,
+                            clipPath: {
+                                type: 'polygon',
+                                shape: {
+                                    points: makePionterPoints(params, polarEndRadian),
+                                    polarEndRadian: polarEndRadian
+                                },
+                                during: function (elProps) {
+                                    elProps.shape.points = makePionterPoints(params, elProps.shape.polarEndRadian);
+                                }
+                            },
+                        }, {
+                            type: 'circle',
+                            shape: {
+                                cx: params.coordSys.cx,
+                                cy: params.coordSys.cy,
+                                r: _insidePanelRadius
+                            },
+                            style: {
+                                fill: '#fff',
+                                shadowBlur: 25,
+                                shadowOffsetX: 0,
+                                shadowOffsetY: 0,
+                                shadowColor: 'rgb(0,0,50)'
+                            }
+                        }, {
+                            type: 'text',
+                            shape: {
+                                valOnRadian: valOnRadian
+                            },
+                            style: {
+                                text: makeText(valOnRadian),
+                                fontSize: 40,
+                                x: params.coordSys.cx,
+                                y: params.coordSys.cy,
+                                fill: 'rgb(0,50,190)',
+                                align: 'center',
+                                verticalAlign: 'middle',
+                            },
+                            during: function (elProps) {
+                                elProps.style.text = makeText(elProps.shape.valOnRadian);
+                            }
+                        }]
+                    };
+                }
+
+                function convertToPolarPoint(renderItemParams, radius, radian) {
+                    return [
+                        Math.cos(radian) * radius + renderItemParams.coordSys.cx,
+                        -Math.sin(radian) * radius + renderItemParams.coordSys.cy
+                    ];
+                }
+
+                function makePionterPoints(renderItemParams, polarEndRadian) {
+                    return [
+                        convertToPolarPoint(renderItemParams, _outerRadius, polarEndRadian),
+                        convertToPolarPoint(renderItemParams, _outerRadius, polarEndRadian + Math.PI * 0.03),
+                        convertToPolarPoint(renderItemParams, _pointerInnerRadius, polarEndRadian)
+                    ];
+                }
+
+                function makeText(valOnRadian) {
+                    return (valOnRadian / _valOnRadianMax * 100).toFixed(0) + '%'
+                }
+
+                var option = {
+                    animationDuration: _animationDuration,
+                    animationDurationUpdate: _animationDurationUpdate,
+                    animationEasingUpdate: _animationEasingUpdate,
+                    dataset: {
+                        source: _datasourceList[_currentDataIndex]
+                    },
+                    tooltip: {},
+                    angleAxis: {
+                        type: 'value',
+                        startAngle: 0,
+                        axisLine: { show: false },
+                        axisTick: { show: false },
+                        axisLabel: { show: false },
+                        splitLine: { show: false },
+                        min: 0,
+                        max: _valOnRadianMax
+                    },
+                    radiusAxis: {
+                        type: 'value',
+                        axisLine: { show: false },
+                        axisTick: { show: false },
+                        axisLabel: { show: false },
+                        splitLine: { show: false }
+                    },
+                    polar: {},
+                    series: [{
+                        type: 'custom',
+                        coordinateSystem: 'polar',
+                        renderItem: renderItem
+                    }]
+                };
+
+                var chart = testHelper.create(echarts, 'texture-bar-by-clipPath', {
+                    title: [
+                        'Angle gradient | clipPath animation',
+                        'click **next** to check the transition animation in both bar and text.'
+                    ],
+                    option: option,
+                    height: 300,
+                    buttons: [{
+                        text: 'next',
+                        onclick: function () {
+                            _currentDataIndex++;
+                            _currentDataIndex >= _datasourceList.length && (_currentDataIndex = 0);
+                            chart.setOption({
+                                dataset: {
+                                    source: _datasourceList[_currentDataIndex]
+                                }
+                            });
+                        }
+                    }]
+                });
+            });
+        </script>
+
+
+        <script>
+            require(['echarts'], function (echarts) {
+                var chart = testHelper.create(echarts, 'texture-bar-texture-maker', {
+                    title: [],
+                    width: 200,
+                    height: 200,
+                    option: {},
+                    buttons: [{
+                        text: 'dataURL',
+                        onclick: function () {
+                            console.log(chart.getDataURL({
+                                type: 'png',
+                                backgroundColor: 'rgba(0,0,0,0)'
+                            }));
+                        }
+                    }]
+                });
+                if (!chart) {
+                    return;
+                }
+
+                var zr = chart.getZr();
+                var eles = [];
+                var extent = [0.0, 0.95];
+                var count = 200;
+                var unit = (extent[1] - extent[0]) / count;
+                var baseColor = 'rgb(0,0,255)';
+                for (var i = 0; i < count; i++) {
+                    var oo = extent[0] + (count - i) * unit;
+                    var color = echarts.color.modifyHSL(baseColor, null, null, oo);
+                    var startAngle = 2 * Math.PI / count * i;
+                    var endAngle = Math.min((2 * Math.PI / count * (i + 1) + 0.05), Math.PI * 2);
+                    zr.add(new echarts.graphic.Sector({
+                        type: 'sector',
+                        shape: {
+                            cx: 100,
+                            cy: 100,
+                            r: 100,
+                            r0: 60,
+                            startAngle: startAngle,
+                            endAngle: endAngle
+                        },
+                        style: {
+                            fill: color
+                        }
+                    }));
+                }
+            });
+        </script>
+
+
+
+
+
+
+
     </body>
 </html>
\ No newline at end of file


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


[incubator-echarts] 06/10: feature: support text animation on custom series (via in `during`)

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

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

commit 13093174904971bcacb8ab2bda079df079b0195d
Author: 100pah <su...@gmail.com>
AuthorDate: Tue May 19 04:45:26 2020 +0800

    feature: support text animation on custom series (via in `during`)
---
 src/chart/custom.ts      |  88 ++++++++++++++--------
 test/custom-feature.html | 190 +++++++++++++++++++++++++++++------------------
 2 files changed, 174 insertions(+), 104 deletions(-)

diff --git a/src/chart/custom.ts b/src/chart/custom.ts
index a58633d..cfb9587 100644
--- a/src/chart/custom.ts
+++ b/src/chart/custom.ts
@@ -82,7 +82,7 @@ const inner = makeInner<{
     customPathData: string;
     customGraphicType: string;
     customImagePath: CustomImageOption['style']['image'];
-    customText: string;
+    // customText: string;
     txConZ2Set: number;
     orginalDuring: Element['updateDuringAnimation'];
     customDuring: CustomZRPathOption['during'];
@@ -126,6 +126,7 @@ interface CustomGroupOption extends CustomBaseElementOption {
     type: 'group';
     width?: number;
     height?: number;
+    // @deprecated
     diffChildrenByName?: boolean;
     children: CustomElementOption[];
     $mergeChildren: false | 'byName' | 'byIndex';
@@ -134,6 +135,7 @@ interface CustomZRPathOption extends CustomDisplayableOption, Pick<PathProps, 's
 }
 interface CustomDuringElProps extends Partial<Pick<Element, TransformProps>> {
     shape?: PathProps['shape'];
+    style?: { text: string };
 }
 interface CustomSVGPathOption extends CustomDisplayableOption {
     type: 'path';
@@ -284,8 +286,7 @@ const Z2_SPECIFIED_BIT = {
     emphasis: 1
 } as const;
 
-const tmpDuringElProps = {} as CustomDuringElProps;
-
+const tmpDuringElProps = { style: {} } as CustomDuringElProps;
 
 export type PrepareCustomInfo = (coordSys: CoordinateSystem) => {
     coordSys: CustomSeriesRenderItemParamsCoordSys;
@@ -507,7 +508,7 @@ function createEl(elOption: CustomElementOption): Element {
     }
     else if (graphicType === 'text') {
         el = new graphicUtil.Text({});
-        inner(el).customText = (elOption.style as TextStyleProps).text;
+        // inner(el).customText = (elOption.style as TextStyleProps).text;
     }
     else if (graphicType === 'group') {
         el = new graphicUtil.Group();
@@ -667,39 +668,58 @@ function updateElNormal(
         zrUtil.hasOwn(elOption, 'info') && (inner(el).info = elOption.info);
     }
 
-    el.markRedraw();
+    styleOpt ? el.dirty() : el.markRedraw();
 }
 
-function elUpdateDuringAnimation(this: graphicUtil.Path, key: string): void {
+function elUpdateDuringAnimation(this: Element, key: string): void {
     const innerEl = inner(this);
     // FIXME `this.markRedraw();` directly ?
     innerEl.orginalDuring.call(this, key);
     const customDuring = innerEl.customDuring;
+    const thisPath = this as graphicUtil.Path;
+    const thisText = this as graphicUtil.Text;
+    let dirtyStyle = false;
 
     // Only provide these props. Usually other props do not need to be
     // changed in animation during.
     // Do not give `this` to user util really needed in future.
     // Props in `shape` can be modified directly in the during callback.
-    tmpDuringElProps.shape = this.shape;
-    tmpDuringElProps.x = this.x;
-    tmpDuringElProps.y = this.y;
-    tmpDuringElProps.scaleX = this.scaleX;
-    tmpDuringElProps.scaleX = this.scaleY;
-    tmpDuringElProps.originX = this.originX;
-    tmpDuringElProps.originY = this.originY;
-    tmpDuringElProps.rotation = this.rotation;
+    const shapeCurr = tmpDuringElProps.shape = thisPath.shape;
+    const xCurr = tmpDuringElProps.x = this.x;
+    const yCurr = tmpDuringElProps.y = this.y;
+    const scaleXCurr = tmpDuringElProps.scaleX = this.scaleX;
+    const scaleYCurr = tmpDuringElProps.scaleY = this.scaleY;
+    const originXCurr = tmpDuringElProps.originX = this.originX;
+    const originYCurr = tmpDuringElProps.originY = this.originY;
+    const rotationCurr = tmpDuringElProps.rotation = this.rotation;
+
+    // PENDING:
+    // Do not expose other style in case that is not stable.
+    const isText = this.type === 'text';
+    const textCurr = tmpDuringElProps.style.text = isText ? thisText.style.text : null;
 
     customDuring(tmpDuringElProps);
 
-    tmpDuringElProps.shape !== this.shape && (this.shape = tmpDuringElProps.shape);
+    tmpDuringElProps.shape !== shapeCurr && (thisPath.shape = tmpDuringElProps.shape);
     // Consider prop on prototype.
-    tmpDuringElProps.x !== this.x && (this.x = tmpDuringElProps.x);
-    tmpDuringElProps.y !== this.y && (this.y = tmpDuringElProps.y);
-    tmpDuringElProps.scaleX !== this.scaleX && (this.scaleX = tmpDuringElProps.scaleX);
-    tmpDuringElProps.scaleY !== this.scaleY && (this.scaleY = tmpDuringElProps.scaleY);
-    tmpDuringElProps.originX !== this.originX && (this.originX = tmpDuringElProps.originX);
-    tmpDuringElProps.originY !== this.originY && (this.originY = tmpDuringElProps.originY);
-    tmpDuringElProps.rotation !== this.rotation && (this.rotation = tmpDuringElProps.rotation);
+    tmpDuringElProps.x !== xCurr && (this.x = tmpDuringElProps.x);
+    tmpDuringElProps.y !== yCurr && (this.y = tmpDuringElProps.y);
+    tmpDuringElProps.scaleX !== scaleXCurr && (this.scaleX = tmpDuringElProps.scaleX);
+    tmpDuringElProps.scaleY !== scaleYCurr && (this.scaleY = tmpDuringElProps.scaleY);
+    tmpDuringElProps.originX !== originXCurr && (this.originX = tmpDuringElProps.originX);
+    tmpDuringElProps.originY !== originYCurr && (this.originY = tmpDuringElProps.originY);
+    tmpDuringElProps.rotation !== rotationCurr && (this.rotation = tmpDuringElProps.rotation);
+
+    if (isText) {
+        const currTmpStl = tmpDuringElProps.style;
+        currTmpStl && currTmpStl.text !== textCurr && (thisText.style.text = currTmpStl.text, dirtyStyle = true);
+    }
+
+    dirtyStyle && this.dirty();
+    // markRedraw() will be called by default.
+
+    // FIXME: if in future meet the case that some prop will be both modified in `during` and `state`,
+    // consider the issue that the prop might be incorrect when return to "normal" state.
 }
 
 function updateElOnState(
@@ -1193,6 +1213,7 @@ function doCreateOrUpdate(
     const elOptionType = elOption.type;
     const elOptionShape = (elOption as CustomZRPathOption).shape;
     const elOptionStyle = elOption.style;
+    let toBeReplacedIdx = -1;
 
     if (el) {
         const elInner = inner(el);
@@ -1210,13 +1231,14 @@ function doCreateOrUpdate(
                 && zrUtil.hasOwn(elOptionStyle, 'image')
                 && (elOptionStyle as CustomImageOption['style']).image !== elInner.customImagePath
             )
-            // FIXME test and remove this restriction?
-            || (elOptionType === 'text'
-                && zrUtil.hasOwn(elOptionStyle, 'text')
-                && (elOptionStyle as TextStyleProps).text !== elInner.customText
-            )
+            // // FIXME test and remove this restriction?
+            // || (elOptionType === 'text'
+            //     && zrUtil.hasOwn(elOptionStyle, 'text')
+            //     && (elOptionStyle as TextStyleProps).text !== elInner.customText
+            // )
         ) {
-            group.remove(el);
+            // Should keep at the original index, otherwise "merge by index" will be incorrect.
+            toBeReplacedIdx = group.childrenRef().indexOf(el);
             el = null;
         }
     }
@@ -1254,8 +1276,12 @@ function doCreateOrUpdate(
         );
     }
 
-    // Always add whatever already added to ensure sequence.
-    group.add(el);
+    if (toBeReplacedIdx >= 0) {
+        group.replaceAt(el, toBeReplacedIdx);
+    }
+    else {
+        group.add(el);
+    }
 
     return el;
 }
@@ -1316,7 +1342,7 @@ function doCreateOrUpdateAttachedTx(
             const txConStlOptEmphasis = retrieveStyleOptionOnState(txConOptNormal, txConOptEmphasis, EMPHASIS);
             updateElOnState(EMPHASIS, textContent, txConOptEmphasis, txConStlOptEmphasis, null, false, true);
 
-            textContent.markRedraw();
+            txConStlOptNormal ? textContent.dirty() : textContent.markRedraw();
         }
     }
 }
diff --git a/test/custom-feature.html b/test/custom-feature.html
index 56e106e..dabf8b1 100644
--- a/test/custom-feature.html
+++ b/test/custom-feature.html
@@ -636,7 +636,7 @@ under the License.
                 'echarts'/*, 'map/js/china' */
             ], function (echarts) {
                 var animationDuration = 5000;
-                var animationDurationUpdate = 4000;
+                var animationDurationUpdate = 7000;
                 var animationEasingUpdate = 'elasticOut';
                 var angleLabel = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo', 'Libra', 'Scorpius', 'Sagittarius', 'Capricornus', 'Aquarius', 'Pisces'];
                 var angleRoundValue = angleLabel.length;
@@ -644,85 +644,148 @@ under the License.
                 var angleStep = angleRoundValue / 90;
                 var barWidthValue = 0.4;
                 var radiusStep = 4;
-                var colors = {
-                    'A': { stroke: 'green', fill: 'rgba(0,152,0,0.6)' },
-                    'B': { stroke: 'red', fill: 'rgba(152,0,0,0.6)' },
-                    'C': { stroke: 'blue', fill: 'rgba(0,0, 152,0.6)' },
-                };
-                var allData = [[
-                    [[1, 3, 'A']],
-                    [[2, 6, 'B']],
-                    [[3, 9, 'C']],
-                ], [
-                    [[1, 12, 'A']],
-                    [[2, 16, 'B']],
-                    [[3, 14, 'C']],
-                ], [
-                    [[1, 17, 'A']],
-                    [[2, 22, 'B']],
-                    [[3, 19, 'C']],
-                ]];
-                var currentDataIndex = 0;
 
+                var colors = [
+                    { border: 'green', inner: 'rgba(0,152,0,0.6)' },
+                    { border: 'red', inner: 'rgba(152,0,0,0.6)' },
+                    { border: 'blue', inner: 'rgba(0,0, 152,0.6)' },
+                ];
+                var currentDataIndex = 0;
+                var datasourceList = [
+                    [[3, 6, 9]],
+                    [[12, 16, 14]],
+                    [[17, 22, 19]],
+                ];
+                var barValOnRadiusList = [1, 2, 3];
+
+                // PENDING:
+                // The radius max should be fixed rather than change dynamically.
+                // If need to support dynamic coord sys while animation:
+                // (A) The `api.coord` should be able to accept a customized extent and
+                // return value on the middle state.
+                // or (B) Use "data interpolate".
                 function getMaxRadius() {
                     var radius = 0;
-                    for (var j = 0; j < allData.length; j++) {
-                        var data = allData[j];
-                        for (var i = 0; i < data.length; i++) {
-                            radius = Math.max(radius, getSpiralValueRadius(data[i][0][0], data[i][0][1]));
+                    for (var k = 0; k < barValOnRadiusList.length; k++) {
+                        for (var i = 0; i < datasourceList.length; i++) {
+                            var row = datasourceList[i][0];
+                            for (var j = 0; j < row.length; j++) {
+                                var valOnAngle = row[j];
+                                radius = Math.max(
+                                    radius,
+                                    getSpiralValueRadius(barValOnRadiusList[k], valOnAngle)
+                                );
+                            }
                         }
                     }
                     return Math.ceil(radius * 1.2);
                 }
 
-                function getSpiralValueRadius(valRadius, valAngle) {
-                    return valRadius + radiusStep * (valAngle / angleRoundValue);
+                function getSpiralValueRadius(valOnRadius, valOnAngle) {
+                    return valOnRadius + radiusStep * (valOnAngle / angleRoundValue);
+                }
+
+                function addShapes(api, children, valOnRadius, valOnAngle, color) {
+                    addPolygon(api, children, valOnRadius, valOnAngle, color);
+                    addLabel(api, children, valOnRadius, valOnAngle, color);
                 }
 
-                function makeShapePoints(api, valueRadius, valueAngle) {
+                function addPolygon(api, children, valOnRadius, valOnAngle, color) {
+                    children.push({
+                        type: 'polygon',
+                        shape: {
+                            points: makeShapePoints(api, valOnRadius, valOnAngle),
+                            valOnAngle: valOnAngle
+                        },
+                        style: {
+                            lineWidth: 1,
+                            fill: color.inner,
+                            stroke: color.border
+                        },
+                        during: function (elProps) {
+                            elProps.shape.points = makeShapePoints(
+                                api, valOnRadius, elProps.shape.valOnAngle
+                            );
+                        }
+                    });
+                }
+
+                function makeShapePoints(api, valOnRadius, valOnAngle) {
                     var points = [];
-                    for (var iAngleVal = 0, end = valueAngle + angleStep; iAngleVal < end; iAngleVal += angleStep) {
-                        iAngleVal > valueAngle && (iAngleVal = valueAngle);
-                        var iRadiusVal = getSpiralValueRadius(valueRadius - barWidthValue, iAngleVal);
+                    for (var iAngleVal = 0, end = valOnAngle + angleStep; iAngleVal < end; iAngleVal += angleStep) {
+                        iAngleVal > valOnAngle && (iAngleVal = valOnAngle);
+                        var iRadiusVal = getSpiralValueRadius(valOnRadius - barWidthValue, iAngleVal);
                         var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2);
                         points.push(point);
                     }
-                    for (var iAngleVal = valueAngle; iAngleVal > -angleStep; iAngleVal -= angleStep) {
+                    for (var iAngleVal = valOnAngle; iAngleVal > -angleStep; iAngleVal -= angleStep) {
                         iAngleVal < 0 && (iAngleVal = 0);
-                        var iRadiusVal = getSpiralValueRadius(valueRadius + barWidthValue, iAngleVal);
+                        var iRadiusVal = getSpiralValueRadius(valOnRadius + barWidthValue, iAngleVal);
                         var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2);
                         points.push(point);
                     }
                     return points;
                 }
 
-                function renderItem(params, api) {
-                    var valueRadius = api.value(0);
-                    var valueAngle = api.value(1);
-                    var name = api.value(2);
-                    return {
-                        type: 'polygon',
+                function addLabel(api, children, valOnRadius, valOnAngle, color) {
+                    var point = makeLabelPosition(api, valOnRadius, valOnAngle);
+                    children.push({
+                        type: 'text',
+                        x: point[0],
+                        y: point[1],
                         shape: {
-                            points: makeShapePoints(api, valueRadius, valueAngle),
-                            valueAngle: valueAngle
+                            valOnAngle: valOnAngle
                         },
                         style: {
-                            lineWidth: 1,
-                            fill: colors[name].fill,
-                            stroke: colors[name].stroke
+                            text: getText(valOnAngle),
+                            fill: color.inner,
+                            stroke: '#fff',
+                            lineWidth: 3,
+                            fontSize: 16,
+                            align: 'center',
+                            verticalAlign: 'middle'
                         },
+                        z2: 50,
                         during: function (elProps) {
-                            elProps.shape.points = makeShapePoints(
-                                api, valueRadius, elProps.shape.valueAngle
-                            );
+                            var iValOnAngle = elProps.shape.valOnAngle;
+                            var point = makeLabelPosition(api, valOnRadius, iValOnAngle);
+                            elProps.x = point[0];
+                            elProps.y = point[1];
+                            elProps.style.text = getText(iValOnAngle);
                         }
+                    });
+
+                    function getText(iValOnAngle) {
+                        return (iValOnAngle / angleRoundValue * 100).toFixed(0) + '%'
+                    }
+                }
+
+                function makeLabelPosition(api, valOnRadius, valOnAngle) {
+                    var iRadiusVal = getSpiralValueRadius(valOnRadius, valOnAngle);
+                    return api.coord([iRadiusVal, valOnAngle + 1 / iRadiusVal / (2 * Math.PI) * angleRoundValue]);
+                }
+
+                function renderItem(params, api) {
+                    var children = [];
+
+                    addShapes(api, children, barValOnRadiusList[0], api.value(0), colors[0]);
+                    addShapes(api, children, barValOnRadiusList[1], api.value(1), colors[1]);
+                    addShapes(api, children, barValOnRadiusList[2], api.value(2), colors[2]);
+
+                    return {
+                        type: 'group',
+                        children: children
                     };
                 }
 
                 var option = {
+                    // animation: false,
                     animationDuration: animationDuration,
                     animationDurationUpdate: animationDurationUpdate,
                     animationEasingUpdate: animationEasingUpdate,
+                    dataset: {
+                        source: datasourceList[currentDataIndex]
+                    },
                     angleAxis: {
                         type: 'value',
                         // splitLine: { show: false },
@@ -745,49 +808,30 @@ under the License.
                         min: 0,
                         max: getMaxRadius()
                     },
-                    polar: {
-                    },
+                    polar: {},
                     tooltip: {},
                     series: [{
                         type: 'custom',
-                        name: 'A',
-                        coordinateSystem: 'polar',
-                        renderItem: renderItem,
-                        data: allData[currentDataIndex][0]
-                    }, {
-                        type: 'custom',
-                        name: 'B',
                         coordinateSystem: 'polar',
-                        renderItem: renderItem,
-                        data: allData[currentDataIndex][1]
-                    }, {
-                        type: 'custom',
-                        name: 'C',
-                        coordinateSystem: 'polar',
-                        renderItem: renderItem,
-                        data: allData[currentDataIndex][2]
+                        renderItem: renderItem
                     }]
                 };
 
                 var chart = testHelper.create(echarts, 'spiral2', {
                     title: [
-                        'animation: ',
+                        'polygon animation should be corrent. (coordSys extent is fixed)',
                     ],
                     option: option,
                     buttons: [{
                         text: 'next',
                         onclick: function () {
                             currentDataIndex++;
-                            currentDataIndex >= allData.length && (currentDataIndex = 0);
+                            currentDataIndex >= datasourceList.length && (currentDataIndex = 0);
                             chart.setOption({
-                                series: [{
-                                    data: allData[currentDataIndex][0]
-                                }, {
-                                    data: allData[currentDataIndex][1]
-                                }, {
-                                    data: allData[currentDataIndex][2]
-                                }]
-                            })
+                                dataset: {
+                                    source: datasourceList[currentDataIndex]
+                                }
+                            });
                         }
                     }, {
                         text: 'enable animation',


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


[incubator-echarts] 03/10: Merge branch 'next' into custom-series-enhance

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

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

commit eab38da7197487be3ab17f7f08afcb1eefa2c1b4
Merge: b65758c 9db5c3e
Author: 100pah <su...@gmail.com>
AuthorDate: Wed May 13 02:11:03 2020 +0800

    Merge branch 'next' into custom-series-enhance

 src/component/axis/AngleAxisView.ts |   7 +-
 src/model/Component.ts              |  14 +--
 src/model/Model.ts                  |  14 +--
 src/model/Series.ts                 |  14 +--
 src/view/Chart.ts                   |  14 +--
 src/view/Component.ts               |  14 +--
 test/axis-splitArea.html            | 170 ++++++++++++++++++++++++++++++++++++
 7 files changed, 219 insertions(+), 28 deletions(-)



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


[incubator-echarts] 07/10: feature: support axis label/tick animation on polar radius axis, the same as cartesian axis.

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

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

commit a631dba659430989d131a2030d8f70c361173175
Author: 100pah <su...@gmail.com>
AuthorDate: Thu May 21 22:34:59 2020 +0800

    feature: support axis label/tick animation on polar radius axis, the same as cartesian axis.
---
 src/component/axis/RadiusAxisView.ts | 11 ++++++++++-
 1 file changed, 10 insertions(+), 1 deletion(-)

diff --git a/src/component/axis/RadiusAxisView.ts b/src/component/axis/RadiusAxisView.ts
index e40abeb..439953f 100644
--- a/src/component/axis/RadiusAxisView.ts
+++ b/src/component/axis/RadiusAxisView.ts
@@ -42,11 +42,18 @@ class RadiusAxisView extends AxisView {
 
     axisPointerClass = 'PolarAxisPointer';
 
+    private _axisGroup: graphic.Group;
+
     render(radiusAxisModel: RadiusAxisModel, ecModel: GlobalModel) {
         this.group.removeAll();
         if (!radiusAxisModel.get('show')) {
             return;
         }
+
+        const oldAxisGroup = this._axisGroup;
+        const newAxisGroup = this._axisGroup = new graphic.Group();
+        this.group.add(newAxisGroup);
+
         const radiusAxis = radiusAxisModel.axis;
         const polar = radiusAxis.polar;
         const angleAxis = polar.getAngleAxis();
@@ -58,7 +65,9 @@ class RadiusAxisView extends AxisView {
         const layout = layoutAxis(polar, radiusAxisModel, axisAngle);
         const axisBuilder = new AxisBuilder(radiusAxisModel, layout);
         zrUtil.each(axisBuilderAttrs, axisBuilder.add, axisBuilder);
-        this.group.add(axisBuilder.getGroup());
+        newAxisGroup.add(axisBuilder.getGroup());
+
+        graphic.groupTransition(oldAxisGroup, newAxisGroup, radiusAxisModel);
 
         zrUtil.each(selfBuilderAttrs, function (name) {
             if (radiusAxisModel.get([name, 'show']) && !radiusAxis.scale.isBlank()) {


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


[incubator-echarts] 02/10: Merge branch 'next' into custom-series-enhance

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

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

commit b65758c9ce1c80f3bafc41af5722f29fed5f264a
Merge: d0e5f43 95e9b27
Author: 100pah <su...@gmail.com>
AuthorDate: Fri May 1 00:35:52 2020 +0800

    Merge branch 'next' into custom-series-enhance

 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 +++++++++++++++++++++++++++++++++++++--------
 test/stream-basic.css      |  39 ++++++++
 test/stream-basic.js       |  99 ++++++++++++++++++++
 test/stream-basic1.html    | 106 +++++++++++++++++++++
 test/stream-basic2.html    | 114 +++++++++++++++++++++++
 10 files changed, 578 insertions(+), 44 deletions(-)



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


[incubator-echarts] 01/10: feat: custom series compat.

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

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

commit d0e5f43998996909fcc6d4c0ddc7c886b2796386
Author: 100pah <su...@gmail.com>
AuthorDate: Wed Apr 29 01:57:05 2020 +0800

    feat: custom series compat.
---
 src/chart/custom.ts                                | 1252 +++++++++++++++-----
 src/coord/CoordinateSystem.ts                      |    3 +
 src/echarts.ts                                     |    4 +
 src/model/Component.ts                             |    5 +
 src/model/mixin/itemStyle.ts                       |    2 +-
 src/util/graphic.ts                                |   39 +-
 src/util/styleCompat.ts                            |  256 ++++
 src/util/types.ts                                  |    2 +
 ...-d3.html => circle-packing-with-d3.compat.html} |    0
 test/circle-packing-with-d3.html                   |   50 +-
 test/custom-feature.html                           |    1 +
 test/custom-text-content.html                      | 1193 +++++++++++++++++++
 test/hoverStyle.html                               |    3 +-
 13 files changed, 2488 insertions(+), 322 deletions(-)

diff --git a/src/chart/custom.ts b/src/chart/custom.ts
index 3b801ff..3ed15b9 100644
--- a/src/chart/custom.ts
+++ b/src/chart/custom.ts
@@ -17,7 +17,6 @@
 * under the License.
 */
 
-// @ts-nocheck
 
 import {__DEV__} from '../config';
 import * as zrUtil from 'zrender/src/core/util';
@@ -30,24 +29,261 @@ import SeriesModel from '../model/Series';
 import Model from '../model/Model';
 import ChartView from '../view/Chart';
 import {createClipPath} from './helper/createClipPathFromCoordSys';
-import {EventQueryItem, ECEvent} from '../util/types';
-import Element from 'zrender/src/Element';
-
+import {
+    EventQueryItem, ECEvent, SeriesOption, SeriesOnCartesianOptionMixin,
+    SeriesOnPolarOptionMixin, SeriesOnSingleOptionMixin, SeriesOnGeoOptionMixin,
+    SeriesOnCalendarOptionMixin, ItemStyleOption, SeriesEncodeOptionMixin,
+    SeriesTooltipOption,
+    DimensionLoose,
+    ParsedValue,
+    Dictionary,
+    CallbackDataParams,
+    Payload,
+    StageHandlerProgressParams,
+    LabelOption,
+    ViewRootGroup,
+    OptionDataValue,
+    ZRStyleProps,
+    DisplayState,
+    ECElement,
+    DisplayStateNonNormal
+} from '../util/types';
+import Element, { ElementProps, ElementTextConfig } from 'zrender/src/Element';
 import prepareCartesian2d from '../coord/cartesian/prepareCustom';
 import prepareGeo from '../coord/geo/prepareCustom';
 import prepareSingleAxis from '../coord/single/prepareCustom';
 import preparePolar from '../coord/polar/prepareCustom';
 import prepareCalendar from '../coord/calendar/prepareCustom';
+import ComponentModel from '../model/Component';
+import List, { DefaultDataVisual } from '../data/List';
+import GlobalModel from '../model/Global';
+import { makeInner } from '../util/model';
+import ExtensionAPI from '../ExtensionAPI';
+import Displayable from 'zrender/src/graphic/Displayable';
+import Axis2D from '../coord/cartesian/Axis2D';
+import { RectLike } from 'zrender/src/core/BoundingRect';
+import { PathProps } from 'zrender/src/graphic/Path';
+import { ImageStyleProps } from 'zrender/src/graphic/Image';
+import { CoordinateSystem } from '../coord/CoordinateSystem';
+import { TextStyleProps } from 'zrender/src/graphic/Text';
+import {
+    convertToEC4StyleForCustomSerise,
+    isEC4CompatibleStyle,
+    convertFromEC4CompatibleStyle,
+    LegacyStyleProps,
+    warnDeprecated
+} from '../util/styleCompat';
+import Transformable from 'zrender/src/core/Transformable';
+import { ItemStyleProps } from '../model/mixin/itemStyle';
+
+
+const inner = makeInner<{
+    info: CustomExtraElementInfo;
+    customPathData: string;
+    customGraphicType: string;
+    customImagePath: CustomImageOption['style']['image'];
+    customText: string;
+    txConZ2Set: number;
+}, Element>();
+
+type CustomExtraElementInfo = Dictionary<unknown>;
+type TransformPropsX = 'x' | 'scaleX' | 'originX';
+type TransformPropsY = 'y' | 'scaleY' | 'originY';
+type TransformProps = TransformPropsX | TransformPropsY | 'rotation';
+
+
+interface CustomBaseElementOption extends Partial<Pick<
+    Element, TransformProps | 'silent' | 'ignore' | 'textConfig'
+>> {
+    // element type, mandatory.
+    type: string;
+    id?: string;
+    // For animation diff.
+    name?: string;
+    info?: CustomExtraElementInfo;
+    // `false` means remove the textContent.
+    textContent?: CustomTextOption | false;
+};
+interface CustomDisplayableOption extends CustomBaseElementOption, Partial<Pick<
+    Displayable, 'zlevel' | 'z' | 'z2' | 'invisible'
+>> {
+    style?: ZRStyleProps;
+    // `false` means remove emphasis trigger.
+    styleEmphasis?: ZRStyleProps | false;
+    emphasis?: CustomDisplayableOptionOnState;
+}
+interface CustomDisplayableOptionOnState extends Partial<Pick<
+    Displayable, TransformProps | 'textConfig' | 'z2'
+>> {
+    // `false` means remove emphasis trigger.
+    style?: ZRStyleProps | false;
+}
+interface CustomGroupOption extends CustomBaseElementOption {
+    type: 'group';
+    width?: number;
+    height?: number;
+    diffChildrenByName?: boolean;
+    children: CustomElementOption[];
+    $mergeChildren: false | 'byName' | 'byIndex';
+}
+interface CustomZRPathOption extends CustomDisplayableOption, Pick<PathProps, 'shape'> {
+}
+interface CustomSVGPathOption extends CustomDisplayableOption {
+    type: 'path';
+    shape?: {
+        // SVG Path, like 'M0,0 L0,-20 L70,-1 L70,0 Z'
+        pathData?: string;
+        // "d" is the alias of `pathData` follows the SVG convention.
+        d?: string;
+        layout?: 'center' | 'cover';
+        x?: number;
+        y?: number;
+        width?: number;
+        height?: number;
+    };
+}
+interface CustomImageOption extends CustomDisplayableOption {
+    type: 'image';
+    style?: ImageStyleProps;
+    emphasis?: CustomImageOptionOnState;
+}
+interface CustomImageOptionOnState extends CustomDisplayableOptionOnState {
+    style?: ImageStyleProps;
+}
+interface CustomTextOption extends CustomDisplayableOption {
+    type: 'text';
+}
+type CustomElementOption = CustomZRPathOption | CustomSVGPathOption | CustomImageOption | CustomTextOption;
+type CustomElementOptionOnState = CustomDisplayableOptionOnState | CustomImageOptionOnState;
+type StyleOption = ZRStyleProps | ImageStyleProps | false;
+
+
+interface CustomSeriesRenderItemAPI extends
+        CustomSeriesRenderItemCoordinateSystemAPI,
+        Pick<ExtensionAPI, 'getWidth' | 'getHeight' | 'getZr' | 'getDevicePixelRatio'> {
+    value(dim: DimensionLoose, dataIndexInside?: number): ParsedValue;
+    style(extra?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps;
+    styleEmphasis(extra?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps;
+    visual(visualType: string, dataIndexInside?: number): ReturnType<List['getItemVisual']>;
+    barLayout(opt: Omit<Parameters<typeof getLayoutOnAxis>[0], 'axis'>): ReturnType<typeof getLayoutOnAxis>;
+    currentSeriesIndices(): ReturnType<GlobalModel['getCurrentSeriesIndices']>;
+    font(opt: Parameters<typeof graphicUtil.getFont>[0]): ReturnType<typeof graphicUtil.getFont>;
+}
+interface CustomSeriesRenderItemParamsCoordSys {
+    type: string;
+    // And extra params for each coordinate systems.
+}
+interface CustomSeriesRenderItemCoordinateSystemAPI {
+    coord(
+        data: OptionDataValue | OptionDataValue[],
+        clamp?: boolean
+    ): number[];
+    size?(
+        dataSize: OptionDataValue | OptionDataValue[],
+        dataItem: OptionDataValue | OptionDataValue[]
+    ): number | number[];
+}
+interface CustomSeriesRenderItemParams {
+    context: {};
+    seriesId: string;
+    seriesName: string;
+    seriesIndex: number;
+    coordSys: CustomSeriesRenderItemParamsCoordSys;
+    dataInsideLength: number;
+    encode: ReturnType<typeof wrapEncodeDef>
+}
+type CustomSeriesRenderItem = (
+    params: CustomSeriesRenderItemParams,
+    api: CustomSeriesRenderItemAPI
+) => CustomElementOption;
+
+
+interface CustomSeriesOption extends
+    SeriesOption,
+    SeriesEncodeOptionMixin,
+    SeriesOnCartesianOptionMixin,
+    SeriesOnPolarOptionMixin,
+    SeriesOnSingleOptionMixin,
+    SeriesOnGeoOptionMixin,
+    SeriesOnCalendarOptionMixin {
+
+    // If set as 'none', do not depends on coord sys.
+    coordinateSystem?: string | 'none';
+
+    renderItem?: CustomSeriesRenderItem;
+
+    // Only works on polar and cartesian2d coordinate system.
+    clip?: boolean;
 
-const CACHED_LABEL_STYLE_PROPERTIES = graphicUtil.CACHED_LABEL_STYLE_PROPERTIES;
-const ITEM_STYLE_NORMAL_PATH = ['itemStyle'];
-const ITEM_STYLE_EMPHASIS_PATH = ['emphasis', 'itemStyle'];
-const LABEL_NORMAL = ['label'];
-const LABEL_EMPHASIS = ['emphasis', 'label'];
+    // FIXME needed?
+    tooltip?: SeriesTooltipOption;
+
+    itemStyle?: ItemStyleOption;
+    label?: LabelOption;
+    emphasis?: {
+        itemStyle?: ItemStyleOption;
+        label?: LabelOption;
+    };
+}
+
+// Also compat with ec4, where
+// `visual('color') visual('borderColor')` is supported.
+const STYLE_VISUAL_TYPE = {
+    color: 'fill',
+    borderColor: 'stroke'
+} as const;
+
+const VISUAL_PROPS = {
+    symbol: 1,
+    symbolSize: 1,
+    symbolKeepAspect: 1,
+    legendSymbol: 1,
+    visualMeta: 1,
+    liftZ: 1
+} as const;
+
+const EMPHASIS = 'emphasis' as const;
+const NORMAL = 'normal' as const;
+const PATH_ITEM_STYLE = {
+    normal: ['itemStyle'],
+    emphasis: [EMPHASIS, 'itemStyle']
+} as const;
+const PATH_LABEL = {
+    normal: ['label'],
+    emphasis: [EMPHASIS, 'label']
+} as const;
 // Use prefix to avoid index to be the same as el.name,
-// which will cause weird udpate animation.
+// which will cause weird update animation.
 const GROUP_DIFF_PREFIX = 'e\0\0';
 
+type AttachedTxInfo = {
+    isLegacy: boolean;
+    normal: {
+        cfg: ElementTextConfig;
+        conOpt: CustomElementOption | false;
+    };
+    emphasis: {
+        cfg: ElementTextConfig;
+        conOpt: CustomElementOptionOnState;
+    };
+};
+const attachedTxInfoTmp = {
+    normal: {},
+    emphasis: {}
+} as AttachedTxInfo;
+
+const Z2_SPECIFIED_BIT = {
+    normal: 0,
+    emphasis: 1
+} as const;
+
+
+
+
+export type PrepareCustomInfo = (coordSys: CoordinateSystem) => {
+    coordSys: CustomSeriesRenderItemParamsCoordSys;
+    api: CustomSeriesRenderItemCoordinateSystemAPI
+};
 
 /**
  * To reduce total package size of each coordinate systems, the modules `prepareCustom`
@@ -60,7 +296,7 @@ const GROUP_DIFF_PREFIX = 'e\0\0';
  *         size: function (dataSize, dataItem) {} // return size of each axis in coordSys.
  *     }}
  */
-const prepareCustoms = {
+const prepareCustoms: Dictionary<PrepareCustomInfo> = {
     cartesian2d: prepareCartesian2d,
     geo: prepareGeo,
     singleAxis: prepareSingleAxis,
@@ -68,29 +304,27 @@ const prepareCustoms = {
     calendar: prepareCalendar
 };
 
+class CustomSeriesModel extends SeriesModel<CustomSeriesOption> {
 
-// ------
-// Model
-// ------
+    static type = 'series.custom';
+    readonly type = CustomSeriesModel.type;
 
-SeriesModel.extend({
+    static dependencies = ['grid', 'polar', 'geo', 'singleAxis', 'calendar'];
 
-    type: 'series.custom',
+    preventAutoZ = true;
 
-    dependencies: ['grid', 'polar', 'geo', 'singleAxis', 'calendar'],
+    currentZLevel: number;
+    currentZ: number;
 
-    defaultOption: {
+    static defaultOption: CustomSeriesOption = {
         coordinateSystem: 'cartesian2d', // Can be set as 'none'
         zlevel: 0,
         z: 2,
         legendHoverLink: true,
 
-        useTransform: true,
-
         // Custom series will not clip by default.
         // Some case will use custom series to draw label
         // For example https://echarts.apache.org/examples/en/editor.html?c=custom-gantt-flight
-        // Only works on polar and cartesian2d coordinate system.
         clip: false
 
         // Cartesian coordinate system
@@ -105,43 +339,44 @@ SeriesModel.extend({
 
         // label: {}
         // itemStyle: {}
-    },
+    };
 
-    /**
-     * @override
-     */
-    getInitialData: function (option, ecModel) {
+    optionUpdated() {
+        this.currentZLevel = this.get('zlevel', true);
+        this.currentZ = this.get('z', true);
+    }
+
+    getInitialData(option: CustomSeriesOption, ecModel: GlobalModel): List {
         return createListFromArray(this.getSource(), this);
-    },
+    }
 
-    /**
-     * @override
-     */
-    getDataParams: function (dataIndex, dataType, el) {
-        const params = SeriesModel.prototype.getDataParams.apply(this, arguments);
-        el && (params.info = el.info);
+    getDataParams(dataIndex: number, dataType: string, el: Element): CallbackDataParams & {
+        info: CustomExtraElementInfo
+    } {
+        const params = super.getDataParams(dataIndex, dataType, el) as ReturnType<CustomSeriesModel['getDataParams']>;
+        el && (params.info = inner(el).info);
         return params;
     }
-});
+}
 
-// -----
-// View
-// -----
+ComponentModel.registerClass(CustomSeriesModel);
 
-ChartView.extend({
 
-    type: 'custom',
 
-    /**
-     * @private
-     * @type {module:echarts/data/List}
-     */
-    _data: null,
+class CustomSeriesView extends ChartView {
 
-    /**
-     * @override
-     */
-    render: function (customSeries, ecModel, api, payload) {
+    static type = 'custom';
+    readonly type = CustomSeriesView.type;
+
+    private _data: List;
+
+
+    render(
+        customSeries: CustomSeriesModel,
+        ecModel: GlobalModel,
+        api: ExtensionAPI,
+        payload: Payload
+    ): void {
         const oldData = this._data;
         const data = customSeries.getData();
         const group = this.group;
@@ -182,17 +417,27 @@ ChartView.extend({
         }
 
         this._data = data;
-    },
+    }
 
-    incrementalPrepareRender: function (customSeries, ecModel, api) {
+    incrementalPrepareRender(
+        customSeries: CustomSeriesModel,
+        ecModel: GlobalModel,
+        api: ExtensionAPI
+    ): void {
         this.group.removeAll();
         this._data = null;
-    },
+    }
 
-    incrementalRender: function (params, customSeries, ecModel, api, payload) {
+    incrementalRender(
+        params: StageHandlerProgressParams,
+        customSeries: CustomSeriesModel,
+        ecModel: GlobalModel,
+        api: ExtensionAPI,
+        payload: Payload
+    ): void {
         const data = customSeries.getData();
         const renderItem = makeRenderItem(customSeries, data, ecModel, api);
-        function setIncrementalAndHoverLayer(el) {
+        function setIncrementalAndHoverLayer(el: Displayable) {
             if (!el.isGroup) {
                 el.incremental = true;
                 el.useHoverLayer = true;
@@ -202,17 +447,9 @@ ChartView.extend({
             const el = createOrUpdate(null, idx, renderItem(idx, payload), customSeries, this.group, data);
             el.traverse(setIncrementalAndHoverLayer);
         }
-    },
-
-    /**
-     * @override
-     */
-    dispose: zrUtil.noop,
+    }
 
-    /**
-     * @override
-     */
-    filterForExposedEvent: function (
+    filterForExposedEvent(
         eventType: string, query: EventQueryItem, targetEl: Element, packedEvent: ECEvent
     ): boolean {
         const elementName = query.element;
@@ -230,17 +467,19 @@ ChartView.extend({
 
         return false;
     }
-});
+}
+
+ChartView.registerClass(CustomSeriesView);
 
 
-function createEl(elOption) {
+function createEl(elOption: CustomElementOption): Element {
     const graphicType = elOption.type;
     let el;
 
     // Those graphic elements are not shapes. They should not be
     // overwritten by users, so do them first.
     if (graphicType === 'path') {
-        const shape = elOption.shape;
+        const shape = (elOption as CustomSVGPathOption).shape;
         // Using pathRect brings convenience to users sacle svg path.
         const pathRect = (shape.width != null && shape.height != null)
             ? {
@@ -248,20 +487,20 @@ function createEl(elOption) {
                 y: shape.y || 0,
                 width: shape.width,
                 height: shape.height
-            }
+            } as RectLike
             : null;
         const pathData = getPathData(shape);
         // Path is also used for icon, so layout 'center' by default.
         el = graphicUtil.makePath(pathData, null, pathRect, shape.layout || 'center');
-        el.__customPathData = pathData;
+        inner(el).customPathData = pathData;
     }
     else if (graphicType === 'image') {
         el = new graphicUtil.Image({});
-        el.__customImagePath = elOption.style.image;
+        inner(el).customImagePath = (elOption as CustomImageOption).style.image;
     }
     else if (graphicType === 'text') {
         el = new graphicUtil.Text({});
-        el.__customText = elOption.style.text;
+        inner(el).customText = (elOption.style as TextStyleProps).text;
     }
     else if (graphicType === 'group') {
         el = new graphicUtil.Group();
@@ -279,96 +518,305 @@ function createEl(elOption) {
         el = new Clz();
     }
 
-    el.__customGraphicType = graphicType;
+    inner(el).customGraphicType = graphicType;
     el.name = elOption.name;
 
+    // Compat ec4: the default z2 lift is 1. If changing the number,
+    // some cases probably be broken: hierarchy layout along z, like circle packing,
+    // where emphasis only intending to modify color/border rather than lift z2.
+    (el as ECElement).z2EmphasisLift = 1;
+
     return el;
 }
 
-function updateEl(el, dataIndex, elOption, animatableModel, data, isInit, isRoot) {
-    const transitionProps = {};
-    const elOptionStyle = elOption.style || {};
-
-    elOption.shape && (transitionProps.shape = zrUtil.clone(elOption.shape));
-    elOption.position && (transitionProps.position = elOption.position.slice());
-    elOption.scale && (transitionProps.scale = elOption.scale.slice());
-    elOption.origin && (transitionProps.origin = elOption.origin.slice());
-    elOption.rotation && (transitionProps.rotation = elOption.rotation);
+/**
+ * [STRATEGY] Merge properties or erase all properties:
+ *
+ * Based on the fact that the existing zr element probably be reused, we discuss whether
+ * merge or erase all properties to the exsiting elements.
+ * + "Merge" means that if a certain props is not specified, do not assign to the existing element.
+ * + "Erase all" means that assign all of the available props whatever it specified by users.
+ *
+ * "Merge" might bring some unexpected state retaining for users and "erase all" seams to be
+ * more safe. But "erase all" force users to specify all of the props each time, which
+ * theoretically disables the chance of performance optimization (e.g., just generete shape
+ * and style at the first time rather than always do that). And "force user set all of the props"
+ * might bring trouble to specify which props need to perform "transition animation".
+ * So we still use "merge" rather than "erase all". If users need "erase all", they can
+ * simple always set all of the props each time.
+ * Some "object-like" config like `textConfig`, `textContent`, `style` which are not needed for
+ * every elment, so we replace them only when user specify them. And the that is a total replace.
+ *
+ * [STRATEGY] `hasOwnProperty` or `== null`:
+ *
+ * Ditinguishing "own property" probably bring little trouble to user when make el options.
+ * So we  trade a {xx: null} or {xx: undefined} as "not specified" if possible rather than
+ * "set them to null/undefined". In most cases, props can not be cleared. Some typicall
+ * clearable props like `style`/`textConfig`/`textContent` we enable `false` to means
+ * "clear". In some othere special cases that the prop is able to set as null/undefined,
+ * but not suitable to use `false`, `hasOwnProperty` is checked.
+ */
+function updateElNormal(
+    el: Element,
+    dataIndex: number,
+    elOption: CustomElementOption,
+    styleOpt: StyleOption,
+    attachedTxInfo: AttachedTxInfo,
+    seriesModel: CustomSeriesModel,
+    isInit: boolean,
+    isTextContent: boolean
+): void {
+    const transitionProps = {} as ElementProps;
+    const elDisplayable = el.isGroup ? null : el as Displayable;
+
+    (elOption as CustomZRPathOption).shape && (
+        (transitionProps as PathProps).shape = zrUtil.clone((elOption as CustomZRPathOption).shape)
+    );
+    setLagecyProp(elOption, transitionProps, 'position', 'x', 'y');
+    setLagecyProp(elOption, transitionProps, 'scale', 'scaleX', 'scaleY');
+    setLagecyProp(elOption, transitionProps, 'origin', 'originX', 'originY');
+    setTransProp(elOption, transitionProps, 'x');
+    setTransProp(elOption, transitionProps, 'y');
+    setTransProp(elOption, transitionProps, 'scaleX');
+    setTransProp(elOption, transitionProps, 'scaleY');
+    setTransProp(elOption, transitionProps, 'originX');
+    setTransProp(elOption, transitionProps, 'originY');
+    setTransProp(elOption, transitionProps, 'rotation');
+
+    const txCfgOpt = attachedTxInfo && attachedTxInfo.normal.cfg;
+    if (txCfgOpt) {
+        // PENDING: whether use user object directly rather than clone?
+        // TODO:5.0 textConfig transition animation?
+        el.setTextConfig(txCfgOpt);
+    }
 
-    if (el.type === 'image' && elOption.style) {
-        const targetStyle = transitionProps.style = {};
-        zrUtil.each(['x', 'y', 'width', 'height'], function (prop) {
-            prepareStyleTransition(prop, targetStyle, elOptionStyle, el.style, isInit);
-        });
+    if (el.type === 'image' && styleOpt) {
+        const targetStyle = (transitionProps as Displayable).style = {};
+        const imgStyle = (el as graphicUtil.Image).style;
+        prepareStyleTransition('x', targetStyle, styleOpt, imgStyle, isInit);
+        prepareStyleTransition('y', targetStyle, styleOpt, imgStyle, isInit);
+        prepareStyleTransition('width', targetStyle, styleOpt, imgStyle, isInit);
+        prepareStyleTransition('height', targetStyle, styleOpt, imgStyle, isInit);
     }
 
-    if (el.type === 'text' && elOption.style) {
-        const targetStyle = transitionProps.style = {};
-        zrUtil.each(['x', 'y'], function (prop) {
-            prepareStyleTransition(prop, targetStyle, elOptionStyle, el.style, isInit);
-        });
-        // Compatible with previous: both support
-        // textFill and fill, textStroke and stroke in 'text' element.
-        !elOptionStyle.hasOwnProperty('textFill') && elOptionStyle.fill && (
-            elOptionStyle.textFill = elOptionStyle.fill
+    if (el.type === 'text' && styleOpt) {
+        const textOptionStyle = styleOpt as TextStyleProps;
+        const targetStyle = (transitionProps as Displayable).style = {};
+        const textStyle = (el as graphicUtil.Text).style;
+        prepareStyleTransition('x', targetStyle, textOptionStyle, textStyle, isInit);
+        prepareStyleTransition('y', targetStyle, textOptionStyle, textStyle, isInit);
+        // Compatible with ec4: if `textFill` or `textStroke` exists use them.
+        zrUtil.hasOwn(textOptionStyle, 'textFill') && (
+            textOptionStyle.fill = (textOptionStyle as any).textFill
         );
-        !elOptionStyle.hasOwnProperty('textStroke') && elOptionStyle.stroke && (
-            elOptionStyle.textStroke = elOptionStyle.stroke
+        zrUtil.hasOwn(textOptionStyle, 'textStroke') && (
+            textOptionStyle.stroke = (textOptionStyle as any).textStroke
         );
     }
 
-    if (el.type !== 'group') {
-        el.useStyle(elOptionStyle);
+    if (elDisplayable) {
+        // PENDING: here the input style object is used directly.
+        // Good for performance but bad for compatibility control.
+        styleOpt && elDisplayable.useStyle(styleOpt);
 
         // Init animation.
         if (isInit) {
-            el.style.opacity = 0;
-            let targetOpacity = elOptionStyle.opacity;
-            targetOpacity == null && (targetOpacity = 1);
-            graphicUtil.initProps(el, {style: {opacity: targetOpacity}}, animatableModel, dataIndex);
+            elDisplayable.style.opacity = 0;
+            const targetOpacity = (styleOpt && styleOpt.opacity != null) ? styleOpt.opacity : 1;
+            graphicUtil.initProps(elDisplayable, {style: {opacity: targetOpacity}}, seriesModel, dataIndex);
         }
+
+        zrUtil.hasOwn(elOption, 'invisible') && (elDisplayable.invisible = elOption.invisible);
     }
 
     if (isInit) {
         el.attr(transitionProps);
     }
     else {
-        graphicUtil.updateProps(el, transitionProps, animatableModel, dataIndex);
+        graphicUtil.updateProps(el, transitionProps, seriesModel, dataIndex);
     }
 
     // Merge by default.
-    // z2 must not be null/undefined, otherwise sort error may occur.
-    elOption.hasOwnProperty('z2') && el.attr('z2', elOption.z2 || 0);
-    elOption.hasOwnProperty('silent') && el.attr('silent', elOption.silent);
-    elOption.hasOwnProperty('invisible') && el.attr('invisible', elOption.invisible);
-    elOption.hasOwnProperty('ignore') && el.attr('ignore', elOption.ignore);
-    // `elOption.info` enables user to mount some info on
-    // elements and use them in event handlers.
-    // Update them only when user specified, otherwise, remain.
-    elOption.hasOwnProperty('info') && el.attr('info', elOption.info);
-
-    // If `elOption.styleEmphasis` is `false`, remove hover style. The
-    // logic is ensured by `graphicUtil.setElementHoverStyle`.
-    const styleEmphasis = elOption.styleEmphasis;
-    // hoverStyle should always be set here, because if the hover style
-    // may already be changed, where the inner cache should be reset.
-    graphicUtil.enableElementHoverEmphasis(el, styleEmphasis);
+    zrUtil.hasOwn(elOption, 'silent') && (el.silent = elOption.silent);
+    zrUtil.hasOwn(elOption, 'ignore') && (el.ignore = elOption.ignore);
+
+    if (!isTextContent) {
+        // `elOption.info` enables user to mount some info on
+        // elements and use them in event handlers.
+        // Update them only when user specified, otherwise, remain.
+        zrUtil.hasOwn(elOption, 'info') && (inner(el).info = elOption.info);
+    }
+
+    el.markRedraw();
+}
+
+function updateElOnState(
+    state: DisplayStateNonNormal,
+    el: Element,
+    elStateOpt: CustomElementOptionOnState,
+    styleOpt: StyleOption,
+    attachedTxInfo: AttachedTxInfo,
+    isRoot: boolean,
+    isTextContent: boolean
+): void {
+    const elDisplayable = el.isGroup ? null : el as Displayable;
+    const txCfgOpt = attachedTxInfo && attachedTxInfo[state].cfg;
+
+    // PENDING:5.0 support customize scale change and transition animation?
+
+    if (elDisplayable) {
+        // By default support auto lift color when hover whether `emphasis` specified.
+        const stateObj = elDisplayable.ensureState(state);
+        if (styleOpt === false) {
+            const existingEmphasisState = elDisplayable.getState(state);
+            if (existingEmphasisState) {
+                existingEmphasisState.style = null;
+            }
+        }
+        else {
+            // style is needed to enable defaut emphasis.
+            stateObj.style = styleOpt || {};
+        }
+        // If `elOption.styleEmphasis` or `elOption.emphasis.style` is `false`,
+        // remove hover style.
+        // If `elOption.textConfig` or `elOption.emphasis.textConfig` is null/undefined, it does not
+        // make sense. So for simplicity, we do not ditinguish `hasOwnProperty` and null/undefined.
+        if (txCfgOpt) {
+            stateObj.textConfig = txCfgOpt;
+        }
+
+        graphicUtil.enableElementHoverEmphasis(elDisplayable);
+    }
+
     if (isRoot) {
-        graphicUtil.setAsHighDownDispatcher(el, styleEmphasis !== false);
+        graphicUtil.setAsHighDownDispatcher(el, styleOpt !== false);
+    }
+}
+
+function updateZ(
+    el: Element,
+    elOption: CustomElementOption,
+    seriesModel: CustomSeriesModel,
+    attachedTxInfo: AttachedTxInfo
+): void {
+    // Group not support textContent and not support z yet.
+    if (el.isGroup) {
+        return;
+    }
+
+    const elDisplayable = el as Displayable;
+    const currentZ = seriesModel.currentZ;
+    const currentZLevel = seriesModel.currentZLevel;
+    // Always erase.
+    elDisplayable.z = currentZ;
+    elDisplayable.zlevel = currentZLevel;
+    // z2 must not be null/undefined, otherwise sort error may occur.
+    const optZ2 = elOption.z2;
+    optZ2 != null && (elDisplayable.z2 = optZ2 || 0);
+
+    const textContent = elDisplayable.getTextContent();
+    if (textContent) {
+        textContent.z = currentZ;
+        textContent.zlevel = currentZLevel;
+    }
+
+    updateZForEachState(elDisplayable, textContent, elOption, attachedTxInfo, NORMAL);
+    updateZForEachState(elDisplayable, textContent, elOption, attachedTxInfo, EMPHASIS);
+}
+
+function updateZForEachState(
+    elDisplayable: Displayable,
+    textContent: Displayable,
+    elOption: CustomDisplayableOption,
+    attachedTxInfo: AttachedTxInfo,
+    state: DisplayState
+): void {
+    const isNormal = state === NORMAL;
+    const elStateOpt = isNormal ? elOption : retrieveStateOption(elOption, state as DisplayStateNonNormal);
+    const optZ2 = elStateOpt ? elStateOpt.z2 : null;
+    let stateObj;
+    if (optZ2 != null) {
+        // Do not `ensureState` until required.
+        stateObj = isNormal ? elDisplayable : elDisplayable.ensureState(state);
+        stateObj.z2 = optZ2 || 0;
+    }
+
+    const txConOpt = attachedTxInfo[state].conOpt;
+    if (textContent) {
+        const innerEl = inner(elDisplayable);
+        const txConZ2Set = innerEl.txConZ2Set || 0;
+        const txOptZ2 = txConOpt ? txConOpt.z2 : null;
+        const z2SetMask = 1 << Z2_SPECIFIED_BIT[state];
+
+        // Set textContent z2 as hostEl.z2 + 1 only if
+        // textContent z2 is not specified.
+        if (txOptZ2 != null) {
+            // Do not `ensureState` until required.
+            (isNormal ? textContent : textContent.ensureState(state)).z2 = txOptZ2;
+            innerEl.txConZ2Set = txConZ2Set | z2SetMask;
+        }
+        // If stateObj exists, that means stateObj.z2 has been updated, where the textContent z2
+        // should be followed, no matter textContent or textContent.emphasis is specified in elOption.
+        else if (stateObj && (txConZ2Set & z2SetMask) === 0) {
+            (isNormal ? textContent : textContent.ensureState(state)).z2 = stateObj.z2 + 1;
+        }
     }
 }
 
-function prepareStyleTransition(prop, targetStyle, elOptionStyle, oldElStyle, isInit) {
+function setLagecyProp(
+    elOption: CustomElementOption,
+    transitionProps: Partial<Pick<Transformable, TransformProps>>,
+    legacyName: 'position' | 'scale' | 'origin',
+    xName: TransformPropsX,
+    yName: TransformPropsY
+): void {
+    const legacyArr = (elOption as any)[legacyName];
+    legacyArr && (transitionProps[xName] = legacyArr[0], transitionProps[yName] = legacyArr[1]);
+}
+function setTransProp(
+    elOption: CustomElementOption,
+    transitionProps: Partial<Pick<Transformable, TransformProps>>,
+    name: TransformProps
+): void {
+    elOption[name] != null && (transitionProps[name] = elOption[name]);
+}
+
+function prepareStyleTransition(
+    prop: 'x' | 'y',
+    targetStyle: CustomTextOption['style'],
+    elOptionStyle: CustomTextOption['style'],
+    oldElStyle: graphicUtil.Text['style'],
+    isInit: boolean
+): void;
+function prepareStyleTransition(
+    prop: 'x' | 'y' | 'width' | 'height',
+    targetStyle: CustomImageOption['style'],
+    elOptionStyle: CustomImageOption['style'],
+    oldElStyle: graphicUtil.Image['style'],
+    isInit: boolean
+): void;
+function prepareStyleTransition(
+    prop: string,
+    targetStyle: any,
+    elOptionStyle: any,
+    oldElStyle: any,
+    isInit: boolean
+): void {
     if (elOptionStyle[prop] != null && !isInit) {
         targetStyle[prop] = elOptionStyle[prop];
         elOptionStyle[prop] = oldElStyle[prop];
     }
 }
 
-function makeRenderItem(customSeries, data, ecModel, api) {
+function makeRenderItem(
+    customSeries: CustomSeriesModel,
+    data: List<CustomSeriesModel>,
+    ecModel: GlobalModel,
+    api: ExtensionAPI
+) {
     const renderItem = customSeries.get('renderItem');
     const coordSys = customSeries.coordinateSystem;
-    let prepareResult = {};
+    let prepareResult = {} as ReturnType<PrepareCustomInfo>;
 
     if (coordSys) {
         if (__DEV__) {
@@ -379,8 +827,9 @@ function makeRenderItem(customSeries, data, ecModel, api) {
             );
         }
 
+        // `coordSys.prepareCustoms` is used for external coord sys like bmap.
         prepareResult = coordSys.prepareCustoms
-            ? coordSys.prepareCustoms()
+            ? coordSys.prepareCustoms(coordSys)
             : prepareCustoms[coordSys.type](coordSys);
     }
 
@@ -396,9 +845,9 @@ function makeRenderItem(customSeries, data, ecModel, api) {
         barLayout: barLayout,
         currentSeriesIndices: currentSeriesIndices,
         font: font
-    }, prepareResult.api || {});
+    }, prepareResult.api || {}) as CustomSeriesRenderItemAPI;
 
-    const userParams = {
+    const userParams: CustomSeriesRenderItemParams = {
         // The life cycle of context: current round of rendering.
         // The global life cycle is probably not necessary, because
         // user can store global status by themselves.
@@ -411,17 +860,54 @@ function makeRenderItem(customSeries, data, ecModel, api) {
         encode: wrapEncodeDef(customSeries.getData())
     };
 
+    // If someday intending to refactor them to a class, should consider do not
+    // break change: currently these attribute member are encapsulated in a closure
+    // so that do not need to force user to call these method with a scope.
+
     // Do not support call `api` asynchronously without dataIndexInside input.
-    let currDataIndexInside;
-    let currDirty = true;
-    let currItemModel;
-    let currLabelNormalModel;
-    let currLabelEmphasisModel;
-    let currVisualColor;
-
-    return function (dataIndexInside, payload) {
+    let currDataIndexInside: number;
+    let currItemModel: Model<CustomSeriesOption>;
+    let currItemStyleModels: Partial<Record<DisplayState, Model<CustomSeriesOption['itemStyle']>>> = {};
+    let currLabelModels: Partial<Record<DisplayState, Model<CustomSeriesOption['label']>>> = {};
+
+    const seriesItemStyleModels = {
+        normal: customSeries.getModel(PATH_ITEM_STYLE.normal),
+        emphasis: customSeries.getModel(PATH_ITEM_STYLE.emphasis)
+    } as Record<DisplayState, Model<CustomSeriesOption['label']>>;
+    const seriesLabelModels = {
+        normal: customSeries.getModel(PATH_LABEL.normal),
+        emphasis: customSeries.getModel(PATH_LABEL.emphasis)
+    } as Record<DisplayState, Model<CustomSeriesOption['label']>>;
+
+    function getItemModel(dataIndexInside: number): Model<CustomSeriesOption> {
+        return dataIndexInside === currDataIndexInside
+            ? (currItemModel || (currItemModel = data.getItemModel(dataIndexInside)))
+            : data.getItemModel(dataIndexInside);
+    }
+    function getItemStyleModel(dataIndexInside: number, state: DisplayState) {
+        return !data.hasItemOption
+            ? seriesItemStyleModels[state]
+            : dataIndexInside === currDataIndexInside
+            ? (currItemStyleModels[state] || (
+                currItemStyleModels[state] = getItemModel(dataIndexInside).getModel(PATH_ITEM_STYLE[state])
+            ))
+            : getItemModel(dataIndexInside).getModel(PATH_ITEM_STYLE[state]);
+    }
+    function getLabelModel(dataIndexInside: number, state: DisplayState) {
+        return !data.hasItemOption
+            ? seriesLabelModels[state]
+            : dataIndexInside === currDataIndexInside
+            ? (currLabelModels[state] || (
+                currLabelModels[state] = getItemModel(dataIndexInside).getModel(PATH_LABEL[state])
+            ))
+            : getItemModel(dataIndexInside).getModel(PATH_LABEL[state]);
+    }
+
+    return function (dataIndexInside: number, payload: Payload): CustomElementOption {
         currDataIndexInside = dataIndexInside;
-        currDirty = true;
+        currItemModel = null;
+        currItemStyleModels = {};
+        currLabelModels = {};
 
         return renderItem && renderItem(
             zrUtil.defaults({
@@ -434,158 +920,172 @@ function makeRenderItem(customSeries, data, ecModel, api) {
         );
     };
 
-    // Do not update cache until api called.
-    function updateCache(dataIndexInside) {
-        dataIndexInside == null && (dataIndexInside = currDataIndexInside);
-        if (currDirty) {
-            currItemModel = data.getItemModel(dataIndexInside);
-            currLabelNormalModel = currItemModel.getModel(LABEL_NORMAL);
-            currLabelEmphasisModel = currItemModel.getModel(LABEL_EMPHASIS);
-            currVisualColor = data.getItemVisual(dataIndexInside, 'color');
-
-            currDirty = false;
-        }
-    }
-
     /**
      * @public
-     * @param {number|string} dim
-     * @param {number} [dataIndexInside=currDataIndexInside]
-     * @return {number|string} value
+     * @param dim by default 0.
+     * @param dataIndexInside by default `currDataIndexInside`.
      */
-    function value(dim, dataIndexInside) {
+    function value(dim?: DimensionLoose, dataIndexInside?: number): ParsedValue {
         dataIndexInside == null && (dataIndexInside = currDataIndexInside);
         return data.get(data.getDimension(dim || 0), dataIndexInside);
     }
 
     /**
+     * @deprecated The orgininal intention of `api.style` is enable to set itemStyle
+     * like other series. But it not necessary and not easy to give a strict definition
+     * of what it return. And since echarts5 it needs to be make compat work. So
+     * deprecates it since echarts5.
+     *
      * By default, `visual` is applied to style (to support visualMap).
      * `visual.color` is applied at `fill`. If user want apply visual.color on `stroke`,
      * it can be implemented as:
      * `api.style({stroke: api.visual('color'), fill: null})`;
+     *
+     * [Compat]: since ec5, RectText has been separated from its hosts el.
+     * so `api.style()` will only return the style from `itemStyle` but not handle `label`
+     * any more. But `series.label` config is never published in doc.
+     * We still compat it in `api.style()`. But not encourage to use it and will still not
+     * to pulish it to doc.
      * @public
-     * @param {Object} [extra]
-     * @param {number} [dataIndexInside=currDataIndexInside]
+     * @param dataIndexInside by default `currDataIndexInside`.
      */
-    function style(extra, dataIndexInside) {
+    function style(extra?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps {
+        if (__DEV__) {
+            warnDeprecated('api.style', 'Please write literal style directly instead.');
+        }
+
         dataIndexInside == null && (dataIndexInside = currDataIndexInside);
-        updateCache(dataIndexInside);
 
-        const itemStyle = currItemModel.getModel(ITEM_STYLE_NORMAL_PATH).getItemStyle();
+        const style = data.getItemVisual(dataIndexInside, 'style');
+        const visualColor = style && style.fill;
+        const opacity = style && style.opacity;
 
-        currVisualColor != null && (itemStyle.fill = currVisualColor);
-        const opacity = data.getItemVisual(dataIndexInside, 'opacity');
+        let itemStyle = getItemStyleModel(dataIndexInside, NORMAL).getItemStyle();
+        visualColor != null && (itemStyle.fill = visualColor);
         opacity != null && (itemStyle.opacity = opacity);
 
-        const labelModel = extra
-            ? applyExtraBefore(extra, currLabelNormalModel)
-            : currLabelNormalModel;
-
-        const textStyle = graphicUtil.createTextStyle(labelModel, null, {
-            autoColor: currVisualColor,
-            isRectText: true
-        });
-
-        // TODO
-        zrUtil.extend(itemStyle, textStyle);
-
-        itemStyle.text = labelModel.getShallow('show')
+        const opt = {autoColor: zrUtil.isString(visualColor) ? visualColor : '#000'};
+        const labelModel = getLabelModel(dataIndexInside, NORMAL);
+        // Now that the feture of "auto adjust text fill/stroke" has been migrated to zrender
+        // since ec5, we should set `isAttached` as `false` here and make compat in
+        // `convertToEC4StyleForCustomSerise`.
+        const textStyle = graphicUtil.createTextStyle(labelModel, null, opt, false, true);
+        textStyle.text = labelModel.getShallow('show')
             ? zrUtil.retrieve2(
-                customSeries.getFormattedLabel(dataIndexInside, 'normal'),
+                customSeries.getFormattedLabel(dataIndexInside, NORMAL),
                 getDefaultLabel(data, dataIndexInside)
             )
             : null;
+        const textConfig = graphicUtil.createTextConfig(textStyle, labelModel, opt, false);
+
+        preFetchFromExtra(extra, itemStyle);
+        itemStyle = convertToEC4StyleForCustomSerise(itemStyle, textStyle, textConfig);
 
         extra && applyExtraAfter(itemStyle, extra);
+        (itemStyle as LegacyStyleProps).legacy = true;
 
         return itemStyle;
     }
 
     /**
+     * @deprecated The reason see `api.style()`
      * @public
-     * @param {Object} [extra]
-     * @param {number} [dataIndexInside=currDataIndexInside]
+     * @param dataIndexInside by default `currDataIndexInside`.
      */
-    function styleEmphasis(extra, dataIndexInside) {
-        dataIndexInside == null && (dataIndexInside = currDataIndexInside);
-        updateCache(dataIndexInside);
-
-        const itemStyle = currItemModel.getModel(ITEM_STYLE_EMPHASIS_PATH).getItemStyle();
-
-        const labelModel = extra
-            ? applyExtraBefore(extra, currLabelEmphasisModel)
-            : currLabelEmphasisModel;
-
-        const textStyle = graphicUtil.createTextStyle(labelModel, null, {
-            isRectText: true
-        }, true);
-        zrUtil.extend(itemStyle, textStyle);
+    function styleEmphasis(extra?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps {
+        if (__DEV__) {
+            warnDeprecated('api.styleEmphasis', 'Please write literal style directly instead.');
+        }
 
+        dataIndexInside == null && (dataIndexInside = currDataIndexInside);
 
-        itemStyle.text = labelModel.getShallow('show')
+        let itemStyle = getItemStyleModel(dataIndexInside, EMPHASIS).getItemStyle();
+        const labelModel = getLabelModel(dataIndexInside, EMPHASIS);
+        const textStyle = graphicUtil.createTextStyle(labelModel, null, null, true, true);
+        textStyle.text = labelModel.getShallow('show')
             ? zrUtil.retrieve3(
-                customSeries.getFormattedLabel(dataIndexInside, 'emphasis'),
-                customSeries.getFormattedLabel(dataIndexInside, 'normal'),
+                customSeries.getFormattedLabel(dataIndexInside, EMPHASIS),
+                customSeries.getFormattedLabel(dataIndexInside, NORMAL),
                 getDefaultLabel(data, dataIndexInside)
             )
             : null;
+        const textConfig = graphicUtil.createTextConfig(textStyle, labelModel, null, true);
+
+        preFetchFromExtra(extra, itemStyle);
+        itemStyle = convertToEC4StyleForCustomSerise(itemStyle, textStyle, textConfig);
 
         extra && applyExtraAfter(itemStyle, extra);
+        (itemStyle as LegacyStyleProps).legacy = true;
 
         return itemStyle;
     }
 
+    function preFetchFromExtra(extra: ZRStyleProps, itemStyle: ItemStyleProps): void {
+        // A trick to retrieve those props firstly, which are used to
+        // apply auto inside fill/stroke in `convertToEC4StyleForCustomSerise`.
+        // (It's not reasonable but only for a degree of compat)
+        if (extra) {
+            (extra as any).textFill && ((itemStyle as any).textFill = (extra as any).textFill);
+            (extra as any).textPosition && ((itemStyle as any).textPosition = (extra as any).textPosition);
+        }
+    }
+
     /**
      * @public
-     * @param {string} visualType
-     * @param {number} [dataIndexInside=currDataIndexInside]
+     * @param dataIndexInside by default `currDataIndexInside`.
      */
-    function visual(visualType, dataIndexInside) {
+    function visual(
+        visualType: keyof DefaultDataVisual,
+        dataIndexInside?: number
+    ): ReturnType<List['getItemVisual']> {
         dataIndexInside == null && (dataIndexInside = currDataIndexInside);
-        return data.getItemVisual(dataIndexInside, visualType);
+
+        if (zrUtil.hasOwn(STYLE_VISUAL_TYPE, visualType)) {
+            const style = data.getItemVisual(dataIndexInside, 'style');
+            return style
+                ? style[STYLE_VISUAL_TYPE[visualType as keyof typeof STYLE_VISUAL_TYPE]] as any
+                : null;
+        }
+        // Only support these visuals. Other visual might be inner tricky
+        // for performance (like `style`), do not expose to users.
+        if (zrUtil.hasOwn(VISUAL_PROPS, visualType)) {
+            return data.getItemVisual(dataIndexInside, visualType);
+        }
     }
 
     /**
      * @public
-     * @param {number} opt.count Positive interger.
-     * @param {number} [opt.barWidth]
-     * @param {number} [opt.barMaxWidth]
-     * @param {number} [opt.barMinWidth]
-     * @param {number} [opt.barGap]
-     * @param {number} [opt.barCategoryGap]
-     * @return {Object} {width, offset, offsetCenter} is not support, return undefined.
+     * @return If not support, return undefined.
      */
-    function barLayout(opt) {
-        if (coordSys.getBaseAxis) {
-            const baseAxis = coordSys.getBaseAxis();
-            return getLayoutOnAxis(zrUtil.defaults({axis: baseAxis}, opt), api);
+    function barLayout(
+        opt: Omit<Parameters<typeof getLayoutOnAxis>[0], 'axis'>
+    ): ReturnType<typeof getLayoutOnAxis> {
+        if (coordSys.type === 'cartesian2d') {
+            const baseAxis = coordSys.getBaseAxis() as Axis2D;
+            return getLayoutOnAxis(zrUtil.defaults({axis: baseAxis}, opt));
         }
     }
 
     /**
      * @public
-     * @return {Array.<number>}
      */
-    function currentSeriesIndices() {
+    function currentSeriesIndices(): ReturnType<GlobalModel['getCurrentSeriesIndices']> {
         return ecModel.getCurrentSeriesIndices();
     }
 
     /**
      * @public
-     * @param {Object} opt
-     * @param {string} [opt.fontStyle]
-     * @param {number} [opt.fontWeight]
-     * @param {number} [opt.fontSize]
-     * @param {string} [opt.fontFamily]
-     * @return {string} font string
+     * @return font string
      */
-    function font(opt) {
+    function font(
+        opt: Parameters<typeof graphicUtil.getFont>[0]
+    ): ReturnType<typeof graphicUtil.getFont> {
         return graphicUtil.getFont(opt, ecModel);
     }
 }
 
-function wrapEncodeDef(data) {
-    const encodeDef = {};
+function wrapEncodeDef(data: List<CustomSeriesModel>): Dictionary<number[]> {
+    const encodeDef = {} as Dictionary<number[]>;
     zrUtil.each(data.dimensions, function (dimName, dataDimIndex) {
         const dimInfo = data.getDimensionInfo(dimName);
         if (!dimInfo.isExtraCoord) {
@@ -597,14 +1097,29 @@ function wrapEncodeDef(data) {
     return encodeDef;
 }
 
-function createOrUpdate(el, dataIndex, elOption, animatableModel, group, data) {
-    el = doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data, true);
+function createOrUpdate(
+    el: Element,
+    dataIndex: number,
+    elOption: CustomElementOption,
+    seriesModel: CustomSeriesModel,
+    group: ViewRootGroup,
+    data: List<CustomSeriesModel>
+): Element {
+    el = doCreateOrUpdate(el, dataIndex, elOption, seriesModel, group, data, true);
     el && data.setItemGraphicEl(dataIndex, el);
 
     return el;
 }
 
-function doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data, isRoot) {
+function doCreateOrUpdate(
+    el: Element,
+    dataIndex: number,
+    elOption: CustomElementOption,
+    seriesModel: CustomSeriesModel,
+    group: ViewRootGroup,
+    data: List<CustomSeriesModel>,
+    isRoot: boolean
+): Element {
 
     // [Rule]
     // By default, follow merge mode.
@@ -616,45 +1131,75 @@ function doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data,
     //     regard "return;" as "show nothing element whatever", so make a exception to meet the
     //     most cases.)
 
-    const simplyRemove = !elOption; // `null`/`undefined`/`false`
-    elOption = elOption || {};
+    // If `elOption` is `null`/`undefined`/`false` (when `renderItem` returns nothing).
+    if (!elOption) {
+        el && group.remove(el);
+        return;
+    }
+
+    elOption = elOption || {} as CustomElementOption;
     const elOptionType = elOption.type;
-    const elOptionShape = elOption.shape;
+    const elOptionShape = (elOption as CustomZRPathOption).shape;
     const elOptionStyle = elOption.style;
 
-    if (el && (
-        simplyRemove
-        // || elOption.$merge === false
-        // If `elOptionType` is `null`, follow the merge principle.
-        || (elOptionType != null
-            && elOptionType !== el.__customGraphicType
-        )
-        || (elOptionType === 'path'
-            && hasOwnPathData(elOptionShape) && getPathData(elOptionShape) !== el.__customPathData
-        )
-        || (elOptionType === 'image'
-            && hasOwn(elOptionStyle, 'image') && elOptionStyle.image !== el.__customImagePath
-        )
-        // FIXME test and remove this restriction?
-        || (elOptionType === 'text'
-            && hasOwn(elOptionShape, 'text') && elOptionStyle.text !== el.__customText
-        )
-    )) {
-        group.remove(el);
-        el = null;
+    if (el) {
+        const elInner = inner(el);
+        if (
+            // || elOption.$merge === false
+            // If `elOptionType` is `null`, follow the merge principle.
+            (elOptionType != null
+                && elOptionType !== elInner.customGraphicType
+            )
+            || (elOptionType === 'path'
+                && hasOwnPathData(elOptionShape)
+                && getPathData(elOptionShape) !== elInner.customPathData
+            )
+            || (elOptionType === 'image'
+                && zrUtil.hasOwn(elOptionStyle, 'image')
+                && (elOptionStyle as CustomImageOption['style']).image !== elInner.customImagePath
+            )
+            // FIXME test and remove this restriction?
+            || (elOptionType === 'text'
+                && zrUtil.hasOwn(elOptionStyle, 'text')
+                && (elOptionStyle as TextStyleProps).text !== elInner.customText
+            )
+        ) {
+            group.remove(el);
+            el = null;
+        }
     }
 
-    // `elOption.type` is undefined when `renderItem` returns nothing.
-    if (simplyRemove) {
-        return;
+    const isInit = !el;
+
+    if (!el) {
+        el = createEl(elOption);
+    }
+    else {
+        // If in some case the performance issue arised, consider
+        // do not clearState but update cached normal state directly.
+        el.clearStates();
     }
 
-    const isInit = !el;
-    !el && (el = createEl(elOption));
-    updateEl(el, dataIndex, elOption, animatableModel, data, isInit, isRoot);
+    attachedTxInfoTmp.normal.cfg = attachedTxInfoTmp.normal.conOpt =
+        attachedTxInfoTmp.emphasis.cfg = attachedTxInfoTmp.emphasis.conOpt = null;
+    attachedTxInfoTmp.isLegacy = false;
+
+    doCreateOrUpdateAttachedTx(
+        el, dataIndex, elOption, seriesModel, isInit, attachedTxInfoTmp
+    );
+
+    const stateOptEmphasis = retrieveStateOption(elOption, EMPHASIS);
+    const styleOptEmphasis = retrieveStyleOptionOnState(elOption, stateOptEmphasis, EMPHASIS);
+
+    updateElNormal(el, dataIndex, elOption, elOption.style, attachedTxInfoTmp, seriesModel, isInit, false);
+    updateElOnState(EMPHASIS, el, stateOptEmphasis, styleOptEmphasis, attachedTxInfoTmp, isRoot, false);
+
+    updateZ(el, elOption, seriesModel, attachedTxInfoTmp);
 
     if (elOptionType === 'group') {
-        mergeChildren(el, dataIndex, elOption, animatableModel, data);
+        mergeChildren(
+            el as graphicUtil.Group, dataIndex, elOption as CustomGroupOption, seriesModel, data
+        );
     }
 
     // Always add whatever already added to ensure sequence.
@@ -663,6 +1208,137 @@ function doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data,
     return el;
 }
 
+function doCreateOrUpdateAttachedTx(
+    el: Element,
+    dataIndex: number,
+    elOption: CustomElementOption,
+    seriesModel: CustomSeriesModel,
+    isInit: boolean,
+    attachedTxInfo: AttachedTxInfo
+): void {
+    // group do not support textContent temporarily untill necessary.
+    if (el.isGroup) {
+        return;
+    }
+
+    // Normal must be called before emphasis, for `isLegacy` detection.
+    processTxInfo(elOption, null, attachedTxInfo);
+    processTxInfo(elOption, EMPHASIS, attachedTxInfo);
+
+    // If `elOption.textConfig` or `elOption.textContent` is null/undefined, it does not make sence.
+    // So for simplicity, if "elOption hasOwnProperty of them but be null/undefined", we do not
+    // trade them as set to null to el.
+    // Especially:
+    // `elOption.textContent: false` means remove textContent.
+    // `elOption.textContent.emphasis.style: false` means remove the style from emphasis state.
+    let txConOptNormal = attachedTxInfo.normal.conOpt as CustomElementOption | false;
+    const txConOptEmphasis = attachedTxInfo.emphasis.conOpt as CustomElementOptionOnState;
+
+    if (txConOptEmphasis != null) {
+        // If textContent has emphasis state, el should auto has emphasis
+        // state, otherwise it can not be triggered.
+        el.ensureState(EMPHASIS);
+    }
+
+    if (txConOptNormal != null || txConOptEmphasis != null) {
+        let textContent = el.getTextContent();
+        if (txConOptNormal === false) {
+            textContent && el.removeTextContent();
+        }
+        else {
+            txConOptNormal = attachedTxInfo.normal.conOpt = txConOptNormal || {type: 'text'};
+            if (!textContent) {
+                textContent = createEl(txConOptNormal) as graphicUtil.Text;
+                el.setTextContent(textContent);
+            }
+            else {
+                // If in some case the performance issue arised, consider
+                // do not clearState but update cached normal state directly.
+                textContent.clearStates();
+            }
+            const txConStlOptNormal = txConOptNormal && txConOptNormal.style;
+
+            updateElNormal(
+                textContent, dataIndex, txConOptNormal, txConStlOptNormal, null, seriesModel, isInit, true
+            );
+            const txConStlOptEmphasis = retrieveStyleOptionOnState(txConOptNormal, txConOptEmphasis, EMPHASIS);
+            updateElOnState(EMPHASIS, textContent, txConOptEmphasis, txConStlOptEmphasis, null, false, true);
+
+            textContent.markRedraw();
+        }
+    }
+}
+
+function processTxInfo(
+    elOption: CustomElementOption,
+    state: DisplayStateNonNormal,
+    attachedTxInfo: AttachedTxInfo
+): void {
+    const stateOpt = !state ? elOption : retrieveStateOption(elOption, state);
+    const styleOpt = !state ? elOption.style : retrieveStyleOptionOnState(elOption, stateOpt, EMPHASIS);
+
+    const elType = elOption.type;
+    let txCfg = stateOpt ? stateOpt.textConfig : null;
+    const txConOptNormal = elOption.textContent;
+    let txConOpt: CustomElementOption | CustomElementOptionOnState =
+        !txConOptNormal ? null : !state ? txConOptNormal : retrieveStateOption(txConOptNormal, state);
+
+    if (styleOpt && (
+        // Because emphasis style has little info to detect legacy,
+        // if normal is legacy, emphasis is trade as legacy.
+        attachedTxInfo.isLegacy
+        || isEC4CompatibleStyle(styleOpt, elType, !!txCfg, !!txConOpt)
+    )) {
+        attachedTxInfo.isLegacy = true;
+        const convertResult = convertFromEC4CompatibleStyle(styleOpt, elType, !state);
+        // Explicitly specified `textConfig` and `textContent` has higher priority than
+        // the ones generated by legacy style. Otherwise if users use them and `api.style`
+        // at the same time, they not both work and hardly to known why.
+        if (!txCfg && convertResult.textConfig) {
+            txCfg = convertResult.textConfig;
+        }
+        if (!txConOpt && convertResult.textContent) {
+            txConOpt = convertResult.textContent;
+        }
+    }
+
+    if (!state && txConOpt) {
+        const txConOptNormal = txConOpt as CustomElementOption;
+        // `textContent: {type: 'text'}`, the "type" is easy to be missing. So we tolerate it.
+        !txConOptNormal.type && (txConOptNormal.type = 'text');
+        if (__DEV__) {
+            // Do not tolerate incorret type for forward compat.
+            txConOptNormal.type !== 'text' && zrUtil.assert(
+                txConOptNormal.type === 'text',
+                'textContent.type must be "text"'
+            );
+        }
+    }
+
+    const info = !state ? attachedTxInfo.normal : attachedTxInfo[state];
+    info.cfg = txCfg;
+    info.conOpt = txConOpt;
+}
+
+function retrieveStateOption(
+    elOption: CustomElementOption, state: DisplayStateNonNormal
+): CustomElementOptionOnState {
+    return !state ? elOption : elOption ? elOption[state] : null;
+}
+
+function retrieveStyleOptionOnState(
+    stateOptionNormal: CustomElementOption,
+    stateOption: CustomElementOptionOnState,
+    state: DisplayStateNonNormal
+): StyleOption {
+    let style = stateOption && stateOption.style;
+    if (style == null && state === EMPHASIS && stateOptionNormal) {
+        style = stateOptionNormal.styleEmphasis;
+    }
+    return style;
+}
+
+
 // Usage:
 // (1) By default, `elOption.$mergeChildren` is `'byIndex'`, which indicates that
 //     the existing children will not be removed, and enables the feature that
@@ -679,7 +1355,14 @@ function doCreateOrUpdate(el, dataIndex, elOption, animatableModel, group, data,
 // child (otherwise the total indicies of the children array have to be modified).
 // User can remove a single child by set its `ignore` as `true` or replace
 // it by another element, where its `$merge` can be set as `true` if necessary.
-function mergeChildren(el, dataIndex, elOption, animatableModel, data) {
+function mergeChildren(
+    el: graphicUtil.Group,
+    dataIndex: number,
+    elOption: CustomGroupOption,
+    seriesModel: CustomSeriesModel,
+    data: List<CustomSeriesModel>
+): void {
+
     const newChildren = elOption.children;
     const newLen = newChildren ? newChildren.length : 0;
     const mergeChildren = elOption.$mergeChildren;
@@ -697,7 +1380,7 @@ function mergeChildren(el, dataIndex, elOption, animatableModel, data) {
             oldChildren: el.children() || [],
             newChildren: newChildren || [],
             dataIndex: dataIndex,
-            animatableModel: animatableModel,
+            seriesModel: seriesModel,
             group: el,
             data: data
         });
@@ -714,9 +1397,10 @@ function mergeChildren(el, dataIndex, elOption, animatableModel, data) {
             el.childAt(index),
             dataIndex,
             newChildren[index],
-            animatableModel,
+            seriesModel,
             el,
-            data
+            data,
+            false
         );
     }
     if (__DEV__) {
@@ -727,7 +1411,15 @@ function mergeChildren(el, dataIndex, elOption, animatableModel, data) {
     }
 }
 
-function diffGroupChildren(context) {
+type DiffGroupContext = {
+    oldChildren: Element[],
+    newChildren: CustomElementOption[],
+    dataIndex: number,
+    seriesModel: CustomSeriesModel,
+    group: graphicUtil.Group,
+    data: List<CustomSeriesModel>
+};
+function diffGroupChildren(context: DiffGroupContext) {
     (new DataDiffer(
         context.oldChildren,
         context.newChildren,
@@ -741,12 +1433,16 @@ function diffGroupChildren(context) {
         .execute();
 }
 
-function getKey(item, idx) {
+function getKey(item: Element, idx: number): string {
     const name = item && item.name;
     return name != null ? name : GROUP_DIFF_PREFIX + idx;
 }
 
-function processAddUpdate(newIndex, oldIndex) {
+function processAddUpdate(
+    this: DataDiffer<DiffGroupContext>,
+    newIndex: number,
+    oldIndex?: number
+): void {
     const context = this.context;
     const childOption = newIndex != null ? context.newChildren[newIndex] : null;
     const child = oldIndex != null ? context.oldChildren[oldIndex] : null;
@@ -755,50 +1451,36 @@ function processAddUpdate(newIndex, oldIndex) {
         child,
         context.dataIndex,
         childOption,
-        context.animatableModel,
+        context.seriesModel,
         context.group,
-        context.data
+        context.data,
+        false
     );
 }
 
-// `graphic#applyDefaultTextStyle` will cache
-// textFill, textStroke, textStrokeWidth.
-// We have to do this trick.
-function applyExtraBefore(extra, model) {
-    const dummyModel = new Model({}, model);
-    zrUtil.each(CACHED_LABEL_STYLE_PROPERTIES, function (stylePropName, modelPropName) {
-        if (extra.hasOwnProperty(stylePropName)) {
-            dummyModel.option[modelPropName] = extra[stylePropName];
-        }
-    });
-    return dummyModel;
-}
-
-function applyExtraAfter(itemStyle, extra) {
+function applyExtraAfter(itemStyle: ZRStyleProps, extra: ZRStyleProps): void {
     for (const key in extra) {
-        if (extra.hasOwnProperty(key)
-            || !CACHED_LABEL_STYLE_PROPERTIES.hasOwnProperty(key)
-        ) {
-            itemStyle[key] = extra[key];
+        if (zrUtil.hasOwn(extra, key)) {
+            (itemStyle as any)[key] = (extra as any)[key];
         }
     }
 }
 
-function processRemove(oldIndex) {
+function processRemove(this: DataDiffer<DiffGroupContext>, oldIndex: number): void {
     const context = this.context;
     const child = context.oldChildren[oldIndex];
     child && context.group.remove(child);
 }
 
-function getPathData(shape) {
+/**
+ * @return SVG Path data.
+ */
+function getPathData(shape: CustomSVGPathOption['shape']): string {
     // "d" follows the SVG convention.
     return shape && (shape.pathData || shape.d);
 }
 
-function hasOwnPathData(shape) {
-    return shape && (shape.hasOwnProperty('pathData') || shape.hasOwnProperty('d'));
+function hasOwnPathData(shape: CustomSVGPathOption['shape']): boolean {
+    return shape && (zrUtil.hasOwn(shape, 'pathData') || zrUtil.hasOwn(shape, 'd'));
 }
 
-function hasOwn(host, prop) {
-    return host && host.hasOwnProperty(prop);
-}
diff --git a/src/coord/CoordinateSystem.ts b/src/coord/CoordinateSystem.ts
index 2af741a..84fd2f5 100644
--- a/src/coord/CoordinateSystem.ts
+++ b/src/coord/CoordinateSystem.ts
@@ -26,6 +26,7 @@ import { BoundingRect } from '../util/graphic';
 import { MatrixArray } from 'zrender/src/core/matrix';
 import ComponentModel from '../model/Component';
 import { RectLike } from 'zrender/src/core/BoundingRect';
+import { PrepareCustomInfo } from '../chart/custom';
 
 
 export interface CoordinateSystemCreator {
@@ -151,6 +152,8 @@ export interface CoordinateSystem {
     // Currently only Cartesian2D implements it.
     // But if other coordinate systems implement it, should follow this signature.
     getAxesByScale?: (scaleType: string) => Axis[];
+
+    prepareCustoms?: PrepareCustomInfo;
 }
 
 /**
diff --git a/src/echarts.ts b/src/echarts.ts
index d60e366..08fb940 100644
--- a/src/echarts.ts
+++ b/src/echarts.ts
@@ -1729,6 +1729,9 @@ class ECharts extends Eventful {
         };
 
         updateZ = function (model: ComponentModel, view: ComponentView | ChartView): void {
+            if (model.preventAutoZ) {
+                return;
+            }
             const z = model.get('z');
             const zlevel = model.get('zlevel');
             // Set z and zlevel
@@ -1743,6 +1746,7 @@ class ECharts extends Eventful {
                         textContent.z = el.z;
                         textContent.zlevel = el.zlevel;
                         // lift z2 of text content
+                        // TODO if el.emphasis.z2 is spcefied, what about textContent.
                         textContent.z2 = el.z2 + 1;
                     }
                 }
diff --git a/src/model/Component.ts b/src/model/Component.ts
index 0d2b0c0..d1a35e9 100644
--- a/src/model/Component.ts
+++ b/src/model/Component.ts
@@ -123,6 +123,11 @@ class ComponentModel<Opt extends ComponentOption = ComponentOption> extends Mode
      */
     static layoutMode: ComponentLayoutMode | ComponentLayoutMode['type'];
 
+    /**
+     * Prevent from auto set z, zlevel, z2 by the framework.
+     */
+    preventAutoZ: boolean;
+
     // Injectable properties:
     __viewId: string;
 
diff --git a/src/model/mixin/itemStyle.ts b/src/model/mixin/itemStyle.ts
index c0c88e0..129c67f 100644
--- a/src/model/mixin/itemStyle.ts
+++ b/src/model/mixin/itemStyle.ts
@@ -44,7 +44,7 @@ type ItemStyleKeys = 'fill'
     | 'shadowOffsetY'
     | 'shadowColor';
 
-type ItemStyleProps = Pick<PathStyleProps, ItemStyleKeys>;
+export type ItemStyleProps = Pick<PathStyleProps, ItemStyleKeys>;
 
 class ItemStyleMixin {
 
diff --git a/src/util/graphic.ts b/src/util/graphic.ts
index 2875480..cadfa38 100644
--- a/src/util/graphic.ts
+++ b/src/util/graphic.ts
@@ -58,7 +58,8 @@ import {
     ColorString,
     DataModel,
     ECEventData,
-    ZRStyleProps
+    ZRStyleProps,
+    TextCommonOption
 } from './types';
 import GlobalModel from '../model/Global';
 import { makeInner } from './model';
@@ -81,13 +82,6 @@ const EMPTY_OBJ = {};
 
 export const Z2_EMPHASIS_LIFT = 10;
 
-// key: label model property nane, value: style property name.
-export const CACHED_LABEL_STYLE_PROPERTIES = {
-    color: 'textFill',
-    textBorderColor: 'textStroke',
-    textBorderWidth: 'textStrokeWidth'
-};
-
 const EMPHASIS = 'emphasis';
 const NORMAL = 'normal';
 
@@ -126,8 +120,6 @@ type TextCommonParams = {
 
     forceRich?: boolean
 
-    getTextPosition?: (textStyleModel: Model, isEmphasis?: boolean) => string | string[] | number[]
-
     defaultOutsidePosition?: LabelOption['position']
 
     textStyle?: ZRStyleProps
@@ -390,12 +382,14 @@ function singleEnterEmphasis(el: Element) {
         if (!hasFillOrStroke(emphasisStyle.stroke)) {
             disp.style.stroke = liftColor(currentStroke);
         }
-        disp.z2 += Z2_EMPHASIS_LIFT;
+        const z2EmphasisLift = (disp as ECElement).z2EmphasisLift;
+        disp.z2 += z2EmphasisLift != null ? z2EmphasisLift : Z2_EMPHASIS_LIFT;
     }
 
     const textContent = el.getTextContent();
     if (textContent) {
-        textContent.z2 += Z2_EMPHASIS_LIFT;
+        const z2EmphasisLift = (textContent as ECElement).z2EmphasisLift;
+        textContent.z2 += z2EmphasisLift != null ? z2EmphasisLift : Z2_EMPHASIS_LIFT;
     }
     // TODO hover layer
 }
@@ -770,6 +764,7 @@ export function createTextConfig(
     opt: TextCommonParams,
     isEmphasis: boolean
 ) {
+    opt = opt || {};
     const textConfig: ElementTextConfig = {};
     let labelPosition;
     let labelRotate = textStyleModel.getShallow('rotate');
@@ -778,16 +773,11 @@ export function createTextConfig(
     );
     const labelOffset = textStyleModel.getShallow('offset');
 
-    if (opt.getTextPosition) {
-        labelPosition = opt.getTextPosition(textStyleModel, isEmphasis);
-    }
-    else {
-        labelPosition = textStyleModel.getShallow('position')
-            || (isEmphasis ? null : 'inside');
-        // 'outside' is not a valid zr textPostion value, but used
-        // in bar series, and magric type should be considered.
-        labelPosition === 'outside' && (labelPosition = opt.defaultOutsidePosition || 'top');
-    }
+    labelPosition = textStyleModel.getShallow('position')
+        || (isEmphasis ? null : 'inside');
+    // 'outside' is not a valid zr textPostion value, but used
+    // in bar series, and magric type should be considered.
+    labelPosition === 'outside' && (labelPosition = opt.defaultOutsidePosition || 'top');
 
     if (labelPosition != null) {
         textConfig.position = labelPosition;
@@ -1031,7 +1021,10 @@ function setTokenTextStyle(
     }
 }
 
-export function getFont(opt: LabelOption, ecModel: GlobalModel) {
+export function getFont(
+    opt: Pick<TextCommonOption, 'fontStyle' | 'fontWeight' | 'fontSize' | 'fontFamily'>,
+    ecModel: GlobalModel
+) {
     const gTextStyleModel = ecModel && ecModel.getModel('textStyle');
     return trim([
         // FIXME in node-canvas fontWeight is before fontStyle
diff --git a/src/util/styleCompat.ts b/src/util/styleCompat.ts
new file mode 100644
index 0000000..b7e1150
--- /dev/null
+++ b/src/util/styleCompat.ts
@@ -0,0 +1,256 @@
+/*
+* 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.
+*/
+
+import { Dictionary, ZRStyleProps } from './types';
+import { ElementTextConfig } from 'zrender/src/Element';
+import { TextStyleProps, TextStylePropsPart, TextProps } from 'zrender/src/graphic/Text';
+import { each, hasOwn } from 'zrender/src/core/util';
+import { __DEV__ } from '../config';
+import { ItemStyleProps } from '../model/mixin/itemStyle';
+
+export interface LegacyStyleProps {
+    legacy?: boolean
+}
+
+const deprecatedLogs = {} as Dictionary<boolean>;
+
+/**
+ * Whether need to call `convertEC4CompatibleStyle`.
+ */
+export function isEC4CompatibleStyle(
+    style: ZRStyleProps & LegacyStyleProps,
+    elType: string,
+    hasOwnTextContentOption: boolean,
+    hasOwnTextConfig: boolean
+): boolean {
+    // Since echarts5, `RectText` is separated from its host element and style.text
+    // does not exist any more. The compat work brings some extra burden on performance.
+    // So we provide:
+    // `legacy: true` force make compat.
+    // `legacy: false`, force do not compat.
+    // `legacy` not set: auto detect wheter legacy.
+    //     But in this case we do not compat (difficult to detect and rare case):
+    //     Becuse custom series and graphic component support "merge", users may firstly
+    //     only set `textStrokeWidth` style or secondly only set `text`.
+    return style && (
+        style.legacy
+        || (
+            style.legacy !== false
+            && !hasOwnTextContentOption
+            && !hasOwnTextConfig
+            && elType !== 'tspan'
+            // Difficult to detect whether legacy for a "text" el.
+            && (elType === 'text' || hasOwn(style, 'text'))
+        )
+    );
+}
+
+/**
+ * `EC4CompatibleStyle` is style that might be in echarts4 format or echarts5 format.
+ * @param hostStyle The properties might be modified.
+ * @return If be text el, `textContentStyle` and `textConfig` will not be retured.
+ *         Otherwise a `textContentStyle` and `textConfig` will be created, whose props area
+ *         retried from the `hostStyle`.
+ */
+export function convertFromEC4CompatibleStyle(hostStyle: ZRStyleProps, elType: string, isNormal: boolean): {
+    textContent: TextProps & {type: string},
+    textConfig: ElementTextConfig
+} {
+    const srcStyle = hostStyle as Dictionary<any>;
+    let textConfig: ElementTextConfig;
+    let textContent: TextProps & {type: string};
+
+    let textContentStyle: TextStyleProps;
+    if (elType === 'text') {
+        textContentStyle = srcStyle;
+    }
+    else {
+        textContentStyle = {};
+        hasOwn(srcStyle, 'text') && (textContentStyle.text = srcStyle.text);
+        hasOwn(srcStyle, 'rich') && (textContentStyle.rich = srcStyle.rich);
+        hasOwn(srcStyle, 'textFill') && (textContentStyle.fill = srcStyle.textFill);
+        hasOwn(srcStyle, 'textStroke') && (textContentStyle.stroke = srcStyle.textStroke);
+
+        textContent = {
+            type: 'text',
+            style: textContentStyle,
+            // ec4 do not support rectText trigger.
+            // And when text postion is different in normal and emphasis
+            // => hover text trigger emphasis;
+            // => text position changed, leave mouse pointer immediately;
+            // That might cause state incorrect.
+            silent: true
+        };
+        textConfig = {};
+        const hasOwnPos = hasOwn(srcStyle, 'textPosition');
+        if (isNormal) {
+            textConfig.position = hasOwnPos ? srcStyle.textPosition : 'inside';
+        }
+        else {
+            hasOwnPos && (textConfig.position = srcStyle.textPosition);
+        }
+        hasOwn(srcStyle, 'textPosition') && (textConfig.position = srcStyle.textPosition);
+        hasOwn(srcStyle, 'textOffset') && (textConfig.offset = srcStyle.textOffset);
+        hasOwn(srcStyle, 'textRotation') && (textConfig.rotation = srcStyle.textRotation);
+        hasOwn(srcStyle, 'textDistance') && (textConfig.distance = srcStyle.textDistance);
+    }
+
+    convertEC4CompatibleRichItem(textContentStyle, hostStyle);
+
+    each(textContentStyle.rich, function (richItem) {
+        convertEC4CompatibleRichItem(richItem as TextStyleProps, richItem);
+    });
+
+    return {
+        textConfig: textConfig,
+        textContent: textContent
+    };
+}
+
+/**
+ * The result will be set to `out`.
+ */
+function convertEC4CompatibleRichItem(out: TextStylePropsPart, richItem: Dictionary<any>): void {
+    if (!richItem) {
+        return;
+    }
+    // (1) For simplicity, make textXXX properties (deprecated since ec5) has
+    // higher priority. For example, consider in ec4 `borderColor: 5, textBorderColor: 10`
+    // on a rect means `borderColor: 4` on the rect and `borderColor: 10` on an attached
+    // richText in ec5.
+    // (2) `out === richItem` if and only if `out` is text el or rich item.
+    // So we can overwite existing props in `out` since textXXX has higher priority.
+    richItem.font = richItem.textFont || richItem.font;
+    hasOwn(richItem, 'textStrokeWidth') && (out.lineWidth = richItem.textStrokeWidth);
+    hasOwn(richItem, 'textAlign') && (out.align = richItem.textAlign);
+    hasOwn(richItem, 'textVerticalAlign') && (out.verticalAlign = richItem.textVerticalAlign);
+    hasOwn(richItem, 'textLineHeight') && (out.lineHeight = richItem.textLineHeight);
+    hasOwn(richItem, 'textWidth') && (out.width = richItem.textWidth);
+    hasOwn(richItem, 'textHeight') && (out.height = richItem.textHeight);
+    hasOwn(richItem, 'textBackgroundColor') && (out.backgroundColor = richItem.textBackgroundColor);
+    hasOwn(richItem, 'textPadding') && (out.padding = richItem.textPadding);
+    hasOwn(richItem, 'textBorderColor') && (out.borderColor = richItem.textBorderColor);
+    hasOwn(richItem, 'textBorderWidth') && (out.borderWidth = richItem.textBorderWidth);
+    hasOwn(richItem, 'textBorderRadius') && (out.borderRadius = richItem.textBorderRadius);
+    hasOwn(richItem, 'textBoxShadowColor') && (out.shadowColor = richItem.textBoxShadowColor);
+    hasOwn(richItem, 'textBoxShadowBlur') && (out.shadowBlur = richItem.textBoxShadowBlur);
+    hasOwn(richItem, 'textBoxShadowOffsetX') && (out.shadowOffsetX = richItem.textBoxShadowOffsetX);
+    hasOwn(richItem, 'textBoxShadowOffsetY') && (out.shadowOffsetY = richItem.textBoxShadowOffsetY);
+}
+
+/**
+ * Convert to pure echarts4 format style.
+ * `itemStyle` will be modified, added with ec4 style properties from
+ * `textStyle` and `textConfig`.
+ *
+ * [Caveat]: For simplicity, `insideRollback` in ec4 does not compat, where
+ * `styleEmphasis: {textFill: 'red'}` will remove the normal auto added stroke.
+ */
+export function convertToEC4StyleForCustomSerise(
+    itemStl: ItemStyleProps,
+    txStl: TextStyleProps,
+    txCfg: ElementTextConfig
+): ZRStyleProps {
+
+    const out = itemStl as Dictionary<unknown>;
+
+    // See `custom.ts`, a trick to set extra `textPosition` firstly.
+    out.textPosition = out.textPosition || txCfg.position || 'inside';
+    txCfg.offset != null && (out.textOffset = txCfg.offset);
+    txCfg.rotation != null && (out.textRotation = txCfg.rotation);
+    txCfg.distance != null && (out.textDistance = txCfg.distance);
+
+    const isInside = (out.textPosition as string).indexOf('inside') >= 0;
+    const hostFill = itemStl.fill || '#000';
+
+    convertToEC4RichItem(out, txStl);
+
+    const textFillNotSet = out.textFill == null;
+    if (isInside) {
+        if (textFillNotSet) {
+            out.textFill = txCfg.insideFill || '#fff';
+            !out.textStroke && txCfg.insideStroke && (out.textStroke = txCfg.insideStroke);
+            !out.textStroke && (out.textStroke = hostFill);
+            out.textStrokeWidth == null && (out.textStrokeWidth = 2);
+        }
+    }
+    else {
+        if (textFillNotSet) {
+            out.textFill = txCfg.outsideFill || hostFill;
+        }
+        !out.textStroke && txCfg.outsideStroke && (out.textStroke = txCfg.outsideStroke);
+    }
+
+    out.text = txStl.text;
+    out.rich = txStl.rich;
+
+    each(txStl.rich, function (richItem) {
+        convertToEC4RichItem(richItem as Dictionary<unknown>, richItem);
+    });
+
+    return out;
+}
+
+function convertToEC4RichItem(out: Dictionary<unknown>, richItem: TextStylePropsPart) {
+    if (!richItem) {
+        return;
+    }
+
+    hasOwn(richItem, 'fill') && (out.textFill = richItem.fill);
+    hasOwn(richItem, 'stroke') && (out.textStroke = richItem.fill);
+
+    hasOwn(richItem, 'lineWidth') && (out.textStrokeWidth = richItem.lineWidth);
+    hasOwn(richItem, 'font') && (out.textStrokeWidth = richItem.font);
+    hasOwn(richItem, 'fontStyle') && (out.fontStyle = richItem.fontStyle);
+    hasOwn(richItem, 'fontWeight') && (out.fontWeight = richItem.fontWeight);
+    hasOwn(richItem, 'fontSize') && (out.fontSize = richItem.fontSize);
+    hasOwn(richItem, 'fontFamily') && (out.fontFamily = richItem.fontFamily);
+
+    hasOwn(richItem, 'align') && (out.textAlign = richItem.align);
+    hasOwn(richItem, 'verticalAlign') && (out.textVerticalAlign = richItem.verticalAlign);
+    hasOwn(richItem, 'lineHeight') && (out.textLineHeight = richItem.lineHeight);
+    hasOwn(richItem, 'width') && (out.textWidth = richItem.width);
+    hasOwn(richItem, 'height') && (out.textHeight = richItem.height);
+
+    hasOwn(richItem, 'backgroundColor') && (out.textBackgroundColor = richItem.backgroundColor);
+    hasOwn(richItem, 'padding') && (out.textPadding = richItem.padding);
+    hasOwn(richItem, 'borderColor') && (out.textBorderColor = richItem.borderColor);
+    hasOwn(richItem, 'borderWidth') && (out.textBorderWidth = richItem.borderWidth);
+    hasOwn(richItem, 'borderRadius') && (out.textBorderRadius = richItem.borderRadius);
+
+    hasOwn(richItem, 'shadowColor') && (out.textBoxShadowColor = richItem.shadowColor);
+    hasOwn(richItem, 'shadowBlur') && (out.textBoxShadowBlur = richItem.shadowBlur);
+    hasOwn(richItem, 'shadowOffsetX') && (out.textBoxShadowOffsetX = richItem.shadowOffsetX);
+    hasOwn(richItem, 'shadowOffsetY') && (out.textBoxShadowOffsetY = richItem.shadowOffsetY);
+
+    hasOwn(richItem, 'textShadowColor') && (out.textShadowColor = richItem.textShadowColor);
+    hasOwn(richItem, 'textShadowBlur') && (out.textShadowBlur = richItem.textShadowBlur);
+    hasOwn(richItem, 'textShadowOffsetX') && (out.textShadowOffsetX = richItem.textShadowOffsetX);
+    hasOwn(richItem, 'textShadowOffsetY') && (out.textShadowOffsetY = richItem.textShadowOffsetY);
+}
+
+export function warnDeprecated(deprecated: string, insteadApproach: string): void {
+    if (__DEV__) {
+        const key = deprecated + '^_^' + insteadApproach;
+        if (!deprecatedLogs[key]) {
+            console.warn(`DEPRECATED: "${deprecated}" has been deprecated. ${insteadApproach}`);
+            deprecatedLogs[key] = true;
+        }
+    }
+}
diff --git a/src/util/types.ts b/src/util/types.ts
index 07f773e..b74d08c 100644
--- a/src/util/types.ts
+++ b/src/util/types.ts
@@ -106,6 +106,7 @@ export interface ECElement extends Element {
     };
     highDownSilentOnTouch?: boolean;
     onStateChange?: (fromState: 'normal' | 'emphasis', toState: 'normal' | 'emphasis') => void;
+    z2EmphasisLift?: number;
 }
 
 export interface DataHost {
@@ -422,6 +423,7 @@ export type ModelOption = any;
 export type ThemeOption = Dictionary<any>;
 
 export type DisplayState = 'normal' | 'emphasis';
+export type DisplayStateNonNormal = 'emphasis';
 export type DisplayStateHostOption = {
     emphasis?: Dictionary<any>,
     [key: string]: any
diff --git a/test/circle-packing-with-d3.html b/test/circle-packing-with-d3.compat.html
similarity index 100%
copy from test/circle-packing-with-d3.html
copy to test/circle-packing-with-d3.compat.html
diff --git a/test/circle-packing-with-d3.html b/test/circle-packing-with-d3.html
index 486b6e3..06a77ec 100644
--- a/test/circle-packing-with-d3.html
+++ b/test/circle-packing-with-d3.html
@@ -45,6 +45,7 @@ text {
 <svg width="960" height="960"><g transform="translate(1,1)"></g></svg>
 <script src="https://d3js.org/d3.v4.min.js"></script>
 <script src="../dist/echarts.js"></script>
+<script src="./lib/testHelper.js"></script>
 <script>
 
 var stratify = d3.stratify()
@@ -105,6 +106,8 @@ d3.csv("data/flare.csv", function(error, rawData) {
             nodeName = nodePath.slice(nodePath.lastIndexOf('.') + 1).split(/(?=[A-Z][^A-Z])/g).join('\n');
             nodeName = echarts.format.truncateText(nodeName, itemLayout.r, textFont, '.');
         }
+        var z2 = api.value(1) * 2;
+        // console.log(api.style());
 
         return {
             type: 'circle',
@@ -113,19 +116,35 @@ d3.csv("data/flare.csv", function(error, rawData) {
                 cy: itemLayout.y,
                 r: itemLayout.r
             },
-            z2: api.value(1) * 2,
-            style: api.style({
-                text: nodeName,
-                textFont: textFont,
-                textPosition: 'inside'
-            }),
-            styleEmphasis: api.style({
+            z2: z2,
+            textContent: {
+                type: 'text',
+                style: {
+                    text: nodeName,
+                    // fill: 'blue'
+                },
+                emphasis: {
+                    style: {
+                        fontSize: 16,
+                        // fill: 'red'
+                    }
+                }
+            },
+            textConfig: {
+                position: 'inside'
+            },
+            style: {
+                fill: api.visual('color'),
                 text: nodeName,
-                textPosition: 'inside',
-                textFont: textFont,
-                stroke: 'rgba(0,0,0,0.5)',
-                lineWidth: 3
-            })
+                font: textFont,
+            },
+            emphasis: {
+                style: {
+                    font: textFont,
+                    stroke: 'rgba(0,0,0,0.5)',
+                    lineWidth: 3
+                }
+            }
         };
     }
 
@@ -165,6 +184,13 @@ d3.csv("data/flare.csv", function(error, rawData) {
 
     chart.setOption(option);
 
+    // testHelper.printElements(chart, {
+    //     attr: ['z', 'z2', 'style.text', 'style.fill', 'style.stroke'],
+    //     filter: function (el) {
+    //         return el.style && el.style.text;
+    //     }
+    // });
+
 });
 
 </script>
diff --git a/test/custom-feature.html b/test/custom-feature.html
index 7ec7696..03457ed 100644
--- a/test/custom-feature.html
+++ b/test/custom-feature.html
@@ -85,6 +85,7 @@ under the License.
                                         style: {
                                             fill: 'red',
                                             text: 'dataIndex: ' + params.dataIndex,
+                                            textFill: '#000',
                                             textStroke: '#fff',
                                             textStrokeWidth: 1
                                         }
diff --git a/test/custom-text-content.html b/test/custom-text-content.html
new file mode 100644
index 0000000..d999acd
--- /dev/null
+++ b/test/custom-text-content.html
@@ -0,0 +1,1193 @@
+<!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>
+        </style>
+
+
+        <div id="Legacy_compat_test"></div>
+        <div id="insideFill_Stroke_auto_test"></div>
+        <div id="textConfig_other_feature_test"></div>
+        <div id="insideFill_Stroke_hover_test"></div>
+        <div id="z_order_test"></div>
+        <div id="hover_style_disable"></div>
+        <div id="hover_style_remove"></div>
+
+
+        <!-- Utils ----------------------------------------------- -->
+        <script>
+            function createMaker(opt) {
+                opt = opt || {};
+                var xStart = opt.xCurr || 80;
+                var xCurr = xStart;
+                var yStart = opt.yCurr || 20;
+                var yCurr = yStart;
+                var xStep = opt.xStep || 100;
+                var yStep = opt.yStep || 65;
+                var colCount = 0;
+                var yMax = 0;
+                var maxCol = opt.maxCol || Infinity;
+                var children = [];
+
+                function createRectShape(width, height) {
+                    width = width || 20;
+                    height = height || 30;
+                    return {x: -width / 2, y: 0, width: width, height: height};
+                }
+
+                function makeGraphic(text, creators) {
+                    var y = yCurr;
+                    children.push({
+                        type: 'text', x: xCurr, y: y,
+                        style: {text: text, fill: '#900', align: 'center', fontSize: 10}
+                    });
+                    y += yStep;
+                    for (var i = 0; i < creators.length; i++) {
+                        children.push(creators[i](xCurr, y));
+                        y += yStep;
+                    }
+                    xCurr += xStep;
+
+                    yMax = Math.max(y, yMax);
+
+                    colCount++;
+                    if (colCount >= maxCol) {
+                        colCount = 0;
+                        xCurr = xStart;
+                        yCurr = yMax + yStep * 0.7;
+                    }
+                }
+
+                return {
+                    makeGraphic: makeGraphic,
+                    createRectShape: createRectShape,
+                    children: children
+                };
+            }
+
+            // opt: {globalColor, backgroundColor}
+            function createOption(renderItem, opt) {
+                return {
+                    animation: false,
+                    backgroundColor: opt && opt.backgroundColor,
+                    color: opt && opt.globalColor,
+                    xAxis: {axisLine: {lineStyle: {color: 'rgba(0,0,0,0.2)'}}, axisLabel: {show: false}, splitLine: {show: false}},
+                    yAxis: {axisLine: {lineStyle: {color: 'rgba(0,0,0,0.2)'}}, axisLabel: {show: false}, splitLine: {show: false}},
+                    grid: {left: 30},
+                    series: {
+                        type: 'custom',
+                        renderItem: renderItem,
+                        data: [[1, 1]]
+                    }
+                };
+            }
+        </script>
+        <!-- --------------------------------------------------- -->
+
+
+
+
+
+
+        <script>
+        require(['echarts'], function (echarts) {
+
+            function renderItem(params, api) {
+                var maker = createMaker({xStep: 120, yStep: 60, maxCol: 5});
+                var makeGraphic = maker.makeGraphic;
+                var createRectShape = maker.createRectShape;
+
+                makeGraphic([
+                    'normal: green rect', 'inside orange'
+                ].join('\n'), [
+                    function (x, y) {
+                        return {
+                            type: 'rect', position: [x, y], shape: createRectShape(),
+                            style: {
+                                // textPosition not set but by default 'inside'
+                                text: 'legacy1', fill: 'green', textFill: 'orange'
+                            }
+                        };
+                    },
+                    function (x, y) {
+                        return {
+                            type: 'rect', x: x, y: y, shape: createRectShape(),
+                            style: {fill: 'green'},
+                            textContent: {style: {text: 'ec5_api', fill: 'orange'}},
+                            textConfig: {position: 'inside'}
+                        };
+                    }
+                ]);
+
+                makeGraphic([
+                    'normal: green rect', 'inside orange', 'text is number 0', 'should be displayed'
+                ].join('\n'), [
+                    function (x, y) {
+                        return {
+                            type: 'rect', position: [x, y], shape: createRectShape(),
+                            style: {
+                                // text is number 0, can be displayed
+                                text: 0, fill: 'green', textFill: 'orange'
+                            }
+                        };
+                    },
+                    function (x, y) {
+                        return {
+                            type: 'rect', x: x, y: y, shape: createRectShape(),
+                            style: {fill: 'green'},
+                            textContent: {style: {text: 0, fill: 'orange'}},
+                            textConfig: {position: 'inside'}
+                        };
+                    }
+                ]);
+
+                makeGraphic([
+                    'normal: green rect', 'inside white/bordered', 'hover: red rect', 'inside white/bordered'
+                ].join('\n'), [
+                    function (x, y) {
+                        return {
+                            type: 'rect', position: [x, y], shape: createRectShape(),
+                            style: api.style({text: 'legacy0'}),
+                            styleEmphasis: {fill: 'red'}
+                        };
+                    },
+                    function (x, y) {
+                        return {
+                            type: 'rect', position: [x, y], shape: createRectShape(),
+                            style: {
+                                text: 'legacy1', fill: 'green', textPosition: 'inside',
+                                textFill: '#fff', textStroke: 'green', textStrokeWidth: 2
+                            },
+                            styleEmphasis: {fill: 'red'}
+                        };
+                    },
+                    function (x, y) {
+                        return {
+                            type: 'rect', x: x, y: y, shape: createRectShape(),
+                            style: {fill: 'green'},
+                            textContent: {style: {text: 'ec5_api'}},
+                            textConfig: {position: 'inside'},
+                            emphasis: {style: {fill: 'red'}}
+                        };
+                    }
+                ]);
+
+                makeGraphic([
+                    'normal: green rect', 'bottom green', 'hover: red rect', 'bottom green'
+                ].join('\n'), [
+                    function (x, y) {
+                        return {
+                            type: 'rect', position: [x, y], shape: createRectShape(),
+                            style: api.style({text: 'legacy0', textPosition: 'bottom'}),
+                            styleEmphasis: {fill: 'red'}
+                        };
+                    },
+                    function (x, y) {
+                        return {
+                            type: 'rect', x: x, y: y, shape: createRectShape(),
+                            style: {fill: 'green'},
+                            textContent: {style: {text: 'ec5_api'}},
+                            textConfig: {position: 'bottom', outsideFill: 'green'},
+                            emphasis: {style: {fill: 'red'}}
+                        };
+                    }
+                ]);
+
+                makeGraphic([
+                    'green rect', 'normal: inside white/bordered', 'hover: inside red'
+                ].join('\n'), [
+                    function (x, y) {
+                        return {
+                            type: 'rect', position: [x, y], shape: createRectShape(),
+                            style: api.style({text: 'legacy0'}),
+                            styleEmphasis: {textFill: 'red'}
+                        };
+                    },
+                    function (x, y) {
+                        return {
+                            type: 'rect', position: [x, y], shape: createRectShape(),
+                            style: {
+                                text: 'legacy1', fill: 'green', textPosition: 'inside',
+                                textFill: '#fff', textStroke: 'green', textStrokeWidth: 2
+                            },
+                            styleEmphasis: {textFill: 'red'}
+                        };
+                    },
+                    function (x, y) {
+                        return {
+                            type: 'rect', x: x, y: y, shape: createRectShape(),
+                            style: {fill: 'green'},
+                            textContent: {
+                                style: {text: 'ec5_api'},
+                                emphasis: {style: {fill: 'red', stroke: 'green', lineWidth: 2}}
+                            },
+                            textConfig: {position: 'inside'}
+                        };
+                    }
+                ]);
+
+                makeGraphic([
+                    'green rect', 'normal: bottom red', 'hover: bottom blue'
+                ].join('\n'), [
+                    function (x, y) {
+                        return {
+                            type: 'rect', position: [x, y], shape: createRectShape(),
+                            style: api.style({text: 'legacy0', textPosition: 'bottom', textFill: 'red'}),
+                            styleEmphasis: {textFill: 'blue'}
+                        };
+                    },
+                    function (x, y) {
+                        return {
+                            type: 'rect', x: x, y: y, shape: createRectShape(),
+                            style: {fill: 'green'},
+                            textContent: {
+                                style: {text: 'ec5_api', fill: 'red'},
+                                emphasis: {style: {fill: 'blue'}}
+                            },
+                            textConfig: {position: 'bottom'}
+                        };
+                    }
+                ]);
+
+                makeGraphic(['green rect', 'normal: inside white/borded', 'hover: bottom'].join('\n'), [
+                    function (x, y) {
+                        return {
+                            type: 'rect', position: [x, y], shape: createRectShape(),
+                            style: api.style({text: 'legacy0'}),
+                            // Hover not compat to ec4 (to complicated)
+                            styleEmphasis: {textPosition: 'bottom'}
+                        };
+                    }
+                ]);
+
+                makeGraphic(['green rect', 'normal: inside red', 'hover: bottom red'].join('\n'), [
+                    function (x, y) {
+                        return {
+                            type: 'rect', position: [x, y], shape: createRectShape(),
+                            style: api.style({text: 'legacy0', textFill: 'red'}),
+                            styleEmphasis: {textPosition: 'bottom'}
+                        };
+                    }
+                ]);
+
+                makeGraphic(['green rect', 'normal: inside white', 'hover: bottom red'].join('\n'), [
+                    function (x, y) {
+                        return {
+                            type: 'rect', position: [x, y], shape: createRectShape(),
+                            style: api.style({text: 'legacy0', textFill: 'white'}),
+                            styleEmphasis: {textFill: 'red', textPosition: 'bottom'}
+                        };
+                    }
+                ]);
+
+                makeGraphic(['green rect', 'normal: inside white/bordered', 'hover: auto lift'].join('\n'), [
+                    function (x, y) {
+                        return {
+                            type: 'rect', position: [x, y], shape: createRectShape(),
+                            style: api.style({text: 'legacy0'})
+                        };
+                    },
+                    function (x, y) {
+                        return {
+                            type: 'rect', x: x, y: y, shape: createRectShape(),
+                            style: {fill: 'green'},
+                            textContent: {style: {text: 'ec5_api'}},
+                            textConfig: {position: 'inside'}
+                        };
+                    }
+                ]);
+
+                makeGraphic(['green rect', 'normal: bottom green', 'hover: auto lift'].join('\n'), [
+                    function (x, y) {
+                        return {
+                            type: 'rect', position: [x, y], shape: createRectShape(),
+                            style: api.style({text: 'legacy0', textPosition: 'bottom'})
+                        };
+                    },
+                    function (x, y) {
+                        return {
+                            type: 'rect', x: x, y: y, shape: createRectShape(),
+                            style: {fill: 'green'},
+                            textContent: {style: {text: 'ec5_api', fill: 'green'}},
+                            textConfig: {position: 'bottom'}
+                        };
+                    }
+                ]);
+
+                return {
+                    type: 'group',
+                    children: maker.children
+                };
+            }
+
+            var chart = testHelper.create(echarts, 'Legacy_compat_test', {
+                title: [
+                    'Legacy compat test',
+                    'Each column rects are the same effect implemented by',
+                    'legacy API and corresponding new API.',
+                    'So **each column** must be **the same result**',
+                    '(except text string and some ec5 enhancement).',
+                    '**Hover** also needs to be tested'
+                ],
+                height: 550,
+                option: createOption(renderItem, {globalColor: ['green']})
+            });
+        });
+        </script>
+
+
+
+
+
+
+
+
+
+
+        <script>
+        require(['echarts'], function (echarts) {
+            function renderItem() {
+                var maker = createMaker({maxCol: 5});
+                var makeGraphic = maker.makeGraphic;
+                var createRectShape = maker.createRectShape;
+
+                // opt for example:
+                // opt.type
+                // opt.shape
+                // opt.textConfig: {},
+                // opt.textContent: {}, if rich: textContent.style: {rich: {i: xxx}}
+                // opt.emphasis: {}
+                // opt.extraTitle: string[]
+                function makeTest(opt) {
+                    var isRich = opt.textContent.style.rich;
+                    var type = opt.type || 'rect';
+                    var shape = opt.shape || createRectShape(40, 120);
+                    var title = ['green rect', (isRich ? 'rich text' : 'plain text')];
+                    opt.extraTitle && (title = title.concat(opt.extraTitle));
+                    makeGraphic(title.join('\n'), [
+                        function (x, y) {
+                            var result = {
+                                type: type, x: x, y: y, shape: shape, style: {fill: 'green'},
+                                textConfig: opt.textConfig,
+                                textContent: opt.textContent
+                            }
+                            if (opt.emphasis) {
+                                result.emphasis = opt.emphasis;
+                            }
+                            return result;
+                        }
+                    ]);
+                }
+
+                makeTest({
+                    textConfig: {position: 'insideTop', rotation: -0.5 * Math.PI},
+                    textContent: {style: {
+                        text: '90 rotated text align with:\nv: top h: middle\ndefault distance',
+                        align: 'left', verticalAlign: 'middle'
+                    }}
+                });
+
+                makeTest({
+                    textConfig: {position: 'insideTop', rotation: -0.5 * Math.PI, distance: 0},
+                    textContent: {style: {
+                        text: '90 rotated text align with:\n v: top h: middle\nno distance',
+                        align: 'left', verticalAlign: 'middle'
+                    }}
+                });
+
+                makeTest({
+                    textConfig: {position: 'insideTop', rotation: -0.5 * Math.PI, distance: 0, offset: [0, -25]},
+                    textContent: {style: {
+                        text: '90 rotated text outside right rect\nalign top',
+                        fill: '#700',
+                        align: 'left', verticalAlign: 'bottom'
+                    }}
+                });
+
+                makeTest({
+                    textConfig: {position: 'insideTop', rotation: -0.5 * Math.PI},
+                    textContent: {style: {
+                        text: '90 rotated text align with:\n{i|v: top h: middle}\n{a|align right}',
+                        align: 'left', verticalAlign: 'middle',
+                        rich: {
+                            i: {fontSize: 20, fill: 'orange'},
+                            a: {align: 'right'}
+                        }
+                    }}
+                });
+
+                makeTest({
+                    extraTitle: ['emphasis:\nalign/verticalAlign rollback'],
+                    textConfig: {position: 'left'},
+                    textContent: {
+                        style: {
+                            text: '--------\nalign\nright\n----',
+                            fill: '#700'
+                        }
+                    },
+                    emphasis: {
+                        textConfig: {position: null}
+                    }
+                });
+
+                makeTest({
+                    type: 'circle',
+                    shape: {cx: 0, cy: 20, r: 30},
+                    textConfig: {
+                        position: 'right',
+                    },
+                    textContent: {
+                        rotation: -0.3 * Math.PI,
+                        originX: -35,
+                        style: {
+                            text: 'Rotate, outside, origin is center',
+                            fontSize: 20,
+                            fill: '#700',
+                            align: 'left',
+                            verticalAlign: 'middle'
+                        }
+                    }
+                });
+
+                makeTest({
+                    type: 'circle',
+                    shape: {cx: 0, cy: 20, r: 30},
+                    textConfig: {
+                        position: 'inside', offset: [35, 0]
+                    },
+                    textContent: {
+                        rotation: -0.3 * Math.PI,
+                        style: {
+                            text: 'Rotate, outside, origin is center',
+                            fontSize: 20,
+                            fill: '#700',
+                            align: 'left',
+                            verticalAlign: 'middle'
+                        }
+                    }
+                });
+
+                return {
+                    type: 'group',
+                    children: maker.children
+                };
+            }
+
+            var chart = testHelper.create(echarts, 'textConfig_other_feature_test', {
+                title: [
+                    'textConfig other feature test'
+                ],
+                option: createOption(renderItem),
+                // recordCanvas: true,
+                height: 800
+            });
+        });
+        </script>
+
+
+
+
+
+
+
+
+
+
+
+
+        <script>
+        require(['echarts'], function (echarts) {
+            function renderItem() {
+                var maker = createMaker({maxCol: 6});
+                var makeGraphic = maker.makeGraphic;
+                var createRectShape = maker.createRectShape;
+
+                // opt for example:
+                // opt.textConfig: {},
+                // opt.textContentStyle: {}, if rich: textContentStyle: {rich: {i: xxx}}
+                // opt.expect: [ {fill: 'white', stroke: 'green'},  {fill: 'white', stroke: 'green'} ]
+                function makeTest(opt) {
+                    var expect0 = opt.expect[0];
+                    var expect1 = opt.expect[1];
+                    var isRich = opt.textContentStyle.rich;
+                    makeGraphic(['green rect', (isRich ? 'rich text' : 'plain text')].join('\n'), [
+                        function (x, y) {
+                            var text = [
+                                isRich ? '{i|inside}' : 'inside',
+                                'fill: ' + expect0.fill,
+                                'stroke: ' + expect0.stroke
+                            ].join('\n');
+                            return {
+                                type: 'rect', x: x, y: y, shape: createRectShape(), style: {fill: 'green'},
+                                textConfig: echarts.util.extend({position: 'inside'}, opt.textConfig),
+                                textContent: {
+                                    style: echarts.util.extend({text: text}, opt.textContentStyle)
+                                }
+                            }
+                        },
+                        function (x, y) {
+                            var text = [
+                                isRich ? '{i|bottom}' : 'bottom',
+                                'fill: ' + expect1.fill,
+                                'stroke: ' + expect1.stroke
+                            ].join('\n');
+                            return {
+                                type: 'rect', x: x, y: y, shape: createRectShape(), style: {fill: 'green'},
+                                textConfig: echarts.util.extend({position: 'bottom'}, opt.textConfig),
+                                textContent: {
+                                    style: echarts.util.extend({text: text}, opt.textContentStyle)
+                                }
+                            }
+                        }
+                    ]);
+                }
+
+                makeTest({
+                    textConfig: {},
+                    textContentStyle: {},
+                    expect: [
+                        {fill: 'white', stroke: 'green'},
+                        {fill: 'black', stroke: 'none'}
+                    ]
+                });
+
+                makeTest({
+                    textConfig: {},
+                    textContentStyle: {backgroundColor: '#aaa'},
+                    expect: [
+                        {fill: 'white', stroke: 'none'},
+                        {fill: 'black', stroke: 'none'}
+                    ]
+                });
+
+                makeTest({
+                    textConfig: {},
+                    textContentStyle: {borderColor: '#aaa', borderWidth: 2, padding: 5},
+                    expect: [
+                        {fill: 'white', stroke: 'none'},
+                        {fill: 'black', stroke: 'none'}
+                    ]
+                });
+
+                makeTest({
+                    textConfig: {},
+                    textContentStyle: {rich: {i: {fontSize: 30}}},
+                    expect: [
+                        {fill: 'white', stroke: 'green'},
+                        {fill: 'black', stroke: 'none'}
+                    ]
+                });
+
+                makeTest({
+                    textConfig: {},
+                    textContentStyle: {rich: {i: {backgroundColor: '#aaa'}}},
+                    expect: [
+                        {fill: 'white', stroke: 'none'},
+                        {fill: 'black', stroke: 'none'}
+                    ]
+                });
+
+                makeTest({
+                    textConfig: {},
+                    textContentStyle: {rich: {i: {borderColor: '#aaa', borderWidth: 2, fontSize: 20}}},
+                    expect: [
+                        {fill: 'white', stroke: 'none'},
+                        {fill: 'black', stroke: 'green'}
+                    ]
+                });
+
+                makeTest({
+                    textConfig: {},
+                    textContentStyle: {fill: 'orange'},
+                    expect: [
+                        {fill: 'orange', stroke: 'none'},
+                        {fill: 'orange', stroke: 'none'}
+                    ]
+                });
+
+                makeTest({
+                    textConfig: {},
+                    textContentStyle: {fill: 'orange', stroke: 'blue', lineWidth: 2},
+                    expect: [
+                        {fill: 'orange', stroke: 'blue'},
+                        {fill: 'orange', stroke: 'blue'}
+                    ]
+                });
+
+                makeTest({
+                    textConfig: {},
+                    textContentStyle: {stroke: 'blue', lineWidth: 2},
+                    expect: [
+                        {fill: 'white', stroke: 'blue'},
+                        {fill: 'black', stroke: 'blue'}
+                    ]
+                });
+
+                makeTest({
+                    textConfig: {insideFill: 'yellow'},
+                    textContentStyle: {},
+                    expect: [
+                        {fill: 'yellow', stroke: 'green'},
+                        {fill: 'black', stroke: 'none'}
+                    ]
+                });
+
+                makeTest({
+                    textConfig: {insideFill: 'yellow'},
+                    textContentStyle: {fill: 'orange'},
+                    expect: [
+                        {fill: 'orange', stroke: 'none'},
+                        {fill: 'orange', stroke: 'none'}
+                    ]
+                });
+
+                makeTest({
+                    textConfig: {insideFill: 'yellow', insideStroke: 'blue'},
+                    textContentStyle: {},
+                    expect: [
+                        {fill: 'yellow', stroke: 'blue'},
+                        {fill: 'black', stroke: 'none'}
+                    ]
+                });
+
+                makeTest({
+                    textConfig: {insideFill: 'yellow', insideStroke: 'blue'},
+                    textContentStyle: {fill: 'pink'},
+                    expect: [
+                        {fill: 'pink', stroke: 'blue'},
+                        {fill: 'pink', stroke: 'none'}
+                    ]
+                });
+
+                makeTest({
+                    textConfig: {insideStroke: 'blue'},
+                    textContentStyle: {},
+                    expect: [
+                        {fill: 'white', stroke: 'blue'},
+                        {fill: 'black', stroke: 'none'}
+                    ]
+                });
+
+                makeTest({
+                    textConfig: {outsideFill: 'blue', outsideStroke: 'red'},
+                    textContentStyle: {},
+                    expect: [
+                        {fill: 'white', stroke: 'green'},
+                        {fill: 'blue', stroke: 'red'}
+                    ]
+                });
+                makeTest({
+                    textConfig: {outsideFill: 'blue', outsideStroke: 'red'},
+                    textContentStyle: {fill: 'pink'},
+                    expect: [
+                        {fill: 'pink', stroke: 'none'},
+                        {fill: 'pink', stroke: 'red'}
+                    ]
+                });
+
+                return {
+                    type: 'group',
+                    children: maker.children
+                };
+            }
+
+            var chart = testHelper.create(echarts, 'insideFill_Stroke_auto_test', {
+                title: [
+                    'insideFill/insideStroke outsideFill/outsideStroke auto rule test'
+                ],
+                option: createOption(renderItem, {backgroundColor: '#ddd'}),
+                height: 800
+            });
+        });
+        </script>
+
+
+
+
+
+
+
+
+
+        <script>
+        require(['echarts'], function (echarts) {
+
+            function renderItem(params, api) {
+                var maker = createMaker({xCurr: 150, xStep: 150, yStep: 90});
+                var makeGraphic = maker.makeGraphic;
+                var createRectShape = maker.createRectShape;
+
+                makeGraphic(['insideFill/Stroke not set', 'normal: white/bordered'].join('\n'), [
+                    function (x, y) {
+                        return {
+                            type: 'rect', x: x, y: y, shape: createRectShape(),
+                            style: {fill: 'green'},
+                            textContent: {
+                                type: 'text', silent: true, style: {
+                                    text: 'if hover,\ntop black'
+                                }
+                            },
+                            textConfig: {position: 'inside'},
+                            emphasis: {
+                                textConfig: {position: 'top'}
+                            }
+                        };
+                    },
+                    function (x, y) {
+                        return {
+                            type: 'rect', x: x, y: y, shape: createRectShape(),
+                            style: {fill: 'green'},
+                            textContent: {type: 'text', silent: true, style: {text: 'if hover,\nright green'}},
+                            textConfig: {
+                                position: 'inside', outsideFill: 'green'
+                            },
+                            emphasis: {
+                                textConfig: {position: 'right'}
+                            }
+                        };
+                    },
+                    function (x, y) {
+                        return {
+                            type: 'rect', x: x, y: y, shape: createRectShape(),
+                            style: {fill: 'green'},
+                            textContent: {
+                                silent: true, style: {text: 'if hover,\nbottom\nlarge bordered red'},
+                                emphasis: {style: {fontSize: 20}}
+                            },
+                            textConfig: {
+                                position: 'inside', outsideFill: 'green', outsideStroke: 'red'
+                            },
+                            emphasis: {
+                                textConfig: {position: 'bottom'}
+                            }
+                        };
+                    },
+                    function (x, y) {
+                        return {
+                            type: 'rect', x: x, y: y, shape: createRectShape(),
+                            style: {fill: 'green'},
+                            textContent: {
+                                silent: true,
+                                style: {
+                                    text: 'rich text\nif hover,\nbottom black\n{r|normal orange\nhover red}',
+                                    rich: {r: {fontSize: 16, fill: 'orange'}},
+                                },
+                                emphasis: {
+                                    style: {
+                                        rich: {r: {fill: 'red'}}
+                                    }
+                                }
+                            },
+                            textConfig: {position: 'inside'},
+                            emphasis: {
+                                textConfig: {position: 'bottom'}
+                            }
+                        };
+                    }
+                ]);
+
+                makeGraphic(['green rect'].join('\n'), [
+                    function (x, y) {
+                        return {
+                            type: 'rect', x: x, y: y, shape: createRectShape(),
+                            style: {fill: 'green'},
+                            textContent: {silent: true, style: {
+                                text: 'normal:\ninside yellow/bordered\nhover:\ntop black'
+                            }},
+                            textConfig: {position: 'inside', insideFill: 'yellow'},
+                            emphasis: {
+                                textConfig: {position: 'top'}
+                            }
+                        };
+                    },
+                    function (x, y) {
+                        return {
+                            type: 'rect', x: x, y: y, shape: createRectShape(),
+                            style: {fill: 'green'},
+                            textContent: {
+                                silent: true,
+                                style: {text: 'normal:\ninside white/bordered\nhover:\nright green'}
+                            },
+                            textConfig: {
+                                position: 'inside', outsideFill: 'green'
+                            },
+                            emphasis: {
+                                textConfig: {position: 'right'}
+                            }
+                        };
+                    }
+                ]);
+
+                return {
+                    type: 'group',
+                    children: maker.children
+                };
+            }
+
+            var chart = testHelper.create(echarts, 'insideFill_Stroke_hover_test', {
+                title: [
+                    'insideFill/Stroke & hover test. Please **hover any of them**'
+                ],
+                option: createOption(renderItem),
+                height: 600
+            });
+        });
+        </script>
+
+
+
+
+
+
+
+
+
+
+
+
+        <script>
+        require(['echarts'], function (echarts) {
+
+            function renderItem(params, api) {
+                return {
+                    type: 'group',
+                    children: [{
+                        type: 'circle',
+                        shape: {cx: 100, cy: 100, r: 50},
+                        style: {fill: 'red'},
+                        z2: 100,
+                        // textContent do not set z2, but auto follow the circle z2.
+                        textContent: {
+                            type: 'text',
+                            style: {
+                                text: 'normal: {red|red} is over {green|green}.\nHover me',
+                                // stroke: '#fff', lineWidth: 2,
+                                rich: {
+                                    red: {fill: 'red', stroke: '#fff', lineWidth: 2, fontSize: 20},
+                                    green: {fill: 'green', stroke: '#fff', lineWidth: 2, fontSize: 20}
+                                },
+                            },
+                            silent: true,
+                            emphasis: {
+                                style: {
+                                    text: 'emphasis: {green|green} over {red|red}\nText {below|below} green',
+                                    rich: {
+                                        below: {fontSize: 40}
+                                    }
+                                }
+                            }
+                        },
+                        textConfig: {
+                            position: 'inside'
+                        }
+                    }, {
+                        type: 'circle',
+                        shape: {cx: 100, cy: 140, r: 40},
+                        style: {fill: 'green'},
+                        // textContent do not set z2, but auto follow the circle z2.
+                        textContent: {
+                            type: 'text',
+                            style: {
+                                text: 'text should be always\n{over|over} all circles.',
+                                rich: {over: {fontSize: 30}}
+                            },
+                            silent: true
+                        },
+                        textConfig: {
+                            position: 'inside'
+                        },
+                        z2: 80,
+                        emphasis: {
+                            z2: 110
+                        }
+
+                    }, {
+                        type: 'circle',
+                        shape: {cx: 300, cy: 100, r: 50},
+                        style: {fill: 'red'},
+                        z2: 100,
+                        // textContent do not set z2, but auto follow the circle z2.
+                        textContent: {
+                            type: 'text',
+                            style: {
+                                text: 'normal: {red|red} is over {green|green}.\nHover me',
+                                // stroke: '#fff', lineWidth: 2,
+                                rich: {
+                                    red: {fill: 'red', stroke: '#fff', lineWidth: 2, fontSize: 20},
+                                    green: {fill: 'green', stroke: '#fff', lineWidth: 2, fontSize: 20}
+                                },
+                            },
+                            silent: true,
+                            emphasis: {
+                                style: {
+                                    text: 'emphasis: {red|red} over {green|green}\nText {below|over} green',
+                                    rich: {
+                                        below: {fontSize: 40}
+                                    }
+                                }
+                            }
+                        },
+                        textConfig: {
+                            position: 'inside'
+                        }
+                    }, {
+                        type: 'circle',
+                        shape: {cx: 300, cy: 140, r: 40},
+                        style: {fill: 'green'},
+                        // textContent do not set z2, but auto follow the circle z2.
+                        textContent: {
+                            type: 'text',
+                            style: {
+                                text: 'text should be always\n{over|over} all circles.',
+                                rich: {over: {fontSize: 30}}
+                            },
+                            silent: true,
+                            emphasis: {
+                                z2: 110
+                            }
+                        },
+                        textConfig: {
+                            position: 'inside'
+                        },
+                        z2: 80
+
+                    }]
+                };
+            }
+
+            var chart = testHelper.create(echarts, 'z_order_test', {
+                title: [
+                    'z order test. Please **hover any of them**.'
+                ],
+                option: createOption(renderItem)
+            });
+        });
+        </script>
+
+
+
+
+
+
+
+
+        <script>
+        require(['echarts'], function (echarts) {
+
+            var option = {
+                xAxis: {scale: true, min: 0.75, max: 3},
+                yAxis: {},
+                series: [{
+                    type: 'custom',
+                    renderItem: function (params, api) {
+                        var shape0 = {x: 0, y: 30, width: 100, height: 50};
+                        var shape1 = {x: 30, y: 50, width: 100, height: 50};
+                        var shape2 = {x: 60, y: 70, width: 100, height: 50};
+                        var position = api.coord([api.value(0), api.value(1)]);
+                        var name = api.value(2);
+
+                        var map = {
+                            legacy: {
+                                type: 'group',
+                                x: position[0],
+                                y: position[1],
+                                children: [{
+                                    type: 'rect',
+                                    shape: shape0,
+                                    style: {fill: '#333'}
+                                }, {
+                                    type: 'rect',
+                                    shape: shape1,
+                                    style: {
+                                        fill: '#555',
+                                        text: 'has inner text',
+                                        textFill: 'white',
+                                        textPostion: 'insideTop'
+                                    },
+                                    styleEmphasis: {textFill: 'yellow'}
+                                }, {
+                                    type: 'rect',
+                                    shape: shape2,
+                                    style: {fill: '#500'},
+                                    styleEmphasis: false
+                                }]
+                            },
+                            ec5: {
+                                type: 'group',
+                                x: position[0],
+                                y: position[1],
+                                children: [{
+                                    type: 'rect',
+                                    shape: shape0,
+                                    style: {fill: '#333'},
+                                }, {
+                                    type: 'rect',
+                                    shape: shape1,
+                                    style: {fill: '#555', text: 'has inner text'},
+                                    textContent: {
+                                        style: {fill: 'white'},
+                                        emphasis: {style: {fill: 'yellow'}}
+                                    },
+                                    textConfig: {position: 'insideTop'}
+                                }, {
+                                    type: 'rect',
+                                    shape: shape2,
+                                    style: {fill: '#500'},
+                                    emphasis: {
+                                        // set false to disable lift color and z2.
+                                        style: false
+                                    }
+                                }]
+                            }
+                        };
+
+                        return map[name];
+                    },
+                    data: [[1, 1, 'legacy'], [2, 1, 'ec5']]
+                }]
+            };
+
+            var chart = testHelper.create(echarts, 'hover_style_disable', {
+                title: [
+                    'Hover style disable: hover each group of elements,',
+                    'all of the elements should lift color and z2 **except the red rect**'
+                ],
+                height: 300,
+                option: option
+            });
+        });
+        </script>
+
+
+
+
+
+
+
+
+        <script>
+        require(['echarts'], function (echarts) {
+
+            var option = {
+                xAxis: {scale: true, min: 0.75, max: 3},
+                yAxis: {},
+                series: [{
+                    type: 'custom',
+                    renderItem: function (params, api) {
+                        var shape0 = {x: 00, y: 30, width: 100, height: 50};
+                        var shape1 = {x: 30, y: 50, width: 100, height: 50};
+                        var shape2 = {x: 60, y: 70, width: 100, height: 50};
+                        var position = api.coord([api.value(0), api.value(1)]);
+                        var name = api.value(2);
+                        var useHover = !!api.value(3);
+                        var map = {
+                            legacy: {
+                                type: 'group',
+                                x: position[0],
+                                y: position[1],
+                                children: [{
+                                    type: 'rect',
+                                    shape: shape0,
+                                    style: api.style({fill: '#222', text: 'legacy'}),
+                                    styleEmphasis: useHover ? {fill: 'red', textFill: 'yellow'} : false
+                                }]
+                            },
+                            ec5: {
+                                type: 'group',
+                                x: position[0],
+                                y: position[1],
+                                children: [{
+                                    type: 'rect',
+                                    shape: shape0,
+                                    style: {fill: '#222'},
+                                    textContent: {
+                                        style: {text: 'ec5_api'},
+                                        emphasis: {style: useHover ? {fill: 'yellow'} : false}
+                                    },
+                                    textConfig: {position: 'inside'},
+                                    emphasis: {style: useHover ? {fill: 'red'} : false}
+                                }]
+                            }
+                        };
+
+                        return map[name];
+                    },
+                    data: [[1, 1, 'legacy', 1], [2, 1, 'ec5', 1]]
+                }]
+            };
+
+            var chart = testHelper.create(echarts, 'hover_style_remove', {
+                title: [
+                    'Hover style remove test: ',
+                    '(1) Hover each: become **red rect, yellow text**',
+                    '(2) Click "disable hover style", elements should more to right a bit.',
+                    '(3) Hover each again: should no hover style',
+                    '(4) Click "enable hover style", elements should more to left a bit.',
+                    '(5) Check whether resume to (1)',
+                ],
+                height: 300,
+                option: option,
+                buttons: [{
+                    text: 'disable hover style',
+                    onclick: function () {
+                        chart.setOption({
+                            xAxis: {scale: true, min: 0.75, max: 7},
+                            series: {
+                                type: 'custom',
+                                data: [[3, 1, 'legacy', 0], [6, 1, 'ec5', 0]]
+                            }
+                        })
+                    }
+                }, {
+                    text: 'enable hover style',
+                    onclick: function () {
+                        chart.setOption({
+                            xAxis: {scale: true, min: 0.75, max: 3},
+                            series: {
+                                type: 'custom',
+                                data: [[1, 1, 'legacy', 1], [2, 1, 'ec5', 1]]
+                            }
+                        })
+                    }
+                }]
+            });
+        });
+        </script>
+
+
+
+
+    </body>
+</html>
+
diff --git a/test/hoverStyle.html b/test/hoverStyle.html
index 6a5bca7..245068c 100644
--- a/test/hoverStyle.html
+++ b/test/hoverStyle.html
@@ -680,7 +680,8 @@ under the License.
                         // silent: true,
                         label: {
                             show: true,
-                            silent: true,
+                            // silent: true,
+                            position: 'top'
                         },
                         itemStyle: {
                             color: 'green',


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


[incubator-echarts] 05/10: feature: add duration animation for custom series.

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

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

commit c56e2dcd1e553780b91c6a004c1a06b770f082bf
Author: 100pah <su...@gmail.com>
AuthorDate: Wed May 13 04:08:33 2020 +0800

    feature: add duration animation for custom series.
---
 src/chart/custom.ts      |  54 +++++-
 test/custom-feature.html | 442 ++++++++++++++++++++++++++++++++++++++++++++++-
 2 files changed, 494 insertions(+), 2 deletions(-)

diff --git a/src/chart/custom.ts b/src/chart/custom.ts
index 3ed15b9..a58633d 100644
--- a/src/chart/custom.ts
+++ b/src/chart/custom.ts
@@ -84,6 +84,8 @@ const inner = makeInner<{
     customImagePath: CustomImageOption['style']['image'];
     customText: string;
     txConZ2Set: number;
+    orginalDuring: Element['updateDuringAnimation'];
+    customDuring: CustomZRPathOption['during'];
 }, Element>();
 
 type CustomExtraElementInfo = Dictionary<unknown>;
@@ -103,6 +105,8 @@ interface CustomBaseElementOption extends Partial<Pick<
     info?: CustomExtraElementInfo;
     // `false` means remove the textContent.
     textContent?: CustomTextOption | false;
+    // updateDuringAnimation
+    during?(elProps: CustomDuringElProps): void;
 };
 interface CustomDisplayableOption extends CustomBaseElementOption, Partial<Pick<
     Displayable, 'zlevel' | 'z' | 'z2' | 'invisible'
@@ -128,6 +132,9 @@ interface CustomGroupOption extends CustomBaseElementOption {
 }
 interface CustomZRPathOption extends CustomDisplayableOption, Pick<PathProps, 'shape'> {
 }
+interface CustomDuringElProps extends Partial<Pick<Element, TransformProps>> {
+    shape?: PathProps['shape'];
+}
 interface CustomSVGPathOption extends CustomDisplayableOption {
     type: 'path';
     shape?: {
@@ -277,7 +284,7 @@ const Z2_SPECIFIED_BIT = {
     emphasis: 1
 } as const;
 
-
+const tmpDuringElProps = {} as CustomDuringElProps;
 
 
 export type PrepareCustomInfo = (coordSys: CoordinateSystem) => {
@@ -640,6 +647,19 @@ function updateElNormal(
     zrUtil.hasOwn(elOption, 'silent') && (el.silent = elOption.silent);
     zrUtil.hasOwn(elOption, 'ignore') && (el.ignore = elOption.ignore);
 
+    const customDuringMounted = el.updateDuringAnimation === elUpdateDuringAnimation;
+    if (elOption.during) {
+        const innerEl = inner(el);
+        if (!customDuringMounted) {
+            innerEl.orginalDuring = el.updateDuringAnimation;
+            el.updateDuringAnimation = elUpdateDuringAnimation;
+        }
+        innerEl.customDuring = elOption.during;
+    }
+    else if (customDuringMounted) {
+        el.updateDuringAnimation = inner(el).orginalDuring;
+    }
+
     if (!isTextContent) {
         // `elOption.info` enables user to mount some info on
         // elements and use them in event handlers.
@@ -650,6 +670,38 @@ function updateElNormal(
     el.markRedraw();
 }
 
+function elUpdateDuringAnimation(this: graphicUtil.Path, key: string): void {
+    const innerEl = inner(this);
+    // FIXME `this.markRedraw();` directly ?
+    innerEl.orginalDuring.call(this, key);
+    const customDuring = innerEl.customDuring;
+
+    // Only provide these props. Usually other props do not need to be
+    // changed in animation during.
+    // Do not give `this` to user util really needed in future.
+    // Props in `shape` can be modified directly in the during callback.
+    tmpDuringElProps.shape = this.shape;
+    tmpDuringElProps.x = this.x;
+    tmpDuringElProps.y = this.y;
+    tmpDuringElProps.scaleX = this.scaleX;
+    tmpDuringElProps.scaleX = this.scaleY;
+    tmpDuringElProps.originX = this.originX;
+    tmpDuringElProps.originY = this.originY;
+    tmpDuringElProps.rotation = this.rotation;
+
+    customDuring(tmpDuringElProps);
+
+    tmpDuringElProps.shape !== this.shape && (this.shape = tmpDuringElProps.shape);
+    // Consider prop on prototype.
+    tmpDuringElProps.x !== this.x && (this.x = tmpDuringElProps.x);
+    tmpDuringElProps.y !== this.y && (this.y = tmpDuringElProps.y);
+    tmpDuringElProps.scaleX !== this.scaleX && (this.scaleX = tmpDuringElProps.scaleX);
+    tmpDuringElProps.scaleY !== this.scaleY && (this.scaleY = tmpDuringElProps.scaleY);
+    tmpDuringElProps.originX !== this.originX && (this.originX = tmpDuringElProps.originX);
+    tmpDuringElProps.originY !== this.originY && (this.originY = tmpDuringElProps.originY);
+    tmpDuringElProps.rotation !== this.rotation && (this.rotation = tmpDuringElProps.rotation);
+}
+
 function updateElOnState(
     state: DisplayStateNonNormal,
     el: Element,
diff --git a/test/custom-feature.html b/test/custom-feature.html
index 03457ed..56e106e 100644
--- a/test/custom-feature.html
+++ b/test/custom-feature.html
@@ -38,7 +38,10 @@ under the License.
         <div id="main0"></div>
         <div id="main2"></div>
         <div id="main3"></div>
-        <!-- <div id="main1"></div> -->
+        <div id="init-animation-additive"></div>
+        <!-- <div id="spiral"></div> -->
+        <div id="spiral2"></div>
+        <div id="texture-bar"></div>
 
 
         <script>
@@ -365,6 +368,443 @@ under the License.
 
 
 
+        <script>
+
+            require(['echarts'], function (echarts) {
+
+                var animationDuration = 5000;
+                var animationDurationUpdate = 4000;
+                var option = {
+                    xAxis: {},
+                    yAxis: {},
+                    dataZoom: [
+                        { type: 'slider' },
+                        { type: 'inside' }
+                    ],
+                    animationDuration: animationDuration,
+                    animationDurationUpdate: animationDurationUpdate,
+                    series: [{
+                        type: 'custom',
+                        renderItem: function (params, api) {
+                            return {
+                                type: 'group',
+                                position: api.coord([api.value(0), api.value(1)]),
+                                children: [{
+                                    type: 'rect',
+                                    shape: {
+                                        x: -50,
+                                        y: 50,
+                                        width: 100,
+                                        height: 150,
+                                        r: 10
+                                    },
+                                    style: {
+                                        fill: 'rgba(102,241,98,0.9)'
+                                    }
+                                }, {
+                                    type: 'circle',
+                                    shape: {
+                                        cx: -50,
+                                        cy: 50,
+                                        r: 30
+                                    },
+                                    style: {
+                                        fill: 'blue'
+                                    },
+                                    textContent: {
+                                        text: 'data',
+                                        style: {
+                                            fill: '#fff'
+                                        }
+                                    }
+                                }]
+                            };
+                        },
+                        data: [[121, 333], [29, 312]]
+                    }]
+                };
+
+                var chart = testHelper.create(echarts, 'init-animation-additive', {
+                    title: [
+                        'Style merge:',
+                        '(1) dataZoom hide a data item, and then show it, ensure the fade in animation normal.',
+                        '(2) click button to setOption merge.'
+                    ],
+                    option: option,
+                    info: {
+                        animationDuration: animationDuration,
+                        animationDurationUpdate: animationDurationUpdate
+                    },
+                    buttons: [{
+                        text: 'merge style: border become blue, but background not changed',
+                        onclick: function () {
+                            chart.setOption({
+                                type: 'custom',
+                                renderItem: function (params, api) {
+                                    return {
+                                        type: 'group',
+                                        children: [{
+                                            type: 'rect',
+                                            style: {
+                                                stroke: 'red',
+                                                lineWidth: 5
+                                            }
+                                        }]
+                                    };
+                                }
+                            });
+                        }
+                    }]
+                });
+            });
+
+        </script>
+
+
+
+
+<!--
+
+        <script>
+            require([
+                'echarts'/*, 'map/js/china' */
+            ], function (echarts) {
+                var animationDuration = 5000;
+                var animationDurationUpdate = 4000;
+                var angleLabel = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo', 'Libra', 'Scorpius', 'Sagittarius', 'Capricornus', 'Aquarius', 'Pisces'];
+                var angleRoundValue = angleLabel.length;
+                var radiusOffset = 10;
+                var angleStep = angleRoundValue / 90;
+                var barWidthValue = 0.4;
+                var radiusStep = 4;
+                var colors = {
+                    'A': { stroke: 'green', fill: 'rgba(0,152,0,0.6)' },
+                    'B': { stroke: 'red', fill: 'rgba(152,0,0,0.6)' },
+                    'C': { stroke: 'blue', fill: 'rgba(0,0, 152,0.6)' },
+                };
+                var allData = [[
+                    [[1, 3, 'A']],
+                    [[2, 6, 'B']],
+                    [[3, 9, 'C']],
+                ], [
+                    [[1, 12, 'A']],
+                    [[2, 16, 'B']],
+                    [[3, 14, 'C']],
+                ], [
+                    [[1, 17, 'A']],
+                    [[2, 22, 'B']],
+                    [[3, 19, 'C']],
+                ]];
+                var currentDataIndex = 0;
+
+                function getMaxRadius() {
+                    var radius = 0;
+                    for (var j = 0; j < allData.length; j++) {
+                        var data = allData[j];
+                        for (var i = 0; i < data.length; i++) {
+                            radius = Math.max(radius, getSpiralValueRadius(data[i][0][0], data[i][0][1]));
+                        }
+                    }
+                    return Math.ceil(radius * 1.2);
+                }
+
+                function getSpiralValueRadius(valRadius, valAngle) {
+                    return valRadius + radiusStep * (valAngle / angleRoundValue);
+                }
+
+                function renderItem(params, api) {
+                    var valueRadius = api.value(0);
+                    var valueAngle = api.value(1);
+                    var points = [];
+                    for (var iAngleVal = 0, end = valueAngle + angleStep; iAngleVal < end; iAngleVal += angleStep) {
+                        iAngleVal > valueAngle && (iAngleVal = valueAngle);
+                        var iRadiusVal = getSpiralValueRadius(valueRadius - barWidthValue, iAngleVal);
+                        var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2);
+                        points.push(point);
+                    }
+                    for (var iAngleVal = valueAngle; iAngleVal > -angleStep; iAngleVal -= angleStep) {
+                        iAngleVal < 0 && (iAngleVal = 0);
+                        var iRadiusVal = getSpiralValueRadius(valueRadius + barWidthValue, iAngleVal);
+                        var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2);
+                        points.push(point);
+                    }
+                    var name = api.value(2);
+                    return {
+                        type: 'polygon',
+                        shape: { points: points },
+                        style: {
+                            lineWidth: 1,
+                            fill: colors[name].fill,
+                            stroke: colors[name].stroke
+                        }
+                    };
+                }
+
+                var option = {
+                    animationDuration: animationDuration,
+                    animationDurationUpdate: animationDurationUpdate,
+                    angleAxis: {
+                        type: 'value',
+                        // splitLine: { show: false },
+                        splitArea: {show: true},
+                        axisLabel: {
+                            formatter: function(val) {
+                                return angleLabel[val];
+                            },
+                            color: 'rgba(0,0,0,0.2)'
+                        },
+                        axisLine: { lineStyle: { color: 'rgba(0,0,0,0.2)' } },
+                        min: 0,
+                        max: angleRoundValue
+                    },
+                    radiusAxis: {
+                        type: 'value',
+                        splitLine: { show: false },
+                        axisLabel: { color: 'rgba(0,0,0,0.2)' },
+                        axisLine: { lineStyle: { color: 'rgba(0,0,0,0.2)' } },
+                        min: 0,
+                        max: getMaxRadius()
+                    },
+                    polar: {
+                    },
+                    tooltip: {},
+                    series: [{
+                        type: 'custom',
+                        name: 'A',
+                        coordinateSystem: 'polar',
+                        renderItem: renderItem,
+                        data: allData[currentDataIndex][0]
+                    }, {
+                        type: 'custom',
+                        name: 'B',
+                        coordinateSystem: 'polar',
+                        renderItem: renderItem,
+                        data: allData[currentDataIndex][1]
+                    }, {
+                        type: 'custom',
+                        name: 'C',
+                        coordinateSystem: 'polar',
+                        renderItem: renderItem,
+                        data: allData[currentDataIndex][2]
+                    }]
+                };
+
+                var chart = testHelper.create(echarts, 'spiral', {
+                    title: [
+                        'animation: ',
+                    ],
+                    option: option,
+                    buttons: [{
+                        text: 'next',
+                        onclick: function () {
+                            currentDataIndex++;
+                            currentDataIndex >= allData.length && (currentDataIndex = 0);
+                            chart.setOption({
+                                series: [{
+                                    data: allData[currentDataIndex][0]
+                                }, {
+                                    data: allData[currentDataIndex][1]
+                                }, {
+                                    data: allData[currentDataIndex][2]
+                                }]
+                            })
+                        }
+                    }, {
+                        text: 'enable animation',
+                        onclick: function () {
+                            chart.setOption({ animation: true });
+                        }
+                    }, {
+                        text: 'disable animation',
+                        onclick: function () {
+                            chart.setOption({ animation: false });
+                        }
+                    }]
+                });
+            });
+        </script>
+ -->
+
+
+
+
+
+
+
+        <script>
+            require([
+                'echarts'/*, 'map/js/china' */
+            ], function (echarts) {
+                var animationDuration = 5000;
+                var animationDurationUpdate = 4000;
+                var animationEasingUpdate = 'elasticOut';
+                var angleLabel = ['Aries', 'Taurus', 'Gemini', 'Cancer', 'Leo', 'Virgo', 'Libra', 'Scorpius', 'Sagittarius', 'Capricornus', 'Aquarius', 'Pisces'];
+                var angleRoundValue = angleLabel.length;
+                var radiusOffset = 10;
+                var angleStep = angleRoundValue / 90;
+                var barWidthValue = 0.4;
+                var radiusStep = 4;
+                var colors = {
+                    'A': { stroke: 'green', fill: 'rgba(0,152,0,0.6)' },
+                    'B': { stroke: 'red', fill: 'rgba(152,0,0,0.6)' },
+                    'C': { stroke: 'blue', fill: 'rgba(0,0, 152,0.6)' },
+                };
+                var allData = [[
+                    [[1, 3, 'A']],
+                    [[2, 6, 'B']],
+                    [[3, 9, 'C']],
+                ], [
+                    [[1, 12, 'A']],
+                    [[2, 16, 'B']],
+                    [[3, 14, 'C']],
+                ], [
+                    [[1, 17, 'A']],
+                    [[2, 22, 'B']],
+                    [[3, 19, 'C']],
+                ]];
+                var currentDataIndex = 0;
+
+                function getMaxRadius() {
+                    var radius = 0;
+                    for (var j = 0; j < allData.length; j++) {
+                        var data = allData[j];
+                        for (var i = 0; i < data.length; i++) {
+                            radius = Math.max(radius, getSpiralValueRadius(data[i][0][0], data[i][0][1]));
+                        }
+                    }
+                    return Math.ceil(radius * 1.2);
+                }
+
+                function getSpiralValueRadius(valRadius, valAngle) {
+                    return valRadius + radiusStep * (valAngle / angleRoundValue);
+                }
+
+                function makeShapePoints(api, valueRadius, valueAngle) {
+                    var points = [];
+                    for (var iAngleVal = 0, end = valueAngle + angleStep; iAngleVal < end; iAngleVal += angleStep) {
+                        iAngleVal > valueAngle && (iAngleVal = valueAngle);
+                        var iRadiusVal = getSpiralValueRadius(valueRadius - barWidthValue, iAngleVal);
+                        var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2);
+                        points.push(point);
+                    }
+                    for (var iAngleVal = valueAngle; iAngleVal > -angleStep; iAngleVal -= angleStep) {
+                        iAngleVal < 0 && (iAngleVal = 0);
+                        var iRadiusVal = getSpiralValueRadius(valueRadius + barWidthValue, iAngleVal);
+                        var point = api.coord([iRadiusVal, iAngleVal]).slice(0, 2);
+                        points.push(point);
+                    }
+                    return points;
+                }
+
+                function renderItem(params, api) {
+                    var valueRadius = api.value(0);
+                    var valueAngle = api.value(1);
+                    var name = api.value(2);
+                    return {
+                        type: 'polygon',
+                        shape: {
+                            points: makeShapePoints(api, valueRadius, valueAngle),
+                            valueAngle: valueAngle
+                        },
+                        style: {
+                            lineWidth: 1,
+                            fill: colors[name].fill,
+                            stroke: colors[name].stroke
+                        },
+                        during: function (elProps) {
+                            elProps.shape.points = makeShapePoints(
+                                api, valueRadius, elProps.shape.valueAngle
+                            );
+                        }
+                    };
+                }
+
+                var option = {
+                    animationDuration: animationDuration,
+                    animationDurationUpdate: animationDurationUpdate,
+                    animationEasingUpdate: animationEasingUpdate,
+                    angleAxis: {
+                        type: 'value',
+                        // splitLine: { show: false },
+                        splitArea: {show: true},
+                        axisLabel: {
+                            formatter: function(val) {
+                                return angleLabel[val];
+                            },
+                            color: 'rgba(0,0,0,0.2)'
+                        },
+                        axisLine: { lineStyle: { color: 'rgba(0,0,0,0.2)' } },
+                        min: 0,
+                        max: angleRoundValue
+                    },
+                    radiusAxis: {
+                        type: 'value',
+                        splitLine: { show: false },
+                        axisLabel: { color: 'rgba(0,0,0,0.2)' },
+                        axisLine: { lineStyle: { color: 'rgba(0,0,0,0.2)' } },
+                        min: 0,
+                        max: getMaxRadius()
+                    },
+                    polar: {
+                    },
+                    tooltip: {},
+                    series: [{
+                        type: 'custom',
+                        name: 'A',
+                        coordinateSystem: 'polar',
+                        renderItem: renderItem,
+                        data: allData[currentDataIndex][0]
+                    }, {
+                        type: 'custom',
+                        name: 'B',
+                        coordinateSystem: 'polar',
+                        renderItem: renderItem,
+                        data: allData[currentDataIndex][1]
+                    }, {
+                        type: 'custom',
+                        name: 'C',
+                        coordinateSystem: 'polar',
+                        renderItem: renderItem,
+                        data: allData[currentDataIndex][2]
+                    }]
+                };
+
+                var chart = testHelper.create(echarts, 'spiral2', {
+                    title: [
+                        'animation: ',
+                    ],
+                    option: option,
+                    buttons: [{
+                        text: 'next',
+                        onclick: function () {
+                            currentDataIndex++;
+                            currentDataIndex >= allData.length && (currentDataIndex = 0);
+                            chart.setOption({
+                                series: [{
+                                    data: allData[currentDataIndex][0]
+                                }, {
+                                    data: allData[currentDataIndex][1]
+                                }, {
+                                    data: allData[currentDataIndex][2]
+                                }]
+                            })
+                        }
+                    }, {
+                        text: 'enable animation',
+                        onclick: function () {
+                            chart.setOption({ animation: true });
+                        }
+                    }, {
+                        text: 'disable animation',
+                        onclick: function () {
+                            chart.setOption({ animation: false });
+                        }
+                    }]
+                });
+            });
+        </script>
+
+
 
     </body>
 </html>
\ No newline at end of file


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


[incubator-echarts] 04/10: fix: fix custom series api.size in polor.

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

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

commit 88c51a08a6c4ed323b0eb93e7f8ac43e3db2151a
Author: 100pah <su...@gmail.com>
AuthorDate: Wed May 13 02:12:08 2020 +0800

    fix: fix custom series api.size in polor.
---
 src/coord/polar/prepareCustom.ts | 1 +
 1 file changed, 1 insertion(+)

diff --git a/src/coord/polar/prepareCustom.ts b/src/coord/polar/prepareCustom.ts
index 5a69988..1907f7f 100644
--- a/src/coord/polar/prepareCustom.ts
+++ b/src/coord/polar/prepareCustom.ts
@@ -24,6 +24,7 @@ import RadiusAxis from './RadiusAxis';
 
 function dataToCoordSize(this: Polar, dataSize: number[], dataItem: number[]) {
     // dataItem is necessary in log axis.
+    dataItem = dataItem || [0, 0];
     return zrUtil.map(['Radius', 'Angle'], function (dim, dimIdx) {
         const getterName = 'get' + dim + 'Axis' as 'getAngleAxis'| 'getRadiusAxis';
         // TODO: TYPE Check Angle Axis


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