You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by sh...@apache.org on 2022/03/23 12:05:18 UTC

[echarts] 01/01: feat(transform): refact aggregate transform

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

shenyi pushed a commit to branch fix-bar-race
in repository https://gitbox.apache.org/repos/asf/echarts.git

commit 3628b51446b76be9512a46bd41fe2d36cd4cdc17
Author: pissang <bm...@gmail.com>
AuthorDate: Wed Mar 23 20:04:26 2022 +0800

    feat(transform): refact aggregate transform
---
 src/component/transform/aggregateTransform.ts | 627 +++++++++-----------------
 1 file changed, 222 insertions(+), 405 deletions(-)

diff --git a/src/component/transform/aggregateTransform.ts b/src/component/transform/aggregateTransform.ts
index 5bbf131..5b810fb 100644
--- a/src/component/transform/aggregateTransform.ts
+++ b/src/component/transform/aggregateTransform.ts
@@ -17,7 +17,8 @@
 * under the License.
 */
 
-import { assert, hasOwn } from 'zrender/src/core/util';
+import { validate } from '@babel/types';
+import { assert, each, keys, map, retrieve2 } from 'zrender/src/core/util';
 import {
     DataTransformOption,
     ExternalDataTransform,
@@ -27,6 +28,7 @@ import {
 import { asc, quantile } from '../../util/number';
 import { DimensionLoose, DimensionName, OptionDataValue } from '../../util/types';
 
+
 /**
  * @usage
  *
@@ -85,115 +87,74 @@ export interface AggregateTransformOption extends DataTransformOption {
     };
 }
 
-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 AggregateMethodInternal =
+    'SUM' | 'COUNT' | 'FIRST' | 'AVERAGE' | 'Q1' | 'Q2' | 'Q3' | 'MEDIAN' | 'MIN' | 'MAX' | 'VALUES';
 type AggregateMethodLoose =
     AggregateMethodInternal
     | 'sum' | 'count' | 'first' | 'average' | 'Q1' | 'Q2' | 'Q3' | 'median' | 'min' | 'max';
-type AggregateMethodInternal = keyof typeof METHOD_INTERNAL;
 
-
-class ResultDimInfoInternal {
+type AggregateResultValue = OptionDataValue | OptionDataValue[];
+type AggregateResultValueByGroup = Record<string, AggregateResultValue>;
+class AggregateResult {
 
     readonly method: AggregateMethodInternal;
     readonly name: DimensionName;
     readonly index: number;
-    readonly indexInUpstream: number;
+    /**
+     * Index in the upstream
+     */
+    readonly fromIndex: number;
 
-    readonly collectionInfoList = [] as {
-        method: AggregateMethodInternal;
-        indexInLine: number;
-    }[];
+    /**
+     * Deps applied before this method
+     */
+    readonly dep: AggregateResult;
 
-    // FIXME: refactor
-    readonly gatheredValuesByGroup: { [groupVal: string]: number[] } = {};
-    readonly gatheredValuesNoGroup = [] as number[];
-    readonly needGatherValues: boolean = false;
+    readonly groupBy: ExternalDimensionDefinition;
 
-    __collectionResult: TravelResult<CollectionResultLine>;
 
-    private _collectionInfoMap = {} as {
-        // number is the index of `list`
-        [method in AggregateMethodInternal]: number
-    };
+    values: AggregateResultValue | AggregateResultValueByGroup;
 
     constructor(
         index: number,
         indexInUpstream: number,
         method: AggregateMethodInternal,
         name: DimensionName,
-        needGatherValues: boolean
+        groupBy: ExternalDimensionDefinition,
+        dep?: AggregateResult
     ) {
         this.method = method;
         this.name = name;
         this.index = index;
-        this.indexInUpstream = indexInUpstream;
-        this.needGatherValues = needGatherValues;
-    }
+        this.fromIndex = indexInUpstream;
+        this.dep = dep;
+        this.groupBy = groupBy;
 
-    addCollectionInfo(item: ResultDimInfoInternal['collectionInfoList'][number]) {
-        this._collectionInfoMap[item.method] = this.collectionInfoList.length;
-        this.collectionInfoList.push(item);
-    }
+        let valuesByGroup: AggregateResultValueByGroup = {};
+        if (groupBy) {
+            valuesByGroup = this.values = {};
+        }
 
-    getCollectionInfo(method: AggregateMethodInternal) {
-        return this.collectionInfoList[this._collectionInfoMap[method]];
-    }
+        this.set = groupBy
+            ? (groupByVal, value) => valuesByGroup[groupByVal as string] = value
+            : (groupByVal, value) => this.values = value;
+
+        this.get = groupBy
+            ? (groupByVal) => valuesByGroup[groupByVal as string]
+            : () => this.values as AggregateResultValue;
 
-    // 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;
+    set: (groupByVal: OptionDataValue, value: AggregateResultValue) => void;
+    get: (groupByVal: OptionDataValue) => AggregateResultValue;
+
+    // getValues(): AggregateResultValue[] {
+    //     const values = this._values;
+    //     return this.groupBy
+    //         ? map(keys(values as AggregateResultValueByGroup), key => (values as AggregateResultValueByGroup)[key])
+    //         : [values as AggregateResultValue];
+    // }
+}
 
 export const aggregateTransform: ExternalDataTransform<AggregateTransformOption> = {
 
@@ -203,393 +164,249 @@ export const aggregateTransform: ExternalDataTransform<AggregateTransformOption>
         const upstream = params.upstream;
         const config = params.config;
 
-        const groupByDimInfo = prepareGroupByDimInfo(config, upstream);
-        const { finalResultDimInfoList, collectionDimInfoList } = prepareDimensions(
-            config, upstream, groupByDimInfo
-        );
+        const { aggResults, groupByDim } = prepare(config, upstream);
 
-        // Collect
-        let collectionResult: TravelResult<CollectionResultLine>;
-        if (collectionDimInfoList.length) {
-            collectionResult = travel(
-                groupByDimInfo,
-                upstream,
-                collectionDimInfoList,
-                createCollectionResultLine,
-                updateCollectionResultLine
-            );
-        }
+        // Calculate
+        doAggregate(groupByDim, upstream, aggResults);
 
-        for (let i = 0; i < collectionDimInfoList.length; i++) {
-            const dimInfo = collectionDimInfoList[i];
-            dimInfo.__collectionResult = collectionResult;
-            // FIXME: just for Q1, Q2, Q3: need asc.
-            asc(dimInfo.gatheredValuesNoGroup);
+        // Convert to output row format.
+        let data: OptionDataValue[][];
 
-            const gatheredValuesByGroup = dimInfo.gatheredValuesByGroup;
-            for (const key in gatheredValuesByGroup) {
-                if (hasOwn(gatheredValuesByGroup, key)) {
-                    asc(gatheredValuesByGroup[key]);
-                }
-            }
-        }
-
-        // Calculate
-        const finalResult = travel(
-            groupByDimInfo,
-            upstream,
-            finalResultDimInfoList,
-            createFinalResultLine,
-            updateFinalResultLine
-        );
+        if (groupByDim && aggResults.length) {
+            const groupKeys = keys(aggResults[0].values as any);
+            data = map(groupKeys, key => []);
 
-        const dimensions = [];
-        for (let i = 0; i < finalResultDimInfoList.length; i++) {
-            dimensions.push(finalResultDimInfoList[i].name);
+            each(aggResults, (agg, idx0) => {
+                each(groupKeys, (key, idx1) => {
+                    data[idx1][idx0] = (agg.values as any)[key];
+                });
+            });
+        }
+        else {
+            data = [map(aggResults, dim => dim.values) as OptionDataValue[]];
         }
 
         return {
-            dimensions: dimensions,
-            data: finalResult.outList
+            dimensions: map(aggResults, dim => dim.name),
+            // TODO Not provide values to developers?
+            data
         };
     }
 };
 
-function prepareDimensions(
+function prepare(
     config: AggregateTransformOption['config'],
-    upstream: ExternalSource,
-    groupByDimInfo: ExternalDimensionDefinition
+    upstream: ExternalSource
 ): {
-    finalResultDimInfoList: ResultDimInfoInternal[];
-    collectionDimInfoList: ResultDimInfoInternal[];
+    aggResults: AggregateResult[];
+    groupByDim: ExternalDimensionDefinition
 } {
     const outputConfig = config.output;
-    const finalResultDimInfoList: ResultDimInfoInternal[] = [];
-    const collectionDimInfoList: ResultDimInfoInternal[] = [];
-    let gIndexInLine = 0;
+    const aggResults: AggregateResult[] = [];
+
+    const groupByConfig = config.groupBy;
+    let groupByDim: ExternalDimensionDefinition;
+    if (groupByConfig != null) {
+        groupByDim = upstream.getDimensionInfo(groupByConfig);
+        assert(groupByDim, 'Can not find dimension by `groupBy`: ' + groupByConfig);
+    }
 
-    for (let i = 0; i < outputConfig.length; i++) {
-        const resultDimInfoConfig = outputConfig[i];
+    each(outputConfig, resultDimInfoConfig => {
 
         const dimInfoInUpstream = upstream.getDimensionInfo(resultDimInfoConfig.from);
         if (__DEV__) {
             assert(dimInfoInUpstream, 'Can not find dimension by `from`: ' + resultDimInfoConfig.from);
-        }
-
-        const rawMethod = resultDimInfoConfig.method;
-
-        if (__DEV__) {
             assert(
-                groupByDimInfo.index !== dimInfoInUpstream.index || rawMethod == null,
+                groupByDim.index !== dimInfoInUpstream.index || resultDimInfoConfig.method == null,
                 `Dimension ${dimInfoInUpstream.name} is the "groupBy" dimension, must not have any "method".`
             );
         }
 
-        const method = normalizeMethod(rawMethod);
-
+        const methodName = (resultDimInfoConfig.method || '').toUpperCase() as AggregateMethodInternal
+            || 'FIRST';
+        const method = methods[methodName];
         if (__DEV__) {
-            assert(method, 'method is required');
+            assert(method, `Illegal method ${methodName}.`);
         }
 
-        const name = resultDimInfoConfig.name != null ? resultDimInfoConfig.name : dimInfoInUpstream.name;
+        const name = retrieve2(resultDimInfoConfig.name, dimInfoInUpstream.name);
+        const indexInUpStream = dimInfoInUpstream.index;
 
-        const finalResultDimInfo = new ResultDimInfoInternal(
-            finalResultDimInfoList.length,
-            dimInfoInUpstream.index,
-            method,
+        const finalResultDimInfo = new AggregateResult(
+            aggResults.length,
+            indexInUpStream,
+            methodName,
             name,
-            hasOwn(METHOD_NEEDS_GATHER_VALUES, method)
+            groupByDim,
+            method.dep && new AggregateResult(
+                -1, indexInUpStream, method.dep, name, groupByDim
+            )
         );
-        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);
-        }
-    }
+        aggResults.push(finalResultDimInfo);
+    });
 
-    return { collectionDimInfoList, finalResultDimInfoList };
+    return { aggResults, groupByDim };
 }
 
-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,
+function doAggregate(
+    groupByDim: 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 + '';
+    aggResultDims: AggregateResult[]
+) {
 
-            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);
-            }
+    function doCreate(aggResult: AggregateResult, dataIndex: number, groupByVal?: string) {
+        const method = methods[aggResult.method];
+        const val = upstream.retrieveValue(dataIndex, aggResult.fromIndex);
+        aggResult.set(
+            groupByVal,
+            isGroupByDimension(groupByDim, aggResult)
+                ? groupByVal
+                : method.init(val, aggResult, groupByVal)
+        );
+    };
+    function doUpdate(aggResult: AggregateResult, dataIndex: number, groupByVal?: string) {
+        if (isGroupByDimension(groupByDim, aggResult)) {
+            return;
         }
-    }
-    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);
+        const method = methods[aggResult.method];
+        const val = upstream.retrieveValue(dataIndex, aggResult.fromIndex);
+        if (method.add) {
+            aggResult.set(
+                groupByVal,
+                method.add(aggResult.get(groupByVal), val, aggResult, groupByVal)
+            );
         }
-    }
-
-    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;
-}
+    for (let i = 0; i < aggResultDims.length; i++) {
+        const aggResult = aggResultDims[i];
 
+        // TODO share dep result
+        if (aggResult.dep) {
+            doAggregate(groupByDim, upstream, [aggResult.dep]);
+        }
 
+        if (groupByDim) {
+            const groupCreated: Record<string, boolean> = {};
 
-type CollectionResultLine = number[];
+            for (let dataIndex = 0, len = upstream.count(); dataIndex < len; dataIndex++) {
+                let groupByVal = upstream.retrieveValue(dataIndex, groupByDim.index) as string;
 
-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;
-};
+                // PENDING: when value is null/undefined
+                if (groupByVal == null) {
+                    continue;
+                }
+                groupByVal += '';
 
-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
-            );
+                if (!groupCreated[groupByVal]) {
+                    doCreate(aggResult, dataIndex, groupByVal);
+                    groupCreated[groupByVal + ''] = true;
+                }
+                else {
+                    doUpdate(aggResult, dataIndex, groupByVal);
+                }
+            }
         }
-        // FIXME: refactor
-        if (dimInfo.needGatherValues) {
-            const val = upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream);
-            dimInfo.gatherValue(groupByDimInfo, groupByVal, val);
+        else {
+            doCreate(aggResult, 0);
+            for (let dataIndex = 1, len = upstream.count(); dataIndex < len; dataIndex++) {
+                doUpdate(aggResult, dataIndex);
+            }
         }
     }
-};
+}
 
 
+function isGroupByDimension(
+    groupByDim: ExternalDimensionDefinition,
+    targetDimInfo: AggregateResult
+): boolean {
+    return groupByDim && targetDimInfo.fromIndex === groupByDim.index;
+}
 
-type FinalResultLine = OptionDataValue[];
+type MethodInit = (
+    curr: OptionDataValue,
+    aggResult: AggregateResult,
+    groupByVal: OptionDataValue
+) => AggregateResultValue;
+type MethodAdd = (
+    prev: AggregateResultValue,
+    curr: OptionDataValue,
+    dimInfo: AggregateResult,
+    groupByVal: OptionDataValue
+) => AggregateResultValue;
 
-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;
+type Method = {
+    init: MethodInit
+    add?: MethodAdd
+    dep?: AggregateMethodInternal
 };
 
-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 quantileMethod(
+    percent: number,
+    aggResult: AggregateResult,
+    groupByVal: OptionDataValue
+) {
+    return quantile(asc(aggResult.get(groupByVal) as number[]), percent);
 }
-
-const lineCreator: {
-    [key in AggregateMethodInternal]: (
-        upstream: ExternalSource,
-        dataIndex: number,
-        dimInfo: ResultDimInfoInternal,
-        groupByDimInfo: ExternalDimensionDefinition,
-        groupByVal: OptionDataValue
-    ) => OptionDataValue
-} = {
-    'SUM'(upstream, dataIndex, dimInfo) {
-        return upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream);
-    },
-    'COUNT'() {
-        return 1;
+const Q2Method: Method = {
+    init(curr, aggResult, groupByVal) {
+        return quantileMethod(0.5, aggResult.dep, groupByVal);
     },
-    '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) {
-        const collectionResult = dimInfo.__collectionResult;
-        // FIXME: refactor, bad implementation.
-        const collectLine = groupByDimInfo
-            ? collectionResult.mapByGroup[groupByVal + '']
-            : 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);
-    }
+    dep: 'VALUES'
 };
 
-const lineUpdater: {
-    [key in AggregateMethodInternal]: (
-        val: OptionDataValue,
-        upstream: ExternalSource,
-        dataIndex: number,
-        dimInfo: ResultDimInfoInternal,
-        groupByDimInfo: ExternalDimensionDefinition,
-        groupByVal: OptionDataValue
-    ) => OptionDataValue
+function identity(val: any) {
+    return val;
+}
+
+const methods: {
+    [key in AggregateMethodInternal]: Method
 } = {
-    'SUM'(val, upstream, dataIndex, dimInfo) {
-        // FIXME: handle other types
-        return (val as number) + (upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream) as number);
+    VALUES: {
+        init: (curr) => [curr],
+        add(prev, curr) {
+            // FIXME: handle other types
+            (prev as any).push(curr);
+            return prev;
+        }
     },
-    'COUNT'(val) {
-        return (val as number) + 1;
+    SUM: {
+        init: identity,
+        add: (prev, curr) => prev as number + (curr as number)
     },
-    'FIRST'(val) {
-        return val;
+    COUNT: {
+        init: () => 1,
+        add: (prev) => (prev as number)++
     },
-    'MIN'(val, upstream, dataIndex, dimInfo) {
-        return Math.min(val as number, upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream) as number);
+    FIRST: {
+        init: identity,
+        add: identity
     },
-    'MAX'(val, upstream, dataIndex, dimInfo) {
-        return Math.max(val as number, upstream.retrieveValue(dataIndex, dimInfo.indexInUpstream) as number);
+    MIN: {
+        init: identity,
+        add: (prev, curr) => Math.min(prev as number, curr 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];
+    MAX: {
+        init: identity,
+        add: (prev, curr) => Math.max(prev as number, curr as number)
     },
-    'Q1'(val, upstream, dataIndex, dimInfo) {
-        return val;
+    AVERAGE: {
+        init: (curr, aggResult, groupByVal) => (curr as number) / (aggResult.dep.get(groupByVal) as number),
+        add: (prev, curr, aggResult, groupByVal) =>
+            (prev as number) + (curr as number) / (aggResult.dep.get(groupByVal) as number),
+        dep: 'COUNT'
     },
-    'Q2'(val, upstream, dataIndex, dimInfo) {
-        return val;
+    Q1: {
+        init: (curr, aggResult, groupByVal) => quantileMethod(0.25, aggResult.dep, groupByVal),
+        dep: 'VALUES'
     },
-    'Q3'(val, upstream, dataIndex, dimInfo) {
-        return val;
+    Q2: Q2Method,
+    // Alias
+    MEDIAN: Q2Method,
+    Q3: {
+        init: (curr, aggResult, groupByVal) => quantileMethod(0.75, aggResult.dep, groupByVal),
+        dep: 'VALUES'
     }
-};
-
-function lineCreatorForQ(
-    percent: number,
-    dimInfo: ResultDimInfoInternal,
-    groupByDimInfo: ExternalDimensionDefinition,
-    groupByVal: OptionDataValue
-) {
-    const gatheredValues = groupByDimInfo
-        ? dimInfo.gatheredValuesByGroup[groupByVal + '']
-        : dimInfo.gatheredValuesNoGroup;
-    return quantile(gatheredValues, percent);
-}
\ No newline at end of file
+};
\ No newline at end of file

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