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/09/27 17:46:54 UTC

[incubator-echarts] 03/05: fix: enhance transition morphing API.

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

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

commit 58397cd61297beafe27e178e41d12203f08d1ee2
Author: 100pah <su...@gmail.com>
AuthorDate: Fri Sep 25 03:40:15 2020 +0800

    fix: enhance transition morphing API.
---
 src/chart/custom.ts              | 125 +++++----
 src/data/DataDiffer.ts           |   6 +-
 src/data/List.ts                 |   2 +-
 src/data/helper/transform.ts     |  51 +++-
 src/echarts.ts                   |  44 ++--
 src/model/Series.ts              |   2 +
 test/custom-shape-morphing2.html | 532 ++++++++++++++++++++++++++++-----------
 7 files changed, 517 insertions(+), 245 deletions(-)

diff --git a/src/chart/custom.ts b/src/chart/custom.ts
index ca87fb4..fed727d 100644
--- a/src/chart/custom.ts
+++ b/src/chart/custom.ts
@@ -19,7 +19,7 @@
 
 import {
     hasOwn, assert, isString, retrieve2, retrieve3, defaults, each,
-    keys, isArrayLike, bind, isFunction, eqNaN, isArray
+    keys, isArrayLike, bind, isFunction, eqNaN, noop
 } from 'zrender/src/core/util';
 import * as graphicUtil from '../util/graphic';
 import { setDefaultStateProxy, enableHoverEmphasis } from '../util/states';
@@ -27,7 +27,7 @@ import * as labelStyleHelper from '../label/labelStyle';
 import {getDefaultLabel} from './helper/labelHelper';
 import createListFromArray from './helper/createListFromArray';
 import {getLayoutOnAxis} from '../layout/barGrid';
-import DataDiffer from '../data/DataDiffer';
+import DataDiffer, { DataDiffCallbackMode } from '../data/DataDiffer';
 import SeriesModel from '../model/Series';
 import Model from '../model/Model';
 import ChartView from '../view/Chart';
@@ -462,56 +462,69 @@ class CustomSeriesView extends ChartView {
         // roam or data zoom according to `actionType`.
 
         const transOpt = customSeries.__transientTransitionOpt;
+        const cbMode: DataDiffCallbackMode = transOpt ? 'multiple' : 'single';
 
-        (new DataDiffer(
-            oldData ? oldData.getIndices() : [],
-            data.getIndices(),
-            createGetKey(oldData, transOpt && transOpt.from),
-            createGetKey(data, transOpt && transOpt.to),
-            null,
-            transOpt ? 'multiple' : 'single'
-        ))
-        .add(function (newIdx) {
-            createOrUpdateItem(
-                null, newIdx, renderItem(newIdx, payload), customSeries, group, data, null
-            );
-        })
-        .update(function (newIdx, oldIdx) {
-            createOrUpdateItem(
-                oldData.getItemGraphicEl(oldIdx),
-                newIdx, renderItem(newIdx, payload), customSeries, group, data, null
-            );
-        })
-        .remove(function (oldIdx) {
-            doRemoveEl(oldData.getItemGraphicEl(oldIdx), customSeries, group);
-        })
-        .updateManyToOne(function (newIdx, oldIndices) {
-            const oldElsToMerge: graphicUtil.Path[] = [];
-            for (let i = 0; i < oldIndices.length; i++) {
-                const oldEl = oldData.getItemGraphicEl(oldIndices[i]);
-                if (elCanMorph(oldEl)) {
-                    oldElsToMerge.push(oldEl);
+        if (transOpt && (transOpt.from == null || transOpt.to == null)) {
+            oldData && oldData.each(function (oldIdx) {
+                doRemoveEl(oldData.getItemGraphicEl(oldIdx), customSeries, group);
+            });
+            data.each(function (newIdx) {
+                createOrUpdateItem(
+                    null, newIdx, renderItem(newIdx, payload), customSeries, group, data, null
+                );
+            });
+        }
+        else {
+            (new DataDiffer(
+                oldData ? oldData.getIndices() : [],
+                data.getIndices(),
+                createGetKey(oldData, cbMode, transOpt && transOpt.from),
+                createGetKey(data, cbMode, transOpt && transOpt.to),
+                null,
+                cbMode
+            ))
+            .add(function (newIdx) {
+                createOrUpdateItem(
+                    null, newIdx, renderItem(newIdx, payload), customSeries, group, data, null
+                );
+            })
+            .update(function (newIdx, oldIdx) {
+                createOrUpdateItem(
+                    oldData.getItemGraphicEl(oldIdx),
+                    newIdx, renderItem(newIdx, payload), customSeries, group, data, null
+                );
+            })
+            .remove(function (oldIdx) {
+                doRemoveEl(oldData.getItemGraphicEl(oldIdx), customSeries, group);
+            })
+            .updateManyToOne(function (newIdx, oldIndices) {
+                const oldElsToMerge: graphicUtil.Path[] = [];
+                for (let i = 0; i < oldIndices.length; i++) {
+                    const oldEl = oldData.getItemGraphicEl(oldIndices[i]);
+                    if (elCanMorph(oldEl)) {
+                        oldElsToMerge.push(oldEl);
+                    }
+                    removeElementDirectly(oldEl, group);
                 }
-                removeElementDirectly(oldEl, group);
-            }
-            createOrUpdateItem(
-                null, newIdx, renderItem(newIdx, payload), customSeries,
-                group, data, oldElsToMerge
-            );
-        })
-        .updateOneToMany(function (newIndices, oldIdx) {
-            const newLen = newIndices.length;
-            const oldEl = oldData.getItemGraphicEl(oldIdx);
-            const oldElSplitted = elCanMorph(oldEl) ? splitShapeForMorphingFrom(oldEl, newLen) : [];
-            removeElementDirectly(oldEl, group);
-            for (let i = 0; i < newLen; i++) {
                 createOrUpdateItem(
-                    null, newIndices[i], renderItem(newIndices[i], payload), customSeries,
-                    group, data, oldElSplitted[i]
+                    null, newIdx, renderItem(newIdx, payload), customSeries,
+                    group, data, oldElsToMerge
                 );
-            }
-        })
-        .execute();
+            })
+            .updateOneToMany(function (newIndices, oldIdx) {
+                const newLen = newIndices.length;
+                const oldEl = oldData.getItemGraphicEl(oldIdx);
+                const oldElSplitted = elCanMorph(oldEl) ? splitShapeForMorphingFrom(oldEl, newLen) : [];
+                removeElementDirectly(oldEl, group);
+                for (let i = 0; i < newLen; i++) {
+                    createOrUpdateItem(
+                        null, newIndices[i], renderItem(newIndices[i], payload), customSeries,
+                        group, data, oldElSplitted[i]
+                    );
+                }
+            })
+            .execute();
+        }
 
         // Do clipping
         const clipPath = customSeries.get('clip', true)
@@ -582,20 +595,24 @@ class CustomSeriesView extends ChartView {
 ChartView.registerClass(CustomSeriesView);
 
 
-function createGetKey(data: List, dimension: DimensionLoose) {
+function createGetKey(
+    data: List,
+    cbMode: DataDiffCallbackMode,
+    dimension: DimensionLoose
+) {
     if (!data) {
         return;
     }
 
-    const diffBy = data.getDimension(dimension);
-
-    if (diffBy == null) {
+    if (cbMode === 'single') {
         return function (rawIdx: number, dataIndex: number) {
             return data.getId(dataIndex);
         };
     }
 
-    const dimInfo = data.getDimensionInfo(diffBy);
+    const diffByDimName = data.getDimension(dimension);
+    const dimInfo = data.getDimensionInfo(diffByDimName);
+
     if (!dimInfo) {
         let errMsg = '';
         if (__DEV__) {
@@ -605,7 +622,7 @@ function createGetKey(data: List, dimension: DimensionLoose) {
     }
     const ordinalMeta = dimInfo.ordinalMeta;
     return function (rawIdx: number, dataIndex: number) {
-        let key = data.get(diffBy, dataIndex);
+        let key = data.get(diffByDimName, dataIndex);
         if (ordinalMeta) {
             key = ordinalMeta.categories[key as number];
         }
diff --git a/src/data/DataDiffer.ts b/src/data/DataDiffer.ts
index 43baad1..4b56017 100644
--- a/src/data/DataDiffer.ts
+++ b/src/data/DataDiffer.ts
@@ -266,12 +266,12 @@ class DataDiffer<CTX = unknown> {
         keyArr: string[],
         keyGetterName: '_oldKeyGetter' | '_newKeyGetter'
     ): void {
-        const cbModeByKey = this._cbModeMultiple;
+        const cbModeMultiple = this._cbModeMultiple;
 
         for (let i = 0; i < arr.length; i++) {
             // Add prefix to avoid conflict with Object.prototype.
             const key = '_ec_' + this[keyGetterName](arr[i], i);
-            if (!cbModeByKey) {
+            if (!cbModeMultiple) {
                 keyArr[i] = key;
             }
             if (!map) {
@@ -285,7 +285,7 @@ class DataDiffer<CTX = unknown> {
                 // Simple optimize: in most cases, one index has one key,
                 // do not need array.
                 map[key] = i;
-                if (cbModeByKey) {
+                if (cbModeMultiple) {
                     keyArr.push(key);
                 }
             }
diff --git a/src/data/List.ts b/src/data/List.ts
index 1f0d0a8..34b2736 100644
--- a/src/data/List.ts
+++ b/src/data/List.ts
@@ -2100,7 +2100,7 @@ class List<
             dimensions: ItrParamDims
         ): Array<DimensionLoose> {
             if (!zrUtil.isArray(dimensions)) {
-                dimensions = [dimensions];
+                dimensions = dimensions != null ? [dimensions] : [];
             }
             return dimensions;
         };
diff --git a/src/data/helper/transform.ts b/src/data/helper/transform.ts
index 68715a8..bad0de4 100644
--- a/src/data/helper/transform.ts
+++ b/src/data/helper/transform.ts
@@ -47,15 +47,6 @@ export interface DataTransformOption {
     print?: boolean;
 }
 
-export interface DataTransformResult {
-    source: Source;
-}
-
-export interface DataTransform {
-    (sourceList: Source[], config: DataTransformConfig): {
-    }
-}
-
 export interface ExternalDataTransform<TO extends DataTransformOption = DataTransformOption> {
     // Must include namespace like: 'ecStat:regression'
     type: string,
@@ -73,7 +64,21 @@ interface ExternalDataTransformParam<TO extends DataTransformOption = DataTransf
 }
 export interface ExternalDataTransformResultItem {
     data: OptionSourceData;
+    /**
+     * A `transform` can optionally return a dimensions definition.
+     * If the `transform` make sure the dimensions of the result data, it can make that return.
+     * Otherwise, it's recommanded not to make such a `dimensions`. In this case, echarts will
+     * inherit dimensions definition from the upstream. If there is no dimensions definition
+     * of the upstream, the echarts will left it undefined.
+     * Notice: return a incorrect dimensions definition will cause the downstream can not use
+     * the values under that dimensions correctly.
+     *
+     * @see also `source.isDimensionsDefined`.
+     */
     dimensions?: DimensionDefinitionLoose[];
+    /**
+     * Similar to `dimensions`, a `transform` can return that optionally.
+     */
     sourceHeader?: OptionSourceHeader;
 }
 interface ExternalDimensionDefinition extends Partial<DimensionDefinition> {
@@ -97,14 +102,33 @@ class ExternalSource {
     sourceFormat: SourceFormat;
     sourceHeaderCount: number;
 
+    /**
+     * @return If dimension not found, return null/undefined.
+     */
     getDimensionInfo(dim: DimensionLoose): ExternalDimensionDefinition {
         return;
     }
 
+    /**
+     * If dimensions are defined (see `isDimensionsDefined`), `dimensionInfoAll` is corresponding to
+     * the defined dimensions.
+     * Otherwise, `dimensionInfoAll` is determined by data columns.
+     * @return Always return an array (even empty array).
+     */
     getDimensionInfoAll(): ExternalDimensionDefinition[] {
         return;
     }
 
+    /**
+     * dimensions defined if and only if:
+     * (a) dataset.dimensions are declared.
+     * or
+     * (b) dataset data include dimensions definitions in data (detected or via specified `sourceHeader`)
+     */
+    isDimensionsDefined(): boolean {
+        return;
+    }
+
     getRawDataItem(dataIndex: number): OptionDataItem {
         return;
     }
@@ -210,6 +234,7 @@ function createExternalSource(internalSource: Source): ExternalSource {
 
     extSource.getDimensionInfo = bind(getDimensionInfo, null, dimensions, dimsByName);
     extSource.getDimensionInfoAll = bind(getDimensionInfoAll, null, dimensions);
+    extSource.isDimensionsDefined = bind(isDimensionsDefined, null, !!dimsDef);
 
     return extSource;
 }
@@ -235,12 +260,14 @@ function getDimensionInfo(
     }
 }
 
-function getDimensionInfoAll(
-    dimensions: ExternalDimensionDefinition[]
-): ExternalDimensionDefinition[] {
+function getDimensionInfoAll(dimensions: ExternalDimensionDefinition[]): ExternalDimensionDefinition[] {
     return dimensions;
 }
 
+function isDimensionsDefined(defined: boolean): boolean {
+    return defined;
+}
+
 
 
 const externalTransformMap = createHashMap<ExternalDataTransform, string>();
diff --git a/src/echarts.ts b/src/echarts.ts
index c27a65b..9930fe1 100644
--- a/src/echarts.ts
+++ b/src/echarts.ts
@@ -187,16 +187,20 @@ interface SetOptionOpts {
     // Rule: only `id` mapped will be merged,
     // other components of the certain `mainType` will be removed.
     replaceMerge?: GlobalModelSetOptionOpts['replaceMerge'];
-    transition?: SetOptionTransitionOptItem | SetOptionTransitionOptItem[];
+    transition?: SetOptionTransitionOpt
 };
 
 interface SetOptionTransitionOptItem {
-    from: SetOptionTransitionOptFinder | DimensionLoose;
-    to: SetOptionTransitionOptFinder | DimensionLoose;
+    // If `from` not given, it means that do not make series transition mandatorily.
+    // There might be transition mapping dy default. Sometimes we do not need them,
+    // which might bring about misleading.
+    from?: SetOptionTransitionOptFinder;
+    to: SetOptionTransitionOptFinder;
 }
 interface SetOptionTransitionOptFinder extends modelUtil.ModelFinderObject {
     dimension: DimensionLoose;
 }
+type SetOptionTransitionOpt = SetOptionTransitionOptItem | SetOptionTransitionOptItem[];
 
 
 type EventMethodName = 'on' | 'off';
@@ -267,7 +271,7 @@ let createExtensionAPI: (ecIns: ECharts) => ExtensionAPI;
 let enableConnect: (ecIns: ECharts) => void;
 let setTransitionOpt: (
     chart: ECharts,
-    transitionOpt: SetOptionTransitionOptItem | SetOptionTransitionOptItem[]
+    transitionOpt: SetOptionTransitionOpt
 ) => void;
 
 let markStatusToUpdate: (ecIns: ECharts) => void;
@@ -518,7 +522,7 @@ class ECharts extends Eventful {
 
         let silent;
         let replaceMerge;
-        let transitionOpt;
+        let transitionOpt: SetOptionTransitionOpt;
         if (isObject(notMerge)) {
             lazyUpdate = notMerge.lazyUpdate;
             silent = notMerge.silent;
@@ -2284,24 +2288,18 @@ class ECharts extends Eventful {
 
         setTransitionOpt = function (
             chart: ECharts,
-            transitionOpt: SetOptionTransitionOptItem | SetOptionTransitionOptItem[]
+            transitionOpt: SetOptionTransitionOpt
         ): void {
             const ecModel = chart._model;
-            zrUtil.each(modelUtil.normalizeToArray(transitionOpt), transOpt => {
-
-                function normalizeFromTo(fromTo: DimensionLoose | SetOptionTransitionOptFinder) {
-                    return (zrUtil.isString(fromTo) || zrUtil.isNumber(fromTo))
-                        ? { dimension: fromTo }
-                        : fromTo;
-                }
 
+            zrUtil.each(modelUtil.normalizeToArray(transitionOpt), transOpt => {
                 let errMsg;
-                const fromOpt = normalizeFromTo(transOpt.from);
-                const toOpt = normalizeFromTo(transOpt.to);
+                const fromOpt = transOpt.from;
+                const toOpt = transOpt.to;
 
-                if (fromOpt == null || toOpt == null) {
+                if (toOpt == null) {
                     if (__DEV__) {
-                        errMsg = '`transition.from` and `transition.to` must be specified.';
+                        errMsg = '`transition.to` must be specified.';
                     }
                     throwError(errMsg);
                 }
@@ -2312,7 +2310,7 @@ class ECharts extends Eventful {
                     enableAll: false,
                     enableNone: false
                 };
-                const fromResult = modelUtil.parseFinder(ecModel, fromOpt, finderOpt);
+                const fromResult = fromOpt ? modelUtil.parseFinder(ecModel, fromOpt, finderOpt) : null;
                 const toResult = modelUtil.parseFinder(ecModel, toOpt, finderOpt);
                 const toSeries = toResult.seriesModel;
 
@@ -2322,25 +2320,19 @@ class ECharts extends Eventful {
                         errMsg = '`transition` is only supported on series.';
                     }
                 }
-                if (fromResult.seriesModel !== toSeries) {
+                if (fromResult && fromResult.seriesModel !== toSeries) {
                     errMsg = '';
                     if (__DEV__) {
                         errMsg = '`transition.from` and `transition.to` must be specified to the same series.';
                     }
                 }
-                if (fromOpt.dimension == null || toOpt.dimension == null) {
-                    errMsg = '';
-                    if (__DEV__) {
-                        errMsg = '`dimension` must be specified in `transition`.';
-                    }
-                }
                 if (errMsg != null) {
                     throwError(errMsg);
                 }
 
                 // Just a temp solution: mount them on series.
                 toSeries.__transientTransitionOpt = {
-                    from: fromOpt.dimension,
+                    from: fromOpt ? fromOpt.dimension : null,
                     to: toOpt.dimension
                 };
             });
diff --git a/src/model/Series.ts b/src/model/Series.ts
index 3a0f774..78a6776 100644
--- a/src/model/Series.ts
+++ b/src/model/Series.ts
@@ -135,6 +135,8 @@ class SeriesModel<Opt extends SeriesOption = SeriesOption> extends ComponentMode
         // [MEMO] Currently only support single "from". If intending to
         // support multiple "from", if not hard to implement "merge morph",
         // but correspondingly not easy to implement "split morph".
+
+        // Both from and to can be null/undefined, which meams no transform mapping.
         from: DimensionLoose;
         to: DimensionLoose;
     };
diff --git a/test/custom-shape-morphing2.html b/test/custom-shape-morphing2.html
index 01e8bc2..e5a8cb7 100644
--- a/test/custom-shape-morphing2.html
+++ b/test/custom-shape-morphing2.html
@@ -22,10 +22,11 @@ under the License.
     <head>
         <meta charset='utf-8'>
         <meta name="viewport" content="width=device-width, initial-scale=1" />
-        <script src='lib/esl.js'></script>
-        <script src='lib/config.js'></script>
         <script src='lib/jquery.min.js'></script>
+        <script src="../dist/echarts.js"></script>
         <script src="lib/testHelper.js"></script>
+        <!-- <script src="lib/ecStat.min.js"></script> -->
+        <script src="../../echarts-stat/dist/ecStat.js"></script>
         <link rel="stylesheet" href="lib/reset.css" />
     </head>
     <body>
@@ -38,15 +39,121 @@ under the License.
 
         <script>
 
-            require([
-                'echarts'
-            ], function (echarts) {
+            /**
+             * @usage
+             *
+             * dataset: [{
+             *     source: [
+             *         ['aa', 'bb', 'cc', 'tag'],
+             *         [12, 0.33, 5200, 'AA'],
+             *         [21, 0.65, 8100, 'AA'],
+             *         ...
+             *     ]
+             * }, {
+             *     transform: {
+             *         type: 'my:aggregate',
+             *         config: {
+             *             resultDimensions: [
+             *                 // by default, use the same name with `from`.
+             *                 { from: 'aa', method: 'sum' },
+             *                 { from: 'bb', method: 'count' },
+             *                 { from: 'cc' }, // method by default: use the first value.
+             *                 { from: 'tag' }
+             *             ],
+             *             groupBy: 'tag'
+             *         }
+             *     }
+             * }]
+             */
+            var sumTransform = {
+                type: 'my:aggregate',
+                transform: function (params) {
+                    var source = params.source;
+                    var config = params.config;
+                    var resultDimensionsConfig = config.resultDimensions;
+                    var groupBy = config.groupBy;
+
+                    var calcMap = {};
+                    var resultData = [];
+
+                    var groupByDimInfo = source.getDimensionInfo(groupBy);
+                    var resultDimInfoList = [];
+                    var resultDimensions = [];
+                    for (var i = 0; i < resultDimensionsConfig.length; i++) {
+                        var resultDimInfoConfig = resultDimensionsConfig[i];
+                        var resultDimInfo = source.getDimensionInfo(resultDimInfoConfig.from);
+                        resultDimInfo.method = resultDimInfoConfig.method;
+                        resultDimInfoList.push(resultDimInfo);
+                        if (resultDimInfoConfig.name != null) {
+                            resultDimInfo.name = resultDimInfoConfig.name;
+                        }
+                        resultDimensions.push(resultDimInfo.name);
+                    }
 
-                const COLORS = [
-                    '#37A2DA', '#e06343', '#37a354', '#b55dba', '#b5bd48', '#8378EA', '#96BFFF'
-                ];
-                var COUNT = 50;
-                var CONTENT_COLOR = '#37A2DA';
+                    for (var i = 0; i < source.count(); i++) {
+                        var line = source.getRawDataItem(i);
+                        var groupByVal = source.retrieveItemValue(line, groupByDimInfo.index);
+
+                        if (!calcMap.hasOwnProperty(groupByVal)) {
+                            var newLine = [];
+                            calcMap[groupByVal] = newLine;
+                            resultData.push(newLine);
+                            for (var j = 0; j < resultDimInfoList.length; j++) {
+                                var resultDimInfo = resultDimInfoList[j];
+                                var method = resultDimInfo.method;
+                                newLine[j] = resultDimInfo.index === groupByDimInfo.index
+                                    ? groupByVal
+                                    : (method === 'sum' || method === 'count')
+                                    ? 0
+                                    // By default, method: 'first'
+                                    : source.retrieveItemValue(line, resultDimInfo.index);
+                            }
+                        }
+                        else {
+                            var newLine = calcMap[groupByVal];
+                            for (var j = 0; j < resultDimInfoList.length; j++) {
+                                var resultDimInfo = resultDimInfoList[j];
+                                var method = resultDimInfo.method;
+                                if (resultDimInfo.index !== groupByDimInfo.index) {
+                                    if (method === 'sum') {
+                                        newLine[j] += line[resultDimInfo.index];
+                                    }
+                                    else if (method === 'count') {
+                                        newLine[j] += 1;
+                                    }
+                                }
+                            }
+                        }
+                    }
+
+                    return {
+                        dimensions: resultDimensions,
+                        data: resultData
+                    };
+                }
+            };
+
+
+            function initRawData(count) {
+                var M_TAG_LIST = ['MA', 'MB', 'MC', 'MD'];
+                var Z_TAG_LIST = ['ZA', 'ZB', 'ZC', 'ZD', 'ZE'];
+                var data = [];
+                var currDate = +new Date(2015, 2, 1);
+                var ONE_DAY = 3600 * 24 * 1000;
+                for (var i = 0; i < count; i++, currDate += ONE_DAY) {
+                    data.push([
+                        currDate,
+                        makeRandomValue([10, 40], 0),
+                        makeRandomValue([0.01, 0.99], 2),
+                        makeRandomValue([1, 10], 1),
+                        // makeRandomValue([1000, 9000], 0),
+                        // makeRandomValue([1000, 9000], 0),
+                        // makeRandomValue([10, 90], 0),
+                        M_TAG_LIST[makeRandomValue([0, M_TAG_LIST.length - 1], 0)],
+                        Z_TAG_LIST[makeRandomValue([0, Z_TAG_LIST.length - 1], 0)],
+                        'P' + i,
+                    ]);
+                }
 
                 function makeRandomValue(range, precision) {
                     return +(
@@ -54,89 +161,119 @@ under the License.
                     ).toFixed(precision);
                 }
 
-                var M_TAG_LIST = ['MA', 'MB', 'MC', 'MD'];
-                var Z_TAG_LIST = ['ZA', 'ZB', 'ZC', 'ZD', 'ZE'];
+                return data;
+            }
+        </script>
+
+
+
+        <script>
+            (function () {
+
+                echarts.registerTransform(sumTransform);
+                echarts.registerTransform(ecStat.transform.clustering);
+
+
+                const COLORS = [
+                    '#37A2DA', '#e06343', '#37a354', '#b55dba', '#b5bd48', '#8378EA', '#96BFFF'
+                ];
+                var COUNT = 50;
+                var CONTENT_COLOR = '#37A2DA';
+
                 var ANIMATION_DURATION_UPDATE = 1500;
 
-                function initRawData() {
-                    var DIMENSION = {
-                        DATE: 0,
-                        ATA: 1,
-                        STE: 2,
-                        CTZ: 3,
-                        M_TAG: 4,
-                        Z_TAG: 5,
-                        ID: 6
-                    };
-                    var data = [];
-                    var currDate = +new Date(2015, 2, 1);
-                    var ONE_DAY = 3600 * 24 * 1000;
-                    for (var i = 0; i < COUNT; i++, currDate += ONE_DAY) {
-                        var line = [];
-                        data.push(line);
-                        line[DIMENSION.DATE] = currDate;
-                        line[DIMENSION.ATA] = makeRandomValue([10, 40], 0);
-                        line[DIMENSION.STE] = makeRandomValue([0.01, 0.99], 2);
-                        line[DIMENSION.CTZ] = makeRandomValue([1, 10], 1);
-                        line[DIMENSION.M_TAG] = M_TAG_LIST[makeRandomValue([0, M_TAG_LIST.length - 1], 0)];
-                        line[DIMENSION.Z_TAG] = Z_TAG_LIST[makeRandomValue([0, Z_TAG_LIST.length - 1], 0)];
-                        line[DIMENSION.ID] = 'P' + i;
+                // var rawData = initRawData(COUNT);
+                // console.log(JSON.stringify(rawData));
+                var rawData = [[1425139200000,34,0.13,2,"MD","ZD","P0"],[1425225600000,28,0.71,1.5,"MB","ZD","P1"],[1425312000000,23,0.9,2.8,"MA","ZC","P2"],[1425398400000,21,0.58,6,"MB","ZC","P3"],[1425484800000,14,0.1,1.6,"MC","ZA","P4"],[1425571200000,21,0.6,7.7,"MC","ZA","P5"],[1425657600000,23,0.31,2.6,"MC","ZC","P6"],[1425744000000,34,0.74,2.4,"MD","ZE","P7"],[1425830400000,14,0.59,2.3,"MB","ZD","P8"],[1425916800000,18,0.85,5.1,"MB","ZB","P9"],[1426003200000,36,0.96,1.2,"MC","ZC"," [...]
+
+                var RAW_DATA_DIMENSIONS = ['DATE', 'ATA', 'STE', 'CTZ', 'M_TAG', 'Z_TAG', 'ID'];
+                var M_TAG_SUM_DIMENSIONS = ['ATA', 'STE', 'CTZ', 'M_TAG'];
+                var RAW_CLUSTER_DIMENSIONS = ['DATE', 'ATA', 'STE', 'CTZ', 'M_TAG', 'Z_TAG', 'ID', 'CLUSTER_IDX', 'CLUSTER_CENTER_ATA', 'CLUSTER_CENTER_STE'];
+                var RAW_CLUSTER_CENTERS_DIMENSIONS = ['COUNT', 'CLUSTER_IDX', 'CLUSTER_CENTER_ATA', 'CLUSTER_CENTER_STE'];
+
+                // Key: datasetId
+                var TRANSITION_INFO = {
+                    raw: {
+                        dimensions: RAW_DATA_DIMENSIONS
+                    },
+                    mTagSum: {
+                        dimensions: M_TAG_SUM_DIMENSIONS,
+                        uniqueKeyDimension: 'M_TAG',
+                    },
+                    rawClusters: {
+                        dimensions: RAW_CLUSTER_DIMENSIONS
+                    },
+                    rawClusterCenters: {
+                        dimensions: RAW_CLUSTER_CENTERS_DIMENSIONS,
+                        uniqueKeyDimension: 'CLUSTER_IDX'
                     }
-                    return {
-                        DIMENSION: DIMENSION,
-                        data: data
-                    };
-                }
-                function aggregateSum(rawDataWrap, byDimProp, RESULT_DIMENSION) {
-                    var map = {};
-                    var result = [];
-                    var data = rawDataWrap.data;
-
-                    for (var i = 0; i < data.length; i++) {
-                        var line = data[i];
-                        var byVal = line[rawDataWrap.DIMENSION[byDimProp]];
-                        if (!map.hasOwnProperty(byVal)) {
-                            var newLine = [];
-                            map[byVal] = newLine;
-                            result.push(newLine);
-                            newLine[RESULT_DIMENSION.ATA] = 0;
-                            newLine[RESULT_DIMENSION.STE] = 0;
-                            newLine[RESULT_DIMENSION.CTZ] = 0;
-                            newLine[RESULT_DIMENSION[byDimProp]] = byVal;
+                };
+
+                var baseOption = {
+                    dataset: [{
+                        id: 'raw',
+                        dimensions: RAW_DATA_DIMENSIONS,
+                        source: rawData
+                    }, {
+                        id: 'mTagSum',
+                        fromDatasetId: 'raw',
+                        transform: {
+                            type: 'my:aggregate',
+                            config: {
+                                resultDimensions: [
+                                    { from: 'ATA', method: 'sum' },
+                                    { from: 'STE', method: 'sum' },
+                                    { from: 'CTZ', method: 'sum' },
+                                    { from: 'M_TAG' }
+                                ],
+                                groupBy: 'M_TAG'
+                            }
                         }
-                        else {
-                            var newLine = map[byVal];
-                            newLine[RESULT_DIMENSION.ATA] += line[rawDataWrap.DIMENSION.ATA];
-                            newLine[RESULT_DIMENSION.STE] += line[rawDataWrap.DIMENSION.STE];
-                            newLine[RESULT_DIMENSION.CTZ] += line[rawDataWrap.DIMENSION.CTZ];
+                    }, {
+                        id: 'rawClusters',
+                        fromDatasetId: 'raw',
+                        transform: {
+                            type: 'ecStat:clustering',
+                            print: true,
+                            config: {
+                                clusterCount: 4,
+                                dimensions: ['ATA', 'STE'],
+                                outputClusterIndexDimension: {
+                                    index: RAW_CLUSTER_DIMENSIONS.indexOf('CLUSTER_IDX'),
+                                    name: 'CLUSTER_IDX'
+                                },
+                                outputCentroidDimensions: [{
+                                    index: RAW_CLUSTER_DIMENSIONS.indexOf('CLUSTER_CENTER_ATA'),
+                                    name: 'CLUSTER_CENTER_ATA'
+                                }, {
+                                    index: RAW_CLUSTER_DIMENSIONS.indexOf('CLUSTER_CENTER_STE'),
+                                    name: 'CLUSTER_CENTER_STE'
+                                }]
+                            }
                         }
-                    }
-
-                    return {
-                        DIMENSION: RESULT_DIMENSION,
-                        data: result,
-                        uniqueKey: 'M_TAG'
-                    };
-                }
-
-                var rawDataWrap = initRawData();
-                // console.log(JSON.stringify(rawDataWrap.data));
-                rawDataWrap.data = [[1425139200000,34,0.13,2,"MD","ZD","P0"],[1425225600000,28,0.71,1.5,"MB","ZD","P1"],[1425312000000,23,0.9,2.8,"MA","ZC","P2"],[1425398400000,21,0.58,6,"MB","ZC","P3"],[1425484800000,14,0.1,1.6,"MC","ZA","P4"],[1425571200000,21,0.6,7.7,"MC","ZA","P5"],[1425657600000,23,0.31,2.6,"MC","ZC","P6"],[1425744000000,34,0.74,2.4,"MD","ZE","P7"],[1425830400000,14,0.59,2.3,"MB","ZD","P8"],[1425916800000,18,0.85,5.1,"MB","ZB","P9"],[1426003200000,36,0.96,1.2,"MC"," [...]
-                var mTagSumDataWrap = aggregateSum(rawDataWrap, 'M_TAG', {
-                    ATA: 0,
-                    STE: 1,
-                    CTZ: 2,
-                    M_TAG: 3
-                });
-                var zTagSumDataWrap = aggregateSum(rawDataWrap, 'Z_TAG', {
-                    ATA: 0,
-                    STE: 1,
-                    CTZ: 2,
-                    Z_TAG: 3
-                });
+                    }, {
+                        id: 'rawClusterCenters',
+                        fromDatasetId: 'rawClusters',
+                        transform: {
+                            type: 'my:aggregate',
+                            print: true,
+                            config: {
+                                resultDimensions: [
+                                    { name: 'COUNT', from: 'ATA', method: 'count' },
+                                    { from: 'CLUSTER_CENTER_ATA' },
+                                    { from: 'CLUSTER_CENTER_STE' },
+                                    { from: 'CLUSTER_IDX' }
+                                ],
+                                groupBy: 'CLUSTER_IDX'
+                            }
+                        }
+                    }]
+                };
 
 
                 function create_Scatter_ATA_STE() {
+                    // var datasetId = 'raw';
+                    var datasetId = 'rawClusters';
                     var option = {
                         tooltip: {},
                         grid: {
@@ -157,26 +294,23 @@ under the License.
                             type: 'custom',
                             coordinateSystem: 'cartesian2d',
                             animationDurationUpdate: ANIMATION_DURATION_UPDATE,
-                            data: rawDataWrap.data,
+                            datasetId: datasetId,
                             encode: {
-                                itemName: rawDataWrap.DIMENSION.ID,
-                                x: rawDataWrap.DIMENSION.STE,
-                                y: rawDataWrap.DIMENSION.ATA,
-                                tooltip: [rawDataWrap.DIMENSION.STE, rawDataWrap.DIMENSION.ATA]
+                                itemName: 'ID',
+                                x: 'STE',
+                                y: 'ATA',
+                                tooltip: ['STE', 'ATA']
                             },
                             renderItem: function (params, api) {
                                 var pos = api.coord([
-                                    api.value(rawDataWrap.DIMENSION.STE),
-                                    api.value(rawDataWrap.DIMENSION.ATA)
+                                    api.value('STE'),
+                                    api.value('ATA')
                                 ]);
+                                // var clusterIndex = api.value('CLUSTER_IDX');
                                 return {
                                     type: 'circle',
-                                    // x: pos[0],
-                                    // y: pos[1],
                                     morph: true,
                                     shape: {
-                                        // cx: 0,
-                                        // cy: 0,
                                         cx: pos[0],
                                         cy: pos[1],
                                         r: 10,
@@ -185,6 +319,7 @@ under the License.
                                     style: {
                                         transition: 'lineWidth',
                                         fill: CONTENT_COLOR,
+                                        // fill: COLORS[clusterIndex],
                                         stroke: '#555',
                                         lineWidth: 1
                                     }
@@ -195,11 +330,12 @@ under the License.
 
                     return {
                         option: option,
-                        dataWrap: rawDataWrap
+                        datasetId: datasetId
                     };
                 }
 
-                function create_Bar_mSum_ATA(mTagSumDataWrap) {
+                function create_Bar_mSum_ATA() {
+                    var datasetId = 'mTagSum';
                     var option = {
                         tooltip: {},
                         grid: {
@@ -219,15 +355,15 @@ under the License.
                             type: 'custom',
                             coordinateSystem: 'cartesian2d',
                             animationDurationUpdate: ANIMATION_DURATION_UPDATE,
-                            data: mTagSumDataWrap.data,
+                            datasetId: datasetId,
                             encode: {
-                                x: mTagSumDataWrap.DIMENSION.M_TAG,
-                                y: mTagSumDataWrap.DIMENSION.ATA,
-                                tooltip: [mTagSumDataWrap.DIMENSION.M_TAG, mTagSumDataWrap.DIMENSION.ATA]
+                                x: 'M_TAG',
+                                y: 'ATA',
+                                tooltip: ['M_TAG', 'ATA']
                             },
                             renderItem: function (params, api) {
-                                var mTagVal = api.value(mTagSumDataWrap.DIMENSION.M_TAG);
-                                var ataVal = api.value(mTagSumDataWrap.DIMENSION.ATA);
+                                var mTagVal = api.value('M_TAG');
+                                var ataVal = api.value('ATA');
                                 var tarPos = api.coord([mTagVal, ataVal]);
                                 var zeroPos = api.coord([mTagVal, 0]);
                                 var size = api.size([mTagVal, ataVal]);
@@ -255,37 +391,45 @@ under the License.
 
                     return {
                         option: option,
-                        dataWrap: mTagSumDataWrap
+                        datasetId: datasetId
                     };
                 }
 
-                function create_Pie_mSum_ATA(mTagSumDataWrap) {
-                    var totalValue = mTagSumDataWrap.data.reduce(function (val, item) {
-                        return val + item[mTagSumDataWrap.DIMENSION.ATA];
-                    }, 0);
-                    let angles = [];
-                    let currentAngle = -Math.PI / 2;
-                    for (let i = 0; i < mTagSumDataWrap.data.length; i++) {
-                        const angle = mTagSumDataWrap.data[i][mTagSumDataWrap.DIMENSION.ATA] / totalValue * Math.PI * 2;
-                        angles.push([currentAngle, angle + currentAngle]);
-                        currentAngle += angle;
-                    }
 
+                function create_Pie_mSum_ATA() {
+                    var datasetId = 'mTagSum';
                     var option = {
                         tooltip: {},
                         series: {
                             type: 'custom',
                             coordinateSystem: 'none',
                             animationDurationUpdate: ANIMATION_DURATION_UPDATE,
-                            data: mTagSumDataWrap.data,
+                            datasetId: datasetId,
                             encode: {
-                                itemName: mTagSumDataWrap.DIMENSION.M_TAG,
-                                value: mTagSumDataWrap.DIMENSION.ATA,
-                                tooltip: [mTagSumDataWrap.DIMENSION.ATA]
+                                itemName: 'M_TAG',
+                                value: 'ATA',
+                                tooltip: 'ATA'
                             },
                             renderItem: function (params, api) {
-                                const width = chart.getWidth();
-                                const height = chart.getHeight();
+                                var context = params.context;
+                                if (!context.layout) {
+                                    context.layout = true;
+                                    var totalValue = 0;
+                                    for (var i = 0; i < params.dataInsideLength; i++) {
+                                        totalValue += api.value('ATA', i);
+                                    }
+                                    var angles = [];
+                                    var currentAngle = -Math.PI / 2;
+                                    for (var i = 0; i < params.dataInsideLength; i++) {
+                                        var angle = api.value('ATA', i) / totalValue * Math.PI * 2;
+                                        angles.push([currentAngle, angle + currentAngle]);
+                                        currentAngle += angle;
+                                    }
+                                    context.angles = angles;
+                                }
+
+                                var width = chart.getWidth();
+                                var height = chart.getHeight();
                                 return {
                                     type: 'sector',
                                     morph: true,
@@ -294,8 +438,8 @@ under the License.
                                         cy: height / 2,
                                         r: Math.min(width, height) / 3,
                                         r0: Math.min(width, height) / 5,
-                                        startAngle: angles[params.dataIndex][0],
-                                        endAngle: angles[params.dataIndex][1],
+                                        startAngle: context.angles[params.dataIndex][0],
+                                        endAngle: context.angles[params.dataIndex][1],
                                         clockwise: true
                                     },
                                     style: {
@@ -310,31 +454,111 @@ under the License.
 
                     return {
                         option: option,
-                        dataWrap: mTagSumDataWrap
+                        datasetId: datasetId
                     };
                 }
 
-                function createScatter_zSum_ATA(zTagSumDataWrap) {
-                }
+                function create_Scatter_ATA_STE_Cluster_Center() {
+                    var datasetId = 'rawClusterCenters';
+                    var option = {
+                        tooltip: {},
+                        grid: {
+                            containLabel: true
+                        },
+                        xAxis: {
+                            name: 'STE'
+                        },
+                        yAxis: {
+                            name: 'ATA'
+                        },
+                        dataZoom: [{
+                            type: 'slider',
+                        }, {
+                            type: 'inside'
+                        }],
+                        series: {
+                            type: 'custom',
+                            coordinateSystem: 'cartesian2d',
+                            animationDurationUpdate: ANIMATION_DURATION_UPDATE,
+                            datasetId: datasetId,
+                            encode: {
+                                x: 'CLUSTER_CENTER_STE',
+                                y: 'CLUSTER_CENTER_ATA',
+                                tooltip: ['CLUSTER_CENTER_STE', 'CLUSTER_CENTER_ATA']
+                            },
+                            renderItem: function (params, api) {
+                                var context = params.context;
+                                if (!context.layout) {
+                                    context.layout = true;
+                                    context.totalCount = 0;
+                                    for (var i = 0; i < params.dataInsideLength; i++) {
+                                        context.totalCount += api.value('COUNT', i);
+                                    }
+                                }
 
+                                var pos = api.coord([
+                                    api.value('CLUSTER_CENTER_STE'),
+                                    api.value('CLUSTER_CENTER_ATA')
+                                ]);
+                                var count = api.value('COUNT');
+                                var radius = count / context.totalCount * 100 + 10;
+                                return {
+                                    type: 'circle',
+                                    morph: true,
+                                    shape: {
+                                        cx: pos[0],
+                                        cy: pos[1],
+                                        r: radius,
+                                        transition: ['cx', 'cy', 'r']
+                                    },
+                                    style: {
+                                        transition: 'lineWidth',
+                                        fill: CONTENT_COLOR,
+                                        stroke: '#555',
+                                        lineWidth: 0
+                                    }
+                                };
+                            }
+                        }
+                    };
+
+                    return {
+                        option: option,
+                        datasetId: datasetId
+                    };
+                }
 
-                var currOptionName = 'Scatter_ATA_STE';
                 var optionInfoList = {
-                    'Scatter_ATA_STE': create_Scatter_ATA_STE(rawDataWrap),
-                    'Bar_mTagSum_ATA': create_Bar_mSum_ATA(mTagSumDataWrap),
-                    'Pie_mTagSum_ATA': create_Pie_mSum_ATA(mTagSumDataWrap),
+                    'Scatter_ATA_STE': create_Scatter_ATA_STE(),
+                    'Bar_mTagSum_ATA': create_Bar_mSum_ATA(),
+                    'Pie_mTagSum_ATA': create_Pie_mSum_ATA(),
+                    'Scatter_ATA_STE_Cluster_Center': create_Scatter_ATA_STE_Cluster_Center()
                 };
 
+                var currOptionName;
                 function next(nextOptionName) {
-                    const lastOptionInfo = optionInfoList[currOptionName];
-                    const nextOptionInfo = optionInfoList[nextOptionName];
+                    var lastOptionInfo = optionInfoList[currOptionName];
+                    var nextOptionInfo = optionInfoList[nextOptionName];
+                    var transitionOpt = {
+                        to: { seriesIndex: 0 }
+                    };
 
-                    const commonDimension = findCommonDimension(lastOptionInfo, nextOptionInfo)
-                        || findCommonDimension(nextOptionInfo, lastOptionInfo);
-                    const fromDimension = lastOptionInfo.dataWrap.DIMENSION[commonDimension];
-                    const toDimension = nextOptionInfo.dataWrap.DIMENSION[commonDimension];
-                    const transitionOpt = (fromDimension != null && toDimension != null)
-                        ? { from: fromDimension, to: toDimension } : null;
+                    if (lastOptionInfo) {
+                        var commonDimension = findCommonDimension(lastOptionInfo, nextOptionInfo)
+                            || findCommonDimension(nextOptionInfo, lastOptionInfo);
+                        if (commonDimension != null) {
+                            transitionOpt = {
+                                from: {
+                                    seriesIndex: 0,
+                                    dimension: commonDimension
+                                },
+                                to: {
+                                    seriesIndex: 0,
+                                    dimension: commonDimension
+                                }
+                            };
+                        }
+                    }
 
                     currOptionName = nextOptionName;
 
@@ -342,15 +566,17 @@ under the License.
                         replaceMerge: ['xAxis', 'yAxis'],
                         transition: transitionOpt
                     });
-                }
 
-                function findCommonDimension(optionInfoA, optionInfoB) {
-                    var uniqueKey = optionInfoB.dataWrap.uniqueKey;
-                    if (uniqueKey != null && optionInfoA.dataWrap.DIMENSION[uniqueKey] != null) {
-                        return uniqueKey;
+                    function findCommonDimension(optionInfoA, optionInfoB) {
+                        var metaA = TRANSITION_INFO[optionInfoA.datasetId];
+                        var metaB = TRANSITION_INFO[optionInfoB.datasetId];
+                        if (metaA.dimensions.indexOf(metaB.uniqueKeyDimension) >= 0) {
+                            return metaB.uniqueKeyDimension;
+                        }
                     }
                 }
 
+
                 var chart = testHelper.create(echarts, 'main0', {
                     title: [
                         'Test: buttons, should morph animation merge/split.',
@@ -358,27 +584,35 @@ under the License.
                         'Test: click buttons **twice**, should no blink.',
                         'Test: use dataZoom, update animation should exist'
                     ],
-                    option: optionInfoList[currOptionName].option,
+                    option: baseOption,
+                    lazyUpdate: true,
                     height: 600,
                     buttons: [{
-                        text: 'ToBar',
+                        text: 'Bar_mTagSum_ATA',
                         onclick: function () {
                             next('Bar_mTagSum_ATA');
                         }
                     }, {
-                        text: 'ToScatter',
+                        text: 'Scatter_ATA_STE',
                         onclick: function () {
                             next('Scatter_ATA_STE');
                         }
                     }, {
-                        text: 'ToPie',
+                        text: 'Pie_mTagSum_ATA',
                         onclick: function () {
                             next('Pie_mTagSum_ATA');
                         }
+                    }, {
+                        text: 'Scatter_ATA_STE_Cluster_Center',
+                        onclick: function () {
+                            next('Scatter_ATA_STE_Cluster_Center');
+                        }
                     }]
                 });
 
-            });
+                next('Scatter_ATA_STE');
+
+            })();
 
         </script>
 


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