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 <sushuang0...@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-unsubscr...@echarts.apache.org For additional commands, e-mail: commits-h...@echarts.apache.org