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/27 04:38:42 UTC

[incubator-echarts] branch custom-series-enhance updated (a7d4f69 -> 5a6ccc8)

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.


    from a7d4f69  clean code
     new c3329e4  fix: rename $transition $enterFrom $leaveTo => transition enterFrom leaveTo.
     new 4d76f23  feature: custom series, add "extra" in el options for users to config their own properties for animation.
     new 5a6ccc8  fix: fix during call and enhance test cases.

The 3 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 src/chart/custom.ts          | 276 ++++++++++++-------
 src/export.ts                |   1 +
 src/util/graphic.ts          |   3 +
 test/custom-transition.html  |  92 ++++---
 test/custom-transition2.html | 620 +++++++++++++++++++++++++++++++++++++++++--
 5 files changed, 822 insertions(+), 170 deletions(-)


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


[incubator-echarts] 03/03: fix: fix during call and enhance test cases.

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 5a6ccc80c07811a78f31fc4cdaa936f69510db82
Author: 100pah <su...@gmail.com>
AuthorDate: Fri Jun 26 15:35:31 2020 +0800

    fix: fix during call and enhance test cases.
---
 src/chart/custom.ts          | 106 ++++---
 src/export.ts                |   1 +
 src/util/graphic.ts          |   3 +
 test/custom-transition2.html | 678 +++++++++++++++++++++++++++++++++----------
 4 files changed, 603 insertions(+), 185 deletions(-)

diff --git a/src/chart/custom.ts b/src/chart/custom.ts
index 05a9fd7..1a06308 100644
--- a/src/chart/custom.ts
+++ b/src/chart/custom.ts
@@ -20,7 +20,7 @@
 
 import {__DEV__} from '../config';
 import {
-    hasOwn, assert, isString, retrieve2, retrieve3, defaults, each, keys, isArrayLike
+    hasOwn, assert, isString, retrieve2, retrieve3, defaults, each, keys, isArrayLike, bind
 } from 'zrender/src/core/util';
 import * as graphicUtil from '../util/graphic';
 import {getDefaultLabel} from './helper/labelHelper';
@@ -87,9 +87,8 @@ const inner = makeInner<{
     customImagePath: CustomImageOption['style']['image'];
     // customText: string;
     txConZ2Set: number;
-    orginalDuring: Element['updateDuringAnimation'];
-    customDuring: CustomZRPathOption['during'];
-    leaveToProps: ElementProps
+    leaveToProps: ElementProps;
+    userDuring: CustomBaseElementOption['during'];
 }, Element>();
 
 type CustomExtraElementInfo = Dictionary<unknown>;
@@ -263,7 +262,6 @@ interface CustomSeriesOption extends
 interface LooseElementProps extends ElementProps {
     style?: ZRStyleProps;
     shape?: Dictionary<unknown>;
-    extra?: Dictionary<unknown>;
 }
 
 // Also compat with ec4, where
@@ -576,16 +574,17 @@ function createEl(elOption: CustomElementOption): Element {
  * ----------------------------------------------------------
  * [STRATEGY_MERGE] Merge properties or erase all properties:
  *
- * Based on the fact that the existing zr element probably be reused, we discuss whether
+ * Based on the fact that the existing zr element probably be reused, we now consider 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.
+ * That is, if a certain props is not specified in the lastest return of `renderItem`:
+ * + "Merge" means that do not modify the value on the existing element.
+ * + "Erase all" means that use a default value to the existing element.
  *
  * "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".
+ * more safe. "erase all" force users to specify all of the props each time, which is recommanded
+ * in most cases.
+ * But "erase all" theoretically disables the chance of performance optimization (e.g., just
+ * generete shape and style at the first time rather than always do that).
  * 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
@@ -690,29 +689,30 @@ function updateElNormal(
         hasOwn(elOption, 'invisible') && (elDisplayable.invisible = elOption.invisible);
     }
 
+    // Do not use `el.updateDuringAnimation` here becuase `el.updateDuringAnimation` will
+    // be called mutiple time in each animation frame. For example, if both "transform" props
+    // and shape props and style props changed, it will generate three animator and called
+    // one-by-one in each animation frame.
+    // We use the during in `animateTo/From` params.
+    const userDuring = elOption.during;
+    // For simplicity, if during not specified, the previous during will not work any more.
+    inner(el).userDuring = userDuring;
+    const cfgDuringCall = userDuring ? bind(duringCall, { el: el, userDuring: userDuring }) : null;
+
     el.attr(allProps);
-    const params = {dataIndex: dataIndex, isFrom: true};
+    const cfg = {
+        dataIndex: dataIndex,
+        isFrom: true,
+        during: cfgDuringCall
+    };
     isInit
-        ? graphicUtil.initProps(el, transFromProps, seriesModel, params)
-        : graphicUtil.updateProps(el, transFromProps, seriesModel, params);
+        ? graphicUtil.initProps(el, transFromProps, seriesModel, cfg)
+        : graphicUtil.updateProps(el, transFromProps, seriesModel, cfg);
 
     // Merge by default.
     hasOwn(elOption, 'silent') && (el.silent = elOption.silent);
     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.
@@ -723,6 +723,7 @@ function updateElNormal(
     styleOpt ? el.dirty() : el.markRedraw();
 }
 
+
 // See [STRATEGY_TRANSITION]
 function prepareShapeOrExtraUpdate(
     mainAttr: 'shape' | 'extra',
@@ -1023,30 +1024,54 @@ function assertNotReserved(key: string) {
     }
 }
 
-function elUpdateDuringAnimation(this: Element, key: string): void {
+function duringCall(
+    this: {
+        el: Element;
+        userDuring: CustomBaseElementOption['during']
+    }
+): void {
     // Do not provide "percent" until some requirements come.
     // Because consider thies case:
     // enterFrom: {x: 100, y: 30}, transition: 'x'.
     // And enter duration is different from update duration.
     // Thus it might be confused about the meaning of "percent" in during callback.
-    const innerEl = inner(this);
-    // FIXME `this.markRedraw();` directly ?
-    innerEl.orginalDuring.call(this, key);
-    const customDuring = innerEl.customDuring;
+    const scope = this;
+    const el = scope.el;
+    if (!el) {
+        return;
+    }
+    // If el is remove from zr by reason like legend, during still need to called,
+    // becuase el will be added back to zr and the prop value should not be incorrect.
+
+    const newstUserDuring = inner(el).userDuring;
+    const scopeUserDuring = scope.userDuring;
+    // Ensured a during is only called once in each animation frame.
+    // If a during is called multiple times in one frame, maybe some users' calulation logic
+    // might be wrong (not sure whether this usage exists).
+    // The case of a during might be called twice can be: by default there is a animator for
+    // 'x', 'y' when init. Before the init animation finished, call `setOption` to start
+    // another animators for 'style'/'shape'/'extra'.
+    if (newstUserDuring !== scopeUserDuring) {
+        // release
+        scope.el = scope.userDuring = null;
+        return;
+    }
 
-    tmpDuringScope.el = this;
+    tmpDuringScope.el = el;
     tmpDuringScope.isShapeDirty = false;
     tmpDuringScope.isStyleDirty = false;
 
-    customDuring(customDuringAPI);
+    // Give no `this` to user in "during" calling.
+    scopeUserDuring(customDuringAPI);
 
-    if (tmpDuringScope.isShapeDirty && (this as graphicUtil.Path).dirtyShape) {
-        (this as graphicUtil.Path).dirtyShape();
+    if (tmpDuringScope.isShapeDirty && (el as graphicUtil.Path).dirtyShape) {
+        (el as graphicUtil.Path).dirtyShape();
     }
-    if (tmpDuringScope.isStyleDirty && (this as Displayable).dirtyStyle) {
-        (this as Displayable).dirtyStyle();
+    if (tmpDuringScope.isStyleDirty && (el as Displayable).dirtyStyle) {
+        (el as Displayable).dirtyStyle();
     }
     // markRedraw() will be called by default in during.
+    // FIXME `this.markRedraw();` directly ?
 
     // 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.
@@ -1845,6 +1870,9 @@ function mergeChildren(
         );
     }
     for (let i = el.childCount() - 1; i >= index; i--) {
+        // Do not supprot leave elements that are not mentioned in the latest
+        // `renderItem` return. Otherwise users may not have a clear and simple
+        // concept that how to contorl all of the elements.
         doRemoveEl(el.childAt(i), seriesModel, el);
     }
 }
diff --git a/src/export.ts b/src/export.ts
index 7f3e459..f45beb3 100644
--- a/src/export.ts
+++ b/src/export.ts
@@ -74,6 +74,7 @@ const GRAPHIC_KEYS = [
     'Image',
     'Text',
     'Circle',
+    'Ellipse',
     'Sector',
     'Ring',
     'Polygon',
diff --git a/src/util/graphic.ts b/src/util/graphic.ts
index fe27f60..9806368 100644
--- a/src/util/graphic.ts
+++ b/src/util/graphic.ts
@@ -27,6 +27,7 @@ import ZRImage, { ImageStyleProps } from 'zrender/src/graphic/Image';
 import Group from 'zrender/src/graphic/Group';
 import ZRText, { TextStyleProps } from 'zrender/src/graphic/Text';
 import Circle from 'zrender/src/graphic/shape/Circle';
+import Ellipse from 'zrender/src/graphic/shape/Ellipse';
 import Sector from 'zrender/src/graphic/shape/Sector';
 import Ring from 'zrender/src/graphic/shape/Ring';
 import Polygon from 'zrender/src/graphic/shape/Polygon';
@@ -1565,6 +1566,7 @@ export const getECData = makeInner<ECData, Element>();
 // Register built-in shapes. These shapes might be overwirtten
 // by users, although we do not recommend that.
 registerShape('circle', Circle);
+registerShape('ellipse', Ellipse);
 registerShape('sector', Sector);
 registerShape('ring', Ring);
 registerShape('polygon', Polygon);
@@ -1579,6 +1581,7 @@ export {
     ZRImage as Image,
     ZRText as Text,
     Circle,
+    Ellipse,
     Sector,
     Ring,
     Polygon,
diff --git a/test/custom-transition2.html b/test/custom-transition2.html
index 3e9eb56..f9a8338 100644
--- a/test/custom-transition2.html
+++ b/test/custom-transition2.html
@@ -38,6 +38,11 @@ under the License.
 
 
         <div id="main-cluster-step"></div>
+        <div id="during-case-continue"></div>
+        <div id="during-case-get-curr"></div>
+        <div id="during-case-partial-change"></div>
+        <div id="during-first-frame-correct"></div>
+        <div id="during-ensure-once-in-each-frame"></div>
 
 
 
@@ -113,10 +118,12 @@ under the License.
                         centroids: stepData.centroids,
                         points: [],
                         newestClusterIndex: newestClusterIdx,
-                        newestClusterMaxAssessmentIndex: -1
+                        newestClusterMaxDistance: -Infinity
                     };
                     result.push(stepResult);
 
+                    var newestCenter = stepData.centroids[newestClusterIdx];
+                    var newestClusterPoints = ptsInCluster[newestClusterIdx];
 
                     for (var cIdx = 0; cIdx < ptsInCluster.length; cIdx++) {
                         var clusterPoints = ptsInCluster[cIdx];
@@ -128,15 +135,15 @@ under the License.
                                 throw Error(key);
                             }
                             stepResult.points[ptsIdx] = [point[0], point[1], cIdx, newestClusterIdx];
-                        }
-                    }
 
-                    var assessment = -Infinity;
-                    for (var i = 0; i < stepData.clusterAssment.length; i++) {
-                        var assessmentRecord = stepData.clusterAssment[i];
-                        if (assessmentRecord[0] === newestClusterIdx && assessmentRecord[1] > assessment) {
-                            assessment = assessmentRecord[1];
-                            stepResult.newestClusterMaxAssessmentIndex = i;
+                            if (cIdx === newestClusterIdx) {
+                                var dx = newestCenter[0] - point[0];
+                                var dy = newestCenter[1] - point[1];
+                                var dist = Math.pow(dx * dx + dy * dy, 0.5);
+                                if (dist > stepResult.newestClusterMaxDistance) {
+                                    stepResult.newestClusterMaxDistance = dist;
+                                }
+                            }
                         }
                     }
                 }
@@ -154,16 +161,11 @@ under the License.
 
                 var contentColor = colorAll[clusterIdx];
                 addColorTransition(extra, contentColor, 'content');
-                // var borderColor = isNewCluster ? '#333' : '#fff';
-                // addColorTransition(shape, borderColor, 'border');
 
                 return {
                     type: 'circle',
                     x: coord[0],
                     y: coord[1],
-                    // scaleX: isNewCluster ? 1.2 : 1,
-                    // scaleY: isNewCluster ? 1.2 : 1,
-                    // transition: ['scaleX', 'scaleY'],
                     shape: {
                         cx: 0,
                         cy: 0,
@@ -181,8 +183,6 @@ under the License.
                     during: function (apiDuring) {
                         var currContentColor = getColorInTransition(apiDuring, 'content');
                         apiDuring.setStyle('fill', currContentColor);
-                        // var currBorderColor = getColorInTransition(apiDuring, 'border');
-                        // apiDuring.setStyle('stroke', currBorderColor);
                     }
                 };
             }
@@ -208,153 +208,43 @@ under the License.
             }
 
             function renderBoundary(params, api) {
-                var center = api.coord([api.value(2), api.value(3)]);
-                var endP = api.coord([api.value(0), api.value(1)]);
-                var diffX = center[0] - endP[0];
-                var diffY = center[1] - endP[1];
-                var radius = Math.pow(diffX * diffX + diffY * diffY, 0.5) + 10;
-                var newCluIdx = api.value(4);
+                var xVal = api.value(0);
+                var yVal = api.value(1);
+                var maxDist = api.value(2);
+                var center = api.coord([xVal, yVal]);
+                var size = api.size([maxDist, maxDist]);
+                var renderNumberStar;
 
                 return {
-                    type: 'circle',
+                    type: 'ellipse',
                     shape: {
-                        cx: 0,
-                        cy: 0,
-                        r: 0
+                        cx: isNaN(center[0]) ? 0 : center[0],
+                        cy: isNaN(center[1]) ? 0 : center[1],
+                        rx: isNaN(size[0]) ? 0 : size[0] + 15,
+                        ry: isNaN(size[1]) ? 0 : size[1] + 15
                     },
                     extra: {
-                        cxNext: isNaN(center[0]) ? 0 : center[0],
-                        cyNext: isNaN(center[1]) ? 0 : center[1],
-                        rNext: isNaN(radius) ? 0 : radius,
                         renderNumber: ++renderNumber,
                         transition: 'renderNumber'
                     },
                     style: {
                         fill: null,
-                        stroke: '#bbb',
-                        lineDash: [5, 5],
-                        lineWidth: 3,
-                        // opacity: 0
+                        stroke: 'rgba(0,0,0,0.2)',
+                        lineDash: [4, 4],
+                        lineWidth: 4
                     },
                     during: function (apiDuring) {
                         var currNum = apiDuring.getExtra('renderNumber');
-                        if (apiDuring.getStyle('opacity') < 0.9) {
-                            apiDuring
-                                .setShape('cx', apiDuring.getExtra('cxNext'))
-                                .setShape('cy', apiDuring.getExtra('cyNext'))
-                                .setShape('r', apiDuring.getExtra('rNext'));
-                        }
-                        apiDuring.setStyle('opacity', 1 - (renderNumber - currNum));
+                        !renderNumberStar && (renderNumberStar = currNum);
+                        apiDuring.setStyle('opacity', (currNum - renderNumberStar) / (renderNumber - renderNumberStar));
                     }
                 };
-
-                // return {
-                //     type: 'group',
-                //     children: [{
-                //         type: 'circle',
-                //         shape: {
-                //             cx: 0,
-                //             cy: 0,
-                //             r: 0,
-                //         },
-                //         extra: {
-                //             renderNumber: ++renderNumber,
-                //             cxNext: isNaN(center[0]) ? 0 : center[0],
-                //             cyNext: isNaN(center[1]) ? 0 : center[1],
-                //             rNext: isNaN(radius) ? 0 : radius,
-                //             transition: 'renderNumber'
-                //         },
-                //         style: {
-                //             fill: null,
-                //             stroke: '#bbb',
-                //             lineDash: [5, 5],
-                //             lineWidth: 3
-                //         },
-                //         during: function (apiDuring) {
-                //             var currNum = apiDuring.getExtra('renderNumber');
-                //             var progress = 0.5 - (renderNumber - currNum);
-                //             if (progress >= 0) {
-                //                 apiDuring
-                //                     .setShape('cx', apiDuring.getExtra('cxNext'))
-                //                     .setShape('cy', apiDuring.getExtra('cyNext'))
-                //                     .setShape('r', apiDuring.getExtra('rNext'));
-                //             }
-                //             var opacity = Math.abs(progress) / 0.5;
-                //             apiDuring.setStyle('opacity', opacity);
-                //         }
-                //     }, {
-                //         type: 'circle',
-                //         shape: {
-                //             cx: 0,
-                //             cy: 0,
-                //             r: 0,
-                //         },
-                //         extra: {
-                //             renderNumber: ++renderNumber,
-                //             cxNext: isNaN(center[0]) ? 0 : center[0],
-                //             cyNext: isNaN(center[1]) ? 0 : center[1],
-                //             rNext: isNaN(radius) ? 0 : radius,
-                //             transition: 'renderNumber'
-                //         },
-                //         style: {
-                //             fill: null,
-                //             stroke: '#bbb',
-                //             lineDash: [5, 5],
-                //             lineWidth: 3
-                //         },
-                //         during: function (apiDuring) {
-                //             var currNum = apiDuring.getExtra('renderNumber');
-                //             var progress = 0.5 - (renderNumber - currNum);
-                //             if (progress >= 0) {
-                //                 apiDuring
-                //                     .setShape('cx', apiDuring.getExtra('cxNext'))
-                //                     .setShape('cy', apiDuring.getExtra('cyNext'))
-                //                     .setShape('r', apiDuring.getExtra('rNext'));
-                //             }
-                //             var opacity = Math.abs(progress) / 0.5;
-                //             apiDuring.setStyle('opacity', opacity);
-                //         }
-                //     }]
-                // };
-                // return {
-                //     type: 'circle',
-                //     shape: {
-                //         cx: 0,
-                //         cy: 0,
-                //         r: 0,
-                //     },
-                //     extra: {
-                //         renderNumber: ++renderNumber,
-                //         cxNext: isNaN(center[0]) ? 0 : center[0],
-                //         cyNext: isNaN(center[1]) ? 0 : center[1],
-                //         rNext: isNaN(radius) ? 0 : radius,
-                //         transition: 'renderNumber'
-                //     },
-                //     style: {
-                //         fill: null,
-                //         stroke: '#bbb',
-                //         lineDash: [5, 5],
-                //         lineWidth: 3
-                //     },
-                //     during: function (apiDuring) {
-                //         var currNum = apiDuring.getExtra('renderNumber');
-                //         var progress = 0.5 - (renderNumber - currNum);
-                //         if (progress >= 0) {
-                //             apiDuring
-                //                 .setShape('cx', apiDuring.getExtra('cxNext'))
-                //                 .setShape('cy', apiDuring.getExtra('cyNext'))
-                //                 .setShape('r', apiDuring.getExtra('rNext'));
-                //         }
-                //         var opacity = Math.abs(progress) / 0.5;
-                //         apiDuring.setStyle('opacity', opacity);
-                //     }
-                // };
             }
 
             function makeStepOption(option, stepResult) {
                 var centroids = stepResult.centroids || [];
                 var points = stepResult.points;
-                var newMaxAssmIdx = stepResult.newestClusterMaxAssessmentIndex;
+                var maxDist = stepResult.newestClusterMaxDistance;
                 var newCluIdx = stepResult.newestClusterIndex;
 
                 option.options.push({
@@ -369,13 +259,12 @@ under the License.
                         type: 'custom',
                         renderItem: renderBoundary,
                         animationDuration: 3000,
+                        silent: true,
                         data: [
                             [
-                                (points[newMaxAssmIdx] || [])[0],
-                                (points[newMaxAssmIdx] || [])[1],
                                 (centroids[newCluIdx] || [])[0],
                                 (centroids[newCluIdx] || [])[1],
-                                newCluIdx
+                                maxDist
                             ]
                         ]
                     }]
@@ -442,6 +331,503 @@ under the License.
         </script>
 
 
+
+
+
+
+
+
+
+
+
+        <script>
+        require(['echarts'], function (echarts) {
+
+            function renderItem(params, api) {
+                return {
+                    type: 'circle',
+                    shape: {
+                        cx: api.value(0),
+                        cy: api.value(1),
+                        r: 40,
+                        transition: 'cx'
+                    },
+                    style: {
+                        fill: 'green',
+                    },
+                };
+            }
+
+            var baseX = 60;
+            var baseY = 80
+
+            var option = {
+                series: [{
+                    id: 'a',
+                    type: 'custom',
+                    coordinateSystem: 'none',
+                    renderItem: renderItem,
+                    animationDuration: 3000,
+                    animationDurationUpdate: 6000,
+                    data: [[baseX, baseY]]
+                }]
+            };
+
+
+            var chart = testHelper.create(echarts, 'during-case-continue', {
+                title: [
+                    'Click "move" several times **before animation finished**',
+                    'The cirle should keep move to right **without jump**'
+                ],
+                height: 200,
+                option: option,
+                buttons: [{
+                    text: 'move',
+                    onclick: function () {
+                        chart.setOption({
+                            series: {
+                                id: 'a',
+                                data: [[baseX += 60, baseY]]
+                            }
+                        });
+                    }
+                }]
+            });
+        });
+        </script>
+
+
+
+
+
+
+
+
+
+
+
+
+        <script>
+        require(['echarts'], function (echarts) {
+
+            function renderItem(params, api) {
+                var renderNumberStar;
+                return {
+                    type: 'circle',
+                    shape: {
+                        cx: api.value(0),
+                        cy: api.value(1),
+                        r: 40
+                    },
+                    extra: {
+                        renderNumber: ++renderNumber,
+                        transition: 'renderNumber'
+                    },
+                    style: {
+                        fill: 'green'
+                    },
+                    during: function (apiDuring) {
+                        var currNum = apiDuring.getExtra('renderNumber');
+                        !renderNumberStar && (renderNumberStar = currNum);
+                        apiDuring.setStyle('opacity', (currNum - renderNumberStar) / (renderNumber - renderNumberStar));
+                    }
+                };
+            }
+
+            var renderNumber = 1;
+            var baseX = 60;
+            var baseY = 100;
+
+            var option = {
+                series: [{
+                    id: 'a',
+                    type: 'custom',
+                    coordinateSystem: 'none',
+                    renderItem: renderItem,
+                    animationDuration: 3000,
+                    animationDurationUpdate: 10000,
+                    data: [[baseX, baseY]]
+                }]
+            };
+
+            var chart = testHelper.create(echarts, 'during-case-get-curr', {
+                title: [
+                    'Click next several times **before animation finished**',
+                    'Each click, the circle **disappear immediately** and **fade in** at right a bit',
+                    'MUST **not blink**'
+                ],
+                height: 200,
+                option: option,
+                buttons: [{
+                    text: 'next',
+                    onclick: function () {
+                        chart.setOption({
+                            series: {
+                                id: 'a',
+                                data: [[baseX += 20, baseY]]
+                            }
+                        });
+                    }
+                }]
+            });
+        });
+        </script>
+
+
+
+
+
+
+
+
+
+
+
+
+        <script>
+        require(['echarts'], function (echarts) {
+
+            var baseX = 100;
+            var baseY = 100;
+
+            function renderItem(params, api) {
+                var textOpt = {
+                    type: 'text',
+                    extra: { },
+                    transition: [], // disable the default transition of x y.
+                    style: { x: 20, y: 20, fontSize: 20, stroke: 'green' },
+                    during: function (apiDuring) {
+                        var x = apiDuring.getExtra('x');
+                        var y = apiDuring.getExtra('y');
+                        apiDuring.setStyle('text', makeText(x, y));
+                    }
+                };
+                var movingCircleOpt = {
+                    type: 'circle',
+                    shape: { cx: 0, cy: 0, r: 10 },
+                    extra: { },
+                    style: { fill: 'red' },
+                    transition: [], // disable the default transition of x y.
+                    during: function (apiDuring) {
+                        var x = apiDuring.getExtra('x');
+                        var y = apiDuring.getExtra('y');
+                        apiDuring.setTransform('x', x).setTransform('y', y);
+                    }
+                };
+
+                var cmd = api.value(0);
+                if (cmd === 'init') {
+                    textOpt.extra.x = baseX;
+                    textOpt.extra.y = baseY;
+                    textOpt.extra.transition = ['x', 'y'];
+                    textOpt.style.text = makeText(baseX, baseY);
+                    movingCircleOpt.x = baseX;
+                    movingCircleOpt.y = baseY;
+                    movingCircleOpt.extra.x = baseX;
+                    movingCircleOpt.extra.y = baseY;
+                    movingCircleOpt.extra.transition = ['x', 'y'];
+                }
+                else if (cmd === 'x') {
+                    baseX += 100;
+                    textOpt.extra.x = baseX;
+                    textOpt.extra.transition = ['x'];
+                    // textOpt.style.text = makeText(baseX, baseY);
+                    movingCircleOpt.extra.x = baseX;
+                    movingCircleOpt.extra.transition = ['x'];
+                }
+                else if (cmd === 'y') {
+                    baseY += 100;
+                    textOpt.extra.y = baseY;
+                    textOpt.extra.transition = ['y'];
+                    // textOpt.style.text = makeText(baseX, baseY);
+                    movingCircleOpt.extra.y = baseY;
+                    movingCircleOpt.extra.transition = ['y'];
+                }
+
+                return {
+                    type: 'group',
+                    children: [
+                        textOpt,
+                        movingCircleOpt,
+                        {
+                            // Standard circle: used to check the result of moving circle.
+                            type: 'circle',
+                            x: baseX,
+                            y: baseY,
+                            transition: [], // disable the default transition of x y.
+                            shape: {cx: 0, cy: 0, r: 15},
+                            style: {fill: '#aaa'},
+                            z2: -1
+                        }
+                    ]
+                };
+            }
+
+            function makeText(x, y) {
+                return ['x: ' + x.toFixed(2), 'y: ' + y.toFixed(2)].join('\n');
+            }
+
+            var option = {
+                series: [{
+                    id: 'a',
+                    type: 'custom',
+                    coordinateSystem: 'none',
+                    renderItem: renderItem,
+                    animationDuration: 3000,
+                    animationDurationUpdate: 5000,
+                    data: [['init']]
+                }]
+            };
+
+
+            var chart = testHelper.create(echarts, 'during-case-partial-change', {
+                title: [
+                    'Partial change props test:',
+                    'Click "add x" and "add y" before animation finished.',
+                    'The red circle animation should be smooth **without jump**.',
+                    'The red circle should be finally **reach at the grey circle**.',
+                    'The **text should be correct**.',
+                ],
+                height: 500,
+                option: option,
+                buttons: [{
+                    text: 'add x 100',
+                    onclick: function () {
+                        chart.setOption({
+                            series: {
+                                id: 'a',
+                                data: [['x']]
+                            }
+                        });
+                    }
+                }, {
+                    text: 'add y 100',
+                    onclick: function () {
+                        chart.setOption({
+                            series: {
+                                id: 'a',
+                                data: [['y']]
+                            }
+                        });
+                    }
+                }]
+            });
+        });
+        </script>
+
+
+
+
+
+
+        <script>
+        require(['echarts'], function (echarts) {
+            var resultPrinted = false;
+
+            function renderItem(params, api) {
+                return {
+                    type: 'text',
+                    extra: {
+                        renderNumber: renderNumber,
+                        transition: 'renderNumber'
+                    },
+                    style: {
+                        x: 100,
+                        y: 50,
+                        fontSize: 30,
+                        enterFrom: {
+                            x: 10
+                        }
+                    },
+                    during: function (apiDuring) {
+                        var currNum = apiDuring.getExtra('renderNumber');
+                        if (resultPrinted || currNum <= 2) {
+                            return;
+                        }
+                        resultPrinted = true;
+                        if (currNum === 3) {
+                            apiDuring.setStyle('text', 'TEST FAIL');
+                            apiDuring.setStyle('fill', 'red');
+                        }
+                        else {
+                            apiDuring.setStyle('text', 'TEST PASS');
+                            apiDuring.setStyle('fill', 'green');
+                        }
+                    }
+                };
+            }
+
+            var renderNumber = 2;
+
+            var option = {
+                series: [{
+                    id: 'a',
+                    type: 'custom',
+                    coordinateSystem: 'none',
+                    renderItem: renderItem,
+                    animationDuration: 10000,
+                    animationDurationUpdate: 10000,
+                    data: [[10]]
+                }]
+            };
+
+            var chart = testHelper.create(echarts, 'during-first-frame-correct', {
+                title: [
+                    'Test the first during call should not get the target value:',
+                    'Should print TEST PASS'
+                ],
+                option: option,
+                height: 200
+            });
+
+            chart && setTimeout(function () {
+                renderNumber = 3;
+                chart.setOption({
+                    series: {
+                        id: 'a',
+                        data: [[10]]
+                    }
+                });
+                // Set option before init finished.
+            }, 100);
+        });
+        </script>
+
+
+
+
+
+
+
+
+        <script>
+        require(['echarts'], function (echarts) {
+            var resultPrinted = false;
+            var currX = 0;
+            var currFontSize = 16;
+
+            function renderItem(params, api) {
+                var cmd = api.value(0);
+
+                var opt = {
+                    type: 'text',
+                    extra: {
+                        renderNumber: renderNumber,
+                        transition: 'renderNumber'
+                    },
+                    style: {
+                        x: 100,
+                        y: 50,
+                        fontSize: currFontSize,
+                        fill: 'green'
+                    }
+                };
+
+                if (cmd !== 'noDuring') {
+                    opt.during = function (apiDuring) {
+                        duringCount++;
+                        var currNum = apiDuring.getExtra('renderNumber');
+                        apiDuring.setStyle(
+                            'text',
+                            'during count: ' + duringCount + '\n' + 'rAF count: ' + rAFCount
+                        );
+                    };
+                }
+                if (cmd === 'addX') {
+                    currX += 50;
+                    opt.x = currX;
+                    opt.transition = 'x';
+                }
+                if (cmd === 'addFontSize') {
+                    currFontSize += 8;
+                    opt.style.fontSize = currFontSize;
+                    opt.style.transition = 'fontSize';
+                }
+
+                return opt;
+            }
+
+            var renderNumber = 2;
+
+            var option = {
+                series: [{
+                    id: 'a',
+                    type: 'custom',
+                    coordinateSystem: 'none',
+                    renderItem: renderItem,
+                    animationDuration: 3000,
+                    animationDurationUpdate: 3000,
+                    data: [[10]]
+                }]
+            };
+
+            var chart = testHelper.create(echarts, 'during-ensure-once-in-each-frame', {
+                title: [
+                    'Test during only called once in each:',
+                    'In **init** and after **click the buttons**,',
+                    'during count and rAF count',
+                    'should **be the same** (may be 1 different)'
+                ],
+                option: option,
+                height: 200,
+                button: [{
+                    text: 'add x 50',
+                    onclick: function () {
+                        chart.setOption({ series: { id: 'a', data: [['addX']] } });
+                        startCountFrame();
+                    }
+                }, {
+                    text: 'add fontSize 8',
+                    onclick: function () {
+                        chart.setOption({ series: { id: 'a', data: [['addFontSize']] } });
+                        startCountFrame();
+                    }
+                }]
+            });
+
+            var rAFCount = 0;
+            var duringCount = 0;
+            var rAFId;
+
+            function startCountFrame() {
+                stopCountFrame();
+                rAFId = requestAnimationFrame(countFrame);
+
+                function countFrame() {
+                    rAFCount++;
+                    rAFId = requestAnimationFrame(countFrame);
+                }
+            }
+
+            function stopCountFrame() {
+                if (rAFId != null) {
+                    cancelAnimationFrame(rAFId);
+                    rAFId = null;
+                }
+            }
+
+            if (chart) {
+                chart.on('finished', function () {
+                    stopCountFrame();
+                });
+
+                startCountFrame();
+
+                setTimeout(function () {
+                    renderNumber = 3;
+                    chart.setOption({ series: { id: 'a', data: [['init']] } });
+                    // Set option before init finished.
+                    startCountFrame();
+                }, 100);
+            }
+        });
+        </script>
+
+
+
+
+
+
     </body>
 </html>
 


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


[incubator-echarts] 01/03: fix: rename $transition $enterFrom $leaveTo => transition enterFrom leaveTo.

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 c3329e42468dbe162b4bffdce9e50438afbe79b4
Author: 100pah <su...@gmail.com>
AuthorDate: Tue Jun 23 18:49:36 2020 +0800

    fix: rename $transition $enterFrom $leaveTo => transition enterFrom leaveTo.
---
 src/chart/custom.ts          | 66 +++++++++++++++++++---------------
 test/custom-transition.html  | 56 ++++++++++++++---------------
 test/custom-transition2.html | 86 +++++++++++++++++++++++++++++++++++++-------
 3 files changed, 140 insertions(+), 68 deletions(-)

diff --git a/src/chart/custom.ts b/src/chart/custom.ts
index d7fcccc..a29d872 100644
--- a/src/chart/custom.ts
+++ b/src/chart/custom.ts
@@ -78,6 +78,7 @@ import {
 import Transformable from 'zrender/src/core/Transformable';
 import { ItemStyleProps } from '../model/mixin/itemStyle';
 import { cloneValue } from 'zrender/src/animation/Animator';
+import { number } from '../export';
 
 
 const inner = makeInner<{
@@ -109,14 +110,14 @@ type TransitionAnyProps = string | string[];
 type TransitionTransformProps = TransformProps | TransformProps[];
 // Do not declare "Dictionary" in TransitionAnyOption to restrict the type check.
 type TransitionAnyOption = {
-    $transition?: TransitionAnyProps;
-    $enterFrom?: Dictionary<unknown>;
-    $leaveTo?: Dictionary<unknown>;
+    transition?: TransitionAnyProps;
+    enterFrom?: Dictionary<unknown>;
+    leaveTo?: Dictionary<unknown>;
 };
 type TransitionTransformOption = {
-    $transition?: TransitionTransformProps;
-    $enterFrom?: Dictionary<unknown>;
-    $leaveTo?: Dictionary<unknown>;
+    transition?: TransitionTransformProps;
+    enterFrom?: Dictionary<unknown>;
+    leaveTo?: Dictionary<unknown>;
 };
 
 interface CustomBaseElementOption extends Partial<Pick<
@@ -610,12 +611,12 @@ function createEl(elOption: CustomElementOption): Element {
  * ---------------------------------------------
  * [STRATEGY_TRANSITION] The rule of transition:
  * + For props on the root level of a element:
- *      If there is no `$transition` specified, tansform props will be transitioned by default,
+ *      If there is no `transition` specified, tansform props will be transitioned by default,
  *      which is the same as the previous setting in echarts4 and suitable for the scenario
  *      of dataZoom change.
- *      If `$transition` specified, only the specified props will be transitioned.
+ *      If `transition` specified, only the specified props will be transitioned.
  * + For props in `shape` and `style`:
- *      Only props specified in `$transition` will be transitioned.
+ *      Only props specified in `transition` will be transitioned.
  * + Break:
  *      Since ec5, do not make transition to shape by default, because it might result in
  *      performance issue (especially `points` of polygon) and do not necessary in most cases.
@@ -738,22 +739,22 @@ function prepareShapeUpdate(
     const elShape = (el as LooseElementProps).shape;
     let tranFromShapeProps: LooseElementProps['shape'];
 
-    const enterFrom = shapeOpt.$enterFrom;
+    const enterFrom = shapeOpt.enterFrom;
     if (isInit && enterFrom) {
         !tranFromShapeProps && (tranFromShapeProps = transFromProps.shape = {});
         const enterFromKeys = keys(enterFrom);
         for (let i = 0; i < enterFromKeys.length; i++) {
-            // `$enterFrom` props are not necessarily also declared in `shape`/`style`/...,
-            // for example, `opacity` can only declared in `$enterFrom` but not in `style`.
+            // `enterFrom` props are not necessarily also declared in `shape`/`style`/...,
+            // for example, `opacity` can only declared in `enterFrom` but not in `style`.
             const key = enterFromKeys[i];
             // Do not clone, animator will perform that clone.
             tranFromShapeProps[key] = enterFrom[key];
         }
     }
 
-    if (!isInit && elShape && shapeOpt.$transition) {
+    if (!isInit && elShape && shapeOpt.transition) {
         !tranFromShapeProps && (tranFromShapeProps = transFromProps.shape = {});
-        const transitionKeys = normalizeToArray(shapeOpt.$transition);
+        const transitionKeys = normalizeToArray(shapeOpt.transition);
         for (let i = 0; i < transitionKeys.length; i++) {
             const key = transitionKeys[i];
             const elVal = elShape[key];
@@ -774,7 +775,7 @@ function prepareShapeUpdate(
         allPropsShape[key] = cloneValue((shapeOpt as any)[key]);
     }
 
-    const leaveTo = shapeOpt.$leaveTo;
+    const leaveTo = shapeOpt.leaveTo;
     if (leaveTo) {
         const leaveToProps = getOrCreateLeaveToPropsFromEl(el);
         const leaveToShapeProps = leaveToProps.shape || (leaveToProps.shape = {});
@@ -794,13 +795,13 @@ function prepareTransformUpdate(
     transFromProps: ElementProps,
     isInit: boolean
 ): void {
-    const enterFrom = elOption.$enterFrom;
+    const enterFrom = elOption.enterFrom;
     if (isInit && enterFrom) {
         const enterFromKeys = keys(enterFrom);
         for (let i = 0; i < enterFromKeys.length; i++) {
             const key = enterFromKeys[i] as TransformProps;
             if (__DEV__) {
-                checkTransformPropRefer(key, 'el.$enterFrom');
+                checkTransformPropRefer(key, 'el.enterFrom');
             }
             // Do not clone, animator will perform that clone.
             transFromProps[key] = enterFrom[key] as number;
@@ -808,13 +809,13 @@ function prepareTransformUpdate(
     }
 
     if (!isInit) {
-        if (elOption.$transition) {
-            const transitionKeys = normalizeToArray(elOption.$transition);
+        if (elOption.transition) {
+            const transitionKeys = normalizeToArray(elOption.transition);
             for (let i = 0; i < transitionKeys.length; i++) {
                 const key = transitionKeys[i];
                 const elVal = el[key];
                 if (__DEV__) {
-                    checkTransformPropRefer(key, 'el.$transition');
+                    checkTransformPropRefer(key, 'el.transition');
                     checkTansitionRefer(key, elOption[key], elVal);
                 }
                 // Do not clone, see `checkTansitionRefer`.
@@ -840,14 +841,14 @@ function prepareTransformUpdate(
     setTransProp(elOption, allProps, 'originY');
     setTransProp(elOption, allProps, 'rotation');
 
-    const leaveTo = elOption.$leaveTo;
+    const leaveTo = elOption.leaveTo;
     if (leaveTo) {
         const leaveToProps = getOrCreateLeaveToPropsFromEl(el);
         const leaveToKeys = keys(leaveTo);
         for (let i = 0; i < leaveToKeys.length; i++) {
             const key = leaveToKeys[i] as TransformProps;
             if (__DEV__) {
-                checkTransformPropRefer(key, 'el.$leaveTo');
+                checkTransformPropRefer(key, 'el.leaveTo');
             }
             leaveToProps[key] = leaveTo[key] as number;
         }
@@ -868,7 +869,7 @@ function prepareStyleUpdate(
     const elStyle = (el as LooseElementProps).style as LooseElementProps['style'];
     let transFromStyleProps: LooseElementProps['style'];
 
-    const enterFrom = styleOpt.$enterFrom;
+    const enterFrom = styleOpt.enterFrom;
     if (isInit && enterFrom) {
         const enterFromKeys = keys(enterFrom);
         !transFromStyleProps && (transFromStyleProps = transFromProps.style = {});
@@ -879,8 +880,8 @@ function prepareStyleUpdate(
         }
     }
 
-    if (!isInit && elStyle && styleOpt.$transition) {
-        const transitionKeys = normalizeToArray(styleOpt.$transition);
+    if (!isInit && elStyle && styleOpt.transition) {
+        const transitionKeys = normalizeToArray(styleOpt.transition);
         !transFromStyleProps && (transFromStyleProps = transFromProps.style = {});
         for (let i = 0; i < transitionKeys.length; i++) {
             const key = transitionKeys[i];
@@ -893,7 +894,7 @@ function prepareStyleUpdate(
         }
     }
 
-    const leaveTo = styleOpt.$leaveTo;
+    const leaveTo = styleOpt.leaveTo;
     if (leaveTo) {
         const leaveToKeys = keys(leaveTo);
         const leaveToProps = getOrCreateLeaveToPropsFromEl(el);
@@ -941,12 +942,16 @@ const tmpDuringScope = {} as {
 const customDuringAPI = {
     // Usually other props do not need to be changed in animation during.
     setTransform(key: TransformProps, val: unknown) {
-        assert(hasOwn(TRANSFORM_PROPS, key), 'Only ' + transformPropNamesStr + ' available in `setTransform`.');
+        if (__DEV__) {
+            assert(hasOwn(TRANSFORM_PROPS, key), 'Only ' + transformPropNamesStr + ' available in `setTransform`.');
+        }
         tmpDuringScope.el[key] = val as number;
         return this;
     },
     getTransform(key: TransformProps): unknown {
-        assert(hasOwn(TRANSFORM_PROPS, key), 'Only ' + transformPropNamesStr + ' available in `getTransform`.');
+        if (__DEV__) {
+            assert(hasOwn(TRANSFORM_PROPS, key), 'Only ' + transformPropNamesStr + ' available in `getTransform`.');
+        }
         return tmpDuringScope.el[key];
     },
     setShape(key: string, val: unknown) {
@@ -979,6 +984,11 @@ const customDuringAPI = {
 };
 
 function elUpdateDuringAnimation(this: Element, key: string): void {
+    // Do not provide "percent" until some requirements come.
+    // Because consider thies case:
+    // enterFrom: {x: 100, y: 30}, transition: 'x'.
+    // And enter duration is different from update duration.
+    // Thus it might be confused about the meaning of "percent" in during callback.
     const innerEl = inner(this);
     // FIXME `this.markRedraw();` directly ?
     innerEl.orginalDuring.call(this, key);
diff --git a/test/custom-transition.html b/test/custom-transition.html
index f757125..27c0a0b 100644
--- a/test/custom-transition.html
+++ b/test/custom-transition.html
@@ -179,7 +179,7 @@ under the License.
                         shape: {
                             points: makeShapePoints(api, valOnRadius, valOnAngle),
                             valOnAngle: valOnAngle,
-                            $transition: 'valOnAngle'
+                            transition: 'valOnAngle'
                         },
                         style: {
                             lineWidth: 1,
@@ -219,7 +219,7 @@ under the License.
                         y: point[1],
                         shape: {
                             valOnAngle: valOnAngle,
-                            $transition: 'valOnAngle'
+                            transition: 'valOnAngle'
                         },
                         style: {
                             text: getText(valOnAngle),
@@ -407,7 +407,7 @@ under the License.
                             widthRadius: widthRadius,
                             startRadius: startRadius,
                             endRadian: endRadian,
-                            $transition: ['widthRadius', 'startRadius', 'endRadian']
+                            transition: ['widthRadius', 'startRadius', 'endRadian']
                         },
                         style: {
                             lineWidth: 1,
@@ -464,7 +464,7 @@ under the License.
                             startRadius: startRadius,
                             endRadian: endRadian,
                             widthRadius: widthRadius,
-                            $transition: ['startRadius', 'endRadian', 'widthRadius']
+                            transition: ['startRadius', 'endRadian', 'widthRadius']
                         },
                         style: {
                             text: makeText(endRadian),
@@ -652,8 +652,8 @@ under the License.
                                     // polor: anticlockwise-positive radian
                                     // sector: clockwise-positive radian
                                     endAngle: -polarEndRadian,
-                                    $transition: 'endAngle',
-                                    $enterFrom: { endAngle: 0 }
+                                    transition: 'endAngle',
+                                    enterFrom: { endAngle: 0 }
                                 }
                             }
                         }, {
@@ -664,8 +664,8 @@ under the License.
                                 shape: {
                                     points: makePionterPoints(params, polarEndRadian),
                                     polarEndRadian: polarEndRadian,
-                                    $transition: 'polarEndRadian',
-                                    $enterFrom: { polarEndRadian: 0 }
+                                    transition: 'polarEndRadian',
+                                    enterFrom: { polarEndRadian: 0 }
                                 },
                                 during: function (apiDuring) {
                                     apiDuring.setShape(
@@ -692,8 +692,8 @@ under the License.
                             type: 'text',
                             shape: {
                                 valOnRadian: valOnRadian,
-                                $transition: 'valOnRadian',
-                                $enterFrom: { valOnRadian: 0 }
+                                transition: 'valOnRadian',
+                                enterFrom: { valOnRadian: 0 }
                             },
                             style: {
                                 text: makeText(valOnRadian),
@@ -703,7 +703,7 @@ under the License.
                                 fill: 'rgb(0,50,190)',
                                 align: 'center',
                                 verticalAlign: 'middle',
-                                $enterFrom: { opacity: 0 }
+                                enterFrom: { opacity: 0 }
                             },
                             during: function (apiDuring) {
                                 apiDuring.setStyle('text', makeText(apiDuring.getShape('valOnRadian')));
@@ -961,7 +961,7 @@ under the License.
                                     },
                                     style: {
                                         fill: 'blue',
-                                        // $enterFrom: { opacity: 0 }
+                                        // enterFrom: { opacity: 0 }
                                     }
                                 }, {
                                     type: 'circle',
@@ -972,7 +972,7 @@ under the License.
                                     },
                                     style: {
                                         fill: 'green',
-                                        // $enterFrom: { opacity: 0 }
+                                        // enterFrom: { opacity: 0 }
                                     },
                                     textConfig: {
                                         position: 'bottom'
@@ -981,7 +981,7 @@ under the License.
                                         style: {
                                             text: 'xxxx',
                                             fill: 'black',
-                                            // $enterFrom: { opacity: 0 }
+                                            // enterFrom: { opacity: 0 }
                                         }
                                     }
                                 }]
@@ -1078,7 +1078,7 @@ under the License.
                                 type: 'group',
                                 x: pos[0],
                                 y: pos[1],
-                                $enterFrom: {
+                                enterFrom: {
                                     y: 0
                                 },
                                 children: [{
@@ -1164,7 +1164,7 @@ under the License.
                                         y: params.coordSys.y,
                                         width: params.coordSys.width,
                                         height: params.coordSys.height,
-                                        $enterFrom: {width: 0}
+                                        enterFrom: {width: 0}
                                     }
                                 }
                             };
@@ -1225,7 +1225,7 @@ under the License.
                                 style: {
                                     image: weatherIcons.Showers,
                                     width: width,
-                                    $transition: 'width'
+                                    transition: 'width'
                                 }
                             };
                         },
@@ -1277,7 +1277,7 @@ under the License.
                                 x: pos[0],
                                 y: pos[1],
                                 rotation: pos[0] / 500 * Math.PI,
-                                $transition: ['rotation'],
+                                transition: ['rotation'],
                                 originX: -50,
                                 originY: 50,
                                 children: [{
@@ -1291,7 +1291,7 @@ under the License.
                                     },
                                     style: {
                                         fill: 'green',
-                                        $enterFrom: { opacity: 0 }
+                                        enterFrom: { opacity: 0 }
                                     }
                                 }, {
                                     type: 'circle',
@@ -1302,7 +1302,7 @@ under the License.
                                     },
                                     style: {
                                         fill: 'blue',
-                                        $enterFrom: { opacity: 0 }
+                                        enterFrom: { opacity: 0 }
                                     },
                                     textConfig: {
                                         position: 'bottom'
@@ -1311,7 +1311,7 @@ under the License.
                                         style: {
                                             text: 'xxxx',
                                             fill: 'black',
-                                            $enterFrom: { opacity: 0 }
+                                            enterFrom: { opacity: 0 }
                                         }
                                     }
                                 }]
@@ -1363,7 +1363,7 @@ under the License.
                             return {
                                 type: 'rect',
                                 position: pos,
-                                $transition: [],
+                                transition: [],
                                 shape: {
                                     x: -50,
                                     y: 50,
@@ -1373,7 +1373,7 @@ under the License.
                                 },
                                 style: {
                                     fill: 'green',
-                                    $enterFrom: { opacity: 0 }
+                                    enterFrom: { opacity: 0 }
                                 }
                             };
                         },
@@ -1426,10 +1426,10 @@ under the License.
                                 x: pos[0],
                                 y: pos[1],
                                 rotation: pos[0] / 500 * Math.PI,
-                                $transition: ['x', 'y', 'rotation'],
+                                transition: ['x', 'y', 'rotation'],
                                 originX: -50,
                                 originY: 50,
-                                $leaveTo: {scaleX: 0, scaleY: 0},
+                                leaveTo: {scaleX: 0, scaleY: 0},
                                 children: [{
                                     type: 'rect',
                                     shape: {
@@ -1441,7 +1441,7 @@ under the License.
                                     },
                                     style: {
                                         fill: 'green',
-                                        $enterFrom: { opacity: 0 }
+                                        enterFrom: { opacity: 0 }
                                     }
                                 }, {
                                     type: 'circle',
@@ -1452,7 +1452,7 @@ under the License.
                                     },
                                     style: {
                                         fill: 'blue',
-                                        $enterFrom: { opacity: 0 }
+                                        enterFrom: { opacity: 0 }
                                     },
                                     textConfig: {
                                         position: 'bottom'
@@ -1461,7 +1461,7 @@ under the License.
                                         style: {
                                             text: 'xxxx',
                                             fill: 'black',
-                                            $enterFrom: { opacity: 0 }
+                                            enterFrom: { opacity: 0 }
                                         }
                                     }
                                 }]
diff --git a/test/custom-transition2.html b/test/custom-transition2.html
index 7550c55..a2a4caf 100644
--- a/test/custom-transition2.html
+++ b/test/custom-transition2.html
@@ -75,7 +75,7 @@ under the License.
             }];
 
             var colorAll = [
-                '#37A2DA', '#e06343', '#37a354', '#b55dba', '#b5bd48', '#8378EA', '#96BFFF'
+                '#bbb', '#37A2DA', '#e06343', '#37a354', '#b55dba', '#b5bd48', '#8378EA', '#96BFFF'
             ];
             var ANIMATION_DURATION_UPDATE = 1500;
 
@@ -106,13 +106,18 @@ under the License.
 
                 for (var stepIdx = 0; stepIdx < allStepsData.length; stepIdx++) {
                     var stepData = allStepsData[stepIdx];
+                    var ptsInCluster = stepData.pointsInCluster;
+                    var newestClusterIdx = ptsInCluster.length - 1;
+
                     var stepResult = {
                         centroids: stepData.centroids,
-                        points: []
+                        points: [],
+                        newestClusterIndex: newestClusterIdx,
+                        newestClusterMaxAssessmentIndex: -1
                     };
                     result.push(stepResult);
-                    var ptsInCluster = stepData.pointsInCluster;
-                    var newestClusterIdx = ptsInCluster.length - 1;
+
+
                     for (var cIdx = 0; cIdx < ptsInCluster.length; cIdx++) {
                         var clusterPoints = ptsInCluster[cIdx];
                         for (var i = 0; i < clusterPoints.length; i++) {
@@ -125,6 +130,15 @@ under the License.
                             stepResult.points[ptsIdx] = [point[0], point[1], cIdx, newestClusterIdx];
                         }
                     }
+
+                    var assessment = -Infinity;
+                    for (var i = 0; i < stepData.clusterAssment.length; i++) {
+                        var assessmentRecord = stepData.clusterAssment[i];
+                        if (assessmentRecord[0] === newestClusterIdx && assessmentRecord[1] > assessment) {
+                            assessment = assessmentRecord[1];
+                            stepResult.newestClusterMaxAssessmentIndex = i;
+                        }
+                    }
                 }
                 return result;
             }
@@ -138,7 +152,7 @@ under the License.
                     cx: 0,
                     cy: 0,
                     r: 10,
-                    $transition: []
+                    transition: []
                 };
 
                 var contentColor = colorAll[clusterIdx];
@@ -152,15 +166,15 @@ under the License.
                     y: coord[1],
                     // scaleX: isNewCluster ? 1.2 : 1,
                     // scaleY: isNewCluster ? 1.2 : 1,
-                    // $transition: ['scaleX', 'scaleY'],
+                    // transition: ['scaleX', 'scaleY'],
                     shape: shape,
                     style: {
                         fill: contentColor,
                         stroke: '#333',
                         lineWidth: 1,
                         shadowColor: contentColor,
-                        shadowBlur: isNewCluster ? 7 : 0,
-                        $transition: 'shadowBlur'
+                        shadowBlur: isNewCluster ? 12 : 0,
+                        transition: 'shadowBlur'
                     },
                     during: function (apiDuring) {
                         var currContentColor = getColorInTransition(apiDuring, 'content');
@@ -177,7 +191,7 @@ under the License.
                 shape[key + 'G'] = colorRGBA[1];
                 shape[key + 'B'] = colorRGBA[2];
                 shape[key + 'A'] = colorRGBA[3];
-                var transition = shape.$transition || (shape.$transition = []);
+                var transition = shape.transition || (shape.transition = []);
                 transition.push(key + 'R', key + 'G', key + 'B', key + 'A');
             }
 
@@ -191,19 +205,66 @@ under the License.
                 return echarts.color.stringify(colorArr, 'rgba');
             }
 
+            function renderBoundary(params, api) {
+                var center = api.coord([api.value(2), api.value(3)]);
+                var endP = api.coord([api.value(0), api.value(1)]);
+                var diffX = center[0] - endP[0];
+                var diffY = center[1] - endP[1];
+                var radius = Math.pow(diffX * diffX + diffY * diffY, 0.5) + 10;
+                var newCluIdx = api.value(4);
+
+                return {
+                    type: 'circle',
+                    shape: {
+                        cx: isNaN(center[0]) ? 0 : center[0],
+                        cy: isNaN(center[1]) ? 0 : center[1],
+                        r: isNaN(radius) ? 0 : radius,
+                        // transition: 'newCluIdx'
+                    },
+                    style: {
+                        fill: null,
+                        stroke: '#bbb',
+                        lineDash: [5, 5],
+                        lineWidth: 3,
+                        // opacity: 0
+                    },
+                    during: function (apiDuring) {
+                        apiDuring.setShape('cx', Math.random() * 10);
+                        // var cluIdx = apiDuring.setShape('newCluIdx');
+                        // var percent = Math.ceil(cluIdx) - cluIdx;
+                        // var opacity = (Math.ceil(cluIdx) - cluIdx) > 0.5 ?
+                    }
+                };
+            }
+
             function makeStepOption(option, stepResult) {
-                var centroids = stepResult.centroids;
+                var centroids = stepResult.centroids || [];
                 var points = stepResult.points;
+                var newMaxAssmIdx = stepResult.newestClusterMaxAssessmentIndex;
+                var newCluIdx = stepResult.newestClusterIndex;
 
                 option.options.push({
-                    series: {
+                    series: [{
                         type: 'custom',
                         encode: {
                             tooltip: [0, 1]
                         },
                         renderItem: renderItemPoint,
                         data: points
-                    }
+                    }, {
+                        type: 'custom',
+                        renderItem: renderBoundary,
+                        animationDuration: 3000,
+                        data: [
+                            [
+                                (points[newMaxAssmIdx] || [])[0],
+                                (points[newMaxAssmIdx] || [])[1],
+                                (centroids[newCluIdx] || [])[0],
+                                (centroids[newCluIdx] || [])[1],
+                                newCluIdx
+                            ]
+                        ]
+                    }]
                 });
             }
 
@@ -258,6 +319,7 @@ under the License.
                 title: [
                     'Cluster algorithm visualization'
                 ],
+                height: 600,
                 option: option
             });
         });


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


[incubator-echarts] 02/03: feature: custom series, add "extra" in el options for users to config their own properties for animation.

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 4d76f23e3727b296de85d8827ee05bd30d3bf2a3
Author: 100pah <su...@gmail.com>
AuthorDate: Wed Jun 24 00:45:27 2020 +0800

    feature: custom series, add "extra" in el options for users to config their own properties for animation.
---
 src/chart/custom.ts          | 116 ++++++++++++++++++++++-----------
 test/custom-transition.html  |  36 +++++-----
 test/custom-transition2.html | 152 ++++++++++++++++++++++++++++++++++++++-----
 3 files changed, 233 insertions(+), 71 deletions(-)

diff --git a/src/chart/custom.ts b/src/chart/custom.ts
index a29d872..05a9fd7 100644
--- a/src/chart/custom.ts
+++ b/src/chart/custom.ts
@@ -78,7 +78,6 @@ import {
 import Transformable from 'zrender/src/core/Transformable';
 import { ItemStyleProps } from '../model/mixin/itemStyle';
 import { cloneValue } from 'zrender/src/animation/Animator';
-import { number } from '../export';
 
 
 const inner = makeInner<{
@@ -133,8 +132,8 @@ interface CustomBaseElementOption extends Partial<Pick<
     textContent?: CustomTextOption | false;
     // `false` means remove the clipPath
     clipPath?: CustomZRPathOption | false;
-    // Shape can be set in any el option for custom prop for annimation duration.
-    shape?: TransitionAnyOption;
+    // `extra` can be set in any el option for custom prop for annimation duration.
+    extra?: TransitionAnyOption;
     // updateDuringAnimation
     during?(params: typeof customDuringAPI): void;
 };
@@ -197,8 +196,8 @@ 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;
+    style(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps;
+    styleEmphasis(userProps?: 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']>;
@@ -264,6 +263,7 @@ interface CustomSeriesOption extends
 interface LooseElementProps extends ElementProps {
     style?: ZRStyleProps;
     shape?: Dictionary<unknown>;
+    extra?: Dictionary<unknown>;
 }
 
 // Also compat with ec4, where
@@ -635,7 +635,8 @@ function updateElNormal(
     const allProps = {} as ElementProps;
     const elDisplayable = el.isGroup ? null : el as Displayable;
 
-    prepareShapeUpdate(el, elOption, allProps, transFromProps, isInit);
+    prepareShapeOrExtraUpdate('shape', el, elOption, allProps, transFromProps, isInit);
+    prepareShapeOrExtraUpdate('extra', el, elOption, allProps, transFromProps, isInit);
     prepareTransformUpdate(el, elOption, allProps, transFromProps, isInit);
 
     const txCfgOpt = attachedTxInfo && attachedTxInfo.normal.cfg;
@@ -723,7 +724,8 @@ function updateElNormal(
 }
 
 // See [STRATEGY_TRANSITION]
-function prepareShapeUpdate(
+function prepareShapeOrExtraUpdate(
+    mainAttr: 'shape' | 'extra',
     el: Element,
     elOption: CustomElementOption,
     allProps: LooseElementProps,
@@ -731,58 +733,58 @@ function prepareShapeUpdate(
     isInit: boolean
 ): void {
 
-    const shapeOpt = (elOption as CustomElementOption).shape;
-    if (!shapeOpt) {
+    const attrOpt: Dictionary<unknown> & TransitionAnyOption = (elOption as any)[mainAttr];
+    if (!attrOpt) {
         return;
     }
 
-    const elShape = (el as LooseElementProps).shape;
-    let tranFromShapeProps: LooseElementProps['shape'];
+    const elPropsInAttr = (el as LooseElementProps)[mainAttr];
+    let transFromPropsInAttr: Dictionary<unknown>;
 
-    const enterFrom = shapeOpt.enterFrom;
+    const enterFrom = attrOpt.enterFrom;
     if (isInit && enterFrom) {
-        !tranFromShapeProps && (tranFromShapeProps = transFromProps.shape = {});
+        !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {});
         const enterFromKeys = keys(enterFrom);
         for (let i = 0; i < enterFromKeys.length; i++) {
             // `enterFrom` props are not necessarily also declared in `shape`/`style`/...,
             // for example, `opacity` can only declared in `enterFrom` but not in `style`.
             const key = enterFromKeys[i];
             // Do not clone, animator will perform that clone.
-            tranFromShapeProps[key] = enterFrom[key];
+            transFromPropsInAttr[key] = enterFrom[key];
         }
     }
 
-    if (!isInit && elShape && shapeOpt.transition) {
-        !tranFromShapeProps && (tranFromShapeProps = transFromProps.shape = {});
-        const transitionKeys = normalizeToArray(shapeOpt.transition);
+    if (!isInit && elPropsInAttr && attrOpt.transition) {
+        !transFromPropsInAttr && (transFromPropsInAttr = transFromProps[mainAttr] = {});
+        const transitionKeys = normalizeToArray(attrOpt.transition);
         for (let i = 0; i < transitionKeys.length; i++) {
             const key = transitionKeys[i];
-            const elVal = elShape[key];
+            const elVal = elPropsInAttr[key];
             if (__DEV__) {
-                checkTansitionRefer(key, (shapeOpt as any)[key], elVal);
+                checkTansitionRefer(key, (attrOpt as any)[key], elVal);
             }
             // Do not clone, see `checkTansitionRefer`.
-            tranFromShapeProps[key] = elVal;
+            transFromPropsInAttr[key] = elVal;
         }
     }
 
-    const allPropsShape = allProps.shape = {} as LooseElementProps['shape'];
-    const shapeOptKeys = keys(shapeOpt);
-    for (let i = 0; i < shapeOptKeys.length; i++) {
-        const key = shapeOptKeys[i];
+    const allPropsInAttr = allProps[mainAttr] = {} as Dictionary<unknown>;
+    const keysInAttr = keys(attrOpt);
+    for (let i = 0; i < keysInAttr.length; i++) {
+        const key = keysInAttr[i];
         // To avoid share one object with different element, and
         // to avoid user modify the object inexpectedly, have to clone.
-        allPropsShape[key] = cloneValue((shapeOpt as any)[key]);
+        allPropsInAttr[key] = cloneValue((attrOpt as any)[key]);
     }
 
-    const leaveTo = shapeOpt.leaveTo;
+    const leaveTo = attrOpt.leaveTo;
     if (leaveTo) {
         const leaveToProps = getOrCreateLeaveToPropsFromEl(el);
-        const leaveToShapeProps = leaveToProps.shape || (leaveToProps.shape = {});
+        const leaveToPropsInAttr: Dictionary<unknown> = leaveToProps[mainAttr] || (leaveToProps[mainAttr] = {});
         const leaveToKeys = keys(leaveTo);
         for (let i = 0; i < leaveToKeys.length; i++) {
             const key = leaveToKeys[i];
-            leaveToShapeProps[key] = leaveTo[key];
+            leaveToPropsInAttr[key] = leaveTo[key];
         }
     }
 }
@@ -955,19 +957,28 @@ const customDuringAPI = {
         return tmpDuringScope.el[key];
     },
     setShape(key: string, val: unknown) {
-        // In custom series, el other than Path can also has `shape` for intepolating props.
-        const shape = (tmpDuringScope.el as any).shape || ((tmpDuringScope.el as any).shape = {});
+        if (__DEV__) {
+            assertNotReserved(key);
+        }
+        const shape = (tmpDuringScope.el as graphicUtil.Path).shape
+            || ((tmpDuringScope.el as graphicUtil.Path).shape = {});
         shape[key] = val;
         tmpDuringScope.isShapeDirty = true;
         return this;
     },
     getShape(key: string): unknown {
-        const shape = (tmpDuringScope.el as any).shape;
+        if (__DEV__) {
+            assertNotReserved(key);
+        }
+        const shape = (tmpDuringScope.el as graphicUtil.Path).shape;
         if (shape) {
             return shape[key];
         }
     },
     setStyle(key: string, val: unknown) {
+        if (__DEV__) {
+            assertNotReserved(key);
+        }
         const style = (tmpDuringScope.el as Displayable).style;
         if (style) {
             style[key] = val;
@@ -976,13 +987,42 @@ const customDuringAPI = {
         return this;
     },
     getStyle(key: string): unknown {
+        if (__DEV__) {
+            assertNotReserved(key);
+        }
         const style = (tmpDuringScope.el as Displayable).style;
         if (style) {
             return style[key];
         }
+    },
+    setExtra(key: string, val: unknown) {
+        if (__DEV__) {
+            assertNotReserved(key);
+        }
+        const extra = (tmpDuringScope.el as LooseElementProps).extra
+            || ((tmpDuringScope.el as LooseElementProps).extra = {});
+        extra[key] = val;
+        return this;
+    },
+    getExtra(key: string): unknown {
+        if (__DEV__) {
+            assertNotReserved(key);
+        }
+        const extra = (tmpDuringScope.el as LooseElementProps).extra;
+        if (extra) {
+            return extra[key];
+        }
     }
 };
 
+function assertNotReserved(key: string) {
+    if (__DEV__) {
+        if (key === 'transition' || key === 'enterFrom' || key === 'leaveTo') {
+            throw new Error('key must not be "' + key + '"');
+        }
+    }
+}
+
 function elUpdateDuringAnimation(this: Element, key: string): void {
     // Do not provide "percent" until some requirements come.
     // Because consider thies case:
@@ -1297,7 +1337,7 @@ function makeRenderItem(
      * @public
      * @param dataIndexInside by default `currDataIndexInside`.
      */
-    function style(extra?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps {
+    function style(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps {
         if (__DEV__) {
             warnDeprecated('api.style', 'Please write literal style directly instead.');
         }
@@ -1326,10 +1366,10 @@ function makeRenderItem(
             : null;
         const textConfig = graphicUtil.createTextConfig(textStyle, labelModel, opt, false);
 
-        preFetchFromExtra(extra, itemStyle);
+        preFetchFromExtra(userProps, itemStyle);
         itemStyle = convertToEC4StyleForCustomSerise(itemStyle, textStyle, textConfig);
 
-        extra && applyExtraAfter(itemStyle, extra);
+        userProps && applyUserPropsAfter(itemStyle, userProps);
         (itemStyle as LegacyStyleProps).legacy = true;
 
         return itemStyle;
@@ -1340,7 +1380,7 @@ function makeRenderItem(
      * @public
      * @param dataIndexInside by default `currDataIndexInside`.
      */
-    function styleEmphasis(extra?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps {
+    function styleEmphasis(userProps?: ZRStyleProps, dataIndexInside?: number): ZRStyleProps {
         if (__DEV__) {
             warnDeprecated('api.styleEmphasis', 'Please write literal style directly instead.');
         }
@@ -1359,16 +1399,16 @@ function makeRenderItem(
             : null;
         const textConfig = graphicUtil.createTextConfig(textStyle, labelModel, null, true);
 
-        preFetchFromExtra(extra, itemStyle);
+        preFetchFromExtra(userProps, itemStyle);
         itemStyle = convertToEC4StyleForCustomSerise(itemStyle, textStyle, textConfig);
 
-        extra && applyExtraAfter(itemStyle, extra);
+        userProps && applyUserPropsAfter(itemStyle, userProps);
         (itemStyle as LegacyStyleProps).legacy = true;
 
         return itemStyle;
     }
 
-    function applyExtraAfter(itemStyle: ZRStyleProps, extra: ZRStyleProps): void {
+    function applyUserPropsAfter(itemStyle: ZRStyleProps, extra: ZRStyleProps): void {
         for (const key in extra) {
             if (hasOwn(extra, key)) {
                 (itemStyle as any)[key] = (extra as any)[key];
diff --git a/test/custom-transition.html b/test/custom-transition.html
index 27c0a0b..24bd2fc 100644
--- a/test/custom-transition.html
+++ b/test/custom-transition.html
@@ -177,7 +177,9 @@ under the License.
                     children.push({
                         type: 'polygon',
                         shape: {
-                            points: makeShapePoints(api, valOnRadius, valOnAngle),
+                            points: makeShapePoints(api, valOnRadius, valOnAngle)
+                        },
+                        extra: {
                             valOnAngle: valOnAngle,
                             transition: 'valOnAngle'
                         },
@@ -188,7 +190,7 @@ under the License.
                         },
                         during: function (apiDuring) {
                             apiDuring.setShape('points', makeShapePoints(
-                                api, valOnRadius, apiDuring.getShape('valOnAngle')
+                                api, valOnRadius, apiDuring.getExtra('valOnAngle')
                             ));
                         }
                     });
@@ -217,7 +219,7 @@ under the License.
                         type: 'text',
                         x: point[0],
                         y: point[1],
-                        shape: {
+                        extra: {
                             valOnAngle: valOnAngle,
                             transition: 'valOnAngle'
                         },
@@ -232,7 +234,7 @@ under the License.
                         },
                         z2: 50,
                         during: function (apiDuring) {
-                            var iValOnAngle = apiDuring.getShape('valOnAngle');
+                            var iValOnAngle = apiDuring.getExtra('valOnAngle');
                             var point = makeLabelPosition(api, valOnRadius, iValOnAngle);
                             apiDuring.setTransform('x', point[0]).setTransform('y', point[1]);
                             apiDuring.setStyle('text', getText(iValOnAngle));
@@ -403,7 +405,9 @@ under the License.
                     children.push({
                         type: 'polygon',
                         shape: {
-                            points: makeShapePoints(params, widthRadius, startRadius, endRadian),
+                            points: makeShapePoints(params, widthRadius, startRadius, endRadian)
+                        },
+                        extra: {
                             widthRadius: widthRadius,
                             startRadius: startRadius,
                             endRadian: endRadian,
@@ -417,9 +421,9 @@ under the License.
                         during: function (apiDuring) {
                             apiDuring.setShape('points', makeShapePoints(
                                 params,
-                                apiDuring.getShape('widthRadius'),
-                                apiDuring.getShape('startRadius'),
-                                apiDuring.getShape('endRadian')
+                                apiDuring.getExtra('widthRadius'),
+                                apiDuring.getExtra('startRadius'),
+                                apiDuring.getExtra('endRadian')
                             ));
                         }
                     });
@@ -460,7 +464,7 @@ under the License.
                         type: 'text',
                         x: point[0],
                         y: point[1],
-                        shape: {
+                        extra: {
                             startRadius: startRadius,
                             endRadian: endRadian,
                             widthRadius: widthRadius,
@@ -481,11 +485,11 @@ under the License.
                         },
                         z2: 50,
                         during: function (apiDuring) {
-                            var endRadian = apiDuring.getShape('endRadian');
+                            var endRadian = apiDuring.getExtra('endRadian');
                             var point = makeLabelPosition(
                                 params,
-                                apiDuring.getShape('widthRadius'),
-                                apiDuring.getShape('startRadius'),
+                                apiDuring.getExtra('widthRadius'),
+                                apiDuring.getExtra('startRadius'),
                                 endRadian
                             );
                             apiDuring.setTransform('x', point[0]).setTransform('y', point[1]);
@@ -663,6 +667,8 @@ under the License.
                                 type: 'polygon',
                                 shape: {
                                     points: makePionterPoints(params, polarEndRadian),
+                                },
+                                extra: {
                                     polarEndRadian: polarEndRadian,
                                     transition: 'polarEndRadian',
                                     enterFrom: { polarEndRadian: 0 }
@@ -670,7 +676,7 @@ under the License.
                                 during: function (apiDuring) {
                                     apiDuring.setShape(
                                         'points',
-                                        makePionterPoints(params, apiDuring.getShape('polarEndRadian'))
+                                        makePionterPoints(params, apiDuring.getExtra('polarEndRadian'))
                                     );
                                 }
                             },
@@ -690,7 +696,7 @@ under the License.
                             }
                         }, {
                             type: 'text',
-                            shape: {
+                            extra: {
                                 valOnRadian: valOnRadian,
                                 transition: 'valOnRadian',
                                 enterFrom: { valOnRadian: 0 }
@@ -706,7 +712,7 @@ under the License.
                                 enterFrom: { opacity: 0 }
                             },
                             during: function (apiDuring) {
-                                apiDuring.setStyle('text', makeText(apiDuring.getShape('valOnRadian')));
+                                apiDuring.setStyle('text', makeText(apiDuring.getExtra('valOnRadian')));
                             }
                         }]
                     };
diff --git a/test/custom-transition2.html b/test/custom-transition2.html
index a2a4caf..3e9eb56 100644
--- a/test/custom-transition2.html
+++ b/test/custom-transition2.html
@@ -148,15 +148,12 @@ under the License.
                 var clusterIdx = api.value(2);
                 var isNewCluster = clusterIdx === api.value(3);
 
-                var shape = {
-                    cx: 0,
-                    cy: 0,
-                    r: 10,
+                var extra = {
                     transition: []
                 };
 
                 var contentColor = colorAll[clusterIdx];
-                addColorTransition(shape, contentColor, 'content');
+                addColorTransition(extra, contentColor, 'content');
                 // var borderColor = isNewCluster ? '#333' : '#fff';
                 // addColorTransition(shape, borderColor, 'border');
 
@@ -167,7 +164,12 @@ under the License.
                     // scaleX: isNewCluster ? 1.2 : 1,
                     // scaleY: isNewCluster ? 1.2 : 1,
                     // transition: ['scaleX', 'scaleY'],
-                    shape: shape,
+                    shape: {
+                        cx: 0,
+                        cy: 0,
+                        r: 10
+                    },
+                    extra: extra,
                     style: {
                         fill: contentColor,
                         stroke: '#333',
@@ -197,10 +199,10 @@ under the License.
 
             function getColorInTransition(apiDuring, key) {
                 var colorArr = [
-                    apiDuring.getShape(key + 'R'),
-                    apiDuring.getShape(key + 'G'),
-                    apiDuring.getShape(key + 'B'),
-                    apiDuring.getShape(key + 'A')
+                    apiDuring.getExtra(key + 'R'),
+                    apiDuring.getExtra(key + 'G'),
+                    apiDuring.getExtra(key + 'B'),
+                    apiDuring.getExtra(key + 'A')
                 ];
                 return echarts.color.stringify(colorArr, 'rgba');
             }
@@ -216,10 +218,16 @@ under the License.
                 return {
                     type: 'circle',
                     shape: {
-                        cx: isNaN(center[0]) ? 0 : center[0],
-                        cy: isNaN(center[1]) ? 0 : center[1],
-                        r: isNaN(radius) ? 0 : radius,
-                        // transition: 'newCluIdx'
+                        cx: 0,
+                        cy: 0,
+                        r: 0
+                    },
+                    extra: {
+                        cxNext: isNaN(center[0]) ? 0 : center[0],
+                        cyNext: isNaN(center[1]) ? 0 : center[1],
+                        rNext: isNaN(radius) ? 0 : radius,
+                        renderNumber: ++renderNumber,
+                        transition: 'renderNumber'
                     },
                     style: {
                         fill: null,
@@ -229,12 +237,118 @@ under the License.
                         // opacity: 0
                     },
                     during: function (apiDuring) {
-                        apiDuring.setShape('cx', Math.random() * 10);
-                        // var cluIdx = apiDuring.setShape('newCluIdx');
-                        // var percent = Math.ceil(cluIdx) - cluIdx;
-                        // var opacity = (Math.ceil(cluIdx) - cluIdx) > 0.5 ?
+                        var currNum = apiDuring.getExtra('renderNumber');
+                        if (apiDuring.getStyle('opacity') < 0.9) {
+                            apiDuring
+                                .setShape('cx', apiDuring.getExtra('cxNext'))
+                                .setShape('cy', apiDuring.getExtra('cyNext'))
+                                .setShape('r', apiDuring.getExtra('rNext'));
+                        }
+                        apiDuring.setStyle('opacity', 1 - (renderNumber - currNum));
                     }
                 };
+
+                // return {
+                //     type: 'group',
+                //     children: [{
+                //         type: 'circle',
+                //         shape: {
+                //             cx: 0,
+                //             cy: 0,
+                //             r: 0,
+                //         },
+                //         extra: {
+                //             renderNumber: ++renderNumber,
+                //             cxNext: isNaN(center[0]) ? 0 : center[0],
+                //             cyNext: isNaN(center[1]) ? 0 : center[1],
+                //             rNext: isNaN(radius) ? 0 : radius,
+                //             transition: 'renderNumber'
+                //         },
+                //         style: {
+                //             fill: null,
+                //             stroke: '#bbb',
+                //             lineDash: [5, 5],
+                //             lineWidth: 3
+                //         },
+                //         during: function (apiDuring) {
+                //             var currNum = apiDuring.getExtra('renderNumber');
+                //             var progress = 0.5 - (renderNumber - currNum);
+                //             if (progress >= 0) {
+                //                 apiDuring
+                //                     .setShape('cx', apiDuring.getExtra('cxNext'))
+                //                     .setShape('cy', apiDuring.getExtra('cyNext'))
+                //                     .setShape('r', apiDuring.getExtra('rNext'));
+                //             }
+                //             var opacity = Math.abs(progress) / 0.5;
+                //             apiDuring.setStyle('opacity', opacity);
+                //         }
+                //     }, {
+                //         type: 'circle',
+                //         shape: {
+                //             cx: 0,
+                //             cy: 0,
+                //             r: 0,
+                //         },
+                //         extra: {
+                //             renderNumber: ++renderNumber,
+                //             cxNext: isNaN(center[0]) ? 0 : center[0],
+                //             cyNext: isNaN(center[1]) ? 0 : center[1],
+                //             rNext: isNaN(radius) ? 0 : radius,
+                //             transition: 'renderNumber'
+                //         },
+                //         style: {
+                //             fill: null,
+                //             stroke: '#bbb',
+                //             lineDash: [5, 5],
+                //             lineWidth: 3
+                //         },
+                //         during: function (apiDuring) {
+                //             var currNum = apiDuring.getExtra('renderNumber');
+                //             var progress = 0.5 - (renderNumber - currNum);
+                //             if (progress >= 0) {
+                //                 apiDuring
+                //                     .setShape('cx', apiDuring.getExtra('cxNext'))
+                //                     .setShape('cy', apiDuring.getExtra('cyNext'))
+                //                     .setShape('r', apiDuring.getExtra('rNext'));
+                //             }
+                //             var opacity = Math.abs(progress) / 0.5;
+                //             apiDuring.setStyle('opacity', opacity);
+                //         }
+                //     }]
+                // };
+                // return {
+                //     type: 'circle',
+                //     shape: {
+                //         cx: 0,
+                //         cy: 0,
+                //         r: 0,
+                //     },
+                //     extra: {
+                //         renderNumber: ++renderNumber,
+                //         cxNext: isNaN(center[0]) ? 0 : center[0],
+                //         cyNext: isNaN(center[1]) ? 0 : center[1],
+                //         rNext: isNaN(radius) ? 0 : radius,
+                //         transition: 'renderNumber'
+                //     },
+                //     style: {
+                //         fill: null,
+                //         stroke: '#bbb',
+                //         lineDash: [5, 5],
+                //         lineWidth: 3
+                //     },
+                //     during: function (apiDuring) {
+                //         var currNum = apiDuring.getExtra('renderNumber');
+                //         var progress = 0.5 - (renderNumber - currNum);
+                //         if (progress >= 0) {
+                //             apiDuring
+                //                 .setShape('cx', apiDuring.getExtra('cxNext'))
+                //                 .setShape('cy', apiDuring.getExtra('cyNext'))
+                //                 .setShape('r', apiDuring.getExtra('rNext'));
+                //         }
+                //         var opacity = Math.abs(progress) / 0.5;
+                //         apiDuring.setStyle('opacity', opacity);
+                //     }
+                // };
             }
 
             function makeStepOption(option, stepResult) {
@@ -268,6 +382,8 @@ under the License.
                 });
             }
 
+            var renderNumber = 0;
+
             var option = {
                 timeline: {
                     top: 'center',


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