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/11/19 11:32:27 UTC

[incubator-echarts] 01/02: test: migrate test lib myTransform to ts.

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

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

commit be70a3979a48ed250f683e8b4d7a7d657526ee56
Author: 100pah <su...@gmail.com>
AuthorDate: Thu Nov 19 01:25:11 2020 +0800

    test: migrate test lib myTransform to ts.
---
 build/build.js                               |   6 +
 build/config.js                              |  24 ++
 build/release.js                             |   2 +-
 src/data/helper/transform.ts                 |   2 +-
 test/custom-shape-morphing2.html             |   5 +-
 test/custom-shape-morphing3.html             |   8 +-
 test/lib/config.js                           |   1 +
 test/lib/myTransform/aggregate.js            | 174 --------------
 test/lib/myTransform/dist/myTransform.js     | 219 +++++++++++++++++
 test/lib/myTransform/dist/myTransform.js.map |   1 +
 test/lib/myTransform/id.js                   |  88 -------
 test/lib/myTransform/src/.eslintrc.yaml      |  47 ++++
 test/lib/myTransform/src/aggregate.ts        | 342 +++++++++++++++++++++++++++
 test/lib/myTransform/src/id.ts               |  90 +++++++
 test/lib/myTransform/src/index.ts            |   3 +
 tsconfig.json                                |   3 +-
 16 files changed, 742 insertions(+), 273 deletions(-)

diff --git a/build/build.js b/build/build.js
index 24da784..42bb096 100755
--- a/build/build.js
+++ b/build/build.js
@@ -138,6 +138,12 @@ async function run() {
         ];
         await build(cfgs, opt.min, opt.sourcemap);
     }
+    else if (opt.type === 'myTransform') {
+        const cfgs = [
+            config.createMyTransform()
+        ];
+        await build(cfgs, opt.min, opt.sourcemap);
+    }
     else {
         const cfg = config.createECharts(opt);
         await build([cfg], opt.min, opt.sourcemap);
diff --git a/build/config.js b/build/config.js
index 9cc6e82..53cec29 100644
--- a/build/config.js
+++ b/build/config.js
@@ -212,3 +212,27 @@ exports.createDataTool = function () {
         }
     };
 };
+
+exports.createMyTransform = function () {
+    let input = nodePath.resolve(ecDir, `test/lib/myTransform/src/index.ts`);
+
+    return {
+        plugins: preparePlugins({
+            clean: true
+        }, {
+            include: [
+                nodePath.resolve(ecDir, 'test/lib/myTransform/src/**/*.ts')
+            ]
+        }),
+        input: input,
+        output: {
+            name: 'myTransform',
+            format: 'umd',
+            sourcemap: true,
+            file: nodePath.resolve(ecDir, `test/lib/myTransform/dist/myTransform.js`)
+        },
+        watch: {
+            include: [nodePath.resolve(ecDir, 'test/lib/myTransform/src/**')]
+        }
+    };
+};
diff --git a/build/release.js b/build/release.js
index 2a4e070..d9d6762 100644
--- a/build/release.js
+++ b/build/release.js
@@ -42,7 +42,7 @@ function release() {
         }
     }
 
-    const argsList = ['', 'simple', 'common', 'extension'].map((type) => {
+    const argsList = ['', 'simple', 'common', 'extension', 'myTransform'].map((type) => {
         return [
             '--type',
             type,
diff --git a/src/data/helper/transform.ts b/src/data/helper/transform.ts
index 287b55d..349e985 100644
--- a/src/data/helper/transform.ts
+++ b/src/data/helper/transform.ts
@@ -83,7 +83,7 @@ export interface ExternalDataTransformResultItem {
      */
     dimensions?: DimensionDefinitionLoose[];
 }
-interface ExternalDimensionDefinition extends Partial<DimensionDefinition> {
+export interface ExternalDimensionDefinition extends Partial<DimensionDefinition> {
     // Mandatory
     index: DimensionIndex;
 }
diff --git a/test/custom-shape-morphing2.html b/test/custom-shape-morphing2.html
index ac12738..f55d367 100644
--- a/test/custom-shape-morphing2.html
+++ b/test/custom-shape-morphing2.html
@@ -27,7 +27,6 @@ under the License.
         <script src='lib/jquery.min.js'></script>
         <script src="../dist/echarts.js"></script>
         <script src="lib/testHelper.js"></script>
-        <script src="lib/myTransform/aggregate.js"></script>
         <script src="lib/transitionPlayer.js"></script>
         <link rel="stylesheet" href="lib/reset.css" />
     </head>
@@ -76,9 +75,9 @@ under the License.
 
         <script>
 
-            require(['echarts', 'ecStat'], function (echarts, ecStat) {
+            require(['echarts', 'ecStat', 'myTransform'], function (echarts, ecStat, myTransform) {
 
-                echarts.registerTransform(window.myTransform.aggregate);
+                echarts.registerTransform(myTransform.aggregate);
                 echarts.registerTransform(ecStat.transform.clustering);
 
 
diff --git a/test/custom-shape-morphing3.html b/test/custom-shape-morphing3.html
index 748b97a..1e762df 100644
--- a/test/custom-shape-morphing3.html
+++ b/test/custom-shape-morphing3.html
@@ -27,8 +27,6 @@ under the License.
         <script src='lib/jquery.min.js'></script>
         <script src="../dist/echarts.js"></script>
         <script src="lib/testHelper.js"></script>
-        <script src="lib/myTransform/aggregate.js"></script>
-        <script src="lib/myTransform/id.js"></script>
         <script src="lib/transitionPlayer.js"></script>
         <link rel="stylesheet" href="lib/reset.css" />
     </head>
@@ -42,11 +40,11 @@ under the License.
 
         <script>
 
-        require(['echarts', 'ecStat'], function (echarts, ecStat) {
+        require(['echarts', 'ecStat', 'myTransform'], function (echarts, ecStat, myTransform) {
             $.get('data/life-expectancy-table.json', function (rawData) {
 
-                echarts.registerTransform(window.myTransform.aggregate);
-                echarts.registerTransform(window.myTransform.id);
+                echarts.registerTransform(myTransform.aggregate);
+                echarts.registerTransform(myTransform.id);
 
                 const COLORS = [
                     '#37A2DA', '#e06343', '#37a354', '#b55dba', '#b5bd48', '#8378EA', '#96BFFF'
diff --git a/test/lib/config.js b/test/lib/config.js
index 5966c4b..ad8e68b 100644
--- a/test/lib/config.js
+++ b/test/lib/config.js
@@ -64,6 +64,7 @@
                 'echarts': ecDistPath,
                 'zrender': 'node_modules/zrender/dist/zrender',
                 'ecStat': 'test/lib/ecStat.min',
+                'myTransform': 'test/lib/myTransform/dist/myTransform',
                 // 'ecStat': 'http://localhost:8001/echarts/echarts-stat/dist/ecStat',
                 'geoJson': '../geoData/geoJson',
                 'theme': 'theme',
diff --git a/test/lib/myTransform/aggregate.js b/test/lib/myTransform/aggregate.js
deleted file mode 100644
index a5fd4eb..0000000
--- a/test/lib/myTransform/aggregate.js
+++ /dev/null
@@ -1,174 +0,0 @@
-/*
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements.  See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership.  The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License.  You may obtain a copy of the License at
-*
-*   http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied.  See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
-
-(function (exports) {
-
-    /**
-     * @usage
-     *
-     * ```js
-     * dataset: [{
-     *     source: [
-     *         ['aa', 'bb', 'cc', 'tag'],
-     *         [12, 0.33, 5200, 'AA'],
-     *         [21, 0.65, 7100, 'AA'],
-     *         [51, 0.15, 1100, 'BB'],
-     *         [71, 0.75, 9100, 'BB'],
-     *         ...
-     *     ]
-     * }, {
-     *     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'
-     *         }
-     *     }
-     *     // Then the result data will be:
-     *     // [
-     *     //     ['aa', 'bb', 'cc', 'tag'],
-     *     //     [12, 0.33, 5200, 'AA'],
-     *     //     [21, 0.65, 8100, 'BB'],
-     *     //     ...
-     *     // ]
-     * }]
-     * ```
-     */
-    var transform = {
-
-        type: 'myTransform:aggregate',
-
-        /**
-         * @param params
-         * @param params.config.resultDimensions Mandatory.
-         *        {
-         *            // Optional. The name of the result dimensions.
-         *            // If not provided, inherit the name from `from`.
-         *            name: DimensionName;
-         *            // Mandatory. `from` is used to reference dimension from `source`.
-         *            from: DimensionIndex | DimensionName;
-         *            // Optional. Aggregate method. Currently only these method supported.
-         *            // If not provided, use `'first'`.
-         *            method: 'sum' | 'count' | 'first';
-         *        }[]
-         * @param params.config.groupBy DimensionIndex | DimensionName Optional.
-         */
-        transform: function (params) {
-            var upstream = params.upstream;
-            var config = params.config;
-            var resultDimensionsConfig = config.resultDimensions;
-
-            var resultDimInfoList = [];
-            var resultDimensions = [];
-            for (var i = 0; i < resultDimensionsConfig.length; i++) {
-                var resultDimInfoConfig = resultDimensionsConfig[i];
-                var resultDimInfo = upstream.getDimensionInfo(resultDimInfoConfig.from);
-                assert(resultDimInfo, 'Can not find dimension by `from`: ' + resultDimInfoConfig.from);
-                resultDimInfo.method = resultDimInfoConfig.method;
-                resultDimInfoList.push(resultDimInfo);
-                if (resultDimInfoConfig.name != null) {
-                    resultDimInfo.name = resultDimInfoConfig.name;
-                }
-                resultDimensions.push(resultDimInfo.name);
-            }
-
-            var resultData = [];
-
-            var groupBy = config.groupBy;
-            var groupByDimInfo;
-            if (groupBy != null) {
-                var groupMap = {};
-                groupByDimInfo = upstream.getDimensionInfo(groupBy);
-                assert(groupByDimInfo, 'Can not find dimension by `groupBy`: ' + groupBy);
-
-                for (var dataIndex = 0, len = upstream.count(); dataIndex < len; dataIndex++) {
-                    var groupByVal = upstream.retrieveValue(dataIndex, groupByDimInfo.index);
-
-                    if (!groupMap.hasOwnProperty(groupByVal)) {
-                        var newLine = createLine(upstream, dataIndex, resultDimInfoList, groupByDimInfo, groupByVal);
-                        resultData.push(newLine);
-                        groupMap[groupByVal] = newLine;
-                    }
-                    else {
-                        var targetLine = groupMap[groupByVal];
-                        aggregateLine(upstream, dataIndex, targetLine, resultDimInfoList, groupByDimInfo);
-                    }
-                }
-            }
-            else {
-                var targetLine = createLine(upstream, 0, resultDimInfoList);
-                resultData.push(targetLine);
-                for (var dataIndex = 0, len = upstream.count(); dataIndex < len; dataIndex++) {
-                    aggregateLine(upstream, dataIndex, targetLine, resultDimInfoList);
-                }
-            }
-
-            return {
-                dimensions: resultDimensions,
-                data: resultData
-            };
-        }
-    };
-
-    function createLine(upstream, dataIndex, resultDimInfoList, groupByDimInfo, groupByVal) {
-        var newLine = [];
-        for (var j = 0; j < resultDimInfoList.length; j++) {
-            var resultDimInfo = resultDimInfoList[j];
-            var method = resultDimInfo.method;
-            newLine[j] = (groupByDimInfo && resultDimInfo.index === groupByDimInfo.index)
-                ? groupByVal
-                : (method === 'sum' || method === 'count')
-                ? 0
-                // By default, method: 'first'
-                : upstream.retrieveValue(dataIndex, resultDimInfo.index);
-        }
-        return newLine;
-    }
-
-    function aggregateLine(upstream, dataIndex, targetLine, resultDimInfoList, groupByDimInfo) {
-        for (var j = 0; j < resultDimInfoList.length; j++) {
-            var resultDimInfo = resultDimInfoList[j];
-            var method = resultDimInfo.method;
-            if (!groupByDimInfo || resultDimInfo.index !== groupByDimInfo.index) {
-                if (method === 'sum') {
-                    targetLine[j] += upstream.retrieveValue(dataIndex, resultDimInfo.index);
-                }
-                else if (method === 'count') {
-                    targetLine[j] += 1;
-                }
-            }
-        }
-    }
-
-    function assert(cond, msg) {
-        if (!cond) {
-            throw new Error(msg || 'transition player error');
-        }
-    }
-
-    var myTransform = exports.myTransform = exports.myTransform || {};
-    myTransform.aggregate = transform;
-
-})(this);
diff --git a/test/lib/myTransform/dist/myTransform.js b/test/lib/myTransform/dist/myTransform.js
new file mode 100644
index 0000000..9edeb7f
--- /dev/null
+++ b/test/lib/myTransform/dist/myTransform.js
@@ -0,0 +1,219 @@
+(function (global, factory) {
+  typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports) : typeof define === 'function' && define.amd ? define(['exports'], factory) : (global = global || self, factory(global.myTransform = {}));
+})(this, function (exports) {
+  'use strict';
+
+  var transform = {
+    type: 'myTransform:id',
+    transform: function (params) {
+      var upstream = params.upstream;
+      var config = params.config;
+      var dimensionIndex = config.dimensionIndex;
+      var dimensionName = config.dimensionName;
+      var dimsDef = upstream.cloneAllDimensionInfo();
+      dimsDef[dimensionIndex] = dimensionName;
+      var data = upstream.cloneRawData();
+
+      for (var i = 0, len = data.length; i < len; i++) {
+        var line = data[i];
+        line[dimensionIndex] = i;
+      }
+
+      return {
+        dimensions: dimsDef,
+        data: data
+      };
+    }
+  };
+  var arrayProto = Array.prototype;
+  var nativeSlice = arrayProto.slice;
+
+  var ctorFunction = function () {}.constructor;
+
+  var protoFunction = ctorFunction ? ctorFunction.prototype : null;
+
+  function bindPolyfill(func, context) {
+    var args = [];
+
+    for (var _i = 2; _i < arguments.length; _i++) {
+      args[_i - 2] = arguments[_i];
+    }
+
+    return function () {
+      return func.apply(context, args.concat(nativeSlice.call(arguments)));
+    };
+  }
+
+  var bind = protoFunction && isFunction(protoFunction.bind) ? protoFunction.call.bind(protoFunction.bind) : bindPolyfill;
+
+  function isFunction(value) {
+    return typeof value === 'function';
+  }
+
+  function assert(condition, message) {
+    if (!condition) {
+      throw new Error(message);
+    }
+  }
+
+  function hasOwn(own, prop) {
+    return own.hasOwnProperty(prop);
+  }
+
+  var METHOD_INTERNAL = {
+    'SUM': true,
+    'COUNT': true,
+    'FIRST': true,
+    'AVERAGE': true,
+    'Q1': true,
+    'Q2': true,
+    'Q3': true,
+    'MIN': true,
+    'MAX': true
+  };
+  var METHOD_ALIAS = {
+    MEDIAN: 'Q2'
+  };
+  var transform$1 = {
+    type: 'myTransform:aggregate',
+    transform: function (params) {
+      var upstream = params.upstream;
+      var config = params.config;
+      var dimWrap = prepareDimensions(config, upstream);
+      var resultDimInfoList = dimWrap.resultDimInfoList;
+      var resultDimensions = dimWrap.resultDimensions;
+      var groupByDimInfo = prepareGroupByDimInfo(config, upstream);
+      var finalResult = travel(groupByDimInfo, upstream, resultDimInfoList, createResultLine, aggregateResultLine);
+      return {
+        dimensions: resultDimensions,
+        data: finalResult.outList
+      };
+    }
+  };
+
+  function prepareDimensions(config, upstream) {
+    var resultDimensionsConfig = config.resultDimensions;
+    var resultDimInfoList = [];
+    var resultDimensions = [];
+
+    for (var i = 0; i < resultDimensionsConfig.length; i++) {
+      var resultDimInfoConfig = resultDimensionsConfig[i];
+      var resultDimInfo = upstream.getDimensionInfo(resultDimInfoConfig.from);
+      assert(resultDimInfo, 'Can not find dimension by `from`: ' + resultDimInfoConfig.from);
+      resultDimInfo.method = normalizeMethod(resultDimInfoConfig.method);
+      assert(resultDimInfo.method, 'method is required');
+      resultDimInfoList.push(resultDimInfo);
+
+      if (resultDimInfoConfig.name != null) {
+        resultDimInfo.name = resultDimInfoConfig.name;
+      }
+
+      resultDimensions.push(resultDimInfo.name);
+    }
+
+    return {
+      resultDimensions: resultDimensions,
+      resultDimInfoList: resultDimInfoList
+    };
+  }
+
+  function prepareGroupByDimInfo(config, upstream) {
+    var groupByConfig = config.groupBy;
+    var groupByDimInfo;
+
+    if (groupByConfig != null) {
+      groupByDimInfo = upstream.getDimensionInfo(groupByConfig);
+      assert(groupByDimInfo, 'Can not find dimension by `groupBy`: ' + groupByConfig);
+    }
+
+    return groupByDimInfo;
+  }
+
+  function travel(groupByDimInfo, upstream, resultDimInfoList, doCreate, doAggregate) {
+    var outList = [];
+    var groupMap;
+
+    if (groupByDimInfo) {
+      groupMap = {};
+
+      for (var dataIndex = 0, len = upstream.count(); dataIndex < len; dataIndex++) {
+        var groupByVal = upstream.retrieveValue(dataIndex, groupByDimInfo.index);
+
+        if (groupByVal == null) {
+          continue;
+        }
+
+        var groupByValStr = groupByVal + '';
+
+        if (!hasOwn(groupMap, groupByValStr)) {
+          var newLine = doCreate(upstream, dataIndex, resultDimInfoList, groupByDimInfo, groupByVal);
+          outList.push(newLine);
+          groupMap[groupByValStr] = newLine;
+        } else {
+          var targetLine = groupMap[groupByValStr];
+          doAggregate(upstream, dataIndex, targetLine, resultDimInfoList, groupByDimInfo);
+        }
+      }
+    } else {
+      var targetLine = doCreate(upstream, 0, resultDimInfoList);
+      outList.push(targetLine);
+
+      for (var dataIndex = 0, len = upstream.count(); dataIndex < len; dataIndex++) {
+        doAggregate(upstream, dataIndex, targetLine, resultDimInfoList);
+      }
+    }
+
+    return {
+      groupMap: groupMap,
+      outList: outList
+    };
+  }
+
+  function normalizeMethod(method) {
+    if (method == null) {
+      return 'FIRST';
+    }
+
+    var methodInternal = method.toUpperCase();
+    methodInternal = hasOwn(METHOD_ALIAS, methodInternal) ? METHOD_ALIAS[methodInternal] : methodInternal;
+    assert(hasOwn(METHOD_INTERNAL, methodInternal), "Illegal method " + method + ".");
+    return methodInternal;
+  }
+
+  var createResultLine = function (upstream, dataIndex, resultDimInfoList, groupByDimInfo, groupByVal) {
+    var newLine = [];
+
+    for (var j = 0; j < resultDimInfoList.length; j++) {
+      var resultDimInfo = resultDimInfoList[j];
+      var method = resultDimInfo.method;
+      newLine[j] = groupByDimInfo && resultDimInfo.index === groupByDimInfo.index ? groupByVal : method === 'SUM' || method === 'COUNT' ? 0 : upstream.retrieveValue(dataIndex, resultDimInfo.index);
+    }
+
+    return newLine;
+  };
+
+  var aggregateResultLine = function (upstream, dataIndex, targetLine, resultDimInfoList, groupByDimInfo) {
+    for (var j = 0; j < resultDimInfoList.length; j++) {
+      var resultDimInfo = resultDimInfoList[j];
+      var method = resultDimInfo.method;
+
+      if (groupByDimInfo && resultDimInfo.index === groupByDimInfo.index) {
+        continue;
+      }
+
+      if (method === 'SUM') {
+        targetLine[j] += upstream.retrieveValue(dataIndex, resultDimInfo.index);
+      } else if (method === 'COUNT') {
+        targetLine[j] += 1;
+      } else if (method === 'AVERAGE') {
+        targetLine[j] += upstream.retrieveValue(dataIndex, resultDimInfo.index) / 1;
+      }
+    }
+  };
+
+  exports.aggregate = transform$1;
+  exports.id = transform;
+  Object.defineProperty(exports, '__esModule', {
+    value: true
+  });
+});
\ No newline at end of file
diff --git a/test/lib/myTransform/dist/myTransform.js.map b/test/lib/myTransform/dist/myTransform.js.map
new file mode 100644
index 0000000..4b03361
--- /dev/null
+++ b/test/lib/myTransform/dist/myTransform.js.map
@@ -0,0 +1 @@
+{"version":3,"file":"myTransform.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
\ No newline at end of file
diff --git a/test/lib/myTransform/id.js b/test/lib/myTransform/id.js
deleted file mode 100644
index 9e91370..0000000
--- a/test/lib/myTransform/id.js
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
-* Licensed to the Apache Software Foundation (ASF) under one
-* or more contributor license agreements.  See the NOTICE file
-* distributed with this work for additional information
-* regarding copyright ownership.  The ASF licenses this file
-* to you under the Apache License, Version 2.0 (the
-* "License"); you may not use this file except in compliance
-* with the License.  You may obtain a copy of the License at
-*
-*   http://www.apache.org/licenses/LICENSE-2.0
-*
-* Unless required by applicable law or agreed to in writing,
-* software distributed under the License is distributed on an
-* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-* KIND, either express or implied.  See the License for the
-* specific language governing permissions and limitations
-* under the License.
-*/
-
-(function (exports) {
-
-    /**
-     * @usage
-     *
-     * ```js
-     * dataset: [{
-     *     source: [
-     *         ['aa', 'bb', 'cc', 'tag'],
-     *         [12, 0.33, 5200, 'AA'],
-     *         [21, 0.65, 8100, 'AA'],
-     *         ...
-     *     ]
-     * }, {
-     *     transform: {
-     *         type: 'my:id',
-     *         config: {
-     *             dimensionIndex: 4,
-     *             dimensionName: 'ID'
-     *         }
-     *     }
-     *     // Then the result data will be:
-     *     // [
-     *     //     ['aa', 'bb', 'cc', 'tag', 'ID'],
-     *     //     [12, 0.33, 5200, 'AA', 0],
-     *     //     [21, 0.65, 8100, 'BB', 1],
-     *     //     ...
-     *     // ]
-     * }]
-     * ```
-     */
-    var transform = {
-
-        type: 'myTransform:id',
-
-        /**
-         * @param params.config.dimensionIndex DimensionIndex
-         *        Mandatory. Specify where to put the new id dimension.
-         * @param params.config.dimensionName DimensionName
-         *        Optional. If not provided, left the dimension name not defined.
-         */
-        transform: function (params) {
-            var upstream = params.upstream;
-            var config = params.config;
-            var dimensionIndex = config.dimensionIndex;
-            var dimensionName = config.dimensionName;
-
-            var dimsDef = upstream.cloneAllDimensionInfo();
-            dimsDef[dimensionIndex] = dimensionName;
-
-            var data = upstream.cloneRawData();
-
-            for (var i = 0, len = data.length; i < len; i++) {
-                var line = data[i];
-                line[dimensionIndex] = i;
-            }
-
-            return {
-                dimensions: dimsDef,
-                data: upstream.data
-            };
-        }
-    };
-
-    var myTransform = exports.myTransform = exports.myTransform || {};
-    myTransform.id = transform;
-
-})(this);
-
diff --git a/test/lib/myTransform/src/.eslintrc.yaml b/test/lib/myTransform/src/.eslintrc.yaml
new file mode 100644
index 0000000..6bda6d7
--- /dev/null
+++ b/test/lib/myTransform/src/.eslintrc.yaml
@@ -0,0 +1,47 @@
+
+# Licensed to the Apache Software Foundation (ASF) under one
+# or more contributor license agreements.  See the NOTICE file
+# distributed with this work for additional information
+# regarding copyright ownership.  The ASF licenses this file
+# to you under the Apache License, Version 2.0 (the
+# "License"); you may not use this file except in compliance
+# with the License.  You may obtain a copy of the License at
+#
+#   http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing,
+# software distributed under the License is distributed on an
+# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+# KIND, either express or implied.  See the License for the
+# specific language governing permissions and limitations
+# under the License.
+
+# Note:
+# If eslint does not work in VSCode, please check:
+# (1) Whether "@typescript-eslint/eslint-plugin" and "@typescript-eslint/parser"
+# are npm installed locally. Should better in the same version.
+# (2) Whether "VSCode ESlint extension" is installed.
+# (3) If the project folder is not the root folder of your working space, please
+# config the "VSCode ESlint extension" in "settings":
+# ```json
+# "eslint.workingDirectories": [{"mode": "auto"}]
+# ```
+# Note that it should be "workingDirectories" rather than "WorkingDirectories".
+
+parser: "@typescript-eslint/parser"
+parserOptions:
+    ecmaVersion: 6
+    sourceType: module
+    ecmaFeatures:
+        modules: true
+    project: "tsconfig.json"
+plugins: ["@typescript-eslint"]
+env:
+    browser: true
+    node: true
+    es6: false
+globals:
+    jQuery: false
+    Promise: false
+    __DEV__: false
+extends: '../../../../.eslintrc-common.yaml'
diff --git a/test/lib/myTransform/src/aggregate.ts b/test/lib/myTransform/src/aggregate.ts
new file mode 100644
index 0000000..68a4d04
--- /dev/null
+++ b/test/lib/myTransform/src/aggregate.ts
@@ -0,0 +1,342 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied.  See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import {
+    DataTransformOption, ExternalDataTransform, ExternalSource, ExternalDimensionDefinition
+} from '../../../../src/data/helper/transform';
+import {
+    DimensionName, DimensionLoose, DimensionDefinitionLoose, OptionDataValue
+} from '../../../../src/util/types';
+import { hasOwn, assert } from 'zrender/src/core/util';
+
+
+/**
+ * @usage
+ *
+ * ```js
+ * dataset: [{
+ *     source: [
+ *         ['aa', 'bb', 'cc', 'tag'],
+ *         [12, 0.33, 5200, 'AA'],
+ *         [21, 0.65, 7100, 'AA'],
+ *         [51, 0.15, 1100, 'BB'],
+ *         [71, 0.75, 9100, 'BB'],
+ *         ...
+ *     ]
+ * }, {
+ *     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: 'dd', method: 'Q1', boundIQR: 1 },
+ *                 { from: 'tag' }
+ *             ],
+ *             groupBy: 'tag'
+ *         }
+ *     }
+ *     // Then the result data will be:
+ *     // [
+ *     //     ['aa', 'bb', 'cc', 'tag'],
+ *     //     [12, 0.33, 5200, 'AA'],
+ *     //     [21, 0.65, 8100, 'BB'],
+ *     //     ...
+ *     // ]
+ * }]
+ * ```
+ *
+ * Current supported methods (case insensitive):
+ * 'sum'
+ * 'count'
+ * 'average'
+ * 'Q1'
+ * 'Q3'
+ * 'Q2' or 'median'
+ * 'min'
+ * 'max'
+ *
+ * Current supported other arguments:
+ * boundIQR:
+ *     Data less than min bound is outlier.
+ *     default 1.5, means Q1 - 1.5 * (Q3 - Q1).
+ *     If 'none'/0 passed, min bound will not be used.
+ *
+ */
+
+export interface AggregateTransformOption extends DataTransformOption {
+    type: 'myTransform:aggregate';
+    config: {
+        // Mandatory
+        resultDimensions: {
+            // Optional. The name of the result dimensions.
+            // If not provided, inherit the name from `from`.
+            name: DimensionName;
+            // Mandatory. `from` is used to reference dimension from `source`.
+            from: DimensionLoose;
+            // Optional. Aggregate method. Currently only these method supported.
+            // If not provided, use `'first'`.
+            method: AggregateMethodLoose;
+        }[];
+        // Optional
+        groupBy: DimensionLoose;
+    };
+}
+
+const METHOD_INTERNAL = {
+    'SUM': true,
+    'COUNT': true,
+    'FIRST': true,
+    'AVERAGE': true,
+    'Q1': true,
+    'Q2': true,
+    'Q3': true,
+    'MIN': true,
+    'MAX': true
+} as const;
+const METHOD_NEEDS_COLLECT = {
+    AVERAGE: true,
+    Q1: true,
+    Q2: true,
+    Q3: true
+} as const;
+const METHOD_ALIAS = {
+    MEDIAN: 'Q2'
+} as const;
+
+type AggregateMethodLoose =
+    AggregateMethodInternal
+    | 'sum' | 'count' | 'first' | 'average' | 'Q1' | 'Q2' | 'Q3' | 'median' | 'min' | 'max';
+type AggregateMethodInternal = keyof typeof METHOD_INTERNAL;
+
+interface ResultDimInfoInternal extends ExternalDimensionDefinition {
+    method: AggregateMethodInternal;
+}
+
+type CreateInTravel = (
+    upstream: ExternalSource,
+    dataIndex: number,
+    resultDimInfoList: ResultDimInfoInternal[],
+    groupByDimInfo?: ExternalDimensionDefinition,
+    groupByVal?: OptionDataValue
+) => void;
+type AggregateInTravel = (
+    upstream: ExternalSource,
+    dataIndex: number,
+    targetLine: unknown,
+    resultDimInfoList: ResultDimInfoInternal[],
+    groupByDimInfo?: ExternalDimensionDefinition
+) => void;
+
+export const transform: ExternalDataTransform<AggregateTransformOption> = {
+
+    type: 'myTransform:aggregate',
+
+    transform: function (params) {
+        const upstream = params.upstream;
+        const config = params.config;
+
+        const dimWrap = prepareDimensions(config, upstream);
+        const resultDimInfoList = dimWrap.resultDimInfoList;
+        const resultDimensions = dimWrap.resultDimensions;
+
+        const groupByDimInfo = prepareGroupByDimInfo(config, upstream);
+
+        // Collect
+        // const collectResult;
+        // const dimInfoListForCollect = makeDimInfoListForCollect(resultDimInfoList);
+        // if (dimInfoListForCollect.length) {
+        //     collectResult = travel(groupByDimInfo, upstream, resultDimInfoList, doCreate, doAggregate);
+        // }
+
+        // Calculate
+        const finalResult = travel(
+            groupByDimInfo, upstream, resultDimInfoList, createResultLine, aggregateResultLine
+        );
+
+        return {
+            dimensions: resultDimensions,
+            data: finalResult.outList
+        };
+    }
+};
+
+function prepareDimensions(
+    config: AggregateTransformOption['config'],
+    upstream: ExternalSource
+): {
+    resultDimInfoList: ResultDimInfoInternal[];
+    resultDimensions: DimensionDefinitionLoose[];
+} {
+    const resultDimensionsConfig = config.resultDimensions;
+    const resultDimInfoList: ResultDimInfoInternal[] = [];
+    const resultDimensions: DimensionDefinitionLoose[] = [];
+
+    for (let i = 0; i < resultDimensionsConfig.length; i++) {
+        const resultDimInfoConfig = resultDimensionsConfig[i];
+
+        const resultDimInfo = upstream.getDimensionInfo(resultDimInfoConfig.from) as ResultDimInfoInternal;
+        assert(resultDimInfo, 'Can not find dimension by `from`: ' + resultDimInfoConfig.from);
+
+        resultDimInfo.method = normalizeMethod(resultDimInfoConfig.method);
+        assert(resultDimInfo.method, 'method is required');
+
+        resultDimInfoList.push(resultDimInfo);
+
+        if (resultDimInfoConfig.name != null) {
+            resultDimInfo.name = resultDimInfoConfig.name;
+        }
+
+        resultDimensions.push(resultDimInfo.name);
+    }
+
+    return { resultDimensions, resultDimInfoList };
+}
+
+function prepareGroupByDimInfo(
+    config: AggregateTransformOption['config'],
+    upstream: ExternalSource
+): ExternalDimensionDefinition {
+    const groupByConfig = config.groupBy;
+    let groupByDimInfo;
+    if (groupByConfig != null) {
+        groupByDimInfo = upstream.getDimensionInfo(groupByConfig);
+        assert(groupByDimInfo, 'Can not find dimension by `groupBy`: ' + groupByConfig);
+    }
+    return groupByDimInfo;
+}
+
+function travel(
+    groupByDimInfo: ExternalDimensionDefinition,
+    upstream: ExternalSource,
+    resultDimInfoList: ResultDimInfoInternal[],
+    doCreate: CreateInTravel,
+    doAggregate: AggregateInTravel
+): {
+    groupMap: { [groupVal in string]: unknown };
+    outList: unknown[];
+} {
+    const outList: unknown[] = [];
+    let groupMap: { [groupVal in string]: unknown };
+
+    if (groupByDimInfo) {
+        groupMap = {};
+
+        for (let dataIndex = 0, len = upstream.count(); dataIndex < len; dataIndex++) {
+            const groupByVal = upstream.retrieveValue(dataIndex, groupByDimInfo.index);
+
+            // PENDING: when value is null/undefined
+            if (groupByVal == null) {
+                continue;
+            }
+
+            const groupByValStr = groupByVal + '';
+
+            if (!hasOwn(groupMap, groupByValStr)) {
+                const newLine = doCreate(upstream, dataIndex, resultDimInfoList, groupByDimInfo, groupByVal);
+                outList.push(newLine);
+                groupMap[groupByValStr] = newLine;
+            }
+            else {
+                const targetLine = groupMap[groupByValStr];
+                doAggregate(upstream, dataIndex, targetLine, resultDimInfoList, groupByDimInfo);
+            }
+        }
+    }
+    else {
+        const targetLine = doCreate(upstream, 0, resultDimInfoList);
+        outList.push(targetLine);
+        for (let dataIndex = 0, len = upstream.count(); dataIndex < len; dataIndex++) {
+            doAggregate(upstream, dataIndex, targetLine, resultDimInfoList);
+        }
+    }
+
+    return {
+        groupMap: groupMap,
+        outList: outList
+    };
+}
+
+// function makeDimInfoListForCollect(resultDimInfoList) {
+//     const dimInfoListForCollect = [];
+//     for (const j = 0; j < resultDimInfoList.length; j++) {
+//         const resultDimInfo = resultDimInfoList[j];
+//         const method = resultDimInfo.method;
+//         if (hasOwn(METHOD_NEEDS_COLLECT, method)) {
+//             dimInfoListForCollect.push(resultDimInfo);
+//         }
+//     }
+//     return dimInfoListForCollect;
+// }
+
+function normalizeMethod(method: AggregateMethodLoose): AggregateMethodInternal {
+    if (method == null) {
+        return 'FIRST';
+    }
+    let methodInternal = method.toUpperCase() as AggregateMethodInternal;
+    methodInternal = hasOwn(METHOD_ALIAS, methodInternal)
+        ? METHOD_ALIAS[methodInternal as keyof typeof METHOD_ALIAS]
+        : methodInternal;
+    assert(hasOwn(METHOD_INTERNAL, methodInternal), `Illegal method ${method}.`);
+    return methodInternal;
+}
+
+const createResultLine: CreateInTravel = (
+    upstream, dataIndex, resultDimInfoList, groupByDimInfo, groupByVal
+) => {
+    const newLine = [];
+    for (let j = 0; j < resultDimInfoList.length; j++) {
+        const resultDimInfo = resultDimInfoList[j];
+        const method = resultDimInfo.method;
+        newLine[j] = (groupByDimInfo && resultDimInfo.index === groupByDimInfo.index)
+            ? groupByVal
+            : (method === 'SUM' || method === 'COUNT')
+            ? 0
+            // By default, method: 'first'
+            : upstream.retrieveValue(dataIndex, resultDimInfo.index);
+    }
+    return newLine;
+};
+
+const aggregateResultLine: AggregateInTravel = (
+    upstream, dataIndex, targetLine: number[], resultDimInfoList, groupByDimInfo
+) => {
+    for (let j = 0; j < resultDimInfoList.length; j++) {
+        const resultDimInfo = resultDimInfoList[j];
+        const method = resultDimInfo.method;
+
+        if (groupByDimInfo && resultDimInfo.index === groupByDimInfo.index) {
+            continue;
+        }
+
+        if (method === 'SUM') {
+            // FIXME: handle other types
+            targetLine[j] += upstream.retrieveValue(dataIndex, resultDimInfo.index) as number;
+        }
+        else if (method === 'COUNT') {
+            targetLine[j] += 1;
+        }
+        else if (method === 'AVERAGE') {
+            // FIXME: handle other types
+            targetLine[j] += upstream.retrieveValue(dataIndex, resultDimInfo.index) as number / 1;
+        }
+    }
+};
diff --git a/test/lib/myTransform/src/id.ts b/test/lib/myTransform/src/id.ts
new file mode 100644
index 0000000..d466d8b
--- /dev/null
+++ b/test/lib/myTransform/src/id.ts
@@ -0,0 +1,90 @@
+/*
+* Licensed to the Apache Software Foundation (ASF) under one
+* or more contributor license agreements.  See the NOTICE file
+* distributed with this work for additional information
+* regarding copyright ownership.  The ASF licenses this file
+* to you under the Apache License, Version 2.0 (the
+* "License"); you may not use this file except in compliance
+* with the License.  You may obtain a copy of the License at
+*
+*   http://www.apache.org/licenses/LICENSE-2.0
+*
+* Unless required by applicable law or agreed to in writing,
+* software distributed under the License is distributed on an
+* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+* KIND, either express or implied.  See the License for the
+* specific language governing permissions and limitations
+* under the License.
+*/
+
+import { ExternalDataTransform, DataTransformOption } from '../../../../src/data/helper/transform';
+import { DimensionIndex, DimensionName, DimensionDefinitionLoose, OptionSourceDataArrayRows } from '../../../../src/util/types';
+
+
+/**
+ * @usage
+ *
+ * ```js
+ * dataset: [{
+ *     source: [
+ *         ['aa', 'bb', 'cc', 'tag'],
+ *         [12, 0.33, 5200, 'AA'],
+ *         [21, 0.65, 8100, 'AA'],
+ *         ...
+ *     ]
+ * }, {
+ *     transform: {
+ *         type: 'my:id',
+ *         config: {
+ *             dimensionIndex: 4,
+ *             dimensionName: 'ID'
+ *         }
+ *     }
+ *     // Then the result data will be:
+ *     // [
+ *     //     ['aa', 'bb', 'cc', 'tag', 'ID'],
+ *     //     [12, 0.33, 5200, 'AA', 0],
+ *     //     [21, 0.65, 8100, 'BB', 1],
+ *     //     ...
+ *     // ]
+ * }]
+ * ```
+ */
+
+export interface IdTransformOption extends DataTransformOption {
+    type: 'myTransform:id';
+    config: {
+        // Mandatory. Specify where to put the new id dimension.
+        dimensionIndex: DimensionIndex;
+        // Optional. If not provided, left the dimension name not defined.
+        dimensionName: DimensionName;
+    };
+}
+
+export const transform: ExternalDataTransform<IdTransformOption> = {
+
+    type: 'myTransform:id',
+
+    transform: function (params) {
+        const upstream = params.upstream;
+        const config = params.config;
+        const dimensionIndex = config.dimensionIndex;
+        const dimensionName = config.dimensionName;
+
+        const dimsDef = upstream.cloneAllDimensionInfo() as DimensionDefinitionLoose[];
+        dimsDef[dimensionIndex] = dimensionName;
+
+        const data = upstream.cloneRawData() as OptionSourceDataArrayRows;
+
+        // TODO: support objectRows
+        for (let i = 0, len = data.length; i < len; i++) {
+            const line = data[i];
+            line[dimensionIndex] = i;
+        }
+
+        return {
+            dimensions: dimsDef,
+            data: data
+        };
+    }
+};
diff --git a/test/lib/myTransform/src/index.ts b/test/lib/myTransform/src/index.ts
new file mode 100644
index 0000000..a8138ad
--- /dev/null
+++ b/test/lib/myTransform/src/index.ts
@@ -0,0 +1,3 @@
+
+export { transform as id } from './id';
+export { transform as aggregate } from './aggregate';
diff --git a/tsconfig.json b/tsconfig.json
index adedaa4..5c9fc9b 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -22,6 +22,7 @@
     },
     "include": [
         "src/**/*.ts",
-        "extension-src/**/*.ts"
+        "extension-src/**/*.ts",
+        "test/lib/myTransform/src/**/*.ts"
     ]
 }
\ No newline at end of file


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