未验证 提交 9d8cfc7f 编写于 作者: S sushuang 提交者: GitHub

Merge pull request #13648 from apache/fix/aggregate

Fix/aggregate
......@@ -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);
......
......@@ -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/**')]
}
};
};
......@@ -83,7 +83,7 @@ export interface ExternalDataTransformResultItem {
*/
dimensions?: DimensionDefinitionLoose[];
}
interface ExternalDimensionDefinition extends Partial<DimensionDefinition> {
export interface ExternalDimensionDefinition extends Partial<DimensionDefinition> {
// Mandatory
index: DimensionIndex;
}
......
......@@ -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);
......
......@@ -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'
......
<!DOCTYPE html>
<!--
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.
-->
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1" />
<script src="lib/esl.js"></script>
<script src="lib/config.js"></script>
<script src="lib/jquery.min.js"></script>
<script src="lib/facePrint.js"></script>
<script src="lib/testHelper.js"></script>
<!-- <script src="ut/lib/canteen.js"></script> -->
<link rel="stylesheet" href="lib/reset.css" />
</head>
<body>
<style>
</style>
<div id="main_sum"></div>
<div id="main_boxplot"></div>
<div id="main_average"></div>
<script>
require(['echarts', 'myTransform'], function (echarts, myTransform) {
$.get('data/life-expectancy-table.json', function (_rawData) {
echarts.registerTransform(myTransform.aggregate);
option = {
dataset: [{
id: 'raw',
source: _rawData
}, {
id: 'income_aggregate',
fromDatasetId: 'raw',
transform: [{
type: 'filter',
config: {
dimension: 'Year', gte: 1950
}
}, {
type: 'myTransform:aggregate',
config: {
resultDimensions: [
{ from: 'Income', method: 'sum' },
{ from: 'Country' }
],
groupBy: 'Country'
}
}]
}],
title: {
text: 'Income sum since 1950'
},
tooltip: {
trigger: 'axis'
},
xAxis: {
name: 'Income',
nameLocation: 'middle',
nameGap: 30
},
yAxis: {
type: 'category'
},
series: {
type: 'bar',
datasetId: 'income_aggregate',
label: {
show: true,
position: 'right'
},
encode: {
x: 'Income',
y: 'Country',
itemName: ['Country'],
tooltip: ['Income']
}
}
};
var chart = testHelper.create(echarts, 'main_sum', {
title: [
'Aggregate sum'
],
height: 400,
option: option
});
});
});
</script>
<script>
require(['echarts', 'myTransform'], function (echarts, myTransform) {
$.get('data/life-expectancy-table.json', function (_rawData) {
echarts.registerTransform(myTransform.aggregate);
option = {
dataset: [{
id: 'raw',
source: _rawData
}, {
id: 'income_aggregate',
fromDatasetId: 'raw',
transform: [{
type: 'filter',
config: {
and: [{
dimension: 'Year', gte: 1950
// }, {
// dimension: 'Country', eq: 'Japan'
}]
}
}, {
type: 'myTransform:aggregate',
config: {
resultDimensions: [
{ name: 'min', from: 'Income', method: 'min' },
{ name: 'Q1', from: 'Income', method: 'Q1' },
{ name: 'Q2', from: 'Income', method: 'Q2' },
{ name: 'Q3', from: 'Income', method: 'Q3' },
{ name: 'max', from: 'Income', method: 'max' },
{ name: 'Country', from: 'Country' }
],
groupBy: 'Country'
}
}]
}],
title: {
text: 'Income sum since 1950'
},
tooltip: {
trigger: 'axis'
},
xAxis: {
name: 'Income',
nameLocation: 'middle',
nameGap: 30
},
yAxis: {
type: 'category'
},
series: {
type: 'boxplot',
datasetId: 'income_aggregate',
label: {
show: true,
position: 'right'
},
encode: {
x: ['min', 'Q1', 'Q2', 'Q3', 'max'],
y: 'Country',
itemName: ['Country'],
tooltip: ['min', 'Q1', 'Q2', 'Q3', 'max']
}
}
};
var chart = testHelper.create(echarts, 'main_boxplot', {
title: [
'Aggregate Q1 Q2 Q3 min max'
],
height: 600,
option: option
});
});
});
</script>
<script>
require(['echarts', 'myTransform'], function (echarts, myTransform) {
$.get('data/life-expectancy-table.json', function (_rawData) {
echarts.registerTransform(myTransform.aggregate);
option = {
dataset: [{
id: 'raw',
source: _rawData
}, {
id: 'income_aggregate',
fromDatasetId: 'raw',
transform: [{
type: 'filter',
print: true,
config: {
and: [{
dimension: 'Year', gte: 1950
// }, {
// dimension: 'Country', eq: 'Japan'
}]
}
}, {
type: 'myTransform:aggregate',
config: {
resultDimensions: [
{ from: 'Income', method: 'average' },
{ from: 'Country' }
],
groupBy: 'Country'
}
}]
}],
title: {
text: 'Income sum since 1950'
},
tooltip: {
trigger: 'axis'
},
xAxis: {
name: 'Income',
nameLocation: 'middle',
nameGap: 30
},
yAxis: {
type: 'category'
},
series: {
type: 'bar',
datasetId: 'income_aggregate',
label: {
show: true,
position: 'right'
},
encode: {
x: 'Income',
y: 'Country',
itemName: ['Country'],
tooltip: ['Income']
}
}
};
var chart = testHelper.create(echarts, 'main_average', {
title: [
'Aggregate average'
],
height: 600,
option: option
});
});
});
</script>
</body>
</html>
......@@ -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',
......
/*
* 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);
(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
{"version":3,"file":"myTransform.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;"}
\ No newline at end of file
/*
* 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);
# 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'
/*
* 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<CollectionResultLine>;
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<LINE> = (
upstream: ExternalSource,
dataIndex: number,
dimInfoList: ResultDimInfoInternal[],
groupByDimInfo?: ExternalDimensionDefinition,
groupByVal?: OptionDataValue
) => LINE;
type UpdateInTravel<LINE> = (
upstream: ExternalSource,
dataIndex: number,
targetLine: LINE,
dimInfoList: ResultDimInfoInternal[],
groupByDimInfo?: ExternalDimensionDefinition,
groupByVal?: OptionDataValue
) => void;
export const transform: ExternalDataTransform<AggregateTransformOption> = {
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<CollectionResultLine>;
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<LINE> {
mapByGroup: { [groupVal: string]: LINE };
outList: LINE[];
}
function travel<LINE>(
groupByDimInfo: ExternalDimensionDefinition,
upstream: ExternalSource,
resultDimInfoList: ResultDimInfoInternal[],
doCreate: CreateInTravel<LINE>,
doUpdate: UpdateInTravel<LINE>
): TravelResult<LINE> {
const outList: TravelResult<LINE>['outList'] = [];
let mapByGroup: TravelResult<LINE>['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<CollectionResultLine> = (
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<CollectionResultLine> = (
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<FinalResultLine> = (
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<FinalResultLine> = (
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);
}
/*
* 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
};
}
};
export { transform as id } from './id';
export { transform as aggregate } from './aggregate';
......@@ -22,6 +22,7 @@
},
"include": [
"src/**/*.ts",
"extension-src/**/*.ts"
"extension-src/**/*.ts",
"test/lib/myTransform/src/**/*.ts"
]
}
\ No newline at end of file
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册