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:40 UTC

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

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