diff --git a/build/build.js b/build/build.js index 24da784dc3..42bb096f0b 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 9cc6e82b7c..c1c31b78e0 100644 --- a/build/config.js +++ b/build/config.js @@ -212,3 +212,28 @@ 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'), + nodePath.resolve(ecDir, '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/src/data/helper/transform.ts b/src/data/helper/transform.ts index 287b55dafa..349e9857d8 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 { +export interface ExternalDimensionDefinition extends Partial { // Mandatory index: DimensionIndex; } diff --git a/test/custom-shape-morphing2.html b/test/custom-shape-morphing2.html index ac1273861c..f55d36774a 100644 --- a/test/custom-shape-morphing2.html +++ b/test/custom-shape-morphing2.html @@ -27,7 +27,6 @@ - @@ -76,9 +75,9 @@ - - @@ -42,11 +40,11 @@ + + + + + + + + + + + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/lib/config.js b/test/lib/config.js index 5966c4b2a5..ad8e68bfb8 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 a5fd4eb94a..0000000000 --- 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 0000000000..a2f97b57d1 --- /dev/null +++ b/test/lib/myTransform/dist/myTransform.js @@ -0,0 +1,476 @@ +(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 nativeForEach = arrayProto.forEach; + var nativeSlice = arrayProto.slice; + var nativeMap = arrayProto.map; + + var ctorFunction = function () {}.constructor; + + var protoFunction = ctorFunction ? ctorFunction.prototype : null; + + function each(arr, cb, context) { + if (!(arr && cb)) { + return; + } + + if (arr.forEach && arr.forEach === nativeForEach) { + arr.forEach(cb, context); + } else if (arr.length === +arr.length) { + for (var i = 0, len = arr.length; i < len; i++) { + cb.call(context, arr[i], i, arr); + } + } else { + for (var key in arr) { + if (arr.hasOwnProperty(key)) { + cb.call(context, arr[key], key, arr); + } + } + } + } + + function map(arr, cb, context) { + if (!arr) { + return []; + } + + if (!cb) { + return slice(arr); + } + + if (arr.map && arr.map === nativeMap) { + return arr.map(cb, context); + } else { + var result = []; + + for (var i = 0, len = arr.length; i < len; i++) { + result.push(cb.call(context, arr[i], i, arr)); + } + + return result; + } + } + + 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 slice(arr) { + var args = []; + + for (var _i = 1; _i < arguments.length; _i++) { + args[_i - 1] = arguments[_i]; + } + + return nativeSlice.apply(arr, args); + } + + function assert(condition, message) { + if (!condition) { + throw new Error(message); + } + } + + function hasOwn(own, prop) { + return own.hasOwnProperty(prop); + } + + function quantile(ascArr, p) { + var H = (ascArr.length - 1) * p + 1; + var h = Math.floor(H); + var v = +ascArr[h - 1]; + var e = H - h; + return e ? v + e * (ascArr[h] - v) : v; + } + + var METHOD_INTERNAL = { + 'SUM': true, + 'COUNT': true, + 'FIRST': true, + 'AVERAGE': true, + 'Q1': true, + 'Q2': true, + 'Q3': true, + 'MIN': true, + 'MAX': true + }; + var METHOD_NEEDS_COLLECT = { + AVERAGE: ['COUNT'] + }; + var METHOD_NEEDS_GATHER_VALUES = { + Q1: true, + Q2: true, + Q3: true + }; + var METHOD_ALIAS = { + MEDIAN: 'Q2' + }; + + var ResultDimInfoInternal = function () { + function ResultDimInfoInternal(index, indexInUpstream, method, name, needGatherValues) { + this.collectionInfoList = []; + this.gatheredValuesByGroup = {}; + this.gatheredValuesNoGroup = []; + this.needGatherValues = false; + this._collectionInfoMap = {}; + this.method = method; + this.name = name; + this.index = index; + this.indexInUpstream = indexInUpstream; + this.needGatherValues = needGatherValues; + } + + ResultDimInfoInternal.prototype.addCollectionInfo = function (item) { + this._collectionInfoMap[item.method] = this.collectionInfoList.length; + this.collectionInfoList.push(item); + }; + + ResultDimInfoInternal.prototype.getCollectionInfo = function (method) { + return this.collectionInfoList[this._collectionInfoMap[method]]; + }; + + ResultDimInfoInternal.prototype.gatherValue = function (groupByDimInfo, groupVal, value) { + value = +value; + + if (groupByDimInfo) { + if (groupVal != null) { + var groupValStr = groupVal + ''; + var values = this.gatheredValuesByGroup[groupValStr] || (this.gatheredValuesByGroup[groupValStr] = []); + values.push(value); + } + } else { + this.gatheredValuesNoGroup.push(value); + } + }; + + return ResultDimInfoInternal; + }(); + + var transform$1 = { + type: 'myTransform:aggregate', + transform: function (params) { + var upstream = params.upstream; + var config = params.config; + var groupByDimInfo = prepareGroupByDimInfo(config, upstream); + + var _a = prepareDimensions(config, upstream, groupByDimInfo), + finalResultDimInfoList = _a.finalResultDimInfoList, + collectionDimInfoList = _a.collectionDimInfoList; + + var collectionResult; + + if (collectionDimInfoList.length) { + collectionResult = travel(groupByDimInfo, upstream, collectionDimInfoList, createCollectionResultLine, updateCollectionResultLine); + } + + each(collectionDimInfoList, function (dimInfo) { + dimInfo.__collectionResult = collectionResult; + asc(dimInfo.gatheredValuesNoGroup); + each(dimInfo.gatheredValuesByGroup, function (values) { + asc(values); + }); + }); + var finalResult = travel(groupByDimInfo, upstream, finalResultDimInfoList, createFinalResultLine, updateFinalResultLine); + return { + dimensions: map(finalResultDimInfoList, function (item) { + return item.name; + }), + data: finalResult.outList + }; + } + }; + + function prepareDimensions(config, upstream, groupByDimInfo) { + var resultDimensionsConfig = config.resultDimensions; + var finalResultDimInfoList = []; + var collectionDimInfoList = []; + var gIndexInLine = 0; + + for (var i = 0; i < resultDimensionsConfig.length; i++) { + var resultDimInfoConfig = resultDimensionsConfig[i]; + var dimInfoInUpstream = upstream.getDimensionInfo(resultDimInfoConfig.from); + assert(dimInfoInUpstream, 'Can not find dimension by `from`: ' + resultDimInfoConfig.from); + var rawMethod = resultDimInfoConfig.method; + assert(groupByDimInfo.index !== dimInfoInUpstream.index || rawMethod == null, "Dimension " + dimInfoInUpstream.name + " is the \"groupBy\" dimension, must not have any \"method\"."); + var method = normalizeMethod(rawMethod); + assert(method, 'method is required'); + var name_1 = resultDimInfoConfig.name != null ? resultDimInfoConfig.name : dimInfoInUpstream.name; + var finalResultDimInfo = new ResultDimInfoInternal(finalResultDimInfoList.length, dimInfoInUpstream.index, method, name_1, hasOwn(METHOD_NEEDS_GATHER_VALUES, method)); + finalResultDimInfoList.push(finalResultDimInfo); + var needCollect = false; + + if (hasOwn(METHOD_NEEDS_COLLECT, method)) { + needCollect = true; + var collectionTargetMethods = METHOD_NEEDS_COLLECT[method]; + + for (var j = 0; j < collectionTargetMethods.length; j++) { + finalResultDimInfo.addCollectionInfo({ + method: collectionTargetMethods[j], + indexInLine: gIndexInLine++ + }); + } + } + + if (hasOwn(METHOD_NEEDS_GATHER_VALUES, method)) { + needCollect = true; + } + + if (needCollect) { + collectionDimInfoList.push(finalResultDimInfo); + } + } + + return { + collectionDimInfoList: collectionDimInfoList, + finalResultDimInfoList: finalResultDimInfoList + }; + } + + 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, doUpdate) { + var outList = []; + var mapByGroup; + + if (groupByDimInfo) { + mapByGroup = {}; + + 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(mapByGroup, groupByValStr)) { + var newLine = doCreate(upstream, dataIndex, resultDimInfoList, groupByDimInfo, groupByVal); + outList.push(newLine); + mapByGroup[groupByValStr] = newLine; + } else { + var targetLine = mapByGroup[groupByValStr]; + doUpdate(upstream, dataIndex, targetLine, resultDimInfoList, groupByDimInfo, groupByVal); + } + } + } else { + var targetLine = doCreate(upstream, 0, resultDimInfoList); + outList.push(targetLine); + + for (var dataIndex = 1, len = upstream.count(); dataIndex < len; dataIndex++) { + doUpdate(upstream, dataIndex, targetLine, resultDimInfoList); + } + } + + return { + mapByGroup: mapByGroup, + 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 createCollectionResultLine = function (upstream, dataIndex, collectionDimInfoList, groupByDimInfo, groupByVal) { + var newLine = []; + + for (var i = 0; i < collectionDimInfoList.length; i++) { + var dimInfo = collectionDimInfoList[i]; + var collectionInfoList = dimInfo.collectionInfoList; + + for (var j = 0; j < collectionInfoList.length; j++) { + var collectionInfo = collectionInfoList[j]; + newLine[collectionInfo.indexInLine] = +lineCreator[collectionInfo.method](upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal); + } + + if (dimInfo.needGatherValues) { + var val = upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream); + dimInfo.gatherValue(groupByDimInfo, groupByVal, val); + } + } + + return newLine; + }; + + var updateCollectionResultLine = function (upstream, dataIndex, targetLine, collectionDimInfoList, groupByDimInfo, groupByVal) { + for (var i = 0; i < collectionDimInfoList.length; i++) { + var dimInfo = collectionDimInfoList[i]; + var collectionInfoList = dimInfo.collectionInfoList; + + for (var j = 0; j < collectionInfoList.length; j++) { + var collectionInfo = collectionInfoList[j]; + var indexInLine = collectionInfo.indexInLine; + targetLine[indexInLine] = +lineUpdater[collectionInfo.method](targetLine[indexInLine], upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal); + } + + if (dimInfo.needGatherValues) { + var val = upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream); + dimInfo.gatherValue(groupByDimInfo, groupByVal, val); + } + } + }; + + var createFinalResultLine = function (upstream, dataIndex, finalResultDimInfoList, groupByDimInfo, groupByVal) { + var newLine = []; + + for (var i = 0; i < finalResultDimInfoList.length; i++) { + var dimInfo = finalResultDimInfoList[i]; + var method = dimInfo.method; + newLine[i] = isGroupByDimension(groupByDimInfo, dimInfo) ? groupByVal : lineCreator[method](upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal); + } + + return newLine; + }; + + var updateFinalResultLine = function (upstream, dataIndex, targetLine, finalResultDimInfoList, groupByDimInfo, groupByVal) { + for (var i = 0; i < finalResultDimInfoList.length; i++) { + var dimInfo = finalResultDimInfoList[i]; + + if (isGroupByDimension(groupByDimInfo, dimInfo)) { + continue; + } + + var method = dimInfo.method; + targetLine[i] = lineUpdater[method](targetLine[i], upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal); + } + }; + + function isGroupByDimension(groupByDimInfo, targetDimInfo) { + return groupByDimInfo && targetDimInfo.indexInUpstream === groupByDimInfo.index; + } + + function asc(list) { + list.sort(function (a, b) { + return a - b; + }); + } + + var lineCreator = { + 'SUM': function () { + return 0; + }, + 'COUNT': function () { + return 1; + }, + 'FIRST': function (upstream, dataIndex, dimInfo) { + return upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream); + }, + 'MIN': function (upstream, dataIndex, dimInfo) { + return upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream); + }, + 'MAX': function (upstream, dataIndex, dimInfo) { + return upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream); + }, + 'AVERAGE': function (upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal) { + var collectLine = groupByDimInfo ? dimInfo.__collectionResult.mapByGroup[groupByVal + ''] : dimInfo.__collectionResult.outList[0]; + return upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream) / collectLine[dimInfo.getCollectionInfo('COUNT').indexInLine]; + }, + 'Q1': function (upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal) { + return lineCreatorForQ(0.25, dimInfo, groupByDimInfo, groupByVal); + }, + 'Q2': function (upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal) { + return lineCreatorForQ(0.5, dimInfo, groupByDimInfo, groupByVal); + }, + 'Q3': function (upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal) { + return lineCreatorForQ(0.75, dimInfo, groupByDimInfo, groupByVal); + } + }; + var lineUpdater = { + 'SUM': function (val, upstream, dataIndex, dimInfo) { + return val + upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream); + }, + 'COUNT': function (val) { + return val + 1; + }, + 'FIRST': function (val) { + return val; + }, + 'MIN': function (val, upstream, dataIndex, dimInfo) { + return Math.min(val, upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream)); + }, + 'MAX': function (val, upstream, dataIndex, dimInfo) { + return Math.max(val, upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream)); + }, + 'AVERAGE': function (val, upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal) { + var collectLine = groupByDimInfo ? dimInfo.__collectionResult.mapByGroup[groupByVal + ''] : dimInfo.__collectionResult.outList[0]; + return val + upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream) / collectLine[dimInfo.getCollectionInfo('COUNT').indexInLine]; + }, + 'Q1': function (val, upstream, dataIndex, dimInfo) { + return val; + }, + 'Q2': function (val, upstream, dataIndex, dimInfo) { + return val; + }, + 'Q3': function (val, upstream, dataIndex, dimInfo) { + return val; + } + }; + + function lineCreatorForQ(percent, dimInfo, groupByDimInfo, groupByVal) { + var gatheredValues = groupByDimInfo ? dimInfo.gatheredValuesByGroup[groupByVal + ''] : dimInfo.gatheredValuesNoGroup; + return quantile(gatheredValues, percent); + } + + 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 0000000000..7628c230f9 --- /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 9e913709fc..0000000000 --- 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 0000000000..6bda6d7082 --- /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 0000000000..4b6e02a6ee --- /dev/null +++ b/test/lib/myTransform/src/aggregate.ts @@ -0,0 +1,593 @@ +/* +* 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, OptionDataValue +} from '../../../../src/util/types'; +import { hasOwn, assert, map, each } from 'zrender/src/core/util'; +import { quantile } from '../../../../src/util/number'; + + +/** + * @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' }, + * { 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' + */ + +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: ['COUNT'] +} as const; +const METHOD_NEEDS_GATHER_VALUES = { + 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; + + +class ResultDimInfoInternal { + + readonly method: AggregateMethodInternal; + readonly name: DimensionName; + readonly index: number; + readonly indexInUpstream: number; + + readonly collectionInfoList = [] as { + method: AggregateMethodInternal; + indexInLine: number; + }[]; + + // FIXME: refactor + readonly gatheredValuesByGroup: { [groupVal: string]: number[] } = {}; + readonly gatheredValuesNoGroup = [] as number[]; + readonly needGatherValues: boolean = false; + + __collectionResult: TravelResult; + + private _collectionInfoMap = {} as { + // number is the index of `list` + [method in AggregateMethodInternal]: number + }; + + constructor( + index: number, + indexInUpstream: number, + method: AggregateMethodInternal, + name: DimensionName, + needGatherValues: boolean + ) { + this.method = method; + this.name = name; + this.index = index; + this.indexInUpstream = indexInUpstream; + this.needGatherValues = needGatherValues; + } + + addCollectionInfo(item: ResultDimInfoInternal['collectionInfoList'][number]) { + this._collectionInfoMap[item.method] = this.collectionInfoList.length; + this.collectionInfoList.push(item); + } + + getCollectionInfo(method: AggregateMethodInternal) { + return this.collectionInfoList[this._collectionInfoMap[method]]; + } + + // FIXME: temp implementation. Need refactor. + gatherValue(groupByDimInfo: ExternalDimensionDefinition, groupVal: OptionDataValue, value: OptionDataValue) { + // FIXME: convert to number compulsorily temporarily. + value = +value; + if (groupByDimInfo) { + if (groupVal != null) { + const groupValStr = groupVal + ''; + const values = this.gatheredValuesByGroup[groupValStr] + || (this.gatheredValuesByGroup[groupValStr] = []); + values.push(value); + } + } + else { + this.gatheredValuesNoGroup.push(value); + } + } +} + +type CreateInTravel = ( + upstream: ExternalSource, + dataIndex: number, + dimInfoList: ResultDimInfoInternal[], + groupByDimInfo?: ExternalDimensionDefinition, + groupByVal?: OptionDataValue +) => LINE; +type UpdateInTravel = ( + upstream: ExternalSource, + dataIndex: number, + targetLine: LINE, + dimInfoList: ResultDimInfoInternal[], + groupByDimInfo?: ExternalDimensionDefinition, + groupByVal?: OptionDataValue +) => void; + +export const transform: ExternalDataTransform = { + + type: 'myTransform:aggregate', + + transform: function (params) { + const upstream = params.upstream; + const config = params.config; + + const groupByDimInfo = prepareGroupByDimInfo(config, upstream); + const { finalResultDimInfoList, collectionDimInfoList } = prepareDimensions( + config, upstream, groupByDimInfo + ); + + // Collect + let collectionResult: TravelResult; + if (collectionDimInfoList.length) { + collectionResult = travel( + groupByDimInfo, + upstream, + collectionDimInfoList, + createCollectionResultLine, + updateCollectionResultLine + ); + } + + each(collectionDimInfoList, dimInfo => { + dimInfo.__collectionResult = collectionResult; + // FIXME: just for Q1, Q2, Q3: need asc. + asc(dimInfo.gatheredValuesNoGroup); + each(dimInfo.gatheredValuesByGroup, values => { + asc(values); + }); + }); + + // Calculate + const finalResult = travel( + groupByDimInfo, + upstream, + finalResultDimInfoList, + createFinalResultLine, + updateFinalResultLine + ); + + return { + dimensions: map(finalResultDimInfoList, item => item.name), + data: finalResult.outList + }; + } +}; + +function prepareDimensions( + config: AggregateTransformOption['config'], + upstream: ExternalSource, + groupByDimInfo: ExternalDimensionDefinition +): { + finalResultDimInfoList: ResultDimInfoInternal[]; + collectionDimInfoList: ResultDimInfoInternal[]; +} { + const resultDimensionsConfig = config.resultDimensions; + const finalResultDimInfoList: ResultDimInfoInternal[] = []; + const collectionDimInfoList: ResultDimInfoInternal[] = []; + let gIndexInLine = 0; + + for (let i = 0; i < resultDimensionsConfig.length; i++) { + const resultDimInfoConfig = resultDimensionsConfig[i]; + + const dimInfoInUpstream = upstream.getDimensionInfo(resultDimInfoConfig.from); + assert(dimInfoInUpstream, 'Can not find dimension by `from`: ' + resultDimInfoConfig.from); + + const rawMethod = resultDimInfoConfig.method; + + assert( + groupByDimInfo.index !== dimInfoInUpstream.index || rawMethod == null, + `Dimension ${dimInfoInUpstream.name} is the "groupBy" dimension, must not have any "method".` + ); + + const method = normalizeMethod(rawMethod); + assert(method, 'method is required'); + + const name = resultDimInfoConfig.name != null ? resultDimInfoConfig.name : dimInfoInUpstream.name; + + const finalResultDimInfo = new ResultDimInfoInternal( + finalResultDimInfoList.length, + dimInfoInUpstream.index, + method, + name, + hasOwn(METHOD_NEEDS_GATHER_VALUES, method) + ); + finalResultDimInfoList.push(finalResultDimInfo); + + // For collection. + let needCollect = false; + if (hasOwn(METHOD_NEEDS_COLLECT, method)) { + needCollect = true; + const collectionTargetMethods = METHOD_NEEDS_COLLECT[method as keyof typeof METHOD_NEEDS_COLLECT]; + for (let j = 0; j < collectionTargetMethods.length; j++) { + finalResultDimInfo.addCollectionInfo({ + method: collectionTargetMethods[j], + indexInLine: gIndexInLine++ + }); + } + } + if (hasOwn(METHOD_NEEDS_GATHER_VALUES, method)) { + needCollect = true; + } + if (needCollect) { + collectionDimInfoList.push(finalResultDimInfo); + } + } + + return { collectionDimInfoList, finalResultDimInfoList }; +} + +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; +} + +interface TravelResult { + mapByGroup: { [groupVal: string]: LINE }; + outList: LINE[]; +} + +function travel( + groupByDimInfo: ExternalDimensionDefinition, + upstream: ExternalSource, + resultDimInfoList: ResultDimInfoInternal[], + doCreate: CreateInTravel, + doUpdate: UpdateInTravel +): TravelResult { + const outList: TravelResult['outList'] = []; + let mapByGroup: TravelResult['mapByGroup']; + + if (groupByDimInfo) { + mapByGroup = {}; + + 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(mapByGroup, groupByValStr)) { + const newLine = doCreate(upstream, dataIndex, resultDimInfoList, groupByDimInfo, groupByVal); + outList.push(newLine); + mapByGroup[groupByValStr] = newLine; + } + else { + const targetLine = mapByGroup[groupByValStr]; + doUpdate(upstream, dataIndex, targetLine, resultDimInfoList, groupByDimInfo, groupByVal); + } + } + } + else { + const targetLine = doCreate(upstream, 0, resultDimInfoList); + outList.push(targetLine); + for (let dataIndex = 1, len = upstream.count(); dataIndex < len; dataIndex++) { + doUpdate(upstream, dataIndex, targetLine, resultDimInfoList); + } + } + + return { mapByGroup, outList }; +} + +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; +} + + + +type CollectionResultLine = number[]; + +const createCollectionResultLine: CreateInTravel = ( + upstream, dataIndex, collectionDimInfoList, groupByDimInfo, groupByVal +) => { + const newLine = [] as number[]; + for (let i = 0; i < collectionDimInfoList.length; i++) { + const dimInfo = collectionDimInfoList[i]; + const collectionInfoList = dimInfo.collectionInfoList; + for (let j = 0; j < collectionInfoList.length; j++) { + const collectionInfo = collectionInfoList[j]; + // FIXME: convert to number compulsorily temporarily. + newLine[collectionInfo.indexInLine] = +lineCreator[collectionInfo.method]( + upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal + ); + } + // FIXME: refactor + if (dimInfo.needGatherValues) { + const val = upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream); + dimInfo.gatherValue(groupByDimInfo, groupByVal, val); + } + } + return newLine; +}; + +const updateCollectionResultLine: UpdateInTravel = ( + upstream, dataIndex, targetLine: number[], collectionDimInfoList, groupByDimInfo, groupByVal +) => { + for (let i = 0; i < collectionDimInfoList.length; i++) { + const dimInfo = collectionDimInfoList[i]; + const collectionInfoList = dimInfo.collectionInfoList; + for (let j = 0; j < collectionInfoList.length; j++) { + const collectionInfo = collectionInfoList[j]; + const indexInLine = collectionInfo.indexInLine; + // FIXME: convert to number compulsorily temporarily. + targetLine[indexInLine] = +lineUpdater[collectionInfo.method]( + targetLine[indexInLine], upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal + ); + } + // FIXME: refactor + if (dimInfo.needGatherValues) { + const val = upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream); + dimInfo.gatherValue(groupByDimInfo, groupByVal, val); + } + } +}; + + + +type FinalResultLine = OptionDataValue[]; + +const createFinalResultLine: CreateInTravel = ( + upstream, dataIndex, finalResultDimInfoList, groupByDimInfo, groupByVal +) => { + const newLine = []; + for (let i = 0; i < finalResultDimInfoList.length; i++) { + const dimInfo = finalResultDimInfoList[i]; + const method = dimInfo.method; + newLine[i] = isGroupByDimension(groupByDimInfo, dimInfo) + ? groupByVal + : lineCreator[method]( + upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal + ); + } + return newLine; +}; + +const updateFinalResultLine: UpdateInTravel = ( + upstream, dataIndex, targetLine, finalResultDimInfoList, groupByDimInfo, groupByVal +) => { + for (let i = 0; i < finalResultDimInfoList.length; i++) { + const dimInfo = finalResultDimInfoList[i]; + if (isGroupByDimension(groupByDimInfo, dimInfo)) { + continue; + } + const method = dimInfo.method; + targetLine[i] = lineUpdater[method]( + targetLine[i], upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal + ); + } +}; + +function isGroupByDimension( + groupByDimInfo: ExternalDimensionDefinition, + targetDimInfo: ResultDimInfoInternal +): boolean { + return groupByDimInfo && targetDimInfo.indexInUpstream === groupByDimInfo.index; +} + +function asc(list: number[]) { + list.sort((a, b) => { + return a - b; + }); +} + +const lineCreator: { + [key in AggregateMethodInternal]: ( + upstream: ExternalSource, + dataIndex: number, + dimInfo: ResultDimInfoInternal, + groupByDimInfo: ExternalDimensionDefinition, + groupByVal: OptionDataValue + ) => OptionDataValue +} = { + 'SUM'() { + return 0; + }, + 'COUNT'() { + return 1; + }, + 'FIRST'(upstream, dataIndex, dimInfo) { + return upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream); + }, + 'MIN'(upstream, dataIndex, dimInfo) { + return upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream); + }, + 'MAX'(upstream, dataIndex, dimInfo) { + return upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream); + }, + 'AVERAGE'(upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal) { + // FIXME: refactor, bad implementation. + const collectLine = groupByDimInfo + ? dimInfo.__collectionResult.mapByGroup[groupByVal + ''] + : dimInfo.__collectionResult.outList[0]; + return (upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream) as number) + / collectLine[dimInfo.getCollectionInfo('COUNT').indexInLine]; + }, + // FIXME: refactor + 'Q1'(upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal) { + return lineCreatorForQ(0.25, dimInfo, groupByDimInfo, groupByVal); + }, + 'Q2'(upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal) { + return lineCreatorForQ(0.5, dimInfo, groupByDimInfo, groupByVal); + }, + 'Q3'(upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal) { + return lineCreatorForQ(0.75, dimInfo, groupByDimInfo, groupByVal); + } +}; + +const lineUpdater: { + [key in AggregateMethodInternal]: ( + val: OptionDataValue, + upstream: ExternalSource, + dataIndex: number, + dimInfo: ResultDimInfoInternal, + groupByDimInfo: ExternalDimensionDefinition, + groupByVal: OptionDataValue + ) => OptionDataValue +} = { + 'SUM'(val, upstream, dataIndex, dimInfo) { + // FIXME: handle other types + return (val as number) + (upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream) as number); + }, + 'COUNT'(val) { + return (val as number) + 1; + }, + 'FIRST'(val) { + return val; + }, + 'MIN'(val, upstream, dataIndex, dimInfo) { + return Math.min(val as number, upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream) as number); + }, + 'MAX'(val, upstream, dataIndex, dimInfo) { + return Math.max(val as number, upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream) as number); + }, + 'AVERAGE'(val, upstream, dataIndex, dimInfo, groupByDimInfo, groupByVal) { + // FIXME: refactor, bad implementation. + const collectLine = groupByDimInfo + ? dimInfo.__collectionResult.mapByGroup[groupByVal + ''] + : dimInfo.__collectionResult.outList[0]; + return (val as number) + + (upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream) as number) + / collectLine[dimInfo.getCollectionInfo('COUNT').indexInLine]; + }, + 'Q1'(val, upstream, dataIndex, dimInfo) { + return val; + }, + 'Q2'(val, upstream, dataIndex, dimInfo) { + return val; + }, + 'Q3'(val, upstream, dataIndex, dimInfo) { + return val; + } +}; + +function lineCreatorForQ( + percent: number, + dimInfo: ResultDimInfoInternal, + groupByDimInfo: ExternalDimensionDefinition, + groupByVal: OptionDataValue +) { + const gatheredValues = groupByDimInfo + ? dimInfo.gatheredValuesByGroup[groupByVal + ''] + : dimInfo.gatheredValuesNoGroup; + return quantile(gatheredValues, percent); +} diff --git a/test/lib/myTransform/src/id.ts b/test/lib/myTransform/src/id.ts new file mode 100644 index 0000000000..d466d8b450 --- /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 = { + + 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 0000000000..a8138ada42 --- /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 adedaa4bff..5c9fc9b2a2 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