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

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

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

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

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

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

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


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