From ff53c0d3a9bd25eff9496571b6acbea2d9f7e81e Mon Sep 17 00:00:00 2001 From: pissang Date: Wed, 16 Mar 2022 14:03:01 +0800 Subject: [PATCH 01/11] chore: fix map data decode util --- test/data/map/decode.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/data/map/decode.js b/test/data/map/decode.js index 6f61e70428..6e7bbc1903 100644 --- a/test/data/map/decode.js +++ b/test/data/map/decode.js @@ -18,7 +18,7 @@ */ function decode(json) { - if (json.UTF8Encoding) { + if (!json.UTF8Encoding) { return; } var jsonCompressed = json; @@ -48,7 +48,7 @@ function decode(json) { decodeRings(coordinates, encodeOffsets, encodeScale); break; case 'MultiPolygon': - zrUtil.each(coordinates, function (rings, idx) { + coordinates.forEach(function (rings, idx) { return decodeRings(rings, encodeOffsets[idx], encodeScale) }); } From f6a7c3aee0fa7ed717a06da8e94492048eae957c Mon Sep 17 00:00:00 2001 From: pissang Date: Fri, 18 Mar 2022 12:15:00 +0800 Subject: [PATCH 02/11] feat(transform): introduce aggregate and id transform --- src/component/transform/aggregateTransform.ts | 595 ++++++++++++++++++ src/component/transform/idTransform.ts | 95 +++ src/component/transform/install.ts | 4 + test/custom-shape-morphing2.html | 13 +- test/custom-shape-morphing3.html | 13 +- test/data-transform-aggregate.html | 26 +- test/lib/config.js | 1 - test/lib/ecSimpleTransform.js | 362 ----------- test/universalTransition2.html | 10 +- 9 files changed, 718 insertions(+), 401 deletions(-) create mode 100644 src/component/transform/aggregateTransform.ts create mode 100644 src/component/transform/idTransform.ts delete mode 100644 test/lib/ecSimpleTransform.js diff --git a/src/component/transform/aggregateTransform.ts b/src/component/transform/aggregateTransform.ts new file mode 100644 index 0000000000..5bbf13169d --- /dev/null +++ b/src/component/transform/aggregateTransform.ts @@ -0,0 +1,595 @@ +/* +* 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 { assert, hasOwn } from 'zrender/src/core/util'; +import { + DataTransformOption, + ExternalDataTransform, + ExternalDimensionDefinition, + ExternalSource +} from '../../data/helper/transform'; +import { asc, quantile } from '../../util/number'; +import { DimensionLoose, DimensionName, OptionDataValue } from '../../util/types'; + +/** + * @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: 'ecSimpleTransform:aggregate', + * config: { + * output: [ + * // 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'], + * // ... + * // ] + * }] + * ``` + */ + +export interface AggregateTransformOption extends DataTransformOption { + type: 'echarts:aggregate'; + config: { + // Mandatory + output: { + // 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 aggregateTransform: ExternalDataTransform = { + + type: 'echarts: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 + ); + } + + 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); + + 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 + ); + + const dimensions = []; + for (let i = 0; i < finalResultDimInfoList.length; i++) { + dimensions.push(finalResultDimInfoList[i].name); + } + + return { + dimensions: dimensions, + data: finalResult.outList + }; + } +}; + +function prepareDimensions( + config: AggregateTransformOption['config'], + upstream: ExternalSource, + groupByDimInfo: ExternalDimensionDefinition +): { + finalResultDimInfoList: ResultDimInfoInternal[]; + collectionDimInfoList: ResultDimInfoInternal[]; +} { + const outputConfig = config.output; + const finalResultDimInfoList: ResultDimInfoInternal[] = []; + const collectionDimInfoList: ResultDimInfoInternal[] = []; + let gIndexInLine = 0; + + for (let i = 0; i < outputConfig.length; i++) { + const resultDimInfoConfig = outputConfig[i]; + + 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, + `Dimension ${dimInfoInUpstream.name} is the "groupBy" dimension, must not have any "method".` + ); + } + + const method = normalizeMethod(rawMethod); + + if (__DEV__) { + 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; +} + +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; + }, + '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); + } +}; + +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); +} \ No newline at end of file diff --git a/src/component/transform/idTransform.ts b/src/component/transform/idTransform.ts new file mode 100644 index 0000000000..8c016286c3 --- /dev/null +++ b/src/component/transform/idTransform.ts @@ -0,0 +1,95 @@ +/* +* 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 } from '../../data/helper/transform'; +import { + DimensionDefinitionLoose, + DimensionIndex, + DimensionName, + OptionSourceDataArrayRows +} from '../../util/types'; + + +/** + * @usage + * + * ```js + * dataset: [{ + * source: [ + * ['aa', 'bb', 'cc', 'tag'], + * [12, 0.33, 5200, 'AA'], + * [21, 0.65, 8100, 'AA'], + * ... + * ] + * }, { + * transform: { + * type: 'ecSimpleTransform: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: 'echarts: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 idTransform: ExternalDataTransform = { + + type: 'echarts: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 + }; + } +}; \ No newline at end of file diff --git a/src/component/transform/install.ts b/src/component/transform/install.ts index 208f6e0add..01c1074431 100644 --- a/src/component/transform/install.ts +++ b/src/component/transform/install.ts @@ -20,8 +20,12 @@ import { EChartsExtensionInstallRegisters } from '../../extension'; import {filterTransform} from './filterTransform'; import {sortTransform} from './sortTransform'; +import {aggregateTransform} from './aggregateTransform'; +import { idTransform } from './idTransform'; export function install(registers: EChartsExtensionInstallRegisters) { registers.registerTransform(filterTransform); registers.registerTransform(sortTransform); + registers.registerTransform(aggregateTransform); + registers.registerTransform(idTransform); } \ No newline at end of file diff --git a/test/custom-shape-morphing2.html b/test/custom-shape-morphing2.html index 8b5d2bcdff..91e889046d 100644 --- a/test/custom-shape-morphing2.html +++ b/test/custom-shape-morphing2.html @@ -75,10 +75,9 @@ + + + + + + + + + + + + +
+ + + + + + + + + + + + From 7f14e94dc08eaf898d50129d78376c8a199f5a8b Mon Sep 17 00:00:00 2001 From: pissang Date: Thu, 14 Apr 2022 12:38:34 +0800 Subject: [PATCH 10/11] fix(dataset): fix using transform in dataset may cause dead loop. --- src/data/helper/sourceHelper.ts | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/data/helper/sourceHelper.ts b/src/data/helper/sourceHelper.ts index 5408f6adcf..449ff1b5e6 100644 --- a/src/data/helper/sourceHelper.ts +++ b/src/data/helper/sourceHelper.ts @@ -26,7 +26,8 @@ import { isString, isObject, isTypedArray, - HashMap + HashMap, + filter } from 'zrender/src/core/util'; import { Source } from '../Source'; @@ -322,7 +323,7 @@ export function queryDatasetUpstreamDatasetModels( return []; } - return queryReferringComponents( + return filter(queryReferringComponents( datasetModel.ecModel, 'dataset', { @@ -330,7 +331,7 @@ export function queryDatasetUpstreamDatasetModels( id: datasetModel.get('fromDatasetId', true) }, SINGLE_REFERRING - ).models as DatasetModel[]; + ).models as DatasetModel[], model => model !== datasetModel); } /** From 93ff2715f13bb3e3d72a5ae1b79fad63ff750661 Mon Sep 17 00:00:00 2001 From: pissang Date: Mon, 18 Apr 2022 16:07:07 +0800 Subject: [PATCH 11/11] test(dataset): fix unit test after changing from seriesLayoutBy to sourceLayout --- test/ut/spec/data/SeriesData.test.ts | 8 ++++---- test/ut/spec/data/createDimensions.test.ts | 4 ++-- test/ut/spec/data/dataTransform.test.ts | 16 ++++++++-------- 3 files changed, 14 insertions(+), 14 deletions(-) diff --git a/test/ut/spec/data/SeriesData.test.ts b/test/ut/spec/data/SeriesData.test.ts index 564772b310..d200b58304 100644 --- a/test/ut/spec/data/SeriesData.test.ts +++ b/test/ut/spec/data/SeriesData.test.ts @@ -201,7 +201,7 @@ describe('SeriesData', function () { it('should guess ordinal correctly', function () { const source = createSource([['A', 15], ['B', 25], ['C', 35]], { dimensions: ['A', 'B'], - seriesLayoutBy: null, + sourceLayout: null, sourceHeader: false }, SOURCE_FORMAT_ORIGINAL); expect(source.dimensionsDefine[0].type).toEqual('ordinal'); @@ -220,7 +220,7 @@ describe('SeriesData', function () { [['A', 15, 20, 'cat'], ['B', 25, 30, 'mouse'], ['C', 35, 40, 'dog']], { dimensions: null, - seriesLayoutBy: null, + sourceLayout: null, sourceHeader: false }, SOURCE_FORMAT_ARRAY_ROWS @@ -369,7 +369,7 @@ describe('SeriesData', function () { [ 80, 'c' ] ], { - seriesLayoutBy: 'column', + sourceLayout: 'column', sourceHeader: 0, dimensions: null }, @@ -514,7 +514,7 @@ describe('SeriesData', function () { [120, 'myId_better', null] // duplicated id. ], { - seriesLayoutBy: 'column', + sourceLayout: 'column', sourceHeader: 0, dimensions: null }, diff --git a/test/ut/spec/data/createDimensions.test.ts b/test/ut/spec/data/createDimensions.test.ts index 5bb809badd..095cf8e72a 100644 --- a/test/ut/spec/data/createDimensions.test.ts +++ b/test/ut/spec/data/createDimensions.test.ts @@ -21,7 +21,7 @@ import SeriesDimensionDefine from '@/src/data/SeriesDimensionDefine'; import createDimensions from '@/src/data/helper/createDimensions'; import { createSource } from '@/src/data/Source'; -import { SOURCE_FORMAT_ARRAY_ROWS, SERIES_LAYOUT_BY_COLUMN } from '@/src/util/types'; +import { SOURCE_FORMAT_ARRAY_ROWS, SOURCE_LAYOUT_BY_COLUMN } from '@/src/util/types'; type ParametersOfCreateDimensions = Parameters; @@ -71,7 +71,7 @@ describe('createDimensions', function () { [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] ], { - seriesLayoutBy: SERIES_LAYOUT_BY_COLUMN, + sourceLayout: SOURCE_LAYOUT_BY_COLUMN, sourceHeader: 0, dimensions: void 0 }, diff --git a/test/ut/spec/data/dataTransform.test.ts b/test/ut/spec/data/dataTransform.test.ts index 04cc20eae3..0cbfa4ac32 100644 --- a/test/ut/spec/data/dataTransform.test.ts +++ b/test/ut/spec/data/dataTransform.test.ts @@ -58,12 +58,12 @@ describe('dataTransform', function () { }; } - it('forbid_seriesLayoutBy_row', function () { + it('forbid_sourceLayout_row', function () { const option: EChartsOption = { dataset: [{ source: makeDatasetSourceDetection(), // This config should cause error thrown. - seriesLayoutBy: 'row' + sourceLayout: 'row' }, { transform: { type: 'filter', config: { dimension: 0, ne: '' } } }], @@ -77,14 +77,14 @@ describe('dataTransform', function () { }).toThrowError(/column/); }); - it('seriesLayoutBy_changed_no_transform', function () { + it('sourceLayout_changed_no_transform', function () { const option: EChartsOption = { dataset: { source: makeDatasetSourceDetection() }, xAxis: { type: 'category' }, yAxis: {}, - series: { type: 'bar', seriesLayoutBy: 'row' } + series: { type: 'bar', sourceLayout: 'row' } }; chart.setOption(option); @@ -103,14 +103,14 @@ describe('dataTransform', function () { { type: 'filter', config: { dimension: 'product', '!=': 'XXX' } } ] }].forEach((dataset1, itIdx) => { - it(`seriesLayoutBy_changed_transform_detection_${itIdx}`, function () { + it(`sourceLayout_changed_transform_detection_${itIdx}`, function () { const option: EChartsOption = { dataset: [{ source: makeDatasetSourceDetection() }, dataset1], xAxis: { type: 'category' }, yAxis: {}, - series: { type: 'bar', datasetIndex: 1, seriesLayoutBy: 'row' } + series: { type: 'bar', datasetIndex: 1, sourceLayout: 'row' } }; chart.setOption(option); @@ -134,7 +134,7 @@ describe('dataTransform', function () { { type: 'filter', config: { dimension: 0, '!=': 'XXX' } } ] }].forEach((dataset1, itIdx) => { - it(`seriesLayoutBy_changed_transform_non_detection_${itIdx}`, function () { + it(`sourceLayout_changed_transform_non_detection_${itIdx}`, function () { const sourceWrap = makeDatasetSourceNonDetectionByRow(); const option: EChartsOption = { dataset: [{ @@ -143,7 +143,7 @@ describe('dataTransform', function () { }, dataset1], xAxis: {}, yAxis: {}, - series: { type: 'bar', datasetIndex: 1, seriesLayoutBy: 'row' } + series: { type: 'bar', datasetIndex: 1, sourceLayout: 'row' } }; chart.setOption(option);