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

[incubator-echarts] 01/01: feat(custom): add shapeMorphing option in renderItem returns.

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

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

commit 69be972c5ffca252e572fbee913418a1fc811429
Author: pissang <bm...@gmail.com>
AuthorDate: Sun Sep 13 16:01:55 2020 +0800

    feat(custom): add shapeMorphing option in renderItem returns.
---
 src/chart/custom.ts     |  42 +++++++++++++-
 test/custom-hexbin.html | 143 ++++++++++++++++++++++++++++++------------------
 2 files changed, 129 insertions(+), 56 deletions(-)

diff --git a/src/chart/custom.ts b/src/chart/custom.ts
index 5940580..dd60ba1 100644
--- a/src/chart/custom.ts
+++ b/src/chart/custom.ts
@@ -18,7 +18,7 @@
 */
 
 import {
-    hasOwn, assert, isString, retrieve2, retrieve3, defaults, each, keys, isArrayLike, bind
+    hasOwn, assert, isString, retrieve2, retrieve3, defaults, each, keys, isArrayLike, bind, logError, isFunction
 } from 'zrender/src/core/util';
 import * as graphicUtil from '../util/graphic';
 import { setDefaultStateProxy, enableHoverEmphasis } from '../util/states';
@@ -80,6 +80,7 @@ import {
 import Transformable from 'zrender/src/core/Transformable';
 import { ItemStyleProps } from '../model/mixin/itemStyle';
 import { cloneValue } from 'zrender/src/animation/Animator';
+import { morphPath } from 'zrender/src/tool/morphPath';
 
 
 const inner = makeInner<{
@@ -119,6 +120,13 @@ type TransitionTransformOption = {
     enterFrom?: Dictionary<unknown>;
     leaveTo?: Dictionary<unknown>;
 };
+type ShapeMorphingOption = {
+    /**
+     * If do shape morphing animation when type is changed.
+     * Only available on path.
+     */
+    shapeMorphing?: boolean
+};
 
 interface CustomBaseElementOption extends Partial<Pick<
     Element, TransformProps | 'silent' | 'ignore' | 'textConfig'
@@ -167,10 +175,10 @@ interface CustomGroupOption extends CustomBaseElementOption {
     children: Omit<CustomElementOption, 'focus' | 'blurScope'>[];
     $mergeChildren: false | 'byName' | 'byIndex';
 }
-interface CustomZRPathOption extends CustomDisplayableOption {
+interface CustomZRPathOption extends CustomDisplayableOption, ShapeMorphingOption {
     shape?: PathProps['shape'] & TransitionAnyOption;
 }
-interface CustomSVGPathOption extends CustomDisplayableOption {
+interface CustomSVGPathOption extends CustomDisplayableOption, ShapeMorphingOption {
     type: 'path';
     shape?: {
         // SVG Path, like 'M0,0 L0,-20 L70,-1 L70,0 Z'
@@ -1539,6 +1547,28 @@ function createOrUpdateItem(
     return el;
 }
 
+function applyShapeMorphingAnimation(oldEl: Element, el: Element, seriesModel: SeriesModel, dataIndex: number) {
+    if (!((oldEl instanceof graphicUtil.Path) && (el instanceof graphicUtil.Path))) {
+        if (__DEV__) {
+            logError('shapeMorphing can only be applied on two paths.');
+        }
+        return;
+    }
+    if (seriesModel.isAnimationEnabled()) {
+        const duration = seriesModel.get('animationDurationUpdate');
+        const delay = seriesModel.get('animationDelayUpdate');
+        const easing = seriesModel.get('animationEasingUpdate');
+        const durationNumber = isFunction(duration) ? duration(dataIndex) : duration;
+        if (durationNumber > 0) {
+            morphPath(oldEl, el, {
+                duration: durationNumber,
+                delay: isFunction(delay) ? delay(dataIndex) : delay,
+                easing: easing
+            });
+        }
+    }
+}
+
 function doCreateOrUpdateEl(
     el: Element,
     dataIndex: number,
@@ -1553,10 +1583,12 @@ function doCreateOrUpdateEl(
     }
 
     let toBeReplacedIdx = -1;
+    let oldEl: Element;
 
     if (el && doesElNeedRecreate(el, elOption)) {
         // Should keep at the original index, otherwise "merge by index" will be incorrect.
         toBeReplacedIdx = group.childrenRef().indexOf(el);
+        oldEl = el;
         el = null;
     }
 
@@ -1588,6 +1620,10 @@ function doCreateOrUpdateEl(
     );
 
     updateElNormal(el, dataIndex, elOption, elOption.style, attachedTxInfoTmp, seriesModel, isInit, false);
+    // Do shape morphing
+    if ((elOption as CustomZRPathOption).shapeMorphing && el && oldEl) {
+        applyShapeMorphingAnimation(oldEl, el, seriesModel, dataIndex);
+    }
 
     for (let i = 0; i < STATES.length; i++) {
         const stateName = STATES[i];
diff --git a/test/custom-hexbin.html b/test/custom-hexbin.html
index c9c42e2..7d69a93 100644
--- a/test/custom-hexbin.html
+++ b/test/custom-hexbin.html
@@ -38,7 +38,7 @@ under the License.
                 font-weight: bold;
                 font-size: 14px;
             }
-            .chart {
+            .test-chart {
                 height: 500px;
                 margin: 10px auto;
             }
@@ -156,56 +156,71 @@ under the License.
                             return [bin.x, bin.y, bin.points.length, (made / bin.points.length * 100).toFixed(2)];
                         });
 
-                        function renderItemHexBin(params, api) {
-                            var center = api.coord([api.value(0), api.value(1)]);
-                            var points = [];
-                            var pointsBG = [];
-
-                            var maxViewRadius = api.size([hexagonRadiusInGeo, 0])[0];
-                            var minViewRadius = Math.min(maxViewRadius, 4);
-                            var extentMax = Math.log(Math.sqrt(hexBinResult.maxBinLen));
-                            var viewRadius = echarts.number.linearMap(
-                                Math.log(Math.sqrt(api.value(2))),
-                                [0, extentMax],
-                                [minViewRadius, maxViewRadius]
-                            );
-
-                            var angle = Math.PI / 6;
-                            for (var i = 0; i < 6; i++, angle += Math.PI / 3) {
-                                points.push([
-                                    center[0] + viewRadius * Math.cos(angle),
-                                    center[1] + viewRadius * Math.sin(angle)
-                                ]);
-                                pointsBG.push([
-                                    center[0] + maxViewRadius * Math.cos(angle),
-                                    center[1] + maxViewRadius * Math.sin(angle)
-                                ]);
-                            }
+                        function createItemRenderer(type) {
+                            type = type || 'polygon';
+                            return function renderItemHexBin(params, api) {
+                                var center = api.coord([api.value(0), api.value(1)]);
+                                var points = [];
+                                var pointsBG = [];
+
+                                var maxViewRadius = api.size([hexagonRadiusInGeo, 0])[0];
+                                var minViewRadius = Math.min(maxViewRadius, 4);
+                                var extentMax = Math.log(Math.sqrt(hexBinResult.maxBinLen));
+                                var viewRadius = echarts.number.linearMap(
+                                    Math.log(Math.sqrt(api.value(2))),
+                                    [0, extentMax],
+                                    [minViewRadius, maxViewRadius]
+                                );
+
+                                var angle = Math.PI / 6;
+                                for (var i = 0; i < 6; i++, angle += Math.PI / 3) {
+                                    points.push([
+                                        center[0] + viewRadius * Math.cos(angle),
+                                        center[1] + viewRadius * Math.sin(angle)
+                                    ]);
+                                    pointsBG.push([
+                                        center[0] + maxViewRadius * Math.cos(angle),
+                                        center[1] + maxViewRadius * Math.sin(angle)
+                                    ]);
+                                }
 
-                            return {
-                                type: 'group',
-                                children: [{
-                                    type: 'polygon',
-                                    shape: {
-                                        points: points
-                                    },
-                                    style: {
-                                        stroke: '#ccc',
-                                        fill: api.visual('color'),
-                                        lineWidth: 0
-                                    }
-                                }, {
-                                    type: 'polygon',
-                                    shape: {
-                                        points: pointsBG
-                                    },
-                                    style: {
-                                        stroke: null,
-                                        fill: 'rgba(0,0,0,0.5)',
-                                        lineWidth: 0
-                                    },
-                                    z2: -19
-                                }]
+                                return {
+                                    type: 'group',
+                                    children: [{
+                                        type,
+                                        shapeMorphing: true,
+                                        shape: type === 'polygon' ? {
+                                            points: points
+                                        } : {
+                                            // Circle
+                                            cx: center[0],
+                                            cy: center[1],
+                                            r: viewRadius
+                                        },
+                                        style: {
+                                            stroke: '#ccc',
+                                            fill: api.visual('color'),
+                                            lineWidth: 0
+                                        }
+                                    }, {
+                                        type,
+                                        shapeMorphing: true,
+                                        shape: type === 'polygon' ? {
+                                            points: pointsBG
+                                        } : {
+                                            // Circle
+                                            cx: center[0],
+                                            cy: center[1],
+                                            r: maxViewRadius
+                                        },
+                                        style: {
+                                            stroke: null,
+                                            fill: 'rgba(0,0,0,0.5)',
+                                            lineWidth: 0
+                                        },
+                                        z2: -19
+                                    }]
+                                };
                             };
                         }
 
@@ -292,7 +307,7 @@ under the License.
                                 type: 'custom',
                                 coordinateSystem: 'geo',
                                 geoIndex: 0,
-                                renderItem: renderItemHexBin,
+                                renderItem: createItemRenderer(),
                                 dimensions: [null, null, 'Field Goals Attempted (hexagon size)', 'Field Goal Percentage (color)'],
                                 encode: {
                                     tooltip: [2, 3]
@@ -308,8 +323,30 @@ under the License.
                             }]
                         };
 
-                        var width = 700;
-                        testHelper.createChart(echarts, 'hexagonal-binning', option, {
+                        var width = 1000;
+                        const myChart = testHelper.create(echarts, 'hexagonal-binning', {
+                            option,
+                            buttons: [{
+                                text: 'Hexgon',
+                                onClick: function() {
+                                    myChart.setOption({
+                                        series: [{
+                                            type: 'custom',
+                                            renderItem: createItemRenderer('polygon')
+                                        }]
+                                    });
+                                }
+                            }, {
+                                text: 'Circle',
+                                onClick: function() {
+                                    myChart.setOption({
+                                        series: [{
+                                            type: 'custom',
+                                            renderItem: createItemRenderer('circle')
+                                        }]
+                                    });
+                                }
+                            }],
                             width: width,
                             height: width * nbaCourt.height / nbaCourt.width
                         });


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