diff --git a/package-lock.json b/package-lock.json index 37fc8d691d..ccd6a3e810 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1583,9 +1583,9 @@ } }, "@types/json-schema": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.6.tgz", - "integrity": "sha512-3c+yGKvVP5Y9TYBEibGNR+kLtijnj7mYrXRg+WpFb2X9xm04g/DXYkfg4hmzJQosc9snFNUPkbYIhu+KAm6jJw==", + "version": "7.0.9", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.9.tgz", + "integrity": "sha512-qcUXuemtEu+E5wZSJHNxUXeCZhAfXKQ41D+duX+VYPde7xyEVZci+/oXKJL13tnRs9lR2pr4fod59GT6/X1/yQ==", "dev": true }, "@types/node": { @@ -1649,44 +1649,192 @@ "dev": true }, "@typescript-eslint/eslint-plugin": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.13.0.tgz", - "integrity": "sha512-ygqDUm+BUPvrr0jrXqoteMqmIaZ/bixYOc3A4BRwzEPTZPi6E+n44rzNZWaB0YvtukgP+aoj0i/fyx7FkM2p1w==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.29.2.tgz", + "integrity": "sha512-x4EMgn4BTfVd9+Z+r+6rmWxoAzBaapt4QFqE+d8L8sUtYZYLDTK6VG/y/SMMWA5t1/BVU5Kf+20rX4PtWzUYZg==", "dev": true, "requires": { - "@typescript-eslint/experimental-utils": "4.13.0", - "@typescript-eslint/scope-manager": "4.13.0", - "debug": "^4.1.1", + "@typescript-eslint/experimental-utils": "4.29.2", + "@typescript-eslint/scope-manager": "4.29.2", + "debug": "^4.3.1", "functional-red-black-tree": "^1.0.1", - "lodash": "^4.17.15", - "regexpp": "^3.0.0", - "semver": "^7.3.2", - "tsutils": "^3.17.1" + "regexpp": "^3.1.0", + "semver": "^7.3.5", + "tsutils": "^3.21.0" }, "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.29.2.tgz", + "integrity": "sha512-mfHmvlQxmfkU8D55CkZO2sQOueTxLqGvzV+mG6S/6fIunDiD2ouwsAoiYCZYDDK73QCibYjIZmGhpvKwAB5BOA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.29.2", + "@typescript-eslint/visitor-keys": "4.29.2" + } + }, + "@typescript-eslint/types": { + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.29.2.tgz", + "integrity": "sha512-K6ApnEXId+WTGxqnda8z4LhNMa/pZmbTFkDxEBLQAbhLZL50DjeY0VIDCml/0Y3FlcbqXZrABqrcKxq+n0LwzQ==", + "dev": true + }, + "@typescript-eslint/visitor-keys": { + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.2.tgz", + "integrity": "sha512-bDgJLQ86oWHJoZ1ai4TZdgXzJxsea3Ee9u9wsTAvjChdj2WLcVsgWYAPeY7RQMn16tKrlQaBnpKv7KBfs4EQag==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.29.2", + "eslint-visitor-keys": "^2.0.0" + } + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, "semver": { - "version": "7.3.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.4.tgz", - "integrity": "sha512-tCfb2WLjqFAtXn4KEdxIhalnRtoKFN7nAwj0B3ZXCbQloV2tq5eDbcTmT68JJD3nRJq24/XgxtQKFIpQdtvmVw==", + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", "dev": true, "requires": { "lru-cache": "^6.0.0" } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } } } }, "@typescript-eslint/experimental-utils": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.13.0.tgz", - "integrity": "sha512-/ZsuWmqagOzNkx30VWYV3MNB/Re/CGv/7EzlqZo5RegBN8tMuPaBgNK6vPBCQA8tcYrbsrTdbx3ixMRRKEEGVw==", + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.29.2.tgz", + "integrity": "sha512-P6mn4pqObhftBBPAv4GQtEK7Yos1fz/MlpT7+YjH9fTxZcALbiiPKuSIfYP/j13CeOjfq8/fr9Thr2glM9ub7A==", "dev": true, "requires": { - "@types/json-schema": "^7.0.3", - "@typescript-eslint/scope-manager": "4.13.0", - "@typescript-eslint/types": "4.13.0", - "@typescript-eslint/typescript-estree": "4.13.0", - "eslint-scope": "^5.0.0", - "eslint-utils": "^2.0.0" + "@types/json-schema": "^7.0.7", + "@typescript-eslint/scope-manager": "4.29.2", + "@typescript-eslint/types": "4.29.2", + "@typescript-eslint/typescript-estree": "4.29.2", + "eslint-scope": "^5.1.1", + "eslint-utils": "^3.0.0" + }, + "dependencies": { + "@typescript-eslint/scope-manager": { + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.29.2.tgz", + "integrity": "sha512-mfHmvlQxmfkU8D55CkZO2sQOueTxLqGvzV+mG6S/6fIunDiD2ouwsAoiYCZYDDK73QCibYjIZmGhpvKwAB5BOA==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.29.2", + "@typescript-eslint/visitor-keys": "4.29.2" + } + }, + "@typescript-eslint/types": { + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.29.2.tgz", + "integrity": "sha512-K6ApnEXId+WTGxqnda8z4LhNMa/pZmbTFkDxEBLQAbhLZL50DjeY0VIDCml/0Y3FlcbqXZrABqrcKxq+n0LwzQ==", + "dev": true + }, + "@typescript-eslint/typescript-estree": { + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.29.2.tgz", + "integrity": "sha512-TJ0/hEnYxapYn9SGn3dCnETO0r+MjaxtlWZ2xU+EvytF0g4CqTpZL48SqSNn2hXsPolnewF30pdzR9a5Lj3DNg==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.29.2", + "@typescript-eslint/visitor-keys": "4.29.2", + "debug": "^4.3.1", + "globby": "^11.0.3", + "is-glob": "^4.0.1", + "semver": "^7.3.5", + "tsutils": "^3.21.0" + } + }, + "@typescript-eslint/visitor-keys": { + "version": "4.29.2", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.29.2.tgz", + "integrity": "sha512-bDgJLQ86oWHJoZ1ai4TZdgXzJxsea3Ee9u9wsTAvjChdj2WLcVsgWYAPeY7RQMn16tKrlQaBnpKv7KBfs4EQag==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.29.2", + "eslint-visitor-keys": "^2.0.0" + } + }, + "debug": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", + "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + } + }, + "globby": { + "version": "11.0.4", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.4.tgz", + "integrity": "sha512-9O4MVG9ioZJ08ffbcyVYyLOJLk5JQ688pJ4eMGLpdWLHq/Wr1D9BlriLQyL0E+jbkuePVZXYFj47QM/v093wHg==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tsutils": { + "version": "3.21.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz", + "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + } } }, "@typescript-eslint/parser": { @@ -10974,9 +11122,9 @@ } }, "zrender": { - "version": "npm:zrender-nightly@5.1.2-dev.20210701", - "resolved": "https://registry.npmjs.org/zrender-nightly/-/zrender-nightly-5.1.2-dev.20210701.tgz", - "integrity": "sha512-olAJui56YNuM0CmdVpdEPGVjyDGRkYhhGAC4xjFl3DeSw+RO0/vBaJLQj5xPXKHb5aSFYwVlxs58svpz/bnHng==", + "version": "npm:zrender-nightly@5.1.2-dev.20210819", + "resolved": "https://registry.npmjs.org/zrender-nightly/-/zrender-nightly-5.1.2-dev.20210819.tgz", + "integrity": "sha512-iYEp8gxfpvRvEmr6arIpMMsidWhKL9PLWuR6nD8GkR40WV8YDT8XfQgK2kn4EGB+UEHvDJITt+Ps1SupgukmWg==", "requires": { "tslib": "2.3.0" } diff --git a/package.json b/package.json index 284468e9bf..12b24afc3f 100644 --- a/package.json +++ b/package.json @@ -64,7 +64,7 @@ }, "dependencies": { "tslib": "2.3.0", - "zrender": "npm:zrender-nightly@^5.1.2-dev.20210701" + "zrender": "npm:zrender-nightly@^5.1.2-dev.20210720" }, "devDependencies": { "@babel/code-frame": "7.10.4", @@ -78,7 +78,7 @@ "@rollup/plugin-node-resolve": "^11.0.0", "@rollup/plugin-replace": "^2.3.4", "@types/jest": "^26.0.14", - "@typescript-eslint/eslint-plugin": "^4.9.1", + "@typescript-eslint/eslint-plugin": "^4.29.2", "@typescript-eslint/parser": "^4.9.1", "chalk": "^3.0.0", "commander": "2.11.0", diff --git a/src/animation/universalTransition.ts b/src/animation/universalTransition.ts index 140afca2e6..9dc27e20c4 100644 --- a/src/animation/universalTransition.ts +++ b/src/animation/universalTransition.ts @@ -27,7 +27,7 @@ import Path from 'zrender/src/graphic/Path'; import { EChartsExtensionInstallRegisters } from '../extension'; import { initProps } from '../util/graphic'; import DataDiffer from '../data/DataDiffer'; -import List from '../data/List'; +import SeriesData from '../data/SeriesData'; import { Dictionary, DimensionLoose, OptionDataItemObject, UniversalTransitionOption } from '../util/types'; import { UpdateLifecycleParams, @@ -43,22 +43,22 @@ import Displayable from 'zrender/src/graphic/Displayable'; const DATA_COUNT_THRESHOLD = 1e4; -interface GlobalStore { oldSeries: SeriesModel[], oldData: List[] }; +interface GlobalStore { oldSeries: SeriesModel[], oldData: SeriesData[] }; const getUniversalTransitionGlobalStore = makeInner(); interface DiffItem { - data: List + data: SeriesData dim: DimensionLoose divide: UniversalTransitionOption['divideShape'] dataIndex: number } interface TransitionSeries { - data: List + data: SeriesData divide: UniversalTransitionOption['divideShape'] dim?: DimensionLoose } -function getGroupIdDimension(data: List) { +function getGroupIdDimension(data: SeriesData) { const dimensions = data.dimensions; for (let i = 0; i < dimensions.length; i++) { const dimInfo = data.getDimensionInfo(dimensions[i]); @@ -452,7 +452,7 @@ interface SeriesTransitionBatch { newSeries: TransitionSeries[] } -function getDivideShapeFromData(data: List) { +function getDivideShapeFromData(data: SeriesData) { if (data.hostModel) { return ((data.hostModel as SeriesModel) .getModel('universalTransition') as Model) @@ -466,12 +466,12 @@ function findTransitionSeriesBatches( ) { const updateBatches = createHashMap(); - const oldDataMap = createHashMap(); + const oldDataMap = createHashMap(); // Map that only store key in array seriesKey. // Which is used to query the old data when transition from one to multiple series. const oldDataMapForSplit = createHashMap<{ key: string, - data: List + data: SeriesData }>(); each(globalStore.oldSeries, (series, idx) => { @@ -667,7 +667,7 @@ export function installUniversalTransition(registers: EChartsExtensionInstallReg // Save all series of current update. Not only the updated one. const allSeries = ecModel.getSeries(); const savedSeries: SeriesModel[] = globalStore.oldSeries = []; - const savedData: List[] = globalStore.oldData = []; + const savedData: SeriesData[] = globalStore.oldData = []; for (let i = 0; i < allSeries.length; i++) { const data = allSeries[i].getData(); // Only save the data that can have transition. diff --git a/src/chart/bar/BarSeries.ts b/src/chart/bar/BarSeries.ts index e0dbd4532c..8c97a8b616 100644 --- a/src/chart/bar/BarSeries.ts +++ b/src/chart/bar/BarSeries.ts @@ -29,10 +29,10 @@ import { SeriesEncodeOptionMixin } from '../../util/types'; import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; -import createListFromArray from '../helper/createListFromArray'; +import createSeriesData from '../helper/createSeriesData'; import type Polar from '../../coord/polar/Polar'; import { inheritDefaultOption } from '../../util/component'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { BrushCommonSelectorsForSeries } from '../../component/brush/selector'; export type PolarBarLabelPosition = SeriesLabelOption['position'] @@ -89,8 +89,8 @@ class BarSeriesModel extends BaseBarSeriesModel { coordinateSystem: Cartesian2D | Polar; - getInitialData(): List { - return createListFromArray(this.getSource(), this, { + getInitialData(): SeriesData { + return createSeriesData(null, this, { useEncodeDefaulter: true, createInvertedIndices: !!this.get('realtimeSort', true) || null }); @@ -119,7 +119,7 @@ class BarSeriesModel extends BaseBarSeriesModel { return progressiveThreshold; } - brushSelector(dataIndex: number, data: List, selectors: BrushCommonSelectorsForSeries): boolean { + brushSelector(dataIndex: number, data: SeriesData, selectors: BrushCommonSelectorsForSeries): boolean { return selectors.rect(data.getItemLayout(dataIndex)); } diff --git a/src/chart/bar/BarView.ts b/src/chart/bar/BarView.ts index b948582bd7..efd6da4188 100644 --- a/src/chart/bar/BarView.ts +++ b/src/chart/bar/BarView.ts @@ -35,7 +35,7 @@ import {throttle} from '../../util/throttle'; import {createClipPath} from '../helper/createClipPathFromCoordSys'; import Sausage from '../../util/shape/sausage'; import ChartView from '../../view/Chart'; -import List, {DefaultDataVisual} from '../../data/List'; +import SeriesData, {DefaultDataVisual} from '../../data/SeriesData'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; import { @@ -88,7 +88,7 @@ type RealtimeSortConfig = { // Return a number, based on which the ordinal sorted. type OrderMapping = (dataIndex: number) => number; -function getClipArea(coord: CoordSysOfBar, data: List) { +function getClipArea(coord: CoordSysOfBar, data: SeriesData) { const coordSysClipArea = coord.getArea && coord.getArea(); if (isCoordinateSystemType(coord, 'cartesian2d')) { const baseAxis = coord.getBaseAxis(); @@ -115,7 +115,7 @@ class BarView extends ChartView { static type = 'bar' as const; type = BarView.type; - private _data: List; + private _data: SeriesData; private _isLargeDraw: boolean; @@ -467,7 +467,7 @@ class BarView extends ChartView { } private _dataSort( - data: List, + data: SeriesData, baseAxis: Axis2D, orderMapping: OrderMapping ): OrdinalSortInfo { @@ -498,7 +498,7 @@ class BarView extends ChartView { } private _isOrderChangedWithinSameData( - data: List, + data: SeriesData, orderMapping: OrderMapping, baseAxis: Axis2D ): boolean { @@ -543,7 +543,7 @@ class BarView extends ChartView { } private _updateSortWithinSameData( - data: List, + data: SeriesData, orderMapping: OrderMapping, baseAxis: Axis2D, api: ExtensionAPI @@ -566,7 +566,7 @@ class BarView extends ChartView { } private _dispatchInitSort( - data: List, + data: SeriesData, realtimeSortCfg: RealtimeSortConfig, api: ExtensionAPI ) { @@ -709,7 +709,7 @@ const clip: { interface ElementCreator { ( - seriesModel: BarSeriesModel, data: List, newIndex: number, + seriesModel: BarSeriesModel, data: SeriesData, newIndex: number, layout: RectLayout | SectorLayout, isHorizontalOrRadial: boolean, animationModel: BarSeriesModel, axisModel: CartesianAxisModel | AngleAxisModel | RadiusAxisModel, @@ -875,7 +875,7 @@ const isValidLayout: Record<'cartesian2d' | 'polar', (layout: RectLayout | Secto } as const; interface GetLayout { - (data: List, dataIndex: number, itemModel?: Model): RectLayout | SectorLayout + (data: SeriesData, dataIndex: number, itemModel?: Model): RectLayout | SectorLayout } const getLayout: { [key in 'cartesian2d' | 'polar']: GetLayout @@ -936,7 +936,7 @@ function createPolarPositionMapping(isRadial: boolean) function updateStyle( el: BarPossiblePath, - data: List, dataIndex: number, + data: SeriesData, dataIndex: number, itemModel: Model, layout: RectLayout | SectorLayout, seriesModel: BarSeriesModel, @@ -1170,7 +1170,7 @@ function largePathFindDataIndex(largePath: LargePath, x: number, y: number) { function setLargeStyle( el: LargePath, seriesModel: BarSeriesModel, - data: List + data: SeriesData ) { const globalStyle = data.getVisual('style'); @@ -1184,7 +1184,7 @@ function setLargeStyle( function setLargeBackgroundStyle( el: LargePath, backgroundModel: Model, - data: List + data: SeriesData ) { const borderColor = backgroundModel.get('borderColor') || backgroundModel.get('color'); const itemStyle = backgroundModel.getItemStyle(); diff --git a/src/chart/bar/BaseBarSeries.ts b/src/chart/bar/BaseBarSeries.ts index 36ea14a511..d4c3fa5d19 100644 --- a/src/chart/bar/BaseBarSeries.ts +++ b/src/chart/bar/BaseBarSeries.ts @@ -18,7 +18,7 @@ */ import SeriesModel from '../../model/Series'; -import createListFromArray from '../helper/createListFromArray'; +import createSeriesData from '../helper/createSeriesData'; import { SeriesOption, SeriesOnCartesianOptionMixin, @@ -28,7 +28,7 @@ import { } from '../../util/types'; import GlobalModel from '../../model/Global'; import Cartesian2D from '../../coord/cartesian/Cartesian2D'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; export interface BaseBarSeriesOption @@ -78,8 +78,8 @@ class BaseBarSeriesModel = BaseBarSeri static type = 'series.__base_bar__'; type = BaseBarSeriesModel.type; - getInitialData(option: Opts, ecModel: GlobalModel): List { - return createListFromArray(this.getSource(), this, {useEncodeDefaulter: true}); + getInitialData(option: Opts, ecModel: GlobalModel): SeriesData { + return createSeriesData(null, this, {useEncodeDefaulter: true}); } getMarkerPosition(value: ScaleDataValue[]) { diff --git a/src/chart/bar/PictorialBarView.ts b/src/chart/bar/PictorialBarView.ts index 565b44ed75..1a81fed829 100644 --- a/src/chart/bar/PictorialBarView.ts +++ b/src/chart/bar/PictorialBarView.ts @@ -27,7 +27,7 @@ import {parsePercent, isNumeric} from '../../util/number'; import ChartView from '../../view/Chart'; import PictorialBarSeriesModel, {PictorialBarDataItemOption} from './PictorialBarSeries'; import ExtensionAPI from '../../core/ExtensionAPI'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import GlobalModel from '../../model/Global'; import Model from '../../model/Model'; import { ColorString, AnimationOptionMixin, ECElement } from '../../util/types'; @@ -131,7 +131,7 @@ class PictorialBarView extends ChartView { static type = 'pictorialBar'; readonly type = PictorialBarView.type; - private _data: List; + private _data: SeriesData; render( seriesModel: PictorialBarSeriesModel, @@ -239,7 +239,7 @@ class PictorialBarView extends ChartView { // Set or calculate default value about symbol, and calculate layout info. function getSymbolMeta( - data: List, + data: SeriesData, dataIndex: number, itemModel: ItemModel, opt: CreateOpts @@ -338,7 +338,7 @@ function convertToCoordOnAxis(axis: Axis2D, value: number) { // Support ['100%', '100%'] function prepareSymbolSize( - data: List, + data: SeriesData, dataIndex: number, layout: RectLayout, symbolRepeat: PictorialBarDataItemOption['symbolRepeat'], @@ -726,7 +726,7 @@ function createOrUpdateClip( } } -function getItemModel(data: List, dataIndex: number) { +function getItemModel(data: SeriesData, dataIndex: number) { const itemModel = data.getItemModel(dataIndex) as ItemModel; itemModel.getAnimationDelayParams = getAnimationDelayParams; itemModel.isAnimationEnabled = isAnimationEnabled; @@ -746,7 +746,7 @@ function isAnimationEnabled(this: ItemModel) { return this.parentModel.isAnimationEnabled() && !!this.getShallow('animation'); } -function createBar(data: List, opt: CreateOpts, symbolMeta: SymbolMeta, isUpdate?: boolean) { +function createBar(data: SeriesData, opt: CreateOpts, symbolMeta: SymbolMeta, isUpdate?: boolean) { // bar is the main element for each data. const bar = new graphic.Group() as PictorialBarElement; // bundle is used for location and clip. @@ -798,7 +798,7 @@ function updateBar(bar: PictorialBarElement, opt: CreateOpts, symbolMeta: Symbol } function removeBar( - data: List, dataIndex: number, animationModel: Model, bar: PictorialBarElement + data: SeriesData, dataIndex: number, animationModel: Model, bar: PictorialBarElement ) { // Not show text when animating const labelRect = bar.__pictorialBarRect; @@ -825,7 +825,7 @@ function removeBar( data.setItemGraphicEl(dataIndex, null); } -function getShapeStr(data: List, symbolMeta: SymbolMeta) { +function getShapeStr(data: SeriesData, symbolMeta: SymbolMeta) { return [ data.getItemVisual(symbolMeta.dataIndex, 'symbol') || 'none', !!symbolMeta.symbolRepeat, diff --git a/src/chart/boxplot/BoxplotView.ts b/src/chart/boxplot/BoxplotView.ts index 68ac23a0a4..748ff6ed20 100644 --- a/src/chart/boxplot/BoxplotView.ts +++ b/src/chart/boxplot/BoxplotView.ts @@ -25,7 +25,7 @@ import Path, { PathProps } from 'zrender/src/graphic/Path'; import BoxplotSeriesModel, { BoxplotDataItemOption } from './BoxplotSeries'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { BoxplotItemLayout } from './boxplotLayout'; import { saveOldStyle } from '../../animation/basicTrasition'; @@ -33,7 +33,7 @@ class BoxplotView extends ChartView { static type = 'boxplot'; type = BoxplotView.type; - private _data: List; + private _data: SeriesData; render(seriesModel: BoxplotSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) { const data = seriesModel.getData(); @@ -141,7 +141,7 @@ class BoxPath extends Path { function createNormalBox( itemLayout: BoxplotItemLayout, - data: List, + data: SeriesData, dataIndex: number, constDim: number, isInit?: boolean @@ -164,7 +164,7 @@ function createNormalBox( function updateNormalBoxData( itemLayout: BoxplotItemLayout, el: BoxPath, - data: List, + data: SeriesData, dataIndex: number, isInit?: boolean ) { diff --git a/src/chart/candlestick/CandlestickSeries.ts b/src/chart/candlestick/CandlestickSeries.ts index 68e8b8a128..d71cecd57d 100644 --- a/src/chart/candlestick/CandlestickSeries.ts +++ b/src/chart/candlestick/CandlestickSeries.ts @@ -33,7 +33,7 @@ import { SeriesEncodeOptionMixin, DefaultEmphasisFocus } from '../../util/types'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import Cartesian2D from '../../coord/cartesian/Cartesian2D'; import { BrushCommonSelectorsForSeries } from '../../component/brush/selector'; import { mixin } from 'zrender/src/core/util'; @@ -151,7 +151,7 @@ class CandlestickSeriesModel extends SeriesModel { return 'open'; } - brushSelector(dataIndex: number, data: List, selectors: BrushCommonSelectorsForSeries): boolean { + brushSelector(dataIndex: number, data: SeriesData, selectors: BrushCommonSelectorsForSeries): boolean { const itemLayout = data.getItemLayout(dataIndex); return itemLayout && selectors.rect(itemLayout.brushRect); } diff --git a/src/chart/candlestick/CandlestickView.ts b/src/chart/candlestick/CandlestickView.ts index 3a8e971d78..7e62360074 100644 --- a/src/chart/candlestick/CandlestickView.ts +++ b/src/chart/candlestick/CandlestickView.ts @@ -27,7 +27,7 @@ import CandlestickSeriesModel, { CandlestickDataItemOption } from './Candlestick import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; import { StageHandlerProgressParams } from '../../util/types'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import {CandlestickItemLayout} from './candlestickLayout'; import { CoordinateSystemClipArea } from '../../coord/CoordinateSystem'; import Model from '../../model/Model'; @@ -42,7 +42,7 @@ class CandlestickView extends ChartView { private _isLargeDraw: boolean; - private _data: List; + private _data: SeriesData; render(seriesModel: CandlestickSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) { // If there is clipPath created in large mode. Remove it. @@ -273,7 +273,7 @@ function isNormalBoxClipped(clipArea: CoordinateSystemClipArea, itemLayout: Cand return clipped; } -function setBoxCommon(el: NormalBoxPath, data: List, dataIndex: number, isSimpleBox?: boolean) { +function setBoxCommon(el: NormalBoxPath, data: SeriesData, dataIndex: number, isSimpleBox?: boolean) { const itemModel = data.getItemModel(dataIndex) as Model; el.useStyle(data.getItemVisual(dataIndex, 'style')); @@ -359,7 +359,7 @@ function createLarge(seriesModel: CandlestickSeriesModel, group: graphic.Group, } } -function setLargeStyle(sign: number, el: LargeBoxPath, seriesModel: CandlestickSeriesModel, data: List) { +function setLargeStyle(sign: number, el: LargeBoxPath, seriesModel: CandlestickSeriesModel, data: SeriesData) { // TODO put in visual? const borderColor = seriesModel.get(['itemStyle', sign > 0 ? 'borderColor' : 'borderColor0']) || seriesModel.get(['itemStyle', sign > 0 ? 'color' : 'color0']); diff --git a/src/chart/candlestick/candlestickLayout.ts b/src/chart/candlestick/candlestickLayout.ts index 615c684968..e81f80b7a1 100644 --- a/src/chart/candlestick/candlestickLayout.ts +++ b/src/chart/candlestick/candlestickLayout.ts @@ -22,11 +22,12 @@ import {subPixelOptimize} from '../../util/graphic'; import createRenderPlanner from '../helper/createRenderPlanner'; import {parsePercent} from '../../util/number'; -import {retrieve2} from 'zrender/src/core/util'; -import { StageHandler, StageHandlerProgressParams } from '../../util/types'; +import {map, retrieve2} from 'zrender/src/core/util'; +import { DimensionIndex, StageHandler, StageHandlerProgressParams } from '../../util/types'; import CandlestickSeriesModel from './CandlestickSeries'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { RectLike } from 'zrender/src/core/BoundingRect'; +import DataStore from '../../data/DataStore'; const LargeArr = typeof Float32Array !== 'undefined' ? Float32Array : Array; @@ -56,12 +57,12 @@ const candlestickLayout: StageHandler = { const cDimIdx = 0; const vDimIdx = 1; const coordDims = ['x', 'y']; - const cDim = data.mapDimension(coordDims[cDimIdx]); - const vDims = data.mapDimensionsAll(coordDims[vDimIdx]); - const openDim = vDims[0]; - const closeDim = vDims[1]; - const lowestDim = vDims[2]; - const highestDim = vDims[3]; + const cDimI = data.getDimensionIndex(data.mapDimension(coordDims[cDimIdx])); + const vDimsI = map(data.mapDimensionsAll(coordDims[vDimIdx]), data.getDimensionIndex, data); + const openDimI = vDimsI[0]; + const closeDimI = vDimsI[1]; + const lowestDimI = vDimsI[2]; + const highestDimI = vDimsI[3]; data.setLayout({ candleWidth: candleWidth, @@ -69,7 +70,7 @@ const candlestickLayout: StageHandler = { isSimpleBox: candleWidth <= 1.3 } as CandlestickLayoutMeta); - if (cDim == null || vDims.length < 4) { + if (cDimI < 0 || vDimsI.length < 4) { return; } @@ -78,15 +79,16 @@ const candlestickLayout: StageHandler = { ? largeProgress : normalProgress }; - function normalProgress(params: StageHandlerProgressParams, data: List) { + function normalProgress(params: StageHandlerProgressParams, data: SeriesData) { let dataIndex; + const store = data.getStore(); while ((dataIndex = params.next()) != null) { - const axisDimVal = data.get(cDim, dataIndex) as number; - const openVal = data.get(openDim, dataIndex) as number; - const closeVal = data.get(closeDim, dataIndex) as number; - const lowestVal = data.get(lowestDim, dataIndex) as number; - const highestVal = data.get(highestDim, dataIndex) as number; + const axisDimVal = store.get(cDimI, dataIndex) as number; + const openVal = store.get(openDimI, dataIndex) as number; + const closeVal = store.get(closeDimI, dataIndex) as number; + const lowestVal = store.get(lowestDimI, dataIndex) as number; + const highestVal = store.get(highestDimI, dataIndex) as number; const ocLow = Math.min(openVal, closeVal); const ocHigh = Math.max(openVal, closeVal); @@ -108,7 +110,7 @@ const candlestickLayout: StageHandler = { ); data.setItemLayout(dataIndex, { - sign: getSign(data, dataIndex, openVal, closeVal, closeDim), + sign: getSign(store, dataIndex, openVal, closeVal, closeDimI), initBaseline: openVal > closeVal ? ocHighPoint[vDimIdx] : ocLowPoint[vDimIdx], // open point. ends: ends, @@ -162,7 +164,7 @@ const candlestickLayout: StageHandler = { } } - function largeProgress(params: StageHandlerProgressParams, data: List) { + function largeProgress(params: StageHandlerProgressParams, data: SeriesData) { // Structure: [sign, x, yhigh, ylow, sign, x, yhigh, ylow, ...] const points = new LargeArr(params.count * 4); let offset = 0; @@ -170,13 +172,14 @@ const candlestickLayout: StageHandler = { const tmpIn: number[] = []; const tmpOut: number[] = []; let dataIndex; + const store = data.getStore(); while ((dataIndex = params.next()) != null) { - const axisDimVal = data.get(cDim, dataIndex) as number; - const openVal = data.get(openDim, dataIndex) as number; - const closeVal = data.get(closeDim, dataIndex) as number; - const lowestVal = data.get(lowestDim, dataIndex) as number; - const highestVal = data.get(highestDim, dataIndex) as number; + const axisDimVal = store.get(cDimI, dataIndex) as number; + const openVal = store.get(openDimI, dataIndex) as number; + const closeVal = store.get(closeDimI, dataIndex) as number; + const lowestVal = store.get(lowestDimI, dataIndex) as number; + const highestVal = store.get(highestDimI, dataIndex) as number; if (isNaN(axisDimVal) || isNaN(lowestVal) || isNaN(highestVal)) { points[offset++] = NaN; @@ -184,7 +187,7 @@ const candlestickLayout: StageHandler = { continue; } - points[offset++] = getSign(data, dataIndex, openVal, closeVal, closeDim); + points[offset++] = getSign(store, dataIndex, openVal, closeVal, closeDimI); tmpIn[cDimIdx] = axisDimVal; @@ -202,8 +205,10 @@ const candlestickLayout: StageHandler = { } }; -function getSign(data: List, dataIndex: number, openVal: number, closeVal: number, closeDim: string) { - let sign; +function getSign( + store: DataStore, dataIndex: number, openVal: number, closeVal: number, closeDimI: DimensionIndex +): -1 | 1 { + let sign: -1 | 1; if (openVal > closeVal) { sign = -1; } @@ -213,7 +218,7 @@ function getSign(data: List, dataIndex: number, openVal: number, closeVal: numbe else { sign = dataIndex > 0 // If close === open, compare with close of last record - ? (data.get(closeDim, dataIndex - 1) <= closeVal ? 1 : -1) + ? (store.get(closeDimI, dataIndex - 1) <= closeVal ? 1 : -1) // No record of previous, set to be positive : 1; } @@ -221,7 +226,7 @@ function getSign(data: List, dataIndex: number, openVal: number, closeVal: numbe return sign; } -function calculateCandleWidth(seriesModel: CandlestickSeriesModel, data: List) { +function calculateCandleWidth(seriesModel: CandlestickSeriesModel, data: SeriesData) { const baseAxis = seriesModel.getBaseAxis(); let extent; diff --git a/src/chart/custom/CustomSeries.ts b/src/chart/custom/CustomSeries.ts index b027f4143b..2b8bb3c9b2 100644 --- a/src/chart/custom/CustomSeries.ts +++ b/src/chart/custom/CustomSeries.ts @@ -44,9 +44,9 @@ import { ZRStyleProps } from '../../util/types'; import Element, { ElementProps } from 'zrender/src/Element'; -import List, { DefaultDataVisual } from '../../data/List'; +import SeriesData, { DefaultDataVisual } from '../../data/SeriesData'; import GlobalModel from '../../model/Global'; -import createListFromArray from '../helper/createListFromArray'; +import createSeriesData from '../helper/createSeriesData'; import { makeInner } from '../../util/model'; import { CoordinateSystem } from '../../coord/CoordinateSystem'; import SeriesModel from '../../model/Series'; @@ -413,8 +413,8 @@ export default class CustomSeriesModel extends SeriesModel { this.currentZ = this.get('z', true); } - getInitialData(option: CustomSeriesOption, ecModel: GlobalModel): List { - return createListFromArray(this.getSource(), this); + getInitialData(option: CustomSeriesOption, ecModel: GlobalModel): SeriesData { + return createSeriesData(null, this); } getDataParams(dataIndex: number, dataType?: SeriesDataType, el?: Element): CallbackDataParams & { diff --git a/src/chart/custom/CustomView.ts b/src/chart/custom/CustomView.ts index 44bc7ad613..1ba6d86b05 100644 --- a/src/chart/custom/CustomView.ts +++ b/src/chart/custom/CustomView.ts @@ -51,7 +51,7 @@ import prepareGeo from '../../coord/geo/prepareCustom'; import prepareSingleAxis from '../../coord/single/prepareCustom'; import preparePolar from '../../coord/polar/prepareCustom'; import prepareCalendar from '../../coord/calendar/prepareCustom'; -import List, { DefaultDataVisual } from '../../data/List'; +import SeriesData, { DefaultDataVisual } from '../../data/SeriesData'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; import Displayable, { DisplayableProps } from 'zrender/src/graphic/Displayable'; @@ -199,7 +199,7 @@ export default class CustomChartView extends ChartView { static type = 'custom'; readonly type = CustomChartView.type; - private _data: List; + private _data: SeriesData; render( customSeries: CustomSeriesModel, @@ -822,7 +822,7 @@ function updateZForEachState( function makeRenderItem( customSeries: CustomSeriesModel, - data: List, + data: SeriesData, ecModel: GlobalModel, api: ExtensionAPI ) { @@ -943,7 +943,7 @@ function makeRenderItem( */ function value(dim?: DimensionLoose, dataIndexInside?: number): ParsedValue { dataIndexInside == null && (dataIndexInside = currDataIndexInside); - return data.get(data.getDimension(dim || 0), dataIndexInside); + return data.getStore().get(data.getDimensionIndex(dim || 0), dataIndexInside); } /** @@ -953,9 +953,11 @@ function makeRenderItem( */ function ordinalRawValue(dim?: DimensionLoose, dataIndexInside?: number): ParsedValue | OrdinalRawValue { dataIndexInside == null && (dataIndexInside = currDataIndexInside); - const dimInfo = data.getDimensionInfo(dim || 0); + dim = dim || 0; + const dimInfo = data.getDimensionInfo(dim); if (!dimInfo) { - return; + const dimIndex = data.getDimensionIndex(dim); + return dimIndex >= 0 ? data.getStore().get(dimIndex, dataIndexInside) : undefined; } const val = data.get(dimInfo.name, dataIndexInside); const ordinalMeta = dimInfo && dimInfo.ordinalMeta; @@ -1129,14 +1131,14 @@ function makeRenderItem( } } -function wrapEncodeDef(data: List): WrapEncodeDefRet { +function wrapEncodeDef(data: SeriesData): WrapEncodeDefRet { const encodeDef = {} as WrapEncodeDefRet; - each(data.dimensions, function (dimName, dataDimIndex) { + each(data.dimensions, function (dimName) { const dimInfo = data.getDimensionInfo(dimName); if (!dimInfo.isExtraCoord) { const coordDim = dimInfo.coordDim; const dataDims = encodeDef[coordDim] = encodeDef[coordDim] || []; - dataDims[dimInfo.coordDimIndex] = dataDimIndex; + dataDims[dimInfo.coordDimIndex] = data.getDimensionIndex(dimName); } }); return encodeDef; @@ -1149,7 +1151,7 @@ function createOrUpdateItem( elOption: CustomElementOption, seriesModel: CustomSeriesModel, group: ViewRootGroup, - data: List + data: SeriesData ): Element { // [Rule] // If `renderItem` returns `null`/`undefined`/`false`, remove the previous el if existing. diff --git a/src/chart/effectScatter/EffectScatterSeries.ts b/src/chart/effectScatter/EffectScatterSeries.ts index 1c8fc1b101..9632e400bf 100644 --- a/src/chart/effectScatter/EffectScatterSeries.ts +++ b/src/chart/effectScatter/EffectScatterSeries.ts @@ -17,7 +17,7 @@ * under the License. */ -import createListFromArray from '../helper/createListFromArray'; +import createSeriesData from '../helper/createSeriesData'; import SeriesModel from '../../model/Series'; import { SeriesOption, @@ -35,7 +35,7 @@ import { CallbackDataParams } from '../../util/types'; import GlobalModel from '../../model/Global'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import type { SymbolDrawItemModelOption } from '../helper/SymbolDraw'; import { BrushCommonSelectorsForSeries } from '../../component/brush/selector'; @@ -88,11 +88,11 @@ class EffectScatterSeriesModel extends SeriesModel { hasSymbolVisual = true; - getInitialData(option: EffectScatterSeriesOption, ecModel: GlobalModel): List { - return createListFromArray(this.getSource(), this, {useEncodeDefaulter: true}); + getInitialData(option: EffectScatterSeriesOption, ecModel: GlobalModel): SeriesData { + return createSeriesData(null, this, {useEncodeDefaulter: true}); } - brushSelector(dataIndex: number, data: List, selectors: BrushCommonSelectorsForSeries): boolean { + brushSelector(dataIndex: number, data: SeriesData, selectors: BrushCommonSelectorsForSeries): boolean { return selectors.point(data.getItemLayout(dataIndex)); } diff --git a/src/chart/funnel/FunnelSeries.ts b/src/chart/funnel/FunnelSeries.ts index 5d7c4ca1a6..6abe848f0a 100644 --- a/src/chart/funnel/FunnelSeries.ts +++ b/src/chart/funnel/FunnelSeries.ts @@ -18,7 +18,7 @@ */ import * as zrUtil from 'zrender/src/core/util'; -import createListSimply from '../helper/createListSimply'; +import createSeriesDataSimply from '../helper/createSeriesDataSimply'; import {defaultEmphasis} from '../../util/model'; import {makeSeriesEncodeForNameBased} from '../../data/helper/sourceHelper'; import LegendVisualProvider from '../../visual/LegendVisualProvider'; @@ -39,7 +39,7 @@ import { SeriesEncodeOptionMixin } from '../../util/types'; import GlobalModel from '../../model/Global'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; type FunnelLabelOption = Omit & { position?: LabelOption['position'] @@ -102,8 +102,8 @@ class FunnelSeriesModel extends SeriesModel { this._defaultLabelLine(option); } - getInitialData(this: FunnelSeriesModel, option: FunnelSeriesOption, ecModel: GlobalModel): List { - return createListSimply(this, { + getInitialData(this: FunnelSeriesModel, option: FunnelSeriesOption, ecModel: GlobalModel): SeriesData { + return createSeriesDataSimply(this, { coordDimensions: ['value'], encodeDefaulter: zrUtil.curry(makeSeriesEncodeForNameBased, this) }); diff --git a/src/chart/funnel/FunnelView.ts b/src/chart/funnel/FunnelView.ts index 4a588586ea..e379e64f34 100644 --- a/src/chart/funnel/FunnelView.ts +++ b/src/chart/funnel/FunnelView.ts @@ -23,7 +23,7 @@ import ChartView from '../../view/Chart'; import FunnelSeriesModel, {FunnelDataItemOption} from './FunnelSeries'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { ColorString } from '../../util/types'; import { setLabelLineStyle, getLabelLineStatesModels } from '../../label/labelGuideHelper'; import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle'; @@ -36,7 +36,7 @@ const opacityAccessPath = ['itemStyle', 'opacity'] as const; */ class FunnelPiece extends graphic.Polygon { - constructor(data: List, idx: number) { + constructor(data: SeriesData, idx: number) { super(); const polygon = this; @@ -48,7 +48,7 @@ class FunnelPiece extends graphic.Polygon { this.updateData(data, idx, true); } - updateData(data: List, idx: number, firstCreate?: boolean) { + updateData(data: SeriesData, idx: number, firstCreate?: boolean) { const polygon = this; @@ -95,7 +95,7 @@ class FunnelPiece extends graphic.Polygon { enableHoverEmphasis(this, emphasisModel.get('focus'), emphasisModel.get('blurScope')); } - _updateLabel(data: List, idx: number) { + _updateLabel(data: SeriesData, idx: number) { const polygon = this; const labelLine = this.getTextGuideLine(); const labelText = polygon.getTextContent(); @@ -168,7 +168,7 @@ class FunnelView extends ChartView { static type = 'funnel' as const; type = FunnelView.type; - private _data: List; + private _data: SeriesData; ignoreLabelLineUpdate = true; diff --git a/src/chart/funnel/funnelLayout.ts b/src/chart/funnel/funnelLayout.ts index 71111c3804..cb11676b2d 100644 --- a/src/chart/funnel/funnelLayout.ts +++ b/src/chart/funnel/funnelLayout.ts @@ -21,7 +21,7 @@ import * as layout from '../../util/layout'; import {parsePercent, linearMap} from '../../util/number'; import FunnelSeriesModel, { FunnelSeriesOption, FunnelDataItemOption } from './FunnelSeries'; import ExtensionAPI from '../../core/ExtensionAPI'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import GlobalModel from '../../model/Global'; function getViewRect(seriesModel: FunnelSeriesModel, api: ExtensionAPI) { @@ -33,7 +33,7 @@ function getViewRect(seriesModel: FunnelSeriesModel, api: ExtensionAPI) { ); } -function getSortedIndices(data: List, sort: FunnelSeriesOption['sort']) { +function getSortedIndices(data: SeriesData, sort: FunnelSeriesOption['sort']) { const valueDim = data.mapDimension('value'); const valueArr = data.mapArray(valueDim, function (val: number) { return val; @@ -58,7 +58,7 @@ function getSortedIndices(data: List, sort: FunnelSeriesOption['sort']) { return indices; } -function labelLayout(data: List) { +function labelLayout(data: SeriesData) { const seriesModel = data.hostModel; const orient = seriesModel.get('orient'); data.each(function (idx) { diff --git a/src/chart/gauge/GaugeSeries.ts b/src/chart/gauge/GaugeSeries.ts index e51c816e26..729af9463d 100644 --- a/src/chart/gauge/GaugeSeries.ts +++ b/src/chart/gauge/GaugeSeries.ts @@ -17,7 +17,7 @@ * under the License. */ -import createListSimply from '../helper/createListSimply'; +import createSeriesDataSimply from '../helper/createSeriesDataSimply'; import SeriesModel from '../../model/Series'; import { SeriesOption, @@ -31,7 +31,7 @@ import { SeriesEncodeOptionMixin } from '../../util/types'; import GlobalModel from '../../model/Global'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; // [percent, color] type GaugeColorStop = [number, ColorString]; @@ -183,8 +183,8 @@ class GaugeSeriesModel extends SeriesModel { visualStyleAccessPath = 'itemStyle'; - getInitialData(option: GaugeSeriesOption, ecModel: GlobalModel): List { - return createListSimply(this, ['value']); + getInitialData(option: GaugeSeriesOption, ecModel: GlobalModel): SeriesData { + return createSeriesDataSimply(this, ['value']); } static defaultOption: GaugeSeriesOption = { diff --git a/src/chart/gauge/GaugeView.ts b/src/chart/gauge/GaugeView.ts index 1b21c4499d..d81e10ba59 100644 --- a/src/chart/gauge/GaugeView.ts +++ b/src/chart/gauge/GaugeView.ts @@ -27,7 +27,7 @@ import GaugeSeriesModel, { GaugeDataItemOption } from './GaugeSeries'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; import { ColorString, ECElement } from '../../util/types'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import Sausage from '../../util/shape/sausage'; import {createSymbol} from '../../util/symbol'; import ZRImage from 'zrender/src/graphic/Image'; @@ -78,7 +78,7 @@ class GaugeView extends ChartView { static type = 'gauge' as const; type = GaugeView.type; - private _data: List; + private _data: SeriesData; private _progressEls: graphic.Path[]; private _titleEls: graphic.Text[]; diff --git a/src/chart/graph/GraphSeries.ts b/src/chart/graph/GraphSeries.ts index 54f9f36e0e..42b833d9c9 100644 --- a/src/chart/graph/GraphSeries.ts +++ b/src/chart/graph/GraphSeries.ts @@ -17,7 +17,7 @@ * under the License. */ -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import * as zrUtil from 'zrender/src/core/util'; import {defaultEmphasis} from '../../util/model'; import Model from '../../model/Model'; @@ -230,7 +230,7 @@ class GraphSeriesModel extends SeriesModel { static readonly dependencies = ['grid', 'polar', 'geo', 'singleAxis', 'calendar']; - private _categoriesData: List; + private _categoriesData: SeriesData; private _categoriesModels: Model[]; /** @@ -272,7 +272,7 @@ class GraphSeriesModel extends SeriesModel { defaultEmphasis(option, 'edgeLabel', ['show']); } - getInitialData(option: GraphSeriesOption, ecModel: GlobalModel): List { + getInitialData(option: GraphSeriesOption, ecModel: GlobalModel): SeriesData { const edges = option.edges || option.links || []; const nodes = option.data || option.nodes || []; const self = this; @@ -287,7 +287,7 @@ class GraphSeriesModel extends SeriesModel { return graph.data; } - function beforeLink(nodeData: List, edgeData: List) { + function beforeLink(nodeData: SeriesData, edgeData: SeriesData) { // Overwrite nodeData.getItemModel to nodeData.wrapMethod('getItemModel', function (model) { const categoriesModels = self._categoriesModels; @@ -335,10 +335,10 @@ class GraphSeriesModel extends SeriesModel { } getEdgeData() { - return this.getGraph().edgeData as List; + return this.getGraph().edgeData as SeriesData; } - getCategoriesData(): List { + getCategoriesData(): SeriesData { return this._categoriesData; } @@ -380,7 +380,7 @@ class GraphSeriesModel extends SeriesModel { value: 0 }, category); }); - const categoriesData = new List(['value'], this); + const categoriesData = new SeriesData(['value'], this); categoriesData.initData(categories); this._categoriesData = categoriesData; diff --git a/src/chart/graph/GraphView.ts b/src/chart/graph/GraphView.ts index 1c8d10f921..08cc6ae46f 100644 --- a/src/chart/graph/GraphView.ts +++ b/src/chart/graph/GraphView.ts @@ -33,7 +33,7 @@ import GraphSeriesModel, { GraphNodeItemOption, GraphEdgeItemOption } from './Gr import { CoordinateSystem } from '../../coord/CoordinateSystem'; import View from '../../coord/View'; import Symbol from '../helper/Symbol'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import Line from '../helper/Line'; import { getECData } from '../../util/innerStore'; @@ -109,7 +109,7 @@ class GraphView extends ChartView { const edgeData = seriesModel.getEdgeData(); // TODO: TYPE - lineDraw.updateData(edgeData as List); + lineDraw.updateData(edgeData as SeriesData); this._updateNodeAndLinkScale(); diff --git a/src/chart/graph/circularLayoutHelper.ts b/src/chart/graph/circularLayoutHelper.ts index a81538c377..91265bea62 100644 --- a/src/chart/graph/circularLayoutHelper.ts +++ b/src/chart/graph/circularLayoutHelper.ts @@ -22,7 +22,7 @@ import * as vec2 from 'zrender/src/core/vector'; import {getSymbolSize, getNodeGlobalScale} from './graphHelper'; import GraphSeriesModel, { GraphEdgeItemOption } from './GraphSeries'; import Graph from '../../data/Graph'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import * as zrUtil from 'zrender/src/core/util'; import {getCurvenessForEdge} from '../helper/multipleGraphEdgeHelper'; @@ -105,7 +105,7 @@ interface LayoutNode { ( seriesModel: GraphSeriesModel, graph: Graph, - nodeData: List, + nodeData: SeriesData, r: number, cx: number, cy: number, diff --git a/src/chart/heatmap/HeatmapSeries.ts b/src/chart/heatmap/HeatmapSeries.ts index af831ef135..80fbb72dd4 100644 --- a/src/chart/heatmap/HeatmapSeries.ts +++ b/src/chart/heatmap/HeatmapSeries.ts @@ -18,7 +18,7 @@ */ import SeriesModel from '../../model/Series'; -import createListFromArray from '../helper/createListFromArray'; +import createSeriesData from '../helper/createSeriesData'; import CoordinateSystem from '../../core/CoordinateSystem'; import { SeriesOption, @@ -32,7 +32,7 @@ import { SeriesOnCalendarOptionMixin } from '../../util/types'; import GlobalModel from '../../model/Global'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import type Geo from '../../coord/geo/Geo'; import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; import type Calendar from '../../coord/calendar/Calendar'; @@ -72,8 +72,8 @@ class HeatmapSeriesModel extends SeriesModel { // @ts-ignore coordinateSystem: Cartesian2D | Geo | Calendar; - getInitialData(option: HeatmapSeriesOption, ecModel: GlobalModel): List { - return createListFromArray(this.getSource(), this, { + getInitialData(option: HeatmapSeriesOption, ecModel: GlobalModel): SeriesData { + return createSeriesData(null, this, { generateCoord: 'value' }); } diff --git a/src/chart/helper/EffectLine.ts b/src/chart/helper/EffectLine.ts index d269faad9d..bc92620754 100644 --- a/src/chart/helper/EffectLine.ts +++ b/src/chart/helper/EffectLine.ts @@ -27,7 +27,7 @@ import * as zrUtil from 'zrender/src/core/util'; import {createSymbol} from '../../util/symbol'; import * as vec2 from 'zrender/src/core/vector'; import * as curveUtil from 'zrender/src/core/curve'; -import type List from '../../data/List'; +import type SeriesData from '../../data/SeriesData'; import { LineDrawSeriesScope, LineDrawModelOption } from './LineDraw'; import Model from '../../model/Model'; import { ColorString } from '../../util/types'; @@ -49,18 +49,18 @@ class EffectLine extends graphic.Group { private _symbolScale: number[]; - constructor(lineData: List, idx: number, seriesScope: LineDrawSeriesScope) { + constructor(lineData: SeriesData, idx: number, seriesScope: LineDrawSeriesScope) { super(); this.add(this.createLine(lineData, idx, seriesScope)); this._updateEffectSymbol(lineData, idx); } - createLine(lineData: List, idx: number, seriesScope: LineDrawSeriesScope): graphic.Group { + createLine(lineData: SeriesData, idx: number, seriesScope: LineDrawSeriesScope): graphic.Group { return new Line(lineData, idx, seriesScope); } - private _updateEffectSymbol(lineData: List, idx: number) { + private _updateEffectSymbol(lineData: SeriesData, idx: number) { const itemModel = lineData.getItemModel(idx); const effectModel = itemModel.getModel('effect'); let size = effectModel.get('symbolSize'); @@ -107,7 +107,7 @@ class EffectLine extends graphic.Group { } private _updateEffectAnimation( - lineData: List, + lineData: SeriesData, effectModel: Model, idx: number ) { @@ -189,7 +189,7 @@ class EffectLine extends graphic.Group { ]; } - updateData(lineData: List, idx: number, seriesScope: LineDrawSeriesScope) { + updateData(lineData: SeriesData, idx: number, seriesScope: LineDrawSeriesScope) { (this.childAt(0) as Line).updateData(lineData, idx, seriesScope); this._updateEffectSymbol(lineData, idx); } @@ -236,7 +236,7 @@ class EffectLine extends graphic.Group { } - updateLayout(lineData: List, idx: number) { + updateLayout(lineData: SeriesData, idx: number) { (this.childAt(0) as Line).updateLayout(lineData, idx); const effectModel = lineData.getItemModel(idx).getModel('effect'); diff --git a/src/chart/helper/EffectPolyline.ts b/src/chart/helper/EffectPolyline.ts index 80499750c6..a250cb590a 100644 --- a/src/chart/helper/EffectPolyline.ts +++ b/src/chart/helper/EffectPolyline.ts @@ -21,7 +21,7 @@ import Polyline from './Polyline'; import EffectLine, {ECSymbolOnEffectLine} from './EffectLine'; import * as vec2 from 'zrender/src/core/vector'; import { LineDrawSeriesScope } from './LineDraw'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; class EffectPolyline extends EffectLine { @@ -33,7 +33,7 @@ class EffectPolyline extends EffectLine { private _offsets: number[]; // Override - createLine(lineData: List, idx: number, seriesScope: LineDrawSeriesScope) { + createLine(lineData: SeriesData, idx: number, seriesScope: LineDrawSeriesScope) { return new Polyline(lineData, idx, seriesScope); }; diff --git a/src/chart/helper/EffectSymbol.ts b/src/chart/helper/EffectSymbol.ts index 43bb25f936..d36a6ec4d8 100644 --- a/src/chart/helper/EffectSymbol.ts +++ b/src/chart/helper/EffectSymbol.ts @@ -21,7 +21,7 @@ import {createSymbol, normalizeSymbolOffset, normalizeSymbolSize} from '../../ut import {Group, Path} from '../../util/graphic'; import { enterEmphasis, leaveEmphasis, enableHoverEmphasis } from '../../util/states'; import SymbolClz from './Symbol'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import type { ZRColor, ECElement } from '../../util/types'; import type Displayable from 'zrender/src/graphic/Displayable'; import { SymbolDrawItemModelOption } from './SymbolDraw'; @@ -58,7 +58,7 @@ class EffectSymbol extends Group { private _effectCfg: RippleEffectCfg; - constructor(data: List, idx: number) { + constructor(data: SeriesData, idx: number) { super(); const symbol = new SymbolClz(data, idx); @@ -161,7 +161,7 @@ class EffectSymbol extends Group { /** * Update symbol properties */ - updateData(data: List, idx: number) { + updateData(data: SeriesData, idx: number) { const seriesModel = data.hostModel; (this.childAt(0) as SymbolClz).updateData(data, idx); diff --git a/src/chart/helper/LargeLineDraw.ts b/src/chart/helper/LargeLineDraw.ts index 50e4f8d0bd..fca6979fea 100644 --- a/src/chart/helper/LargeLineDraw.ts +++ b/src/chart/helper/LargeLineDraw.ts @@ -24,7 +24,7 @@ import IncrementalDisplayable from 'zrender/src/graphic/IncrementalDisplayable'; import * as lineContain from 'zrender/src/contain/line'; import * as quadraticContain from 'zrender/src/contain/quadratic'; import { PathProps } from 'zrender/src/graphic/Path'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { StageHandlerProgressParams, LineStyleOption, ColorString } from '../../util/types'; import Model from '../../model/Model'; import { getECData } from '../../util/innerStore'; @@ -49,7 +49,7 @@ interface LargeLinesCommonOption { /** * Data which can support large lines. */ -type LargeLinesData = List & { +type LargeLinesData = SeriesData & { seriesIndex?: number }>; diff --git a/src/chart/helper/LargeSymbolDraw.ts b/src/chart/helper/LargeSymbolDraw.ts index 1840c54fb6..3f3bcbbac1 100644 --- a/src/chart/helper/LargeSymbolDraw.ts +++ b/src/chart/helper/LargeSymbolDraw.ts @@ -24,7 +24,7 @@ import * as graphic from '../../util/graphic'; import {createSymbol} from '../../util/symbol'; import IncrementalDisplayable from 'zrender/src/graphic/IncrementalDisplayable'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { PathProps } from 'zrender/src/graphic/Path'; import PathProxy from 'zrender/src/core/PathProxy'; import SeriesModel from '../../model/Series'; @@ -182,7 +182,7 @@ class LargeSymbolDraw { /** * Update symbols draw by new data */ - updateData(data: List, opt?: UpdateOpt) { + updateData(data: SeriesData, opt?: UpdateOpt) { this.group.removeAll(); const symbolEl = new LargeSymbolPath({ rectHover: true, @@ -198,7 +198,7 @@ class LargeSymbolDraw { this._incremental = null; } - updateLayout(data: List) { + updateLayout(data: SeriesData) { if (this._incremental) { return; } @@ -214,7 +214,7 @@ class LargeSymbolDraw { }); } - incrementalPrepareUpdate(data: List) { + incrementalPrepareUpdate(data: SeriesData) { this.group.removeAll(); this._clearIncremental(); @@ -233,7 +233,7 @@ class LargeSymbolDraw { } } - incrementalUpdate(taskParams: StageHandlerProgressParams, data: List, opt: UpdateOpt) { + incrementalUpdate(taskParams: StageHandlerProgressParams, data: SeriesData, opt: UpdateOpt) { let symbolEl; if (this._incremental) { symbolEl = new LargeSymbolPath(); @@ -258,7 +258,7 @@ class LargeSymbolDraw { _setCommon( symbolEl: LargeSymbolPath, - data: List, + data: SeriesData, isIncremental: boolean, opt: UpdateOpt ) { diff --git a/src/chart/helper/Line.ts b/src/chart/helper/Line.ts index 140509dd39..b806d0f7b8 100644 --- a/src/chart/helper/Line.ts +++ b/src/chart/helper/Line.ts @@ -25,7 +25,7 @@ import * as graphic from '../../util/graphic'; import { enableHoverEmphasis, enterEmphasis, leaveEmphasis, SPECIAL_STATES } from '../../util/states'; import {getLabelStatesModels, setLabelStyle} from '../../label/labelStyle'; import {round} from '../../util/number'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { ZRTextAlign, ZRTextVerticalAlign, LineLabelOption, ColorString } from '../../util/types'; import SeriesModel from '../../model/Series'; import type { LineDrawSeriesScope, LineDrawModelOption } from './LineDraw'; @@ -41,7 +41,7 @@ type LineECSymbol = ECSymbol & { __specifiedRotation: number }; -type LineList = List; +type LineList = SeriesData; export interface LineLabel extends graphic.Text { lineLabelOriginalOpacity: number @@ -133,7 +133,7 @@ class Line extends graphic.Group { private _fromSymbolType: string; private _toSymbolType: string; - constructor(lineData: List, idx: number, seriesScope?: LineDrawSeriesScope) { + constructor(lineData: SeriesData, idx: number, seriesScope?: LineDrawSeriesScope) { super(); this._createLine(lineData as LineList, idx, seriesScope); } @@ -164,7 +164,7 @@ class Line extends graphic.Group { } // TODO More strict on the List type in parameters? - updateData(lineData: List, idx: number, seriesScope: LineDrawSeriesScope) { + updateData(lineData: SeriesData, idx: number, seriesScope: LineDrawSeriesScope) { const seriesModel = lineData.hostModel; const line = this.childOfName('line') as ECLinePath; @@ -195,7 +195,7 @@ class Line extends graphic.Group { return this.childAt(0) as graphic.Line; } - _updateCommonStl(lineData: List, idx: number, seriesScope?: LineDrawSeriesScope) { + _updateCommonStl(lineData: SeriesData, idx: number, seriesScope?: LineDrawSeriesScope) { const seriesModel = lineData.hostModel as SeriesModel; const line = this.childOfName('line') as ECLinePath; @@ -307,7 +307,7 @@ class Line extends graphic.Group { leaveEmphasis(this); } - updateLayout(lineData: List, idx: number) { + updateLayout(lineData: SeriesData, idx: number) { this.setLinePoints(lineData.getItemLayout(idx)); } diff --git a/src/chart/helper/LineDraw.ts b/src/chart/helper/LineDraw.ts index 1dde5d39d4..60317bc7f1 100644 --- a/src/chart/helper/LineDraw.ts +++ b/src/chart/helper/LineDraw.ts @@ -19,7 +19,7 @@ import * as graphic from '../../util/graphic'; import LineGroup from './Line'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { StageHandlerProgressParams, LineStyleOption, @@ -36,13 +36,13 @@ import Model from '../../model/Model'; import { getLabelStatesModels } from '../../label/labelStyle'; interface LineLike extends graphic.Group { - updateData(data: List, idx: number, scope?: LineDrawSeriesScope): void - updateLayout(data: List, idx: number): void + updateData(data: SeriesData, idx: number, scope?: LineDrawSeriesScope): void + updateLayout(data: SeriesData, idx: number): void fadeOut?(cb: () => void): void } interface LineLikeCtor { - new(data: List, idx: number, scope?: LineDrawSeriesScope): LineLike + new(data: SeriesData, idx: number, scope?: LineDrawSeriesScope): LineLike } interface LineDrawStateOption { @@ -76,7 +76,7 @@ export interface LineDrawModelOption extends LineDrawStateOption, StatesOptionMi } } -type ListForLineDraw = List>; +type ListForLineDraw = SeriesData>; export interface LineDrawSeriesScope { lineStyle?: ZRStyleProps diff --git a/src/chart/helper/Polyline.ts b/src/chart/helper/Polyline.ts index 29d0715312..a371edcfdd 100644 --- a/src/chart/helper/Polyline.ts +++ b/src/chart/helper/Polyline.ts @@ -20,15 +20,15 @@ import * as graphic from '../../util/graphic'; import { enableHoverEmphasis } from '../../util/states'; import type { LineDrawSeriesScope, LineDrawModelOption } from './LineDraw'; -import type List from '../../data/List'; +import type SeriesData from '../../data/SeriesData'; class Polyline extends graphic.Group { - constructor(lineData: List, idx: number, seriesScope: LineDrawSeriesScope) { + constructor(lineData: SeriesData, idx: number, seriesScope: LineDrawSeriesScope) { super(); this._createPolyline(lineData, idx, seriesScope); } - private _createPolyline(lineData: List, idx: number, seriesScope: LineDrawSeriesScope) { + private _createPolyline(lineData: SeriesData, idx: number, seriesScope: LineDrawSeriesScope) { // let seriesModel = lineData.hostModel; const points = lineData.getItemLayout(idx); @@ -43,7 +43,7 @@ class Polyline extends graphic.Group { this._updateCommonStl(lineData, idx, seriesScope); }; - updateData(lineData: List, idx: number, seriesScope: LineDrawSeriesScope) { + updateData(lineData: SeriesData, idx: number, seriesScope: LineDrawSeriesScope) { const seriesModel = lineData.hostModel; const line = this.childAt(0) as graphic.Polyline; @@ -57,7 +57,7 @@ class Polyline extends graphic.Group { this._updateCommonStl(lineData, idx, seriesScope); }; - _updateCommonStl(lineData: List, idx: number, seriesScope: LineDrawSeriesScope) { + _updateCommonStl(lineData: SeriesData, idx: number, seriesScope: LineDrawSeriesScope) { const line = this.childAt(0) as graphic.Polyline; const itemModel = lineData.getItemModel(idx); @@ -77,7 +77,7 @@ class Polyline extends graphic.Group { enableHoverEmphasis(this); }; - updateLayout(lineData: List, idx: number) { + updateLayout(lineData: SeriesData, idx: number) { const polyline = this.childAt(0) as graphic.Polyline; polyline.setShape('points', lineData.getItemLayout(idx)); }; diff --git a/src/chart/helper/Symbol.ts b/src/chart/helper/Symbol.ts index 222776f2b9..be2a5a4a2e 100644 --- a/src/chart/helper/Symbol.ts +++ b/src/chart/helper/Symbol.ts @@ -22,7 +22,7 @@ import * as graphic from '../../util/graphic'; import {getECData} from '../../util/innerStore'; import { enterEmphasis, leaveEmphasis, enableHoverEmphasis } from '../../util/states'; import {getDefaultLabel} from './labelHelper'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { ColorString, BlurScope, AnimationOption, ZRColor } from '../../util/types'; import SeriesModel from '../../model/Series'; import { PathProps } from 'zrender/src/graphic/Path'; @@ -55,14 +55,14 @@ class Symbol extends graphic.Group { private _z2: number; - constructor(data: List, idx: number, seriesScope?: SymbolDrawSeriesScope, opts?: SymbolOpts) { + constructor(data: SeriesData, idx: number, seriesScope?: SymbolDrawSeriesScope, opts?: SymbolOpts) { super(); this.updateData(data, idx, seriesScope, opts); } _createSymbol( symbolType: string, - data: List, + data: SeriesData, idx: number, symbolSize: number[], keepAspect: boolean @@ -151,7 +151,7 @@ class Symbol extends graphic.Group { /** * Update symbol properties */ - updateData(data: List, idx: number, seriesScope?: SymbolDrawSeriesScope, opts?: SymbolOpts) { + updateData(data: SeriesData, idx: number, seriesScope?: SymbolDrawSeriesScope, opts?: SymbolOpts) { this.silent = false; const symbolType = data.getItemVisual(idx, 'symbol') || 'circle'; @@ -206,7 +206,7 @@ class Symbol extends graphic.Group { } _updateCommon( - data: List, + data: SeriesData, idx: number, symbolSize: number[], seriesScope?: SymbolDrawSeriesScope, @@ -395,7 +395,7 @@ class Symbol extends graphic.Group { ); } - static getSymbolSize(data: List, idx: number) { + static getSymbolSize(data: SeriesData, idx: number) { return normalizeSymbolSize(data.getItemVisual(idx, 'symbolSize')); } } diff --git a/src/chart/helper/SymbolDraw.ts b/src/chart/helper/SymbolDraw.ts index e2d909a266..43b1249fc9 100644 --- a/src/chart/helper/SymbolDraw.ts +++ b/src/chart/helper/SymbolDraw.ts @@ -20,7 +20,7 @@ import * as graphic from '../../util/graphic'; import SymbolClz from './Symbol'; import { isObject } from 'zrender/src/core/util'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import type Displayable from 'zrender/src/graphic/Displayable'; import { StageHandlerProgressParams, @@ -49,15 +49,15 @@ interface UpdateOpt { } interface SymbolLike extends graphic.Group { - updateData(data: List, idx: number, scope?: SymbolDrawSeriesScope, opt?: UpdateOpt): void + updateData(data: SeriesData, idx: number, scope?: SymbolDrawSeriesScope, opt?: UpdateOpt): void fadeOut?(cb: () => void): void } interface SymbolLikeCtor { - new(data: List, idx: number, scope?: SymbolDrawSeriesScope, opt?: UpdateOpt): SymbolLike + new(data: SeriesData, idx: number, scope?: SymbolDrawSeriesScope, opt?: UpdateOpt): SymbolLike } -function symbolNeedsDraw(data: List, point: number[], idx: number, opt: UpdateOpt) { +function symbolNeedsDraw(data: SeriesData, point: number[], idx: number, opt: UpdateOpt) { return point && !isNaN(point[0]) && !isNaN(point[1]) && !(opt.isIgnore && opt.isIgnore(idx)) // We do not set clipShape on group, because it will cut part of @@ -130,7 +130,7 @@ export interface SymbolDrawSeriesScope { fadeIn?: boolean } -function makeSeriesScope(data: List): SymbolDrawSeriesScope { +function makeSeriesScope(data: SeriesData): SymbolDrawSeriesScope { const seriesModel = data.hostModel as Model; const emphasisModel = seriesModel.getModel('emphasis'); return { @@ -149,7 +149,7 @@ function makeSeriesScope(data: List): SymbolDrawSeriesScope { }; } -export type ListForSymbolDraw = List>; +export type ListForSymbolDraw = SeriesData>; class SymbolDraw { group = new graphic.Group(); diff --git a/src/chart/helper/createGraphFromNodeEdge.ts b/src/chart/helper/createGraphFromNodeEdge.ts index e2a0fca814..14eea23fd3 100644 --- a/src/chart/helper/createGraphFromNodeEdge.ts +++ b/src/chart/helper/createGraphFromNodeEdge.ts @@ -19,12 +19,12 @@ import * as zrUtil from 'zrender/src/core/util'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import Graph from '../../data/Graph'; -import linkList from '../../data/helper/linkList'; -import createDimensions from '../../data/helper/createDimensions'; +import linkSeriesData from '../../data/helper/linkSeriesData'; +import prepareSeriesDataSchema from '../../data/helper/createDimensions'; import CoordinateSystem from '../../core/CoordinateSystem'; -import createListFromArray from './createListFromArray'; +import createSeriesData from './createSeriesData'; import { OptionSourceDataOriginal, GraphEdgeItemObject, OptionDataValue, OptionDataItemObject @@ -37,7 +37,7 @@ export default function createGraphFromNodeEdge( edges: OptionSourceDataOriginal>, seriesModel: SeriesModel, directed: boolean, - beforeLink: (nodeData: List, edgeData: List) => void + beforeLink: (nodeData: SeriesData, edgeData: SeriesData) => void ): Graph { // ??? TODO // support dataset? @@ -70,7 +70,7 @@ export default function createGraphFromNodeEdge( const coordSys = seriesModel.get('coordinateSystem'); let nodeData; if (coordSys === 'cartesian2d' || coordSys === 'polar') { - nodeData = createListFromArray(nodes, seriesModel); + nodeData = createSeriesData(nodes, seriesModel); } else { const coordSysCtor = CoordinateSystem.get(coordSys); @@ -83,19 +83,20 @@ export default function createGraphFromNodeEdge( coordDimensions.concat(['value']); } - const dimensionNames = createDimensions(nodes, { - coordDimensions: coordDimensions + const { dimensions } = prepareSeriesDataSchema(nodes, { + coordDimensions: coordDimensions, + encodeDefine: seriesModel.getEncode() }); - nodeData = new List(dimensionNames, seriesModel); + nodeData = new SeriesData(dimensions, seriesModel); nodeData.initData(nodes); } - const edgeData = new List(['value'], seriesModel); + const edgeData = new SeriesData(['value'], seriesModel); edgeData.initData(validEdges, linkNameList); beforeLink && beforeLink(nodeData, edgeData); - linkList({ + linkSeriesData({ mainData: nodeData, struct: graph, structAttr: 'graph', diff --git a/src/chart/helper/createListFromArray.ts b/src/chart/helper/createSeriesData.ts similarity index 51% rename from src/chart/helper/createListFromArray.ts rename to src/chart/helper/createSeriesData.ts index a9adc2f209..704eed4c8a 100644 --- a/src/chart/helper/createListFromArray.ts +++ b/src/chart/helper/createSeriesData.ts @@ -18,37 +18,33 @@ */ import * as zrUtil from 'zrender/src/core/util'; -import List from '../../data/List'; -import createDimensions from '../../data/helper/createDimensions'; +import SeriesData from '../../data/SeriesData'; +import prepareSeriesDataSchema from '../../data/helper/createDimensions'; import {getDimensionTypeByAxis} from '../../data/helper/dimensionHelper'; import {getDataItemValue} from '../../util/model'; import CoordinateSystem from '../../core/CoordinateSystem'; import {getCoordSysInfoBySeries} from '../../model/referHelper'; -import { createSourceFromSeriesDataOption, isSourceInstance, Source } from '../../data/Source'; +import { createSourceFromSeriesDataOption, Source } from '../../data/Source'; import {enableDataStack} from '../../data/helper/dataStackHelper'; import {makeSeriesEncodeForAxisCoordSys} from '../../data/helper/sourceHelper'; import { - SOURCE_FORMAT_ORIGINAL, DimensionDefinitionLoose, DimensionDefinition, OptionSourceData, EncodeDefaulter + SOURCE_FORMAT_ORIGINAL, + DimensionDefinitionLoose, + DimensionDefinition, + OptionSourceData, + EncodeDefaulter } from '../../util/types'; import SeriesModel from '../../model/Series'; +import DataStore from '../../data/DataStore'; +import SeriesDimensionDefine from '../../data/SeriesDimensionDefine'; -function createListFromArray(source: Source | OptionSourceData, seriesModel: SeriesModel, opt?: { - generateCoord?: string - useEncodeDefaulter?: boolean | EncodeDefaulter - // By default: auto. If `true`, create inverted indices for all ordinal dimension on coordSys. - createInvertedIndices?: boolean -}): List { - opt = opt || {}; - - if (!isSourceInstance(source)) { - source = createSourceFromSeriesDataOption(source); - } - +function getCoordSysDimDefs( + seriesModel: SeriesModel, + coordSysInfo: ReturnType +) { const coordSysName = seriesModel.get('coordinateSystem'); const registeredCoordSys = CoordinateSystem.get(coordSysName); - const coordSysInfo = getCoordSysInfoBySeries(seriesModel); - let coordSysDimDefs: DimensionDefinitionLoose[]; if (coordSysInfo && coordSysInfo.coordSysDims) { @@ -60,7 +56,6 @@ function createListFromArray(source: Source | OptionSourceData, seriesModel: Ser if (axisModel) { const axisType = axisModel.get('type'); dimInfo.type = getDimensionTypeByAxis(axisType); - // dimInfo.stackable = isStackable(axisType); } return dimInfo; }); @@ -75,17 +70,14 @@ function createListFromArray(source: Source | OptionSourceData, seriesModel: Ser )) || ['x', 'y']; } - const useEncodeDefaulter = opt.useEncodeDefaulter; - const dimInfoList = createDimensions(source, { - coordDimensions: coordSysDimDefs, - generateCoord: opt.generateCoord, - encodeDefaulter: zrUtil.isFunction(useEncodeDefaulter) - ? useEncodeDefaulter - : useEncodeDefaulter - ? zrUtil.curry(makeSeriesEncodeForAxisCoordSys, coordSysDimDefs, seriesModel) - : null - }); + return coordSysDimDefs; +} +function injectOrdinalMeta( + dimInfoList: SeriesDimensionDefine[], + createInvertedIndices: boolean, + coordSysInfo: ReturnType +) { let firstCategoryDimIndex: number; let hasNameEncode: boolean; coordSysInfo && zrUtil.each(dimInfoList, function (dimInfo, dimIndex) { @@ -96,7 +88,7 @@ function createListFromArray(source: Source | OptionSourceData, seriesModel: Ser firstCategoryDimIndex = dimIndex; } dimInfo.ordinalMeta = categoryAxisModel.getOrdinalMeta(); - if (opt.createInvertedIndices) { + if (createInvertedIndices) { dimInfo.createInvertedIndices = true; } } @@ -107,26 +99,85 @@ function createListFromArray(source: Source | OptionSourceData, seriesModel: Ser if (!hasNameEncode && firstCategoryDimIndex != null) { dimInfoList[firstCategoryDimIndex].otherDims.itemName = 0; } + return firstCategoryDimIndex; +} - const stackCalculationInfo = enableDataStack(seriesModel, dimInfoList); - - const list = new List(dimInfoList, seriesModel); +/** + * Caution: there are side effects to `sourceManager` in this method. + * Should better only be called in `Series['getInitialData']`. + */ +function createSeriesData( + sourceRaw: OptionSourceData | null | undefined, + seriesModel: SeriesModel, + opt?: { + generateCoord?: string + useEncodeDefaulter?: boolean | EncodeDefaulter + // By default: auto. If `true`, create inverted indices for all ordinal dimension on coordSys. + createInvertedIndices?: boolean + } +): SeriesData { + opt = opt || {}; - list.setCalculationInfo(stackCalculationInfo); + const sourceManager = seriesModel.getSourceManager(); + let source; + let isOriginalSource = false; + if (sourceRaw) { + isOriginalSource = true; + source = createSourceFromSeriesDataOption(sourceRaw); + } + else { + source = sourceManager.getSource(); + // Is series.data. not dataset. + isOriginalSource = source.sourceFormat === SOURCE_FORMAT_ORIGINAL; + } + const coordSysInfo = getCoordSysInfoBySeries(seriesModel); + const coordSysDimDefs = getCoordSysDimDefs(seriesModel, coordSysInfo); + const useEncodeDefaulter = opt.useEncodeDefaulter; - const dimValueGetter = (firstCategoryDimIndex != null && isNeedCompleteOrdinalData(source)) - ? function (this: List, itemOpt: any, dimName: string, dataIndex: number, dimIndex: number) { - // Use dataIndex as ordinal value in categoryAxis - return dimIndex === firstCategoryDimIndex - ? dataIndex - : this.defaultDimValueGetter(itemOpt, dimName, dataIndex, dimIndex); - } + const encodeDefaulter = zrUtil.isFunction(useEncodeDefaulter) + ? useEncodeDefaulter + : useEncodeDefaulter + ? zrUtil.curry(makeSeriesEncodeForAxisCoordSys, coordSysDimDefs, seriesModel) : null; + const createDimensionOptions = { + coordDimensions: coordSysDimDefs, + generateCoord: opt.generateCoord, + encodeDefine: seriesModel.getEncode(), + encodeDefaulter: encodeDefaulter, + canOmitUnusedDimensions: !isOriginalSource + }; + const schema = prepareSeriesDataSchema(source, createDimensionOptions); + const firstCategoryDimIndex = injectOrdinalMeta( + schema.dimensions, opt.createInvertedIndices, coordSysInfo + ); + + const store = !isOriginalSource ? sourceManager.getSharedDataStore(schema) : null; + + const stackCalculationInfo = enableDataStack(seriesModel, { schema, store }); + + const data = new SeriesData(schema, seriesModel); + data.setCalculationInfo(stackCalculationInfo); + + const dimValueGetter = + firstCategoryDimIndex != null + && isNeedCompleteOrdinalData(source) + ? function (this: DataStore, itemOpt: any, dimName: string, dataIndex: number, dimIndex: number) { + // Use dataIndex as ordinal value in categoryAxis + return dimIndex === firstCategoryDimIndex + ? dataIndex + : this.defaultDimValueGetter(itemOpt, dimName, dataIndex, dimIndex); + } + : null; - list.hasItemOption = false; - list.initData(source, null, dimValueGetter); + data.hasItemOption = false; + data.initData( + // Try to reuse the data store in sourceManager if using dataset. + isOriginalSource ? source : store, + null, + dimValueGetter + ); - return list; + return data; } function isNeedCompleteOrdinalData(source: Source) { @@ -137,12 +188,12 @@ function isNeedCompleteOrdinalData(source: Source) { } } -function firstDataNotNull(data: ArrayLike) { +function firstDataNotNull(arr: ArrayLike) { let i = 0; - while (i < data.length && data[i] == null) { + while (i < arr.length && arr[i] == null) { i++; } - return data[i]; + return arr[i]; } -export default createListFromArray; +export default createSeriesData; diff --git a/src/chart/helper/createListSimply.ts b/src/chart/helper/createSeriesDataSimply.ts similarity index 67% rename from src/chart/helper/createListSimply.ts rename to src/chart/helper/createSeriesDataSimply.ts index b38d3cdcf5..d99c246ecf 100644 --- a/src/chart/helper/createListSimply.ts +++ b/src/chart/helper/createSeriesDataSimply.ts @@ -17,8 +17,8 @@ * under the License. */ -import createDimensions, {CreateDimensionsParams} from '../../data/helper/createDimensions'; -import List from '../../data/List'; +import prepareSeriesDataSchema, {PrepareSeriesDataSchemaParams} from '../../data/helper/createDimensions'; +import SeriesData from '../../data/SeriesData'; import {extend, isArray} from 'zrender/src/core/util'; import SeriesModel from '../../model/Series'; @@ -32,18 +32,22 @@ import SeriesModel from '../../model/Series'; * dimensionsCount: 5 * }); */ -export default function createListSimply( +export default function createSeriesDataSimply( seriesModel: SeriesModel, - opt: CreateDimensionsParams | CreateDimensionsParams['coordDimensions'], + opt: PrepareSeriesDataSchemaParams | PrepareSeriesDataSchemaParams['coordDimensions'], nameList?: string[] -): List { - opt = isArray(opt) && {coordDimensions: opt} || extend({}, opt); +): SeriesData { + opt = isArray(opt) && { + coordDimensions: opt + } || extend({ + encodeDefine: seriesModel.getEncode() + }, opt); const source = seriesModel.getSource(); - const dimensionsInfo = createDimensions(source, opt as CreateDimensionsParams); + const { dimensions } = prepareSeriesDataSchema(source, opt as PrepareSeriesDataSchemaParams); - const list = new List(dimensionsInfo, seriesModel); + const list = new SeriesData(dimensions, seriesModel); list.initData(source, nameList); return list; diff --git a/src/chart/helper/labelHelper.ts b/src/chart/helper/labelHelper.ts index 58a0b0d859..e8d791685f 100644 --- a/src/chart/helper/labelHelper.ts +++ b/src/chart/helper/labelHelper.ts @@ -19,7 +19,7 @@ import {retrieveRawValue} from '../../data/helper/dataProvider'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { InterpolatableValue } from '../../util/types'; import { isArray } from 'zrender/src/core/util'; @@ -27,7 +27,7 @@ import { isArray } from 'zrender/src/core/util'; * @return label string. Not null/undefined */ export function getDefaultLabel( - data: List, + data: SeriesData, dataIndex: number ): string { const labelDims = data.mapDimensionsAll('defaultedLabel'); @@ -48,7 +48,7 @@ export function getDefaultLabel( } export function getDefaultInterpolatedLabel( - data: List, + data: SeriesData, interpolatedValue: InterpolatableValue ): string { const labelDims = data.mapDimensionsAll('defaultedLabel'); @@ -58,9 +58,9 @@ export function getDefaultInterpolatedLabel( const vals = []; for (let i = 0; i < labelDims.length; i++) { - const dimInfo = data.getDimensionInfo(labelDims[i]); - if (dimInfo) { - vals.push(interpolatedValue[dimInfo.index]); + const dimIndex = data.getDimensionIndex(labelDims[i]); + if (dimIndex >= 0) { + vals.push(interpolatedValue[dimIndex]); } } return vals.join(' '); diff --git a/src/chart/helper/whiskerBoxCommon.ts b/src/chart/helper/whiskerBoxCommon.ts index 81fa9d69c5..177105283e 100644 --- a/src/chart/helper/whiskerBoxCommon.ts +++ b/src/chart/helper/whiskerBoxCommon.ts @@ -17,7 +17,7 @@ * under the License. */ -import createListSimply from '../helper/createListSimply'; +import createSeriesDataSimply from './createSeriesDataSimply'; import * as zrUtil from 'zrender/src/core/util'; import {getDimensionTypeByAxis} from '../../data/helper/dimensionHelper'; import {makeSeriesEncodeForAxisCoordSys} from '../../data/helper/sourceHelper'; @@ -25,7 +25,7 @@ import type { SeriesOption, SeriesOnCartesianOptionMixin, LayoutOrient } from '. import type GlobalModel from '../../model/Global'; import type SeriesModel from '../../model/Series'; import type CartesianAxisModel from '../../coord/cartesian/AxisModel'; -import type List from '../../data/List'; +import type SeriesData from '../../data/SeriesData'; import type Axis2D from '../../coord/cartesian/Axis2D'; import { CoordDimensionDefinition } from '../../data/helper/createDimensions'; @@ -55,7 +55,7 @@ class WhiskerBoxCommonMixin { /** * @override */ - getInitialData(option: Opts, ecModel: GlobalModel): List { + getInitialData(option: Opts, ecModel: GlobalModel): SeriesData { // When both types of xAxis and yAxis are 'value', layout is // needed to be specified by user. Otherwise, layout can be // judged by which axis is category. @@ -135,7 +135,7 @@ class WhiskerBoxCommonMixin { dimsDef: defaultValueDimensions.slice() }]; - return createListSimply( + return createSeriesDataSimply( this, { coordDimensions: coordDimensions, diff --git a/src/chart/line/LineSeries.ts b/src/chart/line/LineSeries.ts index 0b685eb39c..a836132e4d 100644 --- a/src/chart/line/LineSeries.ts +++ b/src/chart/line/LineSeries.ts @@ -17,7 +17,7 @@ * under the License. */ -import createListFromArray from '../helper/createListFromArray'; +import createSeriesData from '../helper/createSeriesData'; import SeriesModel from '../../model/Series'; import { SeriesOnCartesianOptionMixin, @@ -36,7 +36,7 @@ import { CallbackDataParams, DefaultEmphasisFocus } from '../../util/types'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; import type Polar from '../../coord/polar/Polar'; import {createSymbol, ECSymbol} from '../../util/symbol'; @@ -130,14 +130,14 @@ class LineSeriesModel extends SeriesModel { hasSymbolVisual = true; - getInitialData(option: LineSeriesOption): List { + getInitialData(option: LineSeriesOption): SeriesData { if (__DEV__) { const coordSys = option.coordinateSystem; if (coordSys !== 'polar' && coordSys !== 'cartesian2d') { throw new Error('Line not support coordinateSystem besides cartesian and polar'); } } - return createListFromArray(this.getSource(), this, { + return createSeriesData(null, this, { useEncodeDefaulter: true }); } diff --git a/src/chart/line/LineView.ts b/src/chart/line/LineView.ts index 893ea43712..9cabed1302 100644 --- a/src/chart/line/LineView.ts +++ b/src/chart/line/LineView.ts @@ -35,7 +35,7 @@ import type ExtensionAPI from '../../core/ExtensionAPI'; // TODO import Cartesian2D from '../../coord/cartesian/Cartesian2D'; import Polar from '../../coord/polar/Polar'; -import type List from '../../data/List'; +import type SeriesData from '../../data/SeriesData'; import type { Payload, Dictionary, @@ -126,7 +126,7 @@ function getSmooth(smooth: number | boolean) { function getStackedOnPoints( coordSys: Cartesian2D | Polar, - data: List, + data: SeriesData, dataCoordInfo: ReturnType ) { if (!dataCoordInfo.valueDim) { @@ -192,7 +192,7 @@ function turnPointsIntoStep( } function getVisualGradient( - data: List, + data: SeriesData, coordSys: Cartesian2D | Polar ) { const visualMetaList = data.getVisual('visualMeta'); @@ -212,9 +212,7 @@ function getVisualGradient( let visualMeta; for (let i = visualMetaList.length - 1; i >= 0; i--) { - const dimIndex = visualMetaList[i].dimension; - const dimName = data.dimensions[dimIndex]; - const dimInfo = data.getDimensionInfo(dimName); + const dimInfo = data.getDimensionInfo(visualMetaList[i].dimension); coordDim = (dimInfo && dimInfo.coordDim) as 'x' | 'y'; // Can only be x or y if (coordDim === 'x' || coordDim === 'y') { @@ -295,7 +293,7 @@ function getVisualGradient( function getIsIgnoreFunc( seriesModel: LineSeriesModel, - data: List, + data: SeriesData, coordSys: Cartesian2D ) { const showAllSymbol = seriesModel.get('showAllSymbol'); @@ -337,7 +335,7 @@ function getIsIgnoreFunc( function canShowAllSymbolForCategory( categoryAxis: Axis2D, - data: List + data: SeriesData ) { // In mose cases, line is monotonous on category axis, and the label size // is close with each other. So we check the symbol size and some of the @@ -549,7 +547,7 @@ class LineView extends ChartView { _clipShapeForSymbol: CoordinateSystemClipArea; - _data: List; + _data: SeriesData; init() { const lineGroup = new graphic.Group(); @@ -967,7 +965,7 @@ class LineView extends ChartView { } _initSymbolLabelAnimation( - data: List, + data: SeriesData, coordSys: Polar | Cartesian2D, clipShape: PolarArea | Cartesian2DArea ) { @@ -1118,7 +1116,7 @@ class LineView extends ChartView { _endLabelOnDuring( percent: number, clipRect: graphic.Rect, - data: List, + data: SeriesData, animationRecord: EndLabelAnimationRecord, valueAnimation: boolean, endLabelModel: Model, @@ -1205,7 +1203,7 @@ class LineView extends ChartView { */ // FIXME Two value axis _doUpdateAnimation( - data: List, + data: SeriesData, stackedOnPoints: ArrayLike, coordSys: Cartesian2D | Polar, api: ExtensionAPI, diff --git a/src/chart/line/helper.ts b/src/chart/line/helper.ts index 200824f2cb..e58d2e70e0 100644 --- a/src/chart/line/helper.ts +++ b/src/chart/line/helper.ts @@ -21,7 +21,7 @@ import {isDimensionStacked} from '../../data/helper/dataStackHelper'; import {map} from 'zrender/src/core/util'; import type Polar from '../../coord/polar/Polar'; import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import Axis from '../../coord/Axis'; import type { LineSeriesOption } from './LineSeries'; @@ -39,7 +39,7 @@ interface CoordInfo { export function prepareDataCoordInfo( coordSys: Cartesian2D | Polar, - data: List, + data: SeriesData, valueOrigin?: LineSeriesOption['areaStyle']['origin'] ): CoordInfo { const baseAxis = coordSys.getBaseAxis(); @@ -109,7 +109,7 @@ function getValueStart(valueAxis: Axis, valueOrigin: LineSeriesOption['areaStyle export function getStackedOnPoint( dataCoordInfo: CoordInfo, coordSys: Cartesian2D | Polar, - data: List, + data: SeriesData, idx: number ) { let value = NaN; diff --git a/src/chart/line/lineAnimationDiff.ts b/src/chart/line/lineAnimationDiff.ts index b491665435..60a93a1b1c 100644 --- a/src/chart/line/lineAnimationDiff.ts +++ b/src/chart/line/lineAnimationDiff.ts @@ -18,7 +18,7 @@ */ import {prepareDataCoordInfo, getStackedOnPoint} from './helper'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; import type Polar from '../../coord/polar/Polar'; import { LineSeriesOption } from './LineSeries'; @@ -30,7 +30,7 @@ interface DiffItem { idx1?: number } -function diffData(oldData: List, newData: List) { +function diffData(oldData: SeriesData, newData: SeriesData) { const diffResult: DiffItem[] = []; newData.diff(oldData) @@ -49,7 +49,7 @@ function diffData(oldData: List, newData: List) { } export default function lineAnimationDiff( - oldData: List, newData: List, + oldData: SeriesData, newData: SeriesData, oldStackedOnPoints: ArrayLike, newStackedOnPoints: ArrayLike, oldCoordSys: Cartesian2D | Polar, newCoordSys: Cartesian2D | Polar, oldValueOrigin: LineSeriesOption['areaStyle']['origin'], diff --git a/src/chart/lines/LinesSeries.ts b/src/chart/lines/LinesSeries.ts index 7da350693a..65355e062f 100644 --- a/src/chart/lines/LinesSeries.ts +++ b/src/chart/lines/LinesSeries.ts @@ -20,7 +20,7 @@ /* global Uint32Array, Float64Array, Float32Array */ import SeriesModel from '../../model/Series'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { concatArray, mergeAll, map } from 'zrender/src/core/util'; import CoordinateSystem from '../../core/CoordinateSystem'; import { @@ -298,7 +298,7 @@ class LinesSeriesModel extends SeriesModel { } } - const lineData = new List(['value'], this); + const lineData = new SeriesData(['value'], this); lineData.hasItemOption = false; lineData.initData(option.data, [], function (dataItem, dimName, dataIndex, dimIndex) { diff --git a/src/chart/lines/LinesView.ts b/src/chart/lines/LinesView.ts index ad1089ea33..6c4479da44 100644 --- a/src/chart/lines/LinesView.ts +++ b/src/chart/lines/LinesView.ts @@ -31,7 +31,7 @@ import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; import CanvasPainter from 'zrender/src/canvas/Painter'; import { StageHandlerProgressParams, StageHandlerProgressExecutor } from '../../util/types'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import type Polar from '../../coord/polar/Polar'; import type Cartesian2D from '../../coord/cartesian/Cartesian2D'; @@ -90,7 +90,7 @@ class LinesView extends ChartView { } } - lineDraw.updateData(data as List); + lineDraw.updateData(data as SeriesData); const clipPath = seriesModel.get('clip', true) && createClipPath( (seriesModel.coordinateSystem as Polar | Cartesian2D), false, seriesModel @@ -156,7 +156,7 @@ class LinesView extends ChartView { } } - _updateLineDraw(data: List, seriesModel: LinesSeriesModel) { + _updateLineDraw(data: SeriesData, seriesModel: LinesSeriesModel) { let lineDraw = this._lineDraw; const hasEffect = this._showEffect(seriesModel); const isPolyline = !!seriesModel.get('polyline'); diff --git a/src/chart/lines/linesVisual.ts b/src/chart/lines/linesVisual.ts index 1e4c931b2b..0e18b691e5 100644 --- a/src/chart/lines/linesVisual.ts +++ b/src/chart/lines/linesVisual.ts @@ -18,7 +18,7 @@ */ import { StageHandler } from '../../util/types'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import LinesSeriesModel, { LinesDataItemOption } from './LinesSeries'; import Model from '../../model/Model'; import { LineDataVisual } from '../../visual/commonVisualTypes'; @@ -37,7 +37,7 @@ const linesVisual: StageHandler = { reset(seriesModel: LinesSeriesModel) { const symbolType = normalize(seriesModel.get('symbol')); const symbolSize = normalize(seriesModel.get('symbolSize')); - const data = seriesModel.getData() as List; + const data = seriesModel.getData() as SeriesData; data.setVisual('fromSymbol', symbolType && symbolType[0]); data.setVisual('toSymbol', symbolType && symbolType[1]); @@ -45,7 +45,7 @@ const linesVisual: StageHandler = { data.setVisual('toSymbolSize', symbolSize && symbolSize[1]); function dataEach( - data: List, + data: SeriesData, idx: number ): void { const itemModel = data.getItemModel(idx) as Model; diff --git a/src/chart/map/MapSeries.ts b/src/chart/map/MapSeries.ts index f0049defc8..2081886e31 100644 --- a/src/chart/map/MapSeries.ts +++ b/src/chart/map/MapSeries.ts @@ -19,7 +19,7 @@ import * as zrUtil from 'zrender/src/core/util'; -import createListSimply from '../helper/createListSimply'; +import createSeriesDataSimply from '../helper/createSeriesDataSimply'; import SeriesModel from '../../model/Series'; import geoSourceManager from '../../coord/geo/geoSourceManager'; import {makeSeriesEncodeForNameBased} from '../../data/helper/sourceHelper'; @@ -36,7 +36,7 @@ import { } from '../../util/types'; import { Dictionary } from 'zrender/src/core/types'; import GeoModel, { GeoCommonOptionMixin, GeoItemStyleOption } from '../../coord/geo/GeoModel'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import Model from '../../model/Model'; import Geo from '../../coord/geo/Geo'; import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup'; @@ -101,7 +101,7 @@ class MapSeries extends SeriesModel { // ----------------- // Injected outside - originalData: List; + originalData: SeriesData; mainSeries: MapSeries; // Only first map series of same mapType will drawMap. needsDrawMap: boolean = false; @@ -109,8 +109,8 @@ class MapSeries extends SeriesModel { seriesGroup: MapSeries[] = []; - getInitialData(this: MapSeries, option: MapSeriesOption): List { - const data = createListSimply(this, { + getInitialData(this: MapSeries, option: MapSeriesOption): SeriesData { + const data = createSeriesDataSimply(this, { coordDimensions: ['value'], encodeDefaulter: zrUtil.curry(makeSeriesEncodeForNameBased, this) }); diff --git a/src/chart/map/mapDataStatistic.ts b/src/chart/map/mapDataStatistic.ts index 0df4040f29..504f5484ff 100644 --- a/src/chart/map/mapDataStatistic.ts +++ b/src/chart/map/mapDataStatistic.ts @@ -19,12 +19,12 @@ import * as zrUtil from 'zrender/src/core/util'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import MapSeries, { MapValueCalculationType } from './MapSeries'; import GlobalModel from '../../model/Global'; // FIXME 公用? -function dataStatistics(datas: List[], statisticType: MapValueCalculationType): List { +function dataStatistics(datas: SeriesData[], statisticType: MapValueCalculationType): SeriesData { const dataNameMap = {} as {[mapKey: string]: number[]}; zrUtil.each(datas, function (data) { diff --git a/src/chart/parallel/ParallelSeries.ts b/src/chart/parallel/ParallelSeries.ts index aaca025b05..c75ea4e8fd 100644 --- a/src/chart/parallel/ParallelSeries.ts +++ b/src/chart/parallel/ParallelSeries.ts @@ -20,7 +20,7 @@ import {each, bind} from 'zrender/src/core/util'; import SeriesModel from '../../model/Series'; -import createListFromArray from '../helper/createListFromArray'; +import createSeriesData from '../helper/createSeriesData'; import { SeriesOption, SeriesEncodeOptionMixin, @@ -35,7 +35,7 @@ import { OptionEncode } from '../../util/types'; import GlobalModel from '../../model/Global'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { ParallelActiveState, ParallelAxisOption } from '../../coord/parallel/AxisModel'; import Parallel from '../../coord/parallel/Parallel'; import ParallelModel from '../../coord/parallel/ParallelModel'; @@ -91,8 +91,8 @@ class ParallelSeriesModel extends SeriesModel { coordinateSystem: Parallel; - getInitialData(this: ParallelSeriesModel, option: ParallelSeriesOption, ecModel: GlobalModel): List { - return createListFromArray(this.getSource(), this, { + getInitialData(this: ParallelSeriesModel, option: ParallelSeriesOption, ecModel: GlobalModel): SeriesData { + return createSeriesData(null, this, { useEncodeDefaulter: bind(makeDefaultEncode, null, this) }); } diff --git a/src/chart/parallel/ParallelView.ts b/src/chart/parallel/ParallelView.ts index c0b57142a4..1704b09c90 100644 --- a/src/chart/parallel/ParallelView.ts +++ b/src/chart/parallel/ParallelView.ts @@ -20,7 +20,7 @@ import * as graphic from '../../util/graphic'; import { setStatesStylesFromModel, enableHoverEmphasis } from '../../util/states'; import ChartView from '../../view/Chart'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import ParallelSeriesModel, { ParallelSeriesDataItemOption } from './ParallelSeries'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; @@ -42,7 +42,7 @@ class ParallelView extends ChartView { private _dataGroup = new graphic.Group(); - private _data: List; + private _data: SeriesData; private _initialized = false; @@ -160,7 +160,7 @@ function createGridClipShape(coordSys: Parallel, seriesModel: ParallelSeriesMode return rectEl; } -function createLinePoints(data: List, dataIndex: number, dimensions: string[], coordSys: Parallel) { +function createLinePoints(data: SeriesData, dataIndex: number, dimensions: string[], coordSys: Parallel) { const points = []; for (let i = 0; i < dimensions.length; i++) { const dimName = dimensions[i]; @@ -172,7 +172,7 @@ function createLinePoints(data: List, dataIndex: number, dimensions: string[], c return points; } -function addEl(data: List, dataGroup: graphic.Group, dataIndex: number, dimensions: string[], coordSys: Parallel) { +function addEl(data: SeriesData, dataGroup: graphic.Group, dataIndex: number, dimensions: string[], coordSys: Parallel) { const points = createLinePoints(data, dataIndex, dimensions, coordSys); const line = new graphic.Polyline({ shape: {points: points}, @@ -195,7 +195,7 @@ function makeSeriesScope(seriesModel: ParallelSeriesModel): ParallelDrawSeriesSc function updateElCommon( el: graphic.Polyline, - data: List, + data: SeriesData, dataIndex: number, seriesScope: ParallelDrawSeriesScope ) { diff --git a/src/chart/pie/PieSeries.ts b/src/chart/pie/PieSeries.ts index e8cfcccb20..b68726285e 100644 --- a/src/chart/pie/PieSeries.ts +++ b/src/chart/pie/PieSeries.ts @@ -17,7 +17,7 @@ * under the License. */ -import createListSimply from '../helper/createListSimply'; +import createSeriesDataSimply from '../helper/createSeriesDataSimply'; import * as zrUtil from 'zrender/src/core/util'; import * as modelUtil from '../../util/model'; import { getPercentWithPrecision } from '../../util/number'; @@ -38,7 +38,7 @@ import { SeriesLabelOption, DefaultEmphasisFocus } from '../../util/types'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; interface PieItemStyleOption extends ItemStyleOption { // can be 10 @@ -154,8 +154,8 @@ class PieSeriesModel extends SeriesModel { /** * @overwrite */ - getInitialData(this: PieSeriesModel): List { - return createListSimply(this, { + getInitialData(this: PieSeriesModel): SeriesData { + return createSeriesDataSimply(this, { coordDimensions: ['value'], encodeDefaulter: zrUtil.curry(makeSeriesEncodeForNameBased, this) }); diff --git a/src/chart/pie/PieView.ts b/src/chart/pie/PieView.ts index 043654d42d..66c07e8fc1 100644 --- a/src/chart/pie/PieView.ts +++ b/src/chart/pie/PieView.ts @@ -26,7 +26,7 @@ import ChartView from '../../view/Chart'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; import { Payload, ColorString } from '../../util/types'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import PieSeriesModel, {PieDataItemOption} from './PieSeries'; import labelLayout from './labelLayout'; import { setLabelLineStyle, getLabelLineStatesModels } from '../../label/labelGuideHelper'; @@ -40,7 +40,7 @@ import { getBasicPieLayout } from './pieLayout'; */ class PiePiece extends graphic.Sector { - constructor(data: List, idx: number, startAngle: number) { + constructor(data: SeriesData, idx: number, startAngle: number) { super(); this.z2 = 2; @@ -52,7 +52,7 @@ class PiePiece extends graphic.Sector { this.updateData(data, idx, startAngle, true); } - updateData(data: List, idx: number, startAngle?: number, firstCreate?: boolean): void { + updateData(data: SeriesData, idx: number, startAngle?: number, firstCreate?: boolean): void { const sector = this; const seriesModel = data.hostModel as PieSeriesModel; @@ -158,7 +158,7 @@ class PiePiece extends graphic.Sector { enableHoverEmphasis(this, emphasisModel.get('focus'), emphasisModel.get('blurScope')); } - private _updateLabel(seriesModel: PieSeriesModel, data: List, idx: number): void { + private _updateLabel(seriesModel: PieSeriesModel, data: SeriesData, idx: number): void { const sector = this; const itemModel = data.getItemModel(idx); const labelLineModel = itemModel.getModel('labelLine'); @@ -223,7 +223,7 @@ class PieView extends ChartView { ignoreLabelLineUpdate = true; private _sectorGroup: graphic.Group; - private _data: List; + private _data: SeriesData; private _emptyCircleSector: graphic.Sector; init(): void { diff --git a/src/chart/radar/RadarSeries.ts b/src/chart/radar/RadarSeries.ts index 960bd36701..ffc8ea47d1 100644 --- a/src/chart/radar/RadarSeries.ts +++ b/src/chart/radar/RadarSeries.ts @@ -18,7 +18,7 @@ */ import SeriesModel from '../../model/Series'; -import createListSimply from '../helper/createListSimply'; +import createSeriesDataSimply from '../helper/createSeriesDataSimply'; import * as zrUtil from 'zrender/src/core/util'; import LegendVisualProvider from '../../visual/LegendVisualProvider'; import { @@ -35,7 +35,7 @@ import { CallbackDataParams } from '../../util/types'; import GlobalModel from '../../model/Global'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import Radar from '../../coord/radar/Radar'; import { createTooltipMarkup, retrieveVisualColorForTooltipMarker @@ -88,8 +88,8 @@ class RadarSeriesModel extends SeriesModel { } - getInitialData(option: RadarSeriesOption, ecModel: GlobalModel): List { - return createListSimply(this, { + getInitialData(option: RadarSeriesOption, ecModel: GlobalModel): SeriesData { + return createSeriesDataSimply(this, { generateCoord: 'indicator_', generateCoordCount: Infinity }); diff --git a/src/chart/radar/RadarView.ts b/src/chart/radar/RadarView.ts index 70e3d43f7d..f38d1df866 100644 --- a/src/chart/radar/RadarView.ts +++ b/src/chart/radar/RadarView.ts @@ -24,7 +24,7 @@ import * as symbolUtil from '../../util/symbol'; import ChartView from '../../view/Chart'; import RadarSeriesModel, { RadarSeriesDataItemOption } from './RadarSeries'; import ExtensionAPI from '../../core/ExtensionAPI'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { ColorString } from '../../util/types'; import GlobalModel from '../../model/Global'; import { VectorArray } from 'zrender/src/core/vector'; @@ -40,7 +40,7 @@ class RadarView extends ChartView { static type = 'radar'; type = RadarView.type; - private _data: List; + private _data: SeriesData; render(seriesModel: RadarSeriesModel, ecModel: GlobalModel, api: ExtensionAPI) { const polar = seriesModel.coordinateSystem; @@ -49,7 +49,7 @@ class RadarView extends ChartView { const data = seriesModel.getData(); const oldData = this._data; - function createSymbol(data: List, idx: number) { + function createSymbol(data: SeriesData, idx: number) { const symbolType = data.getItemVisual(idx, 'symbol') as string || 'circle'; if (symbolType === 'none') { return; @@ -77,7 +77,7 @@ class RadarView extends ChartView { oldPoints: VectorArray[], newPoints: VectorArray[], symbolGroup: graphic.Group, - data: List, + data: SeriesData, idx: number, isInit?: boolean ) { @@ -243,7 +243,7 @@ class RadarView extends ChartView { const pathEmphasisState = symbolPath.ensureState('emphasis'); pathEmphasisState.style = zrUtil.clone(itemHoverStyle); - let defaultText = data.get(data.dimensions[symbolPath.__dimIdx], idx); + let defaultText = data.getStore().get(data.getDimensionIndex(symbolPath.__dimIdx), idx); (defaultText == null || isNaN(defaultText as number)) && (defaultText = ''); setLabelStyle( diff --git a/src/chart/sankey/SankeySeries.ts b/src/chart/sankey/SankeySeries.ts index a915ef6bdd..6131f56e1a 100644 --- a/src/chart/sankey/SankeySeries.ts +++ b/src/chart/sankey/SankeySeries.ts @@ -36,7 +36,7 @@ import { DefaultEmphasisFocus } from '../../util/types'; import GlobalModel from '../../model/Global'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { LayoutRect } from '../../util/layout'; import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup'; @@ -148,9 +148,6 @@ class SankeySeriesModel extends SeriesModel { /** * Init a graph data structure from data in option series - * - * @param {Object} option the object used to config echarts view - * @return {module:echarts/data/List} storage initial data */ getInitialData(option: SankeySeriesOption, ecModel: GlobalModel) { const links = option.edges || option.links; @@ -173,7 +170,7 @@ class SankeySeriesModel extends SeriesModel { const graph = createGraphFromNodeEdge(nodes, links, this, true, beforeLink); return graph.data; } - function beforeLink(nodeData: List, edgeData: List) { + function beforeLink(nodeData: SeriesData, edgeData: SeriesData) { nodeData.wrapMethod('getItemModel', function (model: Model, idx: number) { const seriesModel = model.parentModel as SankeySeriesModel; const layout = seriesModel.getData().getItemLayout(idx); diff --git a/src/chart/sankey/SankeyView.ts b/src/chart/sankey/SankeyView.ts index 2ef69d71d5..863f1c30c1 100644 --- a/src/chart/sankey/SankeyView.ts +++ b/src/chart/sankey/SankeyView.ts @@ -25,7 +25,7 @@ import SankeySeriesModel, { SankeyEdgeItemOption, SankeyNodeItemOption } from '. import ChartView from '../../view/Chart'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { RectLike } from 'zrender/src/core/BoundingRect'; import { setLabelStyle, getLabelStatesModels } from '../../label/labelStyle'; import { getECData } from '../../util/innerStore'; @@ -107,7 +107,7 @@ class SankeyView extends ChartView { private _focusAdjacencyDisabled = false; - private _data: List; + private _data: SeriesData; render(seriesModel: SankeySeriesModel, ecModel: GlobalModel, api: ExtensionAPI) { const sankeyView = this; diff --git a/src/chart/scatter/ScatterSeries.ts b/src/chart/scatter/ScatterSeries.ts index a217c5a659..40c4c73256 100644 --- a/src/chart/scatter/ScatterSeries.ts +++ b/src/chart/scatter/ScatterSeries.ts @@ -17,7 +17,7 @@ * under the License. */ -import createListFromArray from '../helper/createListFromArray'; +import createSeriesData from '../helper/createSeriesData'; import SeriesModel from '../../model/Series'; import { SeriesOption, @@ -39,7 +39,7 @@ import { DefaultEmphasisFocus } from '../../util/types'; import GlobalModel from '../../model/Global'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { BrushCommonSelectorsForSeries } from '../../component/brush/selector'; interface ScatterStateOption { @@ -84,8 +84,8 @@ class ScatterSeriesModel extends SeriesModel { hasSymbolVisual = true; - getInitialData(option: ScatterSeriesOption, ecModel: GlobalModel): List { - return createListFromArray(this.getSource(), this, { + getInitialData(option: ScatterSeriesOption, ecModel: GlobalModel): SeriesData { + return createSeriesData(null, this, { useEncodeDefaulter: true }); } @@ -109,7 +109,7 @@ class ScatterSeriesModel extends SeriesModel { return progressiveThreshold; } - brushSelector(dataIndex: number, data: List, selectors: BrushCommonSelectorsForSeries): boolean { + brushSelector(dataIndex: number, data: SeriesData, selectors: BrushCommonSelectorsForSeries): boolean { return selectors.point(data.getItemLayout(dataIndex)); } diff --git a/src/chart/scatter/ScatterView.ts b/src/chart/scatter/ScatterView.ts index 4a7fbb0b4d..1bf6e3ca32 100644 --- a/src/chart/scatter/ScatterView.ts +++ b/src/chart/scatter/ScatterView.ts @@ -25,7 +25,7 @@ import ChartView from '../../view/Chart'; import ScatterSeriesModel from './ScatterSeries'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { TaskProgressParams } from '../../core/task'; import type { StageHandlerProgressExecutor } from '../../util/types'; @@ -99,7 +99,7 @@ class ScatterView extends ChartView { return seriesModel.get('clip', true) ? clipArea : null; } - _updateSymbolDraw(data: List, seriesModel: ScatterSeriesModel) { + _updateSymbolDraw(data: SeriesData, seriesModel: ScatterSeriesModel) { let symbolDraw = this._symbolDraw; const pipelineContext = seriesModel.pipelineContext; const isLargeDraw = pipelineContext.large; diff --git a/src/chart/sunburst/SunburstSeries.ts b/src/chart/sunburst/SunburstSeries.ts index afea2c7b39..f53b7c3150 100644 --- a/src/chart/sunburst/SunburstSeries.ts +++ b/src/chart/sunburst/SunburstSeries.ts @@ -34,7 +34,7 @@ import { SunburstColorByMixin } from '../../util/types'; import GlobalModel from '../../model/Global'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import Model from '../../model/Model'; import enableAriaDecalForTree from '../helper/enableAriaDecalForTree'; @@ -166,7 +166,7 @@ class SunburstSeriesModel extends SeriesModel { // to choose mappings approach among old shapes and new shapes. const tree = Tree.createTree(root, this, beforeLink); - function beforeLink(nodeData: List) { + function beforeLink(nodeData: SeriesData) { nodeData.wrapMethod('getItemModel', function (model, idx) { const node = tree.getNodeByDataIndex(idx); const levelModel = levelModels[node.depth]; diff --git a/src/chart/themeRiver/ThemeRiverSeries.ts b/src/chart/themeRiver/ThemeRiverSeries.ts index 75d59fb927..4cd6c57fa2 100644 --- a/src/chart/themeRiver/ThemeRiverSeries.ts +++ b/src/chart/themeRiver/ThemeRiverSeries.ts @@ -18,9 +18,9 @@ */ import SeriesModel from '../../model/Series'; -import createDimensions from '../../data/helper/createDimensions'; +import prepareSeriesDataSchema from '../../data/helper/createDimensions'; import {getDimensionTypeByAxis} from '../../data/helper/dimensionHelper'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import * as zrUtil from 'zrender/src/core/util'; import {groupData, SINGLE_REFERRING} from '../../util/model'; import LegendVisualProvider from '../../visual/LegendVisualProvider'; @@ -152,7 +152,7 @@ class ThemeRiverSeriesModel extends SeriesModel { * @param option the initial option that user gived * @param ecModel the model object for themeRiver option */ - getInitialData(option: ThemeRiverSeriesOption, ecModel: GlobalModel): List { + getInitialData(option: ThemeRiverSeriesOption, ecModel: GlobalModel): SeriesData { const singleAxisModel = this.getReferringComponents('singleAxis', SINGLE_REFERRING).models[0]; @@ -177,7 +177,7 @@ class ThemeRiverSeriesModel extends SeriesModel { } } - const dimensionsInfo = createDimensions(data, { + const { dimensions } = prepareSeriesDataSchema(data, { coordDimensions: ['single'], dimensionsDefine: [ { @@ -200,7 +200,7 @@ class ThemeRiverSeriesModel extends SeriesModel { } }); - const list = new List(dimensionsInfo, this); + const list = new SeriesData(dimensions, this); list.initData(data); return list; diff --git a/src/chart/themeRiver/themeRiverLayout.ts b/src/chart/themeRiver/themeRiverLayout.ts index 6de3e658d1..ec51217113 100644 --- a/src/chart/themeRiver/themeRiverLayout.ts +++ b/src/chart/themeRiver/themeRiverLayout.ts @@ -23,7 +23,7 @@ import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; import ThemeRiverSeriesModel, { ThemeRiverSeriesOption } from './ThemeRiverSeries'; import { RectLike } from 'zrender/src/core/BoundingRect'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; export interface ThemeRiverLayoutInfo { rect: RectLike @@ -75,7 +75,7 @@ export default function themeRiverLayout(ecModel: GlobalModel, api: ExtensionAPI * @param seriesModel the model object of themeRiver series * @param height value used to compute every series height */ -function doThemeRiverLayout(data: List, seriesModel: ThemeRiverSeriesModel, height: number) { +function doThemeRiverLayout(data: SeriesData, seriesModel: ThemeRiverSeriesModel, height: number) { if (!data.count()) { return; } diff --git a/src/chart/tree/TreeSeries.ts b/src/chart/tree/TreeSeries.ts index 2c197e444f..118ffb2fad 100644 --- a/src/chart/tree/TreeSeries.ts +++ b/src/chart/tree/TreeSeries.ts @@ -33,7 +33,7 @@ import { CallbackDataParams, DefaultEmphasisFocus } from '../../util/types'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import View from '../../coord/View'; import { LayoutRect } from '../../util/layout'; import Model from '../../model/Model'; @@ -141,10 +141,8 @@ class TreeSeriesModel extends SeriesModel { /** * Init a tree data structure from data in option series - * @param option the object used to config echarts view - * @return storage initial data */ - getInitialData(option: TreeSeriesOption): List { + getInitialData(option: TreeSeriesOption): SeriesData { //create an virtual root const root: TreeSeriesNodeItemOption = { @@ -157,7 +155,7 @@ class TreeSeriesModel extends SeriesModel { const tree = Tree.createTree(root, this, beforeLink); - function beforeLink(nodeData: List) { + function beforeLink(nodeData: SeriesData) { nodeData.wrapMethod('getItemModel', function (model, idx) { const node = tree.getNodeByDataIndex(idx); if (!(node && node.children.length && node.isExpand)) { diff --git a/src/chart/tree/TreeView.ts b/src/chart/tree/TreeView.ts index 5c7941c172..6f9b44aee7 100644 --- a/src/chart/tree/TreeView.ts +++ b/src/chart/tree/TreeView.ts @@ -34,7 +34,7 @@ import Path, { PathProps, PathStyleProps } from 'zrender/src/graphic/Path'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; import { TreeNode } from '../../data/Tree'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { setStatesStylesFromModel, setStatesFlag, setDefaultStateProxy, HOVER_STATE_BLUR } from '../../util/states'; import { AnimationOption, ECElement } from '../../util/types'; @@ -134,7 +134,7 @@ class TreeView extends ChartView { private _controller: RoamController; private _controllerHost: RoamControllerHost; - private _data: List; + private _data: SeriesData; private _nodeScaleRatio: number; private _min: number[]; @@ -358,7 +358,7 @@ class TreeView extends ChartView { } -function symbolNeedsDraw(data: List, dataIndex: number) { +function symbolNeedsDraw(data: SeriesData, dataIndex: number) { const layout = data.getItemLayout(dataIndex); return layout @@ -367,7 +367,7 @@ function symbolNeedsDraw(data: List, dataIndex: number) { function updateNode( - data: List, + data: SeriesData, dataIndex: number, symbolEl: TreeSymbol, group: graphic.Group, @@ -597,7 +597,7 @@ function drawEdge( function removeNodeEdge( node: TreeNode, - data: List, + data: SeriesData, group: graphic.Group, seriesModel: TreeSeriesModel, removeAnimationOpt: AnimationOption @@ -678,7 +678,7 @@ function getSourceNode(virtualRoot: TreeNode, node: TreeNode): { source: TreeNod } function removeNode( - data: List, + data: SeriesData, dataIndex: number, symbolEl: TreeSymbol, group: graphic.Group, diff --git a/src/chart/treemap/TreemapSeries.ts b/src/chart/treemap/TreemapSeries.ts index efc0f06aa1..287540e9c4 100644 --- a/src/chart/treemap/TreemapSeries.ts +++ b/src/chart/treemap/TreemapSeries.ts @@ -41,7 +41,7 @@ import { } from '../../util/types'; import GlobalModel from '../../model/Global'; import { LayoutRect } from '../../util/layout'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { normalizeToArray } from '../../util/model'; import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup'; import enableAriaDecalForTree from '../helper/enableAriaDecalForTree'; @@ -377,7 +377,7 @@ class TreemapSeriesModel extends SeriesModel { // to choose mappings approach among old shapes and new shapes. const tree = Tree.createTree(root, this, beforeLink); - function beforeLink(nodeData: List) { + function beforeLink(nodeData: SeriesData) { nodeData.wrapMethod('getItemModel', function (model, idx) { const node = tree.getNodeByDataIndex(idx); const levelModel = node ? levelModels[node.depth] : null; diff --git a/src/component/dataZoom/AxisProxy.ts b/src/component/dataZoom/AxisProxy.ts index 2d7b0f8a52..e7be785fca 100644 --- a/src/component/dataZoom/AxisProxy.ts +++ b/src/component/dataZoom/AxisProxy.ts @@ -245,16 +245,6 @@ class AxisProxy { // Culculate data window and data extent, and record them. this._dataExtent = calculateDataExtent(this, this._dimName, targetSeries); - // this.hasSeriesStacked = false; - // each(targetSeries, function (series) { - // let data = series.getData(); - // let dataDim = data.mapDimension(this._dimName); - // let stackedDimension = data.getCalculationInfo('stackedDimension'); - // if (stackedDimension && stackedDimension === dataDim) { - // this.hasSeriesStacked = true; - // } - // }, this); - // `calculateDataWindow` uses min/maxSpan. this._updateMinMaxSpan(); @@ -311,12 +301,14 @@ class AxisProxy { } if (filterMode === 'weakFilter') { + const store = seriesData.getStore(); + const dataDimIndices = zrUtil.map(dataDims, dim => seriesData.getDimensionIndex(dim), seriesData); seriesData.filterSelf(function (dataIndex) { let leftOut; let rightOut; let hasValue; for (let i = 0; i < dataDims.length; i++) { - const value = seriesData.get(dataDims[i], dataIndex) as number; + const value = store.get(dataDimIndices[i], dataIndex) as number; const thisHasValue = !isNaN(value); const thisLeftOut = value < valueWindow[0]; const thisRightOut = value > valueWindow[1]; diff --git a/src/component/helper/MapDraw.ts b/src/component/helper/MapDraw.ts index 31ff42d239..56bf2c54bc 100644 --- a/src/component/helper/MapDraw.ts +++ b/src/component/helper/MapDraw.ts @@ -46,7 +46,7 @@ import { ViewCoordSysTransformInfoPart } from '../../coord/View'; import { GeoSVGGraphicRecord, GeoSVGResource } from '../../coord/geo/GeoSVGResource'; import Displayable from 'zrender/src/graphic/Displayable'; import Element from 'zrender/src/Element'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { GeoJSONRegion } from '../../coord/geo/Region'; import { SVGNodeTagLower } from 'zrender/src/tool/parseSVG'; import { makeInner } from '../../util/model'; @@ -62,7 +62,7 @@ interface ViewBuildContext { api: ExtensionAPI; geo: Geo; mapOrGeoModel: GeoModel | MapSeries; - data: List; + data: SeriesData; isVisualEncodedByVisualMap: boolean; isGeo: boolean; transformInfoRaw: ViewCoordSysTransformInfoPart; diff --git a/src/component/marker/MarkAreaView.ts b/src/component/marker/MarkAreaView.ts index bb20679489..e23403cbfe 100644 --- a/src/component/marker/MarkAreaView.ts +++ b/src/component/marker/MarkAreaView.ts @@ -20,19 +20,19 @@ // TODO Optimize on polar import * as colorUtil from 'zrender/src/tool/color'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import * as numberUtil from '../../util/number'; import * as graphic from '../../util/graphic'; import { enableHoverEmphasis, setStatesStylesFromModel } from '../../util/states'; import * as markerHelper from './markerHelper'; import MarkerView from './MarkerView'; -import { retrieve, mergeAll, map, defaults, curry, filter, HashMap } from 'zrender/src/core/util'; +import { retrieve, mergeAll, map, curry, filter, HashMap, extend } from 'zrender/src/core/util'; import { ScaleDataValue, ParsedValue, ZRColor } from '../../util/types'; import { CoordinateSystem, isCoordinateSystemType } from '../../coord/CoordinateSystem'; import MarkAreaModel, { MarkArea2DDataItemOption } from './MarkAreaModel'; import SeriesModel from '../../model/Series'; import Cartesian2D from '../../coord/cartesian/Cartesian2D'; -import DataDimensionInfo from '../../data/DataDimensionInfo'; +import SeriesDimensionDefine from '../../data/SeriesDimensionDefine'; import GlobalModel from '../../model/Global'; import ExtensionAPI from '../../core/ExtensionAPI'; import MarkerModel from './MarkerModel'; @@ -47,7 +47,7 @@ interface MarkAreaDrawGroup { } const inner = makeInner<{ - data: List + data: SeriesData }, MarkAreaDrawGroup>(); // Merge two ends option into one. @@ -137,7 +137,7 @@ function markAreaFilter(coordSys: CoordinateSystem, item: MarkAreaMergedItemOpti // dims can be ['x0', 'y0'], ['x1', 'y1'], ['x0', 'y1'], ['x1', 'y0'] function getSingleMarkerEndPoint( - data: List, + data: SeriesData, idx: number, dims: typeof dimPermutations[number], seriesModel: SeriesModel, @@ -362,8 +362,8 @@ function createList( maModel: MarkAreaModel ) { - let coordDimsInfos: DataDimensionInfo[]; - let areaData: List; + let coordDimsInfos: SeriesDimensionDefine[]; + let areaData: SeriesData; const dims = ['x0', 'y0', 'x1', 'y1']; if (coordSys) { coordDimsInfos = map(coordSys && coordSys.dimensions, function (coordDim) { @@ -372,11 +372,13 @@ function createList( data.mapDimension(coordDim) ) || {}; // In map series data don't have lng and lat dimension. Fallback to same with coordSys - return defaults({ - name: coordDim - }, info); + return extend(extend({}, info), { + name: coordDim, + // DON'T use ordinalMeta to parse and collect ordinal. + ordinalMeta: null + }); }); - areaData = new List(map(dims, function (dim, idx) { + areaData = new SeriesData(map(dims, function (dim, idx) { return { name: dim, type: coordDimsInfos[idx % 2].type @@ -388,7 +390,7 @@ function createList( name: 'value', type: 'float' }]; - areaData = new List(coordDimsInfos, maModel); + areaData = new SeriesData(coordDimsInfos, maModel); } let optData = map(maModel.get('data'), curry( diff --git a/src/component/marker/MarkLineView.ts b/src/component/marker/MarkLineView.ts index d4ed839116..212ce68ced 100644 --- a/src/component/marker/MarkLineView.ts +++ b/src/component/marker/MarkLineView.ts @@ -17,7 +17,7 @@ * under the License. */ -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import * as numberUtil from '../../util/number'; import * as markerHelper from './markerHelper'; import LineDraw from '../../chart/helper/LineDraw'; @@ -57,9 +57,9 @@ type MarkLineMergedItemOption = MarkLine2DDataItemOption[number]; const inner = makeInner<{ // from data - from: List + from: SeriesData // to data - to: List + to: SeriesData }, MarkLineModel>(); const markLineTransform = function ( @@ -196,7 +196,7 @@ function markLineFilter( } function updateSingleMarkerEndLayout( - data: List, + data: SeriesData, idx: number, isFrom: boolean, seriesModel: SeriesModel, @@ -311,7 +311,7 @@ class MarkLineView extends MarkerView { const fromData = mlData.from; const toData = mlData.to; - const lineData = mlData.line as List; + const lineData = mlData.line as SeriesData; inner(mlModel).from = fromData; inner(mlModel).to = toData; @@ -388,7 +388,7 @@ class MarkLineView extends MarkerView { }); function updateDataVisualAndLayout( - data: List, + data: SeriesData, idx: number, isFrom: boolean ) { @@ -442,7 +442,11 @@ function createList(coordSys: CoordinateSystem, seriesModel: SeriesModel, mlMode seriesModel.getData().mapDimension(coordDim) ) || {}; // In map series data don't have lng and lat dimension. Fallback to same with coordSys - return defaults({name: coordDim}, info); + return extend(extend({}, info), { + name: coordDim, + // DON'T use ordinalMeta to parse and collect ordinal. + ordinalMeta: null + }); }); } else { @@ -452,10 +456,10 @@ function createList(coordSys: CoordinateSystem, seriesModel: SeriesModel, mlMode }]; } - const fromData = new List(coordDimsInfos, mlModel); - const toData = new List(coordDimsInfos, mlModel); + const fromData = new SeriesData(coordDimsInfos, mlModel); + const toData = new SeriesData(coordDimsInfos, mlModel); // No dimensions - const lineData = new List([], mlModel); + const lineData = new SeriesData([], mlModel); let optData = map(mlModel.get('data'), curry( markLineTransform, seriesModel, coordSys, mlModel diff --git a/src/component/marker/MarkPointView.ts b/src/component/marker/MarkPointView.ts index b7cf0a21e4..f0693da1fe 100644 --- a/src/component/marker/MarkPointView.ts +++ b/src/component/marker/MarkPointView.ts @@ -20,7 +20,7 @@ import SymbolDraw from '../../chart/helper/SymbolDraw'; import * as numberUtil from '../../util/number'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import * as markerHelper from './markerHelper'; import MarkerView from './MarkerView'; import { CoordinateSystem } from '../../coord/CoordinateSystem'; @@ -29,13 +29,13 @@ import MarkPointModel, {MarkPointDataItemOption} from './MarkPointModel'; import GlobalModel from '../../model/Global'; import MarkerModel from './MarkerModel'; import ExtensionAPI from '../../core/ExtensionAPI'; -import { HashMap, isFunction, map, defaults, filter, curry } from 'zrender/src/core/util'; +import { HashMap, isFunction, map, defaults, filter, curry, extend } from 'zrender/src/core/util'; import { getECData } from '../../util/innerStore'; import { getVisualFromData } from '../../visual/helper'; import { ZRColor } from '../../util/types'; function updateMarkerLayout( - mpData: List, + mpData: SeriesData, seriesModel: SeriesModel, api: ExtensionAPI ) { @@ -59,7 +59,6 @@ function updateMarkerLayout( const x = mpData.get(coordSys.dimensions[0], idx); const y = mpData.get(coordSys.dimensions[1], idx); point = coordSys.dataToPoint([x, y]); - } // Use x, y if has any @@ -108,7 +107,7 @@ class MarkPointView extends MarkerView { const symbolDraw = symbolDrawMap.get(seriesId) || symbolDrawMap.set(seriesId, new SymbolDraw()); - const mpData = createList(coordSys, seriesModel, mpModel); + const mpData = createData(coordSys, seriesModel, mpModel); // FIXME mpModel.setData(mpData); @@ -176,7 +175,7 @@ class MarkPointView extends MarkerView { } } -function createList( +function createData( coordSys: CoordinateSystem, seriesModel: SeriesModel, mpModel: MarkPointModel @@ -188,7 +187,11 @@ function createList( seriesModel.getData().mapDimension(coordDim) ) || {}; // In map series data don't have lng and lat dimension. Fallback to same with coordSys - return defaults({name: coordDim}, info); + return extend(extend({}, info), { + name: coordDim, + // DON'T use ordinalMeta to parse and collect ordinal. + ordinalMeta: null + }); }); } else { @@ -198,7 +201,7 @@ function createList( }]; } - const mpData = new List(coordDimsInfos, mpModel); + const mpData = new SeriesData(coordDimsInfos, mpModel); let dataOpt = map(mpModel.get('data'), curry( markerHelper.dataTransform, seriesModel )); diff --git a/src/component/marker/MarkerModel.ts b/src/component/marker/MarkerModel.ts index 341bf5b347..6a68558cab 100644 --- a/src/component/marker/MarkerModel.ts +++ b/src/component/marker/MarkerModel.ts @@ -32,7 +32,7 @@ import { } from '../../util/types'; import Model from '../../model/Model'; import GlobalModel from '../../model/Global'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { makeInner, defaultEmphasis } from '../../util/model'; import { createTooltipMarkup } from '../tooltip/tooltipMarkup'; @@ -109,7 +109,7 @@ abstract class MarkerModel extends Com __hostSeries: SeriesModel; - private _data: List; + private _data: SeriesData; /** * @overrite @@ -217,11 +217,11 @@ abstract class MarkerModel extends Com }); } - getData(): List { - return this._data as List; + getData(): SeriesData { + return this._data as SeriesData; } - setData(data: List) { + setData(data: SeriesData) { this._data = data; } diff --git a/src/component/marker/markerHelper.ts b/src/component/marker/markerHelper.ts index 4ae4b7573b..0987473a59 100644 --- a/src/component/marker/markerHelper.ts +++ b/src/component/marker/markerHelper.ts @@ -20,18 +20,18 @@ import * as numberUtil from '../../util/number'; import {isDimensionStacked} from '../../data/helper/dataStackHelper'; import SeriesModel from '../../model/Series'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { MarkerStatisticType, MarkerPositionOption } from './MarkerModel'; import { indexOf, curry, clone, isArray } from 'zrender/src/core/util'; import Axis from '../../coord/Axis'; import { CoordinateSystem } from '../../coord/CoordinateSystem'; -import { ScaleDataValue, ParsedValue } from '../../util/types'; +import { ScaleDataValue, ParsedValue, DimensionLoose, DimensionName } from '../../util/types'; interface MarkerAxisInfo { - valueDataDim: string + valueDataDim: DimensionName valueAxis: Axis baseAxis: Axis - baseDataDim: string + baseDataDim: DimensionName } function hasXOrY(item: MarkerPositionOption) { @@ -42,33 +42,9 @@ function hasXAndY(item: MarkerPositionOption) { return !isNaN(parseFloat(item.x as string)) && !isNaN(parseFloat(item.y as string)); } -// Make it simple, do not visit all stacked value to count precision. -// function getPrecision(data, valueAxisDim, dataIndex) { -// let precision = -1; -// let stackedDim = data.mapDimension(valueAxisDim); -// do { -// precision = Math.max( -// numberUtil.getPrecision(data.get(stackedDim, dataIndex)), -// precision -// ); -// let stackedOnSeries = data.getCalculationInfo('stackedOnSeries'); -// if (stackedOnSeries) { -// let byValue = data.get(data.getCalculationInfo('stackedByDimension'), dataIndex); -// data = stackedOnSeries.getData(); -// dataIndex = data.indexOf(data.getCalculationInfo('stackedByDimension'), byValue); -// stackedDim = data.getCalculationInfo('stackedDimension'); -// } -// else { -// data = null; -// } -// } while (data); - -// return precision; -// } - function markerTypeCalculatorWithExtent( markerType: MarkerStatisticType, - data: List, + data: SeriesData, otherDataDim: string, targetDataDim: string, otherCoordIndex: number, @@ -109,10 +85,6 @@ const markerTypeCalculator = { * Transform markPoint data item to format used in List by do the following * 1. Calculate statistic like `max`, `min`, `average` * 2. Convert `item.xAxis`, `item.yAxis` to `item.coord` array - * @param {module:echarts/model/Series} seriesModel - * @param {module:echarts/coord/*} [coordSys] - * @param {Object} item - * @return {Object} */ export function dataTransform( seriesModel: SeriesModel, @@ -171,7 +143,7 @@ export function dataTransform( export function getAxisInfo( item: MarkerPositionOption, - data: List, + data: SeriesData, coordSys: CoordinateSystem, seriesModel: SeriesModel ) { @@ -194,16 +166,9 @@ export function getAxisInfo( return ret; } -function dataDimToCoordDim(seriesModel: SeriesModel, dataDim: string) { - const data = seriesModel.getData(); - const dimensions = data.dimensions; - dataDim = data.getDimension(dataDim); - for (let i = 0; i < dimensions.length; i++) { - const dimItem = data.getDimensionInfo(dimensions[i]); - if (dimItem.name === dataDim) { - return dimItem.coordDim; - } - } +function dataDimToCoordDim(seriesModel: SeriesModel, dataDim: DimensionLoose): DimensionName { + const dimItem = seriesModel.getData().getDimensionInfo(dataDim); + return dimItem && dimItem.coordDim; } /** @@ -236,7 +201,7 @@ export function dimValueGetter( } export function numCalculate( - data: List, + data: SeriesData, valueDataDim: string, type: MarkerStatisticType ) { diff --git a/src/component/timeline/SliderTimelineModel.ts b/src/component/timeline/SliderTimelineModel.ts index 618e49c4cf..ec20bc948b 100644 --- a/src/component/timeline/SliderTimelineModel.ts +++ b/src/component/timeline/SliderTimelineModel.ts @@ -20,7 +20,7 @@ import TimelineModel, { TimelineOption } from './TimelineModel'; import { DataFormatMixin } from '../../model/mixin/dataFormat'; import { mixin } from 'zrender/src/core/util'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { inheritDefaultOption } from '../../util/component'; export interface SliderTimelineOption extends TimelineOption { @@ -149,7 +149,7 @@ class SliderTimelineModel extends TimelineModel { } interface SliderTimelineModel extends DataFormatMixin { - getData(): List + getData(): SeriesData } mixin(SliderTimelineModel, DataFormatMixin.prototype); diff --git a/src/component/timeline/TimelineModel.ts b/src/component/timeline/TimelineModel.ts index 619f468f3a..ecfd963f3b 100644 --- a/src/component/timeline/TimelineModel.ts +++ b/src/component/timeline/TimelineModel.ts @@ -18,7 +18,7 @@ */ import ComponentModel from '../../model/Component'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import { ComponentOption, BoxLayoutOptionMixin, @@ -171,7 +171,7 @@ class TimelineModel extends ComponentModel { layoutMode = 'box'; - private _data: List; + private _data: SeriesData; private _names: string[]; @@ -275,7 +275,7 @@ class TimelineModel extends ComponentModel { value: 'number' })[axisType] || 'number'; - const data = this._data = new List([{ + const data = this._data = new SeriesData([{ name: 'value', type: dimType }], this); diff --git a/src/component/tooltip/TooltipHTMLContent.ts b/src/component/tooltip/TooltipHTMLContent.ts index 33ce852fb9..d6e85c17bd 100644 --- a/src/component/tooltip/TooltipHTMLContent.ts +++ b/src/component/tooltip/TooltipHTMLContent.ts @@ -28,7 +28,7 @@ import type { ZRenderType } from 'zrender/src/zrender'; import type { TooltipOption } from './TooltipModel'; import Model from '../../model/Model'; import type { ZRRawEvent } from 'zrender/src/core/types'; -import type { ColorString, ZRColor } from '../../util/types'; +import type { ZRColor } from '../../util/types'; import type CanvasPainter from 'zrender/src/canvas/Painter'; import type SVGPainter from 'zrender/src/svg/Painter'; import { diff --git a/src/component/visualMap/ContinuousModel.ts b/src/component/visualMap/ContinuousModel.ts index c43e2dd905..ef62a7ca55 100644 --- a/src/component/visualMap/ContinuousModel.ts +++ b/src/component/visualMap/ContinuousModel.ts @@ -203,7 +203,7 @@ class ContinuousModel extends VisualMapModel { const dataIndices: number[] = []; const data = seriesModel.getData(); - data.each(this.getDataDimension(data), function (value, dataIndex) { + data.each(this.getDataDimensionIndex(data), function (value, dataIndex) { range[0] <= value && value <= range[1] && dataIndices.push(dataIndex); }, this); diff --git a/src/component/visualMap/ContinuousView.ts b/src/component/visualMap/ContinuousView.ts index e15a74cd6b..4b9e959487 100644 --- a/src/component/visualMap/ContinuousView.ts +++ b/src/component/visualMap/ContinuousView.ts @@ -832,7 +832,7 @@ class ContinuousView extends VisualMapView { } const data = dataModel.getData(ecData.dataType); - const value = data.get(visualMapModel.getDataDimension(data), ecData.dataIndex) as number; + const value = data.getStore().get(visualMapModel.getDataDimensionIndex(data), ecData.dataIndex) as number; if (!isNaN(value)) { this._showIndicator(value, value); diff --git a/src/component/visualMap/PiecewiseModel.ts b/src/component/visualMap/PiecewiseModel.ts index 520c7094ae..3800d2b72d 100644 --- a/src/component/visualMap/PiecewiseModel.ts +++ b/src/component/visualMap/PiecewiseModel.ts @@ -316,7 +316,7 @@ class PiecewiseModel extends VisualMapModel { const dataIndices: number[] = []; const data = seriesModel.getData(); - data.each(this.getDataDimension(data), function (value: number, dataIndex: number) { + data.each(this.getDataDimensionIndex(data), function (value: number, dataIndex: number) { // Should always base on model pieceList, because it is order sensitive. const pIdx = VisualMapping.findPieceIndex(value, pieceList); pIdx === pieceIndex && dataIndices.push(dataIndex); diff --git a/src/component/visualMap/VisualMapModel.ts b/src/component/visualMap/VisualMapModel.ts index 2ef3c33542..8534b29708 100644 --- a/src/component/visualMap/VisualMapModel.ts +++ b/src/component/visualMap/VisualMapModel.ts @@ -32,13 +32,14 @@ import { ZRColor, BorderOptionMixin, OptionDataValue, - BuiltinVisualProperty + BuiltinVisualProperty, + DimensionIndex } from '../../util/types'; import ComponentModel from '../../model/Component'; import Model from '../../model/Model'; import GlobalModel from '../../model/Global'; import SeriesModel from '../../model/Series'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; const mapVisual = VisualMapping.mapVisual; const eachVisual = VisualMapping.eachVisual; @@ -156,7 +157,7 @@ export interface VisualMeta { stops: { value: number, color: ColorString}[] outerColors: ColorString[] - dimension?: number + dimension?: DimensionIndex } class VisualMapModel extends ComponentModel { @@ -377,25 +378,41 @@ class VisualMapModel extends Com } /** + * PENDING: + * delete this method if no outer usage. + * * Return Concrete dimention. If return null/undefined, no dimension used. */ - getDataDimension(list: List) { + // getDataDimension(data: SeriesData) { + // const optDim = this.option.dimension; + + // if (optDim != null) { + // return data.getDimension(optDim); + // } + + // const dimNames = data.dimensions; + // for (let i = dimNames.length - 1; i >= 0; i--) { + // const dimName = dimNames[i]; + // const dimInfo = data.getDimensionInfo(dimName); + // if (!dimInfo.isCalculationCoord) { + // return dimName; + // } + // } + // } + + getDataDimensionIndex(data: SeriesData): DimensionIndex { const optDim = this.option.dimension; - const listDimensions = list.dimensions; - if (optDim == null && !listDimensions.length) { - return; - } if (optDim != null) { - return list.getDimension(optDim); + return data.getDimensionIndex(optDim); } - const dimNames = list.dimensions; + const dimNames = data.dimensions; for (let i = dimNames.length - 1; i >= 0; i--) { const dimName = dimNames[i]; - const dimInfo = list.getDimensionInfo(dimName); + const dimInfo = data.getDimensionInfo(dimName); if (!dimInfo.isCalculationCoord) { - return dimName; + return dimInfo.storeDimIndex; } } } diff --git a/src/component/visualMap/visualEncoding.ts b/src/component/visualMap/visualEncoding.ts index f2a008bbbb..6f090b3694 100644 --- a/src/component/visualMap/visualEncoding.ts +++ b/src/component/visualMap/visualEncoding.ts @@ -42,7 +42,7 @@ export const visualMapEncodingHandlers: StageHandler[] = [ visualMapModel.stateList, visualMapModel.targetVisuals, zrUtil.bind(visualMapModel.getValueState, visualMapModel), - visualMapModel.getDataDimension(seriesModel.getData()) + visualMapModel.getDataDimensionIndex(seriesModel.getData()) )); }); @@ -65,11 +65,10 @@ export const visualMapEncodingHandlers: StageHandler[] = [ outerColors: [] } as VisualMeta; - const concreteDim = visualMapModel.getDataDimension(data); - const dimInfo = data.getDimensionInfo(concreteDim); - if (dimInfo != null) { + const dimIdx = visualMapModel.getDataDimensionIndex(data); + if (dimIdx >= 0) { // visualMeta.dimension should be dimension index, but not concrete dimension. - visualMeta.dimension = dimInfo.index; + visualMeta.dimension = dimIdx; visualMetaList.push(visualMeta); } } diff --git a/src/coord/axisHelper.ts b/src/coord/axisHelper.ts index 3bc6860442..d058db3ad1 100644 --- a/src/coord/axisHelper.ts +++ b/src/coord/axisHelper.ts @@ -35,7 +35,7 @@ import LogScale from '../scale/Log'; import Axis from './Axis'; import { AxisBaseOption, TimeAxisLabelFormatterOption } from './axisCommonTypes'; import type CartesianAxisModel from './cartesian/AxisModel'; -import List from '../data/List'; +import SeriesData from '../data/SeriesData'; import { getStackedDimension } from '../data/helper/dataStackHelper'; import { Dictionary, DimensionName, ScaleTick, TimeScaleTick } from '../util/types'; import { ensureScaleRawExtentInfo } from './scaleRawExtentInfo'; @@ -361,7 +361,7 @@ export function shouldShowAllLabels(axis: Axis): boolean { && getOptionCategoryInterval(axis.getLabelModel()) === 0; } -export function getDataDimensionsOnAxis(data: List, axisDim: string): DimensionName[] { +export function getDataDimensionsOnAxis(data: SeriesData, axisDim: string): DimensionName[] { // Remove duplicated dat dimensions caused by `getStackedDimension`. const dataDimMap = {} as Dictionary; // Currently `mapDimensionsAll` will contain stack result dimension ('__\0ecstackresult'). @@ -379,7 +379,7 @@ export function getDataDimensionsOnAxis(data: List, axisDim: string): DimensionN return zrUtil.keys(dataDimMap); } -export function unionAxisExtentFromData(dataExtent: number[], data: List, axisDim: string): void { +export function unionAxisExtentFromData(dataExtent: number[], data: SeriesData, axisDim: string): void { if (data) { zrUtil.each(getDataDimensionsOnAxis(data, axisDim), function (dim) { const seriesExtent = data.getApproximateExtent(dim); diff --git a/src/coord/axisModelCreator.ts b/src/coord/axisModelCreator.ts index e525f76f26..5d275ee459 100644 --- a/src/coord/axisModelCreator.ts +++ b/src/coord/axisModelCreator.ts @@ -69,9 +69,6 @@ export default function axisModelCreator< private __ordinalMeta: OrdinalMeta; - constructor(...args: any[]) { - super(...args); - } mergeDefaultAndTheme(option: AxisOptionT, ecModel: GlobalModel): void { const layoutMode = fetchLayoutMode(this); diff --git a/src/coord/cartesian/Grid.ts b/src/coord/cartesian/Grid.ts index 32c616dbf5..6eef23eac1 100644 --- a/src/coord/cartesian/Grid.ts +++ b/src/coord/cartesian/Grid.ts @@ -44,7 +44,7 @@ import ExtensionAPI from '../../core/ExtensionAPI'; import { Dictionary } from 'zrender/src/core/types'; import {CoordinateSystemMaster} from '../CoordinateSystem'; import { ScaleDataValue } from '../../util/types'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; import OrdinalScale from '../../scale/Ordinal'; import { isCartesian2DSeries, findAxisModels } from './cartesianAxisHelper'; @@ -450,7 +450,7 @@ class Grid implements CoordinateSystemMaster { } }, this); - function unionExtent(data: List, axis: Axis2D): void { + function unionExtent(data: SeriesData, axis: Axis2D): void { each(getDataDimensionsOnAxis(data, axis.dim), function (dim) { axis.scale.unionExtentFromData(data, dim); }); diff --git a/src/coord/parallel/Parallel.ts b/src/coord/parallel/Parallel.ts index 6080b85f2b..e22cf9a8d7 100644 --- a/src/coord/parallel/Parallel.ts +++ b/src/coord/parallel/Parallel.ts @@ -37,7 +37,7 @@ import ExtensionAPI from '../../core/ExtensionAPI'; import { Dictionary, DimensionName, ScaleDataValue } from '../../util/types'; import { CoordinateSystem, CoordinateSystemMaster } from '../CoordinateSystem'; import ParallelAxisModel, { ParallelActiveState } from './AxisModel'; -import List from '../../data/List'; +import SeriesData from '../../data/SeriesData'; const each = zrUtil.each; const mathMin = Math.min; @@ -357,7 +357,7 @@ class Parallel implements CoordinateSystemMaster, CoordinateSystem { * @param end the next dataIndex of the last dataIndex will be travel. */ eachActiveState( - data: List, + data: SeriesData, callback: (activeState: ParallelActiveState, dataIndex: number) => void, start?: number, end?: number diff --git a/src/core/Scheduler.ts b/src/core/Scheduler.ts index 2081e4a094..4d67411a4c 100644 --- a/src/core/Scheduler.ts +++ b/src/core/Scheduler.ts @@ -33,7 +33,7 @@ import { import { EChartsType } from './echarts'; import SeriesModel from '../model/Series'; import ChartView from '../view/Chart'; -import List from '../data/List'; +import SeriesData from '../data/SeriesData'; export type GeneralTask = Task; export type SeriesTask = Task; @@ -76,7 +76,7 @@ type PerformStageTaskOpt = { export interface SeriesTaskContext extends TaskContext { model?: SeriesModel; - data?: List; + data?: SeriesData; view?: ChartView; ecModel?: GlobalModel; api?: ExtensionAPI; diff --git a/src/core/task.ts b/src/core/task.ts index 97bcaeb70c..08d7e23ff6 100644 --- a/src/core/task.ts +++ b/src/core/task.ts @@ -21,12 +21,12 @@ import {assert, isArray} from 'zrender/src/core/util'; import SeriesModel from '../model/Series'; import { Pipeline } from './Scheduler'; import { Payload } from '../util/types'; -import List from '../data/List'; +import SeriesData from '../data/SeriesData'; export interface TaskContext { - outputData?: List; - data?: List; + outputData?: SeriesData; + data?: SeriesData; payload?: Payload; model?: SeriesModel; }; diff --git a/src/data/DataStore.ts b/src/data/DataStore.ts new file mode 100644 index 0000000000..b48cb3e4ad --- /dev/null +++ b/src/data/DataStore.ts @@ -0,0 +1,1297 @@ +/* +* 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, clone, createHashMap, isFunction, keys, map, reduce } from 'zrender/src/core/util'; +import { + DimensionIndex, + DimensionName, + OptionDataItem, + ParsedValue, + ParsedValueNumeric +} from '../util/types'; +import { DataProvider } from './helper/dataProvider'; +import { parseDataValue } from './helper/dataValueHelper'; +import OrdinalMeta from './OrdinalMeta'; +import { shouldRetrieveDataByName, Source } from './Source'; + +const UNDEFINED = 'undefined'; +/* global Float64Array, Int32Array, Uint32Array, Uint16Array */ + +// Caution: MUST not use `new CtorUint32Array(arr, 0, len)`, because the Ctor of array is +// different from the Ctor of typed array. +export const CtorUint32Array = typeof Uint32Array === UNDEFINED ? Array : Uint32Array; +export const CtorUint16Array = typeof Uint16Array === UNDEFINED ? Array : Uint16Array; +export const CtorInt32Array = typeof Int32Array === UNDEFINED ? Array : Int32Array; +export const CtorFloat64Array = typeof Float64Array === UNDEFINED ? Array : Float64Array; +/** + * Multi dimensional data store + */ +const dataCtors = { + 'float': CtorFloat64Array, + 'int': CtorInt32Array, + // Ordinal data type can be string or int + 'ordinal': Array, + 'number': Array, + 'time': CtorFloat64Array +} as const; + +export type DataStoreDimensionType = keyof typeof dataCtors; + +type DataTypedArray = Uint32Array | Int32Array | Uint16Array | Float64Array; +type DataTypedArrayConstructor = typeof Uint32Array | typeof Int32Array | typeof Uint16Array | typeof Float64Array; +type DataArrayLikeConstructor = typeof Array | DataTypedArrayConstructor; + + +type DataValueChunk = ArrayLike; + +// If Ctx not specified, use List as Ctx +type EachCb0 = (idx: number) => void; +type EachCb1 = (x: ParsedValue, idx: number) => void; +type EachCb2 = (x: ParsedValue, y: ParsedValue, idx: number) => void; +type EachCb = (...args: any) => void; +type FilterCb0 = (idx: number) => boolean; +type FilterCb1 = (x: ParsedValue, idx: number) => boolean; +type FilterCb = (...args: any) => boolean; +// type MapArrayCb = (...args: any) => any; +type MapCb = (...args: any) => ParsedValue | ParsedValue[]; + +export type DimValueGetter = ( + this: DataStore, + dataItem: any, + property: string, + dataIndex: number, + dimIndex: DimensionIndex +) => ParsedValue; + +export interface DataStoreDimensionDefine { + /** + * Default to be float. + */ + type?: DataStoreDimensionType; + + /** + * Only used in SOURCE_FORMAT_OBJECT_ROWS and SOURCE_FORMAT_KEYED_COLUMNS to retrieve value + * by "object property". + * For example, in `[{bb: 124, aa: 543}, ...]`, "aa" and "bb" is "object property". + * + * Deliberately name it as "property" rather than "name" to prevent it from been used in + * SOURCE_FORMAT_ARRAY_ROWS, becuase if it comes from series, it probably + * can not be shared by different series. + */ + property?: string; + + /** + * When using category axis. + * Category strings will be collected and stored in ordinalMeta.categories. + * And store will store the index of categories. + */ + ordinalMeta?: OrdinalMeta, + + /** + * Offset for ordinal parsing and collect + */ + ordinalOffset?: number +} + +let defaultDimValueGetters: {[sourceFormat: string]: DimValueGetter}; + +function getIndicesCtor(rawCount: number): DataArrayLikeConstructor { + // The possible max value in this._indicies is always this._rawCount despite of filtering. + return rawCount > 65535 ? CtorUint32Array : CtorUint16Array; +}; +function getInitialExtent(): [number, number] { + return [Infinity, -Infinity]; +}; +function cloneChunk(originalChunk: DataValueChunk): DataValueChunk { + const Ctor = originalChunk.constructor; + // Only shallow clone is enough when Array. + return Ctor === Array + ? (originalChunk as Array).slice() + : new (Ctor as DataTypedArrayConstructor)(originalChunk as DataTypedArray); +} + +function prepareStore( + store: DataValueChunk[], + dimIdx: number, + dimType: DataStoreDimensionType, + end: number, + append?: boolean +): void { + const DataCtor = dataCtors[dimType || 'float']; + + if (append) { + const oldStore = store[dimIdx]; + const oldLen = oldStore && oldStore.length; + if (!(oldLen === end)) { + const newStore = new DataCtor(end); + // The cost of the copy is probably inconsiderable + // within the initial chunkSize. + for (let j = 0; j < oldLen; j++) { + newStore[j] = oldStore[j]; + } + store[dimIdx] = newStore; + } + } + else { + store[dimIdx] = new DataCtor(end); + } +}; + +/** + * Basically, DataStore API keep immutable. + */ +class DataStore { + private _chunks: DataValueChunk[] = []; + + private _provider: DataProvider; + + // It will not be calculated util needed. + private _rawExtent: [number, number][] = []; + + private _extent: [number, number][] = []; + + // Indices stores the indices of data subset after filtered. + // This data subset will be used in chart. + private _indices: ArrayLike; + + private _count: number = 0; + private _rawCount: number = 0; + + private _dimensions: DataStoreDimensionDefine[]; + private _dimValueGetter: DimValueGetter; + + private _calcDimNameToIdx = createHashMap(); + + defaultDimValueGetter: DimValueGetter; + + /** + * Initialize from data + */ + initData( + provider: DataProvider, + inputDimensions: DataStoreDimensionDefine[], + dimValueGetter?: DimValueGetter + ): void { + if (__DEV__) { + assert( + isFunction(provider.getItem) && isFunction(provider.count), + 'Inavlid data provider.' + ); + } + + this._provider = provider; + + // Clear + this._chunks = []; + this._indices = null; + this.getRawIndex = this._getRawIdxIdentity; + + const source = provider.getSource(); + const defaultGetter = this.defaultDimValueGetter = + defaultDimValueGetters[source.sourceFormat]; + // Default dim value getter + this._dimValueGetter = dimValueGetter || defaultGetter; + + // Reset raw extent. + this._rawExtent = []; + const willRetrieveDataByName = shouldRetrieveDataByName(source); + this._dimensions = map(inputDimensions, dim => { + if (__DEV__) { + if (willRetrieveDataByName) { + assert(dim.property != null); + } + } + return { + // Only pick these two props. Not leak other properties like orderMeta. + type: dim.type, + property: dim.property + }; + }); + + this._initDataFromProvider(0, provider.count()); + } + + getProvider(): DataProvider { + return this._provider; + } + + /** + * Caution: even when a `source` instance owned by a series, the created data store + * may still be shared by different sereis (the source hash does not use all `source` + * props, see `sourceManager`). In this case, the `source` props that are not used in + * hash (like `source.dimensionDefine`) probably only belongs to a certain series and + * thus should not be fetch here. + */ + getSource(): Source { + return this._provider.getSource(); + } + + /** + * @caution Only used in dataStack. + */ + ensureCalculationDimension(dimName: DimensionName, type: DataStoreDimensionType): DimensionIndex { + const calcDimNameToIdx = this._calcDimNameToIdx; + const dimensions = this._dimensions; + + let calcDimIdx = calcDimNameToIdx.get(dimName); + if (calcDimIdx != null) { + if (dimensions[calcDimIdx].type === type) { + return calcDimIdx; + } + } + else { + calcDimIdx = dimensions.length; + } + + dimensions[calcDimIdx] = { type: type }; + calcDimNameToIdx.set(dimName, calcDimIdx); + + this._chunks[calcDimIdx] = new dataCtors[type || 'float'](this._rawCount); + this._rawExtent[calcDimIdx] = getInitialExtent(); + + return calcDimIdx; + } + + collectOrdinalMeta( + dimIdx: number, + ordinalMeta: OrdinalMeta + ): void { + const chunk = this._chunks[dimIdx]; + const dim = this._dimensions[dimIdx]; + const rawExtents = this._rawExtent; + + const offset = dim.ordinalOffset || 0; + const len = chunk.length; + + if (offset === 0) { + // We need to reset the rawExtent if collect is from start. + // Because this dimension may be guessed as number and calcuating a wrong extent. + rawExtents[dimIdx] = getInitialExtent(); + } + + const dimRawExtent = rawExtents[dimIdx]; + + // Parse from previous data offset. len may be changed after appendData + for (let i = offset; i < len; i++) { + const val = (chunk as any)[i] = ordinalMeta.parseAndCollect(chunk[i]); + dimRawExtent[0] = Math.min(val, dimRawExtent[0]); + dimRawExtent[1] = Math.max(val, dimRawExtent[1]); + } + + dim.ordinalMeta = ordinalMeta; + dim.ordinalOffset = len; + dim.type = 'ordinal'; // Force to be ordinal + } + + getOrdinalMeta(dimIdx: number): OrdinalMeta { + const dimInfo = this._dimensions[dimIdx]; + const ordinalMeta = dimInfo.ordinalMeta; + return ordinalMeta; + } + + getDimensionProperty(dimIndex: DimensionIndex): DataStoreDimensionDefine['property'] { + const item = this._dimensions[dimIndex]; + return item && item.property; + } + + /** + * Caution: Can be only called on raw data (before `this._indices` created). + */ + appendData(data: ArrayLike): number[] { + if (__DEV__) { + assert(!this._indices, 'appendData can only be called on raw data.'); + } + + const provider = this._provider; + const start = this.count(); + provider.appendData(data); + let end = provider.count(); + if (!provider.persistent) { + end += start; + } + + if (start < end) { + this._initDataFromProvider(start, end, true); + } + + return [start, end]; + } + + appendValues(values: any[][], minFillLen?: number): { start: number; end: number } { + const chunks = this._chunks; + const dimensions = this._dimensions; + const dimLen = dimensions.length; + const rawExtent = this._rawExtent; + + const start = this.count(); + const end = start + Math.max(values.length, minFillLen || 0); + + for (let i = 0; i < dimLen; i++) { + const dim = dimensions[i]; + prepareStore(chunks, i, dim.type, end, true); + } + + const emptyDataItem: number[] = []; + for (let idx = start; idx < end; idx++) { + const sourceIdx = idx - start; + // Store the data by dimensions + for (let dimIdx = 0; dimIdx < dimLen; dimIdx++) { + const dim = dimensions[dimIdx]; + const val = defaultDimValueGetters.arrayRows.call( + this, values[sourceIdx] || emptyDataItem, dim.property, sourceIdx, dimIdx + ) as ParsedValueNumeric; + (chunks[dimIdx] as any)[idx] = val; + + const dimRawExtent = rawExtent[dimIdx]; + val < dimRawExtent[0] && (dimRawExtent[0] = val); + val > dimRawExtent[1] && (dimRawExtent[1] = val); + } + } + + this._rawCount = this._count = end; + + return {start, end}; + } + + private _initDataFromProvider( + start: number, + end: number, + append?: boolean + ): void { + const provider = this._provider; + const chunks = this._chunks; + const dimensions = this._dimensions; + const dimLen = dimensions.length; + const rawExtent = this._rawExtent; + const dimNames = map(dimensions, dim => dim.property); + + for (let i = 0; i < dimLen; i++) { + const dim = dimensions[i]; + if (!rawExtent[i]) { + rawExtent[i] = getInitialExtent(); + } + prepareStore(chunks, i, dim.type, end, append); + } + + if (provider.fillStorage) { + provider.fillStorage(start, end, chunks, rawExtent); + } + else { + let dataItem = [] as OptionDataItem; + for (let idx = start; idx < end; idx++) { + // NOTICE: Try not to write things into dataItem + dataItem = provider.getItem(idx, dataItem); + // Each data item is value + // [1, 2] + // 2 + // Bar chart, line chart which uses category axis + // only gives the 'y' value. 'x' value is the indices of category + // Use a tempValue to normalize the value to be a (x, y) value + + // Store the data by dimensions + for (let dimIdx = 0; dimIdx < dimLen; dimIdx++) { + const dimStorage = chunks[dimIdx]; + // PENDING NULL is empty or zero + const val = this._dimValueGetter( + dataItem, dimNames[dimIdx], idx, dimIdx + ) as ParsedValueNumeric; + (dimStorage as ParsedValue[])[idx] = val; + + const dimRawExtent = rawExtent[dimIdx]; + val < dimRawExtent[0] && (dimRawExtent[0] = val); + val > dimRawExtent[1] && (dimRawExtent[1] = val); + } + } + } + + if (!provider.persistent && provider.clean) { + // Clean unused data if data source is typed array. + provider.clean(); + } + + this._rawCount = this._count = end; + // Reset data extent + this._extent = []; + } + + count(): number { + return this._count; + } + + /** + * Get value. Return NaN if idx is out of range. + */ + get(dim: DimensionIndex, idx: number): ParsedValue { + if (!(idx >= 0 && idx < this._count)) { + return NaN; + } + const dimStore = this._chunks[dim]; + return dimStore ? dimStore[this.getRawIndex(idx)] : NaN; + } + + getValues(idx: number): ParsedValue[]; + getValues(dimensions: readonly DimensionIndex[], idx?: number): ParsedValue[] + getValues(dimensions: readonly DimensionIndex[] | number, idx?: number): ParsedValue[] { + const values = []; + let dimArr: DimensionIndex[] = []; + if (idx == null) { + idx = dimensions as number; + // TODO get all from store? + dimensions = []; + // All dimensions + for (let i = 0; i < this._dimensions.length; i++) { + dimArr.push(i); + } + } + else { + dimArr = dimensions as DimensionIndex[]; + } + + for (let i = 0, len = dimArr.length; i < len; i++) { + values.push(this.get(dimArr[i], idx)); + } + + return values; + } + + /** + * @param dim concrete dim + */ + getByRawIndex(dim: DimensionIndex, rawIdx: number): ParsedValue { + if (!(rawIdx >= 0 && rawIdx < this._rawCount)) { + return NaN; + } + const dimStore = this._chunks[dim]; + return dimStore ? dimStore[rawIdx] : NaN; + } + + /** + * Get sum of data in one dimension + */ + getSum(dim: DimensionIndex): number { + const dimData = this._chunks[dim]; + let sum = 0; + if (dimData) { + for (let i = 0, len = this.count(); i < len; i++) { + const value = this.get(dim, i) as number; + if (!isNaN(value)) { + sum += value; + } + } + } + return sum; + } + + /** + * Get median of data in one dimension + */ + getMedian(dim: DimensionIndex): number { + const dimDataArray: ParsedValue[] = []; + // map all data of one dimension + this.each([dim], function (val) { + if (!isNaN(val as number)) { + dimDataArray.push(val); + } + }); + + // TODO + // Use quick select? + const sortedDimDataArray = dimDataArray.sort(function (a: number, b: number) { + return a - b; + }) as number[]; + const len = this.count(); + // calculate median + return len === 0 + ? 0 + : len % 2 === 1 + ? sortedDimDataArray[(len - 1) / 2] + : (sortedDimDataArray[len / 2] + sortedDimDataArray[len / 2 - 1]) / 2; + } + + /** + * Retreive the index with given raw data index + */ + indexOfRawIndex(rawIndex: number): number { + if (rawIndex >= this._rawCount || rawIndex < 0) { + return -1; + } + + if (!this._indices) { + return rawIndex; + } + + // Indices are ascending + const indices = this._indices; + + // If rawIndex === dataIndex + const rawDataIndex = indices[rawIndex]; + if (rawDataIndex != null && rawDataIndex < this._count && rawDataIndex === rawIndex) { + return rawIndex; + } + + let left = 0; + let right = this._count - 1; + while (left <= right) { + const mid = (left + right) / 2 | 0; + if (indices[mid] < rawIndex) { + left = mid + 1; + } + else if (indices[mid] > rawIndex) { + right = mid - 1; + } + else { + return mid; + } + } + return -1; + } + + + /** + * Retreive the index of nearest value + * @param dim + * @param value + * @param [maxDistance=Infinity] + * @return If and only if multiple indices has + * the same value, they are put to the result. + */ + indicesOfNearest( + dim: DimensionIndex, value: number, maxDistance?: number + ): number[] { + const chunks = this._chunks; + const dimData = chunks[dim]; + const nearestIndices: number[] = []; + + if (!dimData) { + return nearestIndices; + } + + if (maxDistance == null) { + maxDistance = Infinity; + } + + let minDist = Infinity; + let minDiff = -1; + let nearestIndicesLen = 0; + + // Check the test case of `test/ut/spec/data/SeriesData.js`. + for (let i = 0, len = this.count(); i < len; i++) { + const dataIndex = this.getRawIndex(i); + const diff = value - (dimData[dataIndex] as number); + const dist = Math.abs(diff); + if (dist <= maxDistance) { + // When the `value` is at the middle of `this.get(dim, i)` and `this.get(dim, i+1)`, + // we'd better not push both of them to `nearestIndices`, otherwise it is easy to + // get more than one item in `nearestIndices` (more specifically, in `tooltip`). + // So we chose the one that `diff >= 0` in this csae. + // But if `this.get(dim, i)` and `this.get(dim, j)` get the same value, both of them + // should be push to `nearestIndices`. + if (dist < minDist + || (dist === minDist && diff >= 0 && minDiff < 0) + ) { + minDist = dist; + minDiff = diff; + nearestIndicesLen = 0; + } + if (diff === minDiff) { + nearestIndices[nearestIndicesLen++] = i; + } + } + } + nearestIndices.length = nearestIndicesLen; + + return nearestIndices; + } + + getIndices(): ArrayLike { + let newIndices; + + const indices = this._indices; + if (indices) { + const Ctor = indices.constructor as DataArrayLikeConstructor; + const thisCount = this._count; + // `new Array(a, b, c)` is different from `new Uint32Array(a, b, c)`. + if (Ctor === Array) { + newIndices = new Ctor(thisCount); + for (let i = 0; i < thisCount; i++) { + newIndices[i] = indices[i]; + } + } + else { + newIndices = new (Ctor as DataTypedArrayConstructor)( + (indices as DataTypedArray).buffer, 0, thisCount + ); + } + } + else { + const Ctor = getIndicesCtor(this._rawCount); + newIndices = new Ctor(this.count()); + for (let i = 0; i < newIndices.length; i++) { + newIndices[i] = i; + } + } + + return newIndices; + } + + /** + * Data filter. + */ + filter( + dims: DimensionIndex[], + cb: FilterCb + ): DataStore { + if (!this._count) { + return this; + } + + const newStore = this.clone(); + + const count = newStore.count(); + const Ctor = getIndicesCtor(newStore._rawCount); + const newIndices = new Ctor(count); + const value = []; + const dimSize = dims.length; + + let offset = 0; + const dim0 = dims[0]; + const chunks = newStore._chunks; + + for (let i = 0; i < count; i++) { + let keep; + const rawIdx = newStore.getRawIndex(i); + // Simple optimization + if (dimSize === 0) { + keep = (cb as FilterCb0)(i); + } + else if (dimSize === 1) { + const val = chunks[dim0][rawIdx]; + keep = (cb as FilterCb1)(val, i); + } + else { + let k = 0; + for (; k < dimSize; k++) { + value[k] = chunks[dims[k]][rawIdx]; + } + value[k] = i; + keep = (cb as FilterCb).apply(null, value); + } + if (keep) { + newIndices[offset++] = rawIdx; + } + } + + // Set indices after filtered. + if (offset < count) { + newStore._indices = newIndices; + } + newStore._count = offset; + // Reset data extent + newStore._extent = []; + + newStore._updateGetRawIdx(); + + return newStore; + } + + /** + * Select data in range. (For optimization of filter) + * (Manually inline code, support 5 million data filtering in data zoom.) + */ + selectRange(range: {[dimIdx: number]: [number, number]}): DataStore { + const newStore = this.clone(); + + const len = newStore._count; + + if (!len) { + return this; + } + + const dims = keys(range); + const dimSize = dims.length; + if (!dimSize) { + return this; + } + + const originalCount = newStore.count(); + const Ctor = getIndicesCtor(newStore._rawCount); + const newIndices = new Ctor(originalCount); + + let offset = 0; + const dim0 = dims[0]; + + const min = range[dim0][0]; + const max = range[dim0][1]; + const storeArr = newStore._chunks; + + let quickFinished = false; + if (!newStore._indices) { + // Extreme optimization for common case. About 2x faster in chrome. + let idx = 0; + if (dimSize === 1) { + const dimStorage = storeArr[dims[0]]; + for (let i = 0; i < len; i++) { + const val = dimStorage[i]; + // NaN will not be filtered. Consider the case, in line chart, empty + // value indicates the line should be broken. But for the case like + // scatter plot, a data item with empty value will not be rendered, + // but the axis extent may be effected if some other dim of the data + // item has value. Fortunately it is not a significant negative effect. + if ( + (val >= min && val <= max) || isNaN(val as any) + ) { + newIndices[offset++] = idx; + } + idx++; + } + quickFinished = true; + } + else if (dimSize === 2) { + const dimStorage = storeArr[dims[0]]; + const dimStorage2 = storeArr[dims[1]]; + const min2 = range[dims[1]][0]; + const max2 = range[dims[1]][1]; + for (let i = 0; i < len; i++) { + const val = dimStorage[i]; + const val2 = dimStorage2[i]; + // Do not filter NaN, see comment above. + if (( + (val >= min && val <= max) || isNaN(val as any) + ) + && ( + (val2 >= min2 && val2 <= max2) || isNaN(val2 as any) + ) + ) { + newIndices[offset++] = idx; + } + idx++; + } + quickFinished = true; + } + } + if (!quickFinished) { + if (dimSize === 1) { + for (let i = 0; i < originalCount; i++) { + const rawIndex = newStore.getRawIndex(i); + const val = storeArr[dims[0]][rawIndex]; + // Do not filter NaN, see comment above. + if ( + (val >= min && val <= max) || isNaN(val as any) + ) { + newIndices[offset++] = rawIndex; + } + } + } + else { + for (let i = 0; i < originalCount; i++) { + let keep = true; + const rawIndex = newStore.getRawIndex(i); + for (let k = 0; k < dimSize; k++) { + const dimk = dims[k]; + const val = storeArr[dimk][rawIndex]; + // Do not filter NaN, see comment above. + if (val < range[dimk][0] || val > range[dimk][1]) { + keep = false; + } + } + if (keep) { + newIndices[offset++] = newStore.getRawIndex(i); + } + } + } + } + + // Set indices after filtered. + if (offset < originalCount) { + newStore._indices = newIndices; + } + newStore._count = offset; + // Reset data extent + newStore._extent = []; + + newStore._updateGetRawIdx(); + + return newStore; + } + + // /** + // * Data mapping to a plain array + // */ + // mapArray(dims: DimensionIndex[], cb: MapArrayCb): any[] { + // const result: any[] = []; + // this.each(dims, function () { + // result.push(cb && (cb as MapArrayCb).apply(null, arguments)); + // }); + // return result; + // } + + /** + * Data mapping to a new List with given dimensions + */ + map(dims: DimensionIndex[], cb: MapCb): DataStore { + // TODO only clone picked chunks. + const target = this.clone(dims); + this._updateDims(target, dims, cb); + return target; + } + + /** + * @caution Danger!! Only used in dataStack. + */ + modify(dims: DimensionIndex[], cb: MapCb) { + this._updateDims(this, dims, cb); + } + + private _updateDims( + target: DataStore, + dims: DimensionIndex[], + cb: MapCb + ) { + const targetChunks = target._chunks; + + const tmpRetValue = []; + const dimSize = dims.length; + const dataCount = target.count(); + const values = []; + const rawExtent = target._rawExtent; + + for (let i = 0; i < dims.length; i++) { + rawExtent[dims[i]] = getInitialExtent(); + } + + for (let dataIndex = 0; dataIndex < dataCount; dataIndex++) { + const rawIndex = target.getRawIndex(dataIndex); + + for (let k = 0; k < dimSize; k++) { + values[k] = targetChunks[dims[k]][rawIndex]; + } + values[dimSize] = dataIndex; + + let retValue = cb && cb.apply(null, values); + if (retValue != null) { + // a number or string (in oridinal dimension)? + if (typeof retValue !== 'object') { + tmpRetValue[0] = retValue; + retValue = tmpRetValue; + } + + for (let i = 0; i < retValue.length; i++) { + const dim = dims[i]; + const val = retValue[i]; + const rawExtentOnDim = rawExtent[dim]; + + const dimStore = targetChunks[dim]; + if (dimStore) { + (dimStore as ParsedValue[])[rawIndex] = val; + } + + if (val < rawExtentOnDim[0]) { + rawExtentOnDim[0] = val as number; + } + if (val > rawExtentOnDim[1]) { + rawExtentOnDim[1] = val as number; + } + } + } + } + } + + /** + * Large data down sampling using largest-triangle-three-buckets + * @param {string} valueDimension + * @param {number} targetCount + */ + lttbDownSample( + valueDimension: DimensionIndex, + rate: number + ): DataStore { + const target = this.clone([valueDimension], true); + const targetStorage = target._chunks; + const dimStore = targetStorage[valueDimension]; + const len = this.count(); + + let sampledIndex = 0; + + const frameSize = Math.floor(1 / rate); + + let currentRawIndex = this.getRawIndex(0); + let maxArea; + let area; + let nextRawIndex; + + const newIndices = new (getIndicesCtor(this._rawCount))(Math.ceil(len / frameSize) + 2); + + // First frame use the first data. + newIndices[sampledIndex++] = currentRawIndex; + for (let i = 1; i < len - 1; i += frameSize) { + const nextFrameStart = Math.min(i + frameSize, len - 1); + const nextFrameEnd = Math.min(i + frameSize * 2, len); + + const avgX = (nextFrameEnd + nextFrameStart) / 2; + let avgY = 0; + + for (let idx = nextFrameStart; idx < nextFrameEnd; idx++) { + const rawIndex = this.getRawIndex(idx); + const y = dimStore[rawIndex] as number; + if (isNaN(y)) { + continue; + } + avgY += y as number; + } + avgY /= (nextFrameEnd - nextFrameStart); + + const frameStart = i; + const frameEnd = Math.min(i + frameSize, len); + + const pointAX = i - 1; + const pointAY = dimStore[currentRawIndex] as number; + + maxArea = -1; + + nextRawIndex = frameStart; + // Find a point from current frame that construct a triangel with largest area with previous selected point + // And the average of next frame. + for (let idx = frameStart; idx < frameEnd; idx++) { + const rawIndex = this.getRawIndex(idx); + const y = dimStore[rawIndex] as number; + if (isNaN(y)) { + continue; + } + // Calculate triangle area over three buckets + area = Math.abs((pointAX - avgX) * (y - pointAY) + - (pointAX - idx) * (avgY - pointAY) + ); + if (area > maxArea) { + maxArea = area; + nextRawIndex = rawIndex; // Next a is this b + } + } + + newIndices[sampledIndex++] = nextRawIndex; + + currentRawIndex = nextRawIndex; // This a is the next a (chosen b) + } + + // First frame use the last data. + newIndices[sampledIndex++] = this.getRawIndex(len - 1); + target._count = sampledIndex; + target._indices = newIndices; + + target.getRawIndex = this._getRawIdx; + return target; + } + + + /** + * Large data down sampling on given dimension + * @param sampleIndex Sample index for name and id + */ + downSample( + dimension: DimensionIndex, + rate: number, + sampleValue: (frameValues: ArrayLike) => ParsedValueNumeric, + sampleIndex: (frameValues: ArrayLike, value: ParsedValueNumeric) => number + ): DataStore { + const target = this.clone([dimension], true); + const targetStorage = target._chunks; + + const frameValues = []; + let frameSize = Math.floor(1 / rate); + + const dimStore = targetStorage[dimension]; + const len = this.count(); + const rawExtentOnDim = target._rawExtent[dimension] = getInitialExtent(); + + const newIndices = new (getIndicesCtor(this._rawCount))(Math.ceil(len / frameSize)); + + let offset = 0; + for (let i = 0; i < len; i += frameSize) { + // Last frame + if (frameSize > len - i) { + frameSize = len - i; + frameValues.length = frameSize; + } + for (let k = 0; k < frameSize; k++) { + const dataIdx = this.getRawIndex(i + k); + frameValues[k] = dimStore[dataIdx]; + } + const value = sampleValue(frameValues); + const sampleFrameIdx = this.getRawIndex( + Math.min(i + sampleIndex(frameValues, value) || 0, len - 1) + ); + // Only write value on the filtered data + (dimStore as number[])[sampleFrameIdx] = value; + + if (value < rawExtentOnDim[0]) { + rawExtentOnDim[0] = value; + } + if (value > rawExtentOnDim[1]) { + rawExtentOnDim[1] = value; + } + + newIndices[offset++] = sampleFrameIdx; + } + + target._count = offset; + target._indices = newIndices; + + target._updateGetRawIdx(); + + return target; + } + + /** + * Data iteration + * @param ctx default this + * @example + * list.each('x', function (x, idx) {}); + * list.each(['x', 'y'], function (x, y, idx) {}); + * list.each(function (idx) {}) + */ + each(dims: DimensionIndex[], cb: EachCb): void { + if (!this._count) { + return; + } + const dimSize = dims.length; + const chunks = this._chunks; + + for (let i = 0, len = this.count(); i < len; i++) { + const rawIdx = this.getRawIndex(i); + // Simple optimization + switch (dimSize) { + case 0: + (cb as EachCb0)(i); + break; + case 1: + (cb as EachCb1)(chunks[dims[0]][rawIdx], i); + break; + case 2: + (cb as EachCb2)( + chunks[dims[0]][rawIdx], chunks[dims[1]][rawIdx], i + ); + break; + default: + let k = 0; + const value = []; + for (; k < dimSize; k++) { + value[k] = chunks[dims[k]][rawIdx]; + } + // Index + value[k] = i; + (cb as EachCb).apply(null, value); + } + } + } + + /** + * Get extent of data in one dimension + */ + getDataExtent(dim: DimensionIndex): [number, number] { + // Make sure use concrete dim as cache name. + const dimData = this._chunks[dim]; + const initialExtent = getInitialExtent(); + + if (!dimData) { + return initialExtent; + } + + // Make more strict checkings to ensure hitting cache. + const currEnd = this.count(); + + // Consider the most cases when using data zoom, `getDataExtent` + // happened before filtering. We cache raw extent, which is not + // necessary to be cleared and recalculated when restore data. + const useRaw = !this._indices; + let dimExtent: [number, number]; + + if (useRaw) { + return this._rawExtent[dim].slice() as [number, number]; + } + dimExtent = this._extent[dim]; + if (dimExtent) { + return dimExtent.slice() as [number, number]; + } + dimExtent = initialExtent; + + let min = dimExtent[0]; + let max = dimExtent[1]; + + for (let i = 0; i < currEnd; i++) { + const rawIdx = this.getRawIndex(i); + const value = dimData[rawIdx] as ParsedValueNumeric; + value < min && (min = value); + value > max && (max = value); + } + + dimExtent = [min, max]; + + this._extent[dim] = dimExtent; + + return dimExtent; + } + + /** + * Get raw data index. + * Do not initialize. + * Default `getRawIndex`. And it can be changed. + */ + getRawIndex: (idx: number) => number; + + /** + * Get raw data item + */ + getRawDataItem(idx: number): OptionDataItem { + const rawIdx = this.getRawIndex(idx); + if (!this._provider.persistent) { + const val = []; + const chunks = this._chunks; + for (let i = 0; i < chunks.length; i++) { + val.push(chunks[i][rawIdx]); + } + return val; + } + else { + return this._provider.getItem(rawIdx); + } + } + + /** + * Clone shallow. + * + * @param clonedDims Determine which dims to clone. Will share the data if not specified. + */ + clone(clonedDims?: DimensionIndex[], ignoreIndices?: boolean): DataStore { + const target = new DataStore(); + const chunks = this._chunks; + const clonedDimsMap = clonedDims && reduce(clonedDims, (obj, dimIdx) => { + obj[dimIdx] = true; + return obj; + }, {} as Record); + + if (clonedDimsMap) { + for (let i = 0; i < chunks.length; i++) { + // Not clone if dim is not picked. + target._chunks[i] = !clonedDimsMap[i] ? chunks[i] : cloneChunk(chunks[i]); + } + } + else { + target._chunks = chunks; + } + this._copyCommonProps(target); + + if (!ignoreIndices) { + target._indices = this._cloneIndices(); + } + target._updateGetRawIdx(); + return target; + } + + private _copyCommonProps(target: DataStore): void { + target._count = this._count; + target._rawCount = this._rawCount; + target._provider = this._provider; + target._dimensions = this._dimensions; + + target._extent = clone(this._extent); + target._rawExtent = clone(this._rawExtent); + } + + private _cloneIndices(): DataStore['_indices'] { + if (this._indices) { + const Ctor = this._indices.constructor as DataArrayLikeConstructor; + let indices; + if (Ctor === Array) { + const thisCount = this._indices.length; + indices = new Ctor(thisCount); + for (let i = 0; i < thisCount; i++) { + indices[i] = this._indices[i]; + } + } + else { + indices = new (Ctor as DataTypedArrayConstructor)(this._indices); + } + return indices; + } + return null; + } + + private _getRawIdxIdentity(idx: number): number { + return idx; + } + private _getRawIdx(idx: number): number { + if (idx < this._count && idx >= 0) { + return this._indices[idx]; + } + return -1; + } + + private _updateGetRawIdx(): void { + this.getRawIndex = this._indices ? this._getRawIdx : this._getRawIdxIdentity; + } + + private static internalField = (function () { + + function getDimValueSimply( + this: DataStore, dataItem: any, property: string, dataIndex: number, dimIndex: number + ): ParsedValue { + return parseDataValue(dataItem[dimIndex], this._dimensions[dimIndex]); + } + + defaultDimValueGetters = { + + arrayRows: getDimValueSimply, + + objectRows( + this: DataStore, dataItem: any, property: string, dataIndex: number, dimIndex: number + ): ParsedValue { + return parseDataValue(dataItem[property], this._dimensions[dimIndex]); + }, + + keyedColumns: getDimValueSimply, + + original( + this: DataStore, dataItem: any, property: string, dataIndex: number, dimIndex: number + ): ParsedValue { + // Performance sensitive, do not use modelUtil.getDataItemValue. + // If dataItem is an plain object with no value field, the let `value` + // will be assigned with the object, but it will be tread correctly + // in the `convertValue`. + const value = dataItem && (dataItem.value == null ? dataItem : dataItem.value); + + return parseDataValue( + (value instanceof Array) + ? value[dimIndex] + // If value is a single number or something else not array. + : value, + this._dimensions[dimIndex] + ); + }, + + typedArray: function ( + this: DataStore, dataItem: any, property: string, dataIndex: number, dimIndex: number + ): ParsedValue { + return dataItem[dimIndex]; + } + + }; + + })(); +} + +export default DataStore; \ No newline at end of file diff --git a/src/data/Graph.ts b/src/data/Graph.ts index 75ec38f654..4605ff1593 100644 --- a/src/data/Graph.ts +++ b/src/data/Graph.ts @@ -19,7 +19,7 @@ import * as zrUtil from 'zrender/src/core/util'; import { Dictionary } from 'zrender/src/core/types'; -import List from './List'; +import SeriesData from './SeriesData'; import Model from '../model/Model'; import Element from 'zrender/src/Element'; import { DimensionLoose, ParsedValue } from '../util/types'; @@ -36,9 +36,9 @@ class Graph { readonly edges: GraphEdge[] = []; - data: List; + data: SeriesData; - edgeData: List; + edgeData: SeriesData; /** * Whether directed graph. @@ -458,7 +458,7 @@ function createGraphDataProxyMixin( */ getValue(this: Host, dimension?: DimensionLoose): ParsedValue { const data = this[hostName][dataName]; - return data.get(data.getDimension(dimension || 'value'), this.dataIndex); + return data.getStore().get(data.getDimensionIndex(dimension || 'value'), this.dataIndex); }, // TODO: TYPE stricter type. setVisual(this: Host, key: string | Dictionary, value?: any) { diff --git a/src/data/List.ts b/src/data/List.ts deleted file mode 100644 index 3ff4da6719..0000000000 --- a/src/data/List.ts +++ /dev/null @@ -1,2258 +0,0 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one -* or more contributor license agreements. See the NOTICE file -* distributed with this work for additional information -* regarding copyright ownership. The ASF licenses this file -* to you under the Apache License, Version 2.0 (the -* "License"); you may not use this file except in compliance -* with the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, -* software distributed under the License is distributed on an -* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -* KIND, either express or implied. See the License for the -* specific language governing permissions and limitations -* under the License. -*/ - -/* global Float64Array, Int32Array, Uint32Array, Uint16Array */ - -/** - * List for data storage - */ - -import * as zrUtil from 'zrender/src/core/util'; -import {PathStyleProps} from 'zrender/src/graphic/Path'; -import Model from '../model/Model'; -import DataDiffer from './DataDiffer'; -import {DefaultDataProvider, DataProvider} from './helper/dataProvider'; -import {summarizeDimensions, DimensionSummary} from './helper/dimensionHelper'; -import DataDimensionInfo from './DataDimensionInfo'; -import {ArrayLike, Dictionary, FunctionPropertyNames} from 'zrender/src/core/types'; -import Element from 'zrender/src/Element'; -import { - DimensionIndex, DimensionName, DimensionLoose, OptionDataItem, - ParsedValue, ParsedValueNumeric, OrdinalNumber, DimensionUserOuput, - ModelOption, SeriesDataType, OptionSourceData, SOURCE_FORMAT_TYPED_ARRAY, SOURCE_FORMAT_ORIGINAL, - DecalObject -} from '../util/types'; -import {isDataItemOption, convertOptionIdName} from '../util/model'; -import {getECData, setCommonECData} from '../util/innerStore'; -import type Graph from './Graph'; -import type Tree from './Tree'; -import type { VisualMeta } from '../component/visualMap/VisualMapModel'; -import { parseDataValue } from './helper/dataValueHelper'; -import {isSourceInstance, Source} from './Source'; -import OrdinalMeta from './OrdinalMeta'; -import { LineStyleProps } from '../model/mixin/lineStyle'; - -const mathFloor = Math.floor; -const isObject = zrUtil.isObject; -const map = zrUtil.map; - -const UNDEFINED = 'undefined'; -const INDEX_NOT_FOUND = -1; - -// Use prefix to avoid index to be the same as otherIdList[idx], -// which will cause weird udpate animation. -const ID_PREFIX = 'e\0\0'; - -const dataCtors = { - 'float': typeof Float64Array === UNDEFINED - ? Array : Float64Array, - 'int': typeof Int32Array === UNDEFINED - ? Array : Int32Array, - // Ordinal data type can be string or int - 'ordinal': Array, - 'number': Array, - 'time': Array -}; - -export type ListDimensionType = keyof typeof dataCtors; - -// Caution: MUST not use `new CtorUint32Array(arr, 0, len)`, because the Ctor of array is -// different from the Ctor of typed array. -const CtorUint32Array = typeof Uint32Array === UNDEFINED ? Array : Uint32Array; -const CtorInt32Array = typeof Int32Array === UNDEFINED ? Array : Int32Array; -const CtorUint16Array = typeof Uint16Array === UNDEFINED ? Array : Uint16Array; - -type DataTypedArray = Uint32Array | Int32Array | Uint16Array | Float64Array; -type DataTypedArrayConstructor = typeof Uint32Array | typeof Int32Array | typeof Uint16Array | typeof Float64Array; -type DataArrayLikeConstructor = typeof Array | DataTypedArrayConstructor; - - -type DimValueGetter = ( - this: List, - dataItem: any, - dimName: DimensionName, - dataIndex: number, - dimIndex: DimensionIndex -) => ParsedValue; - -type DataValueChunk = ArrayLike; -type DataStorage = {[dimName: string]: DataValueChunk}; -type NameRepeatCount = {[name: string]: number}; - - -type ItrParamDims = DimensionLoose | Array; -// If Ctx not specified, use List as Ctx -type CtxOrList = unknown extends Ctx ? List : Ctx; -type EachCb0 = (this: CtxOrList, idx: number) => void; -type EachCb1 = (this: CtxOrList, x: ParsedValue, idx: number) => void; -type EachCb2 = (this: CtxOrList, x: ParsedValue, y: ParsedValue, idx: number) => void; -type EachCb = (this: CtxOrList, ...args: any) => void; -type FilterCb0 = (this: CtxOrList, idx: number) => boolean; -type FilterCb1 = (this: CtxOrList, x: ParsedValue, idx: number) => boolean; -type FilterCb2 = (this: CtxOrList, x: ParsedValue, y: ParsedValue, idx: number) => boolean; -type FilterCb = (this: CtxOrList, ...args: any) => boolean; -type MapArrayCb0 = (this: CtxOrList, idx: number) => any; -type MapArrayCb1 = (this: CtxOrList, x: ParsedValue, idx: number) => any; -type MapArrayCb2 = (this: CtxOrList, x: ParsedValue, y: ParsedValue, idx: number) => any; -type MapArrayCb = (this: CtxOrList, ...args: any) => any; -type MapCb1 = (this: CtxOrList, x: ParsedValue, idx: number) => ParsedValue | ParsedValue[]; -type MapCb2 = (this: CtxOrList, x: ParsedValue, y: ParsedValue, idx: number) => - ParsedValue | ParsedValue[]; -type MapCb = (this: CtxOrList, ...args: any) => ParsedValue | ParsedValue[]; - - -const TRANSFERABLE_PROPERTIES = [ - 'hasItemOption', '_nameList', '_idList', '_invertedIndicesMap', - '_rawData', '_dimValueGetter', - '_count', '_rawCount', '_nameDimIdx', '_idDimIdx', '_nameRepeatCount' -]; -const CLONE_PROPERTIES = [ - '_extent', '_approximateExtent', '_rawExtent' -]; - -export interface DefaultDataVisual { - style: PathStyleProps - // Draw type determined which prop should be set with encoded color. - // It's only available on the global visual. Use getVisual('drawType') to access it. - // It will be set in visual/style.ts module in the first priority. - drawType: 'fill' | 'stroke' - - symbol?: string - symbolSize?: number | number[] - symbolRotate?: number - symbolKeepAspect?: boolean - symbolOffset?: string | number | (string | number)[] - - liftZ?: number - // For legend. - legendIcon?: string - legendLineStyle?: LineStyleProps - - // visualMap will inject visualMeta data - visualMeta?: VisualMeta[] - - // If color is encoded from palette - colorFromPalette?: boolean - - decal?: DecalObject -} - -export interface DataCalculationInfo { - stackedDimension: string; - stackedByDimension: string; - isStackedByIndex: boolean; - stackedOverDimension: string; - stackResultDimension: string; - stackedOnSeries?: SERIES_MODEL; -} - -// ----------------------------- -// Internal method declarations: -// ----------------------------- -let defaultDimValueGetters: {[sourceFormat: string]: DimValueGetter}; -let prepareInvertedIndex: (list: List) => void; -let getIndicesCtor: (list: List) => DataArrayLikeConstructor; -let prepareStorage: ( - storage: DataStorage, dimInfo: DataDimensionInfo, end: number, append?: boolean -) => void; -let getRawIndexWithoutIndices: (this: List, idx: number) => number; -let getRawIndexWithIndices: (this: List, idx: number) => number; -let getId: (list: List, rawIndex: number) => string; -let getIdNameFromStore: (list: List, dimIdx: number, ordinalMeta: OrdinalMeta, rawIndex: number) => string; -let makeIdFromName: (list: List, idx: number) => void; -let normalizeDimensions: (dimensions: ItrParamDims) => Array; -let validateDimensions: (list: List, dims: DimensionName[]) => void; -let cloneListForMapAndSample: (original: List, excludeDimensions: DimensionName[]) => List; -let getInitialExtent: () => [number, number]; -let transferProperties: (target: List, source: List) => void; - - -class List< - HostModel extends Model = Model, - Visual extends DefaultDataVisual = DefaultDataVisual -> { - - readonly type = 'list'; - - readonly dimensions: string[]; - - // Infomation of each data dimension, like data type. - private _dimensionInfos: {[dimName: string]: DataDimensionInfo}; - - readonly hostModel: HostModel; - - /** - * @readonly - */ - dataType: SeriesDataType; - - /** - * @readonly - * Host graph if List is used to store graph nodes / edges. - */ - graph?: Graph; - - /** - * @readonly - * Host tree if List is used to store tree ndoes. - */ - tree?: Tree; - - // Indices stores the indices of data subset after filtered. - // This data subset will be used in chart. - private _indices: ArrayLike; - - private _count: number = 0; - private _rawCount: number = 0; - private _storage: DataStorage = {}; - // We have an extra array store here. It's faster to be acessed than KV structured `_storage`. - // We profile the code `storage[dim]` and it seems to be KeyedLoadIC_Megamorphic instead of fast property access. - // Not sure why this happens. But using an extra array seems leads to faster `initData` - // See https://github.com/apache/incubator-echarts/pull/13314 for more explanation. - private _storageArr: DataValueChunk[] = []; - private _nameList: string[] = []; - private _idList: string[] = []; - - // Models of data option is stored sparse for optimizing memory cost - // Never used yet (not used yet). - // private _optionModels: Model[] = []; - - // Global visual properties after visual coding - private _visual: Dictionary = {}; - - // Globel layout properties. - private _layout: Dictionary = {}; - - // Item visual properties after visual coding - private _itemVisuals: Dictionary[] = []; - - // Item layout properties after layout - private _itemLayouts: any[] = []; - - // Graphic elemnents - private _graphicEls: Element[] = []; - - private _rawData: DataProvider; - - // Raw extent will not be cloned, but only transfered. - // It will not be calculated util needed. - private _rawExtent: {[dimName: string]: [number, number]} = {}; - - private _extent: {[dimName: string]: [number, number]} = {}; - - // key: dim, value: extent - private _approximateExtent: {[dimName: string]: [number, number]} = {}; - - private _dimensionsSummary: DimensionSummary; - - private _invertedIndicesMap: {[dimName: string]: ArrayLike}; - - private _calculationInfo: DataCalculationInfo = {} as DataCalculationInfo; - - // User output info of this data. - // DO NOT use it in other places! - // When preparing user params for user callbacks, we have - // to clone these inner data structures to prevent users - // from modifying them to effect built-in logic. And for - // performance consideration we make this `userOutput` to - // avoid clone them too many times. - readonly userOutput: DimensionUserOuput; - - // Having detected that there is data item is non primitive type - // (in type `OptionDataItemObject`). - // Like `data: [ { value: xx, itemStyle: {...} }, ...]` - // At present it only happen in `SOURCE_FORMAT_ORIGINAL`. - hasItemOption: boolean = true; - - // @readonly - defaultDimValueGetter: DimValueGetter; - private _dimValueGetter: DimValueGetter; - private _dimValueGetterArrayRows: DimValueGetter; - - // id or name is used on dynamic data, mapping old and new items. - // When generating id from name, avoid repeat. - private _nameRepeatCount: NameRepeatCount; - private _nameDimIdx: number; - private _nameOrdinalMeta: OrdinalMeta; - private _idDimIdx: number; - private _idOrdinalMeta: OrdinalMeta; - private _dontMakeIdFromName: boolean; - - private __wrappedMethods: string[]; - - // Methods that create a new list based on this list should be listed here. - // Notice that those method should `RETURN` the new list. - TRANSFERABLE_METHODS = ['cloneShallow', 'downSample', 'lttbDownSample', 'map'] as const; - // Methods that change indices of this list should be listed here. - CHANGABLE_METHODS = ['filterSelf', 'selectRange'] as const; - DOWNSAMPLE_METHODS = ['downSample', 'lttbDownSample'] as const; - - /** - * @param dimensions - * For example, ['someDimName', {name: 'someDimName', type: 'someDimType'}, ...]. - * Dimensions should be concrete names like x, y, z, lng, lat, angle, radius - */ - constructor(dimensions: Array, hostModel: HostModel) { - dimensions = dimensions || ['x', 'y']; - - const dimensionInfos: Dictionary = {}; - const dimensionNames = []; - const invertedIndicesMap: Dictionary = {}; - - for (let i = 0; i < dimensions.length; i++) { - // Use the original dimensions[i], where other flag props may exists. - const dimInfoInput = dimensions[i]; - - const dimensionInfo: DataDimensionInfo = - zrUtil.isString(dimInfoInput) - ? new DataDimensionInfo({name: dimInfoInput}) - : !(dimInfoInput instanceof DataDimensionInfo) - ? new DataDimensionInfo(dimInfoInput) - : dimInfoInput; - - const dimensionName = dimensionInfo.name; - dimensionInfo.type = dimensionInfo.type || 'float'; - if (!dimensionInfo.coordDim) { - dimensionInfo.coordDim = dimensionName; - dimensionInfo.coordDimIndex = 0; - } - - const otherDims = dimensionInfo.otherDims = dimensionInfo.otherDims || {}; - dimensionNames.push(dimensionName); - dimensionInfos[dimensionName] = dimensionInfo; - - dimensionInfo.index = i; - - if (dimensionInfo.createInvertedIndices) { - invertedIndicesMap[dimensionName] = []; - } - if (otherDims.itemName === 0) { - this._nameDimIdx = i; - this._nameOrdinalMeta = dimensionInfo.ordinalMeta; - } - if (otherDims.itemId === 0) { - this._idDimIdx = i; - this._idOrdinalMeta = dimensionInfo.ordinalMeta; - } - } - - this.dimensions = dimensionNames; - this._dimensionInfos = dimensionInfos; - this.hostModel = hostModel; - - // Cache summary info for fast visit. See "dimensionHelper". - this._dimensionsSummary = summarizeDimensions(this); - - this._invertedIndicesMap = invertedIndicesMap; - - this.userOutput = this._dimensionsSummary.userOutput; - } - - /** - * The meanings of the input parameter `dim`: - * - * + If dim is a number (e.g., `1`), it means the index of the dimension. - * For example, `getDimension(0)` will return 'x' or 'lng' or 'radius'. - * + If dim is a number-like string (e.g., `"1"`): - * + If there is the same concrete dim name defined in `this.dimensions`, it means that concrete name. - * + If not, it will be converted to a number, which means the index of the dimension. - * (why? because of the backward compatbility. We have been tolerating number-like string in - * dimension setting, although now it seems that it is not a good idea.) - * For example, `visualMap[i].dimension: "1"` is the same meaning as `visualMap[i].dimension: 1`, - * if no dimension name is defined as `"1"`. - * + If dim is a not-number-like string, it means the concrete dim name. - * For example, it can be be default name `"x"`, `"y"`, `"z"`, `"lng"`, `"lat"`, `"angle"`, `"radius"`, - * or customized in `dimensions` property of option like `"age"`. - * - * Get dimension name - * @param dim See above. - * @return Concrete dim name. - */ - getDimension(dim: DimensionLoose): DimensionName { - if (typeof dim === 'number' - // If being a number-like string but not being defined a dimension name. - || (!isNaN(dim as any) && !this._dimensionInfos.hasOwnProperty(dim)) - ) { - dim = this.dimensions[dim as DimensionIndex]; - } - return dim as DimensionName; - } - - /** - * Get type and calculation info of particular dimension - * @param dim - * Dimension can be concrete names like x, y, z, lng, lat, angle, radius - * Or a ordinal number. For example getDimensionInfo(0) will return 'x' or 'lng' or 'radius' - */ - getDimensionInfo(dim: DimensionLoose): DataDimensionInfo { - // Do not clone, because there may be categories in dimInfo. - return this._dimensionInfos[this.getDimension(dim)]; - } - - /** - * concrete dimension name list on coord. - */ - getDimensionsOnCoord(): DimensionName[] { - return this._dimensionsSummary.dataDimsOnCoord.slice(); - } - - /** - * @param coordDim - * @param idx A coordDim may map to more than one data dim. - * If not specified, return the first dim not extra. - * @return concrete data dim. If not found, return null/undefined - */ - mapDimension(coordDim: DimensionName): DimensionName; - mapDimension(coordDim: DimensionName, idx: number): DimensionName; - mapDimension(coordDim: DimensionName, idx?: number): DimensionName { - const dimensionsSummary = this._dimensionsSummary; - - if (idx == null) { - return dimensionsSummary.encodeFirstDimNotExtra[coordDim] as any; - } - - const dims = dimensionsSummary.encode[coordDim]; - return dims ? dims[idx as number] as any : null; - } - - mapDimensionsAll(coordDim: DimensionName): DimensionName[] { - const dimensionsSummary = this._dimensionsSummary; - const dims = dimensionsSummary.encode[coordDim]; - return (dims || []).slice(); - } - - /** - * Initialize from data - * @param data source or data or data provider. - * @param nameList The name of a datum is used on data diff and - * default label/tooltip. - * A name can be specified in encode.itemName, - * or dataItem.name (only for series option data), - * or provided in nameList from outside. - */ - initData( - data: Source | OptionSourceData | DataProvider, - nameList?: string[], - dimValueGetter?: DimValueGetter - ): void { - - const notProvider = isSourceInstance(data) || zrUtil.isArrayLike(data); - const provider: DataProvider = notProvider - ? new DefaultDataProvider(data as Source | OptionSourceData, this.dimensions.length) - : data as DataProvider; - - if (__DEV__) { - zrUtil.assert( - notProvider || ( - zrUtil.isFunction(provider.getItem) - && zrUtil.isFunction(provider.count) - ), - 'Inavlid data provider.' - ); - } - - this._rawData = provider; - const sourceFormat = provider.getSource().sourceFormat; - - // Clear - this._storage = {}; - this._indices = null; - this._dontMakeIdFromName = - this._idDimIdx != null - || sourceFormat === SOURCE_FORMAT_TYPED_ARRAY // Cosndier performance. - || !!provider.fillStorage; - - this._nameList = (nameList || []).slice(); - this._idList = []; - - this._nameRepeatCount = {}; - - if (!dimValueGetter) { - this.hasItemOption = false; - } - - this.defaultDimValueGetter = defaultDimValueGetters[sourceFormat]; - // Default dim value getter - this._dimValueGetter = dimValueGetter = dimValueGetter - || this.defaultDimValueGetter; - this._dimValueGetterArrayRows = defaultDimValueGetters.arrayRows; - - // Reset raw extent. - this._rawExtent = {}; - - this._initDataFromProvider(0, provider.count()); - - // If data has no item option. - if (provider.pure) { - this.hasItemOption = false; - } - } - - getProvider(): DataProvider { - return this._rawData; - } - - /** - * Caution: Can be only called on raw data (before `this._indices` created). - */ - appendData(data: ArrayLike): void { - if (__DEV__) { - zrUtil.assert(!this._indices, 'appendData can only be called on raw data.'); - } - - const rawData = this._rawData; - const start = this.count(); - rawData.appendData(data); - let end = rawData.count(); - if (!rawData.persistent) { - end += start; - } - this._initDataFromProvider(start, end, true); - } - - /** - * Caution: Can be only called on raw data (before `this._indices` created). - * This method does not modify `rawData` (`dataProvider`), but only - * add values to storage. - * - * The final count will be increased by `Math.max(values.length, names.length)`. - * - * @param values That is the SourceType: 'arrayRows', like - * [ - * [12, 33, 44], - * [NaN, 43, 1], - * ['-', 'asdf', 0] - * ] - * Each item is exaclty cooresponding to a dimension. - */ - appendValues(values: any[][], names?: string[]): void { - const storage = this._storage; - const dimensions = this.dimensions; - const dimLen = dimensions.length; - const rawExtent = this._rawExtent; - - const start = this.count(); - const end = start + Math.max(values.length, names ? names.length : 0); - - for (let i = 0; i < dimLen; i++) { - const dim = dimensions[i]; - if (!rawExtent[dim]) { - rawExtent[dim] = getInitialExtent(); - } - prepareStorage(storage, this._dimensionInfos[dim], end, true); - } - - const rawExtentArr = map(dimensions, (dim) => { - return rawExtent[dim]; - }); - - const storageArr = this._storageArr = map(dimensions, (dim) => { - return storage[dim]; - }); - - const emptyDataItem: number[] = []; - for (let idx = start; idx < end; idx++) { - const sourceIdx = idx - start; - // Store the data by dimensions - for (let dimIdx = 0; dimIdx < dimLen; dimIdx++) { - const dim = dimensions[dimIdx]; - const val = this._dimValueGetterArrayRows( - values[sourceIdx] || emptyDataItem, dim, sourceIdx, dimIdx - ) as ParsedValueNumeric; - storageArr[dimIdx][idx] = val; - - const dimRawExtent = rawExtentArr[dimIdx]; - val < dimRawExtent[0] && (dimRawExtent[0] = val); - val > dimRawExtent[1] && (dimRawExtent[1] = val); - } - - if (names) { - this._nameList[idx] = names[sourceIdx]; - if (!this._dontMakeIdFromName) { - makeIdFromName(this, idx); - } - } - } - - this._rawCount = this._count = end; - - // Reset data extent - this._extent = {}; - - prepareInvertedIndex(this); - } - - private _initDataFromProvider(start: number, end: number, append?: boolean): void { - if (start >= end) { - return; - } - - const rawData = this._rawData; - const storage = this._storage; - const dimensions = this.dimensions; - const dimLen = dimensions.length; - const dimensionInfoMap = this._dimensionInfos; - const nameList = this._nameList; - const idList = this._idList; - const rawExtent = this._rawExtent; - const sourceFormat = rawData.getSource().sourceFormat; - const isFormatOriginal = sourceFormat === SOURCE_FORMAT_ORIGINAL; - - for (let i = 0; i < dimLen; i++) { - const dim = dimensions[i]; - if (!rawExtent[dim]) { - rawExtent[dim] = getInitialExtent(); - } - prepareStorage(storage, dimensionInfoMap[dim], end, append); - } - - const storageArr = this._storageArr = map(dimensions, (dim) => { - return storage[dim]; - }); - - const rawExtentArr = map(dimensions, (dim) => { - return rawExtent[dim]; - }); - - if (rawData.fillStorage) { - rawData.fillStorage(start, end, storageArr, rawExtentArr); - } - else { - let dataItem = [] as OptionDataItem; - for (let idx = start; idx < end; idx++) { - // NOTICE: Try not to write things into dataItem - dataItem = rawData.getItem(idx, dataItem); - // Each data item is value - // [1, 2] - // 2 - // Bar chart, line chart which uses category axis - // only gives the 'y' value. 'x' value is the indices of category - // Use a tempValue to normalize the value to be a (x, y) value - - // Store the data by dimensions - for (let dimIdx = 0; dimIdx < dimLen; dimIdx++) { - const dim = dimensions[dimIdx]; - const dimStorage = storageArr[dimIdx]; - // PENDING NULL is empty or zero - const val = this._dimValueGetter(dataItem, dim, idx, dimIdx) as ParsedValueNumeric; - dimStorage[idx] = val; - - const dimRawExtent = rawExtentArr[dimIdx]; - val < dimRawExtent[0] && (dimRawExtent[0] = val); - val > dimRawExtent[1] && (dimRawExtent[1] = val); - } - - // If dataItem is {name: ...} or {id: ...}, it has highest priority. - // This kind of ids and names are always stored `_nameList` and `_idList`. - if (isFormatOriginal && !rawData.pure && dataItem) { - const itemName = (dataItem as any).name; - if (nameList[idx] == null && itemName != null) { - nameList[idx] = convertOptionIdName(itemName, null); - } - const itemId = (dataItem as any).id; - if (idList[idx] == null && itemId != null) { - idList[idx] = convertOptionIdName(itemId, null); - } - } - - if (!this._dontMakeIdFromName) { - makeIdFromName(this, idx); - } - } - } - - if (!rawData.persistent && rawData.clean) { - // Clean unused data if data source is typed array. - rawData.clean(); - } - - this._rawCount = this._count = end; - - // Reset data extent - this._extent = {}; - - prepareInvertedIndex(this); - } - - count(): number { - return this._count; - } - - getIndices(): ArrayLike { - let newIndices; - - const indices = this._indices; - if (indices) { - const Ctor = indices.constructor as DataArrayLikeConstructor; - const thisCount = this._count; - // `new Array(a, b, c)` is different from `new Uint32Array(a, b, c)`. - if (Ctor === Array) { - newIndices = new Ctor(thisCount); - for (let i = 0; i < thisCount; i++) { - newIndices[i] = indices[i]; - } - } - else { - newIndices = new (Ctor as DataTypedArrayConstructor)( - (indices as DataTypedArray).buffer, 0, thisCount - ); - } - } - else { - const Ctor = getIndicesCtor(this); - newIndices = new Ctor(this.count()); - for (let i = 0; i < newIndices.length; i++) { - newIndices[i] = i; - } - } - - return newIndices; - } - - // Get data by index of dimension. - // Because in v8 access array by number variable is faster than access object by string variable - // Not sure why but the optimization just works. - getByDimIdx(dimIdx: number, idx: number): ParsedValue { - if (!(idx >= 0 && idx < this._count)) { - return NaN; - } - const dimStore = this._storageArr[dimIdx]; - return dimStore ? dimStore[this.getRawIndex(idx)] : NaN; - } - - /** - * Get value. Return NaN if idx is out of range. - * @param dim Dim must be concrete name. - */ - get(dim: DimensionName, idx: number): ParsedValue { - if (!(idx >= 0 && idx < this._count)) { - return NaN; - } - const dimStore = this._storage[dim]; - return dimStore ? dimStore[this.getRawIndex(idx)] : NaN; - } - - /** - * @param dim concrete dim - */ - getByRawIndex(dim: DimensionName, rawIdx: number): ParsedValue { - if (!(rawIdx >= 0 && rawIdx < this._rawCount)) { - return NaN; - } - const dimStore = this._storage[dim]; - return dimStore ? dimStore[rawIdx] : NaN; - } - - /** - * Get value for multi dimensions. - * @param dimensions If ignored, using all dimensions. - */ - getValues(idx: number): ParsedValue[]; - getValues(dimensions: readonly DimensionName[], idx: number): ParsedValue[]; - getValues(dimensions: readonly DimensionName[] | number, idx?: number): ParsedValue[] { - const values = []; - - if (!zrUtil.isArray(dimensions)) { - // stack = idx; - idx = dimensions as number; - dimensions = this.dimensions; - } - - for (let i = 0, len = dimensions.length; i < len; i++) { - values.push(this.get(dimensions[i], idx /*, stack */)); - } - - return values; - } - - /** - * If value is NaN. Inlcuding '-' - * Only check the coord dimensions. - */ - hasValue(idx: number): boolean { - const dataDimsOnCoord = this._dimensionsSummary.dataDimsOnCoord; - for (let i = 0, len = dataDimsOnCoord.length; i < len; i++) { - // Ordinal type originally can be string or number. - // But when an ordinal type is used on coord, it can - // not be string but only number. So we can also use isNaN. - if (isNaN(this.get(dataDimsOnCoord[i], idx) as any)) { - return false; - } - } - return true; - } - - /** - * Get extent of data in one dimension - */ - getDataExtent(dim: DimensionLoose): [number, number] { - // Make sure use concrete dim as cache name. - dim = this.getDimension(dim); - const dimData = this._storage[dim]; - const initialExtent = getInitialExtent(); - - // stack = !!((stack || false) && this.getCalculationInfo(dim)); - - if (!dimData) { - return initialExtent; - } - - // Make more strict checkings to ensure hitting cache. - const currEnd = this.count(); - // let cacheName = [dim, !!stack].join('_'); - // let cacheName = dim; - - // Consider the most cases when using data zoom, `getDataExtent` - // happened before filtering. We cache raw extent, which is not - // necessary to be cleared and recalculated when restore data. - const useRaw = !this._indices; // && !stack; - let dimExtent: [number, number]; - - if (useRaw) { - return this._rawExtent[dim].slice() as [number, number]; - } - dimExtent = this._extent[dim]; - if (dimExtent) { - return dimExtent.slice() as [number, number]; - } - dimExtent = initialExtent; - - let min = dimExtent[0]; - let max = dimExtent[1]; - - for (let i = 0; i < currEnd; i++) { - const rawIdx = this.getRawIndex(i); - const value = dimData[rawIdx] as ParsedValueNumeric; - value < min && (min = value); - value > max && (max = value); - } - - dimExtent = [min, max]; - - this._extent[dim] = dimExtent; - - return dimExtent; - } - - /** - * PENDING: In fact currently this function is only used to short-circuit - * the calling of `scale.unionExtentFromData` when data have been filtered by modules - * like "dataZoom". `scale.unionExtentFromData` is used to calculate data extent for series on - * an axis, but if a "axis related data filter module" is used, the extent of the axis have - * been fixed and no need to calling `scale.unionExtentFromData` actually. - * But if we add "custom data filter" in future, which is not "axis related", this method may - * be still needed. - * - * Optimize for the scenario that data is filtered by a given extent. - * Consider that if data amount is more than hundreds of thousand, - * extent calculation will cost more than 10ms and the cache will - * be erased because of the filtering. - */ - getApproximateExtent(dim: DimensionLoose): [number, number] { - dim = this.getDimension(dim); - return this._approximateExtent[dim] || this.getDataExtent(dim); - } - - /** - * Calculate extent on a filtered data might be time consuming. - * Approximate extent is only used for: calculte extent of filtered data outside. - */ - setApproximateExtent(extent: [number, number], dim: DimensionLoose): void { - dim = this.getDimension(dim); - this._approximateExtent[dim] = extent.slice() as [number, number]; - } - - getCalculationInfo>( - key: CALC_INFO_KEY - ): DataCalculationInfo[CALC_INFO_KEY] { - return this._calculationInfo[key]; - } - - /** - * @param key or k-v object - */ - setCalculationInfo( - key: DataCalculationInfo - ): void; - setCalculationInfo>( - key: CALC_INFO_KEY, - value: DataCalculationInfo[CALC_INFO_KEY] - ): void; - setCalculationInfo( - key: (keyof DataCalculationInfo) | DataCalculationInfo, - value?: DataCalculationInfo[keyof DataCalculationInfo] - ): void { - isObject(key) - ? zrUtil.extend(this._calculationInfo, key as object) - : ((this._calculationInfo as any)[key] = value); - } - - /** - * Get sum of data in one dimension - */ - getSum(dim: DimensionName): number { - const dimData = this._storage[dim]; - let sum = 0; - if (dimData) { - for (let i = 0, len = this.count(); i < len; i++) { - const value = this.get(dim, i) as number; - if (!isNaN(value)) { - sum += value; - } - } - } - return sum; - } - - /** - * Get median of data in one dimension - */ - getMedian(dim: DimensionLoose): number { - const dimDataArray: ParsedValue[] = []; - // map all data of one dimension - this.each(dim, function (val) { - if (!isNaN(val as number)) { - dimDataArray.push(val); - } - }); - - // TODO - // Use quick select? - const sortedDimDataArray = dimDataArray.sort(function (a: number, b: number) { - return a - b; - }) as number[]; - const len = this.count(); - // calculate median - return len === 0 - ? 0 - : len % 2 === 1 - ? sortedDimDataArray[(len - 1) / 2] - : (sortedDimDataArray[len / 2] + sortedDimDataArray[len / 2 - 1]) / 2; - } - - // /** - // * Retreive the index with given value - // * @param {string} dim Concrete dimension. - // * @param {number} value - // * @return {number} - // */ - // Currently incorrect: should return dataIndex but not rawIndex. - // Do not fix it until this method is to be used somewhere. - // FIXME Precision of float value - // indexOf(dim, value) { - // let storage = this._storage; - // let dimData = storage[dim]; - // let chunkSize = this._chunkSize; - // if (dimData) { - // for (let i = 0, len = this.count(); i < len; i++) { - // let chunkIndex = mathFloor(i / chunkSize); - // let chunkOffset = i % chunkSize; - // if (dimData[chunkIndex][chunkOffset] === value) { - // return i; - // } - // } - // } - // return -1; - // } - - /** - * Only support the dimension which inverted index created. - * Do not support other cases until required. - * @param dim concrete dim - * @param value ordinal index - * @return rawIndex - */ - rawIndexOf(dim: DimensionName, value: OrdinalNumber): number { - const invertedIndices = dim && this._invertedIndicesMap[dim]; - if (__DEV__) { - if (!invertedIndices) { - throw new Error('Do not supported yet'); - } - } - const rawIndex = invertedIndices[value]; - if (rawIndex == null || isNaN(rawIndex)) { - return INDEX_NOT_FOUND; - } - return rawIndex; - } - - /** - * Retreive the index with given name - */ - indexOfName(name: string): number { - for (let i = 0, len = this.count(); i < len; i++) { - if (this.getName(i) === name) { - return i; - } - } - - return -1; - } - - /** - * Retreive the index with given raw data index - */ - indexOfRawIndex(rawIndex: number): number { - if (rawIndex >= this._rawCount || rawIndex < 0) { - return -1; - } - - if (!this._indices) { - return rawIndex; - } - - // Indices are ascending - const indices = this._indices; - - // If rawIndex === dataIndex - const rawDataIndex = indices[rawIndex]; - if (rawDataIndex != null && rawDataIndex < this._count && rawDataIndex === rawIndex) { - return rawIndex; - } - - let left = 0; - let right = this._count - 1; - while (left <= right) { - const mid = (left + right) / 2 | 0; - if (indices[mid] < rawIndex) { - left = mid + 1; - } - else if (indices[mid] > rawIndex) { - right = mid - 1; - } - else { - return mid; - } - } - return -1; - } - - /** - * Retreive the index of nearest value - * @param dim - * @param value - * @param [maxDistance=Infinity] - * @return If and only if multiple indices has - * the same value, they are put to the result. - */ - indicesOfNearest( - dim: DimensionName, value: number, maxDistance?: number - ): number[] { - const storage = this._storage; - const dimData = storage[dim]; - const nearestIndices: number[] = []; - - if (!dimData) { - return nearestIndices; - } - - if (maxDistance == null) { - maxDistance = Infinity; - } - - let minDist = Infinity; - let minDiff = -1; - let nearestIndicesLen = 0; - - - // Check the test case of `test/ut/spec/data/List.js`. - for (let i = 0, len = this.count(); i < len; i++) { - const dataIndex = this.getRawIndex(i); - const diff = value - (dimData[dataIndex] as number); - const dist = Math.abs(diff); - if (dist <= maxDistance) { - // When the `value` is at the middle of `this.get(dim, i)` and `this.get(dim, i+1)`, - // we'd better not push both of them to `nearestIndices`, otherwise it is easy to - // get more than one item in `nearestIndices` (more specifically, in `tooltip`). - // So we chose the one that `diff >= 0` in this csae. - // But if `this.get(dim, i)` and `this.get(dim, j)` get the same value, both of them - // should be push to `nearestIndices`. - if (dist < minDist - || (dist === minDist && diff >= 0 && minDiff < 0) - ) { - minDist = dist; - minDiff = diff; - nearestIndicesLen = 0; - } - if (diff === minDiff) { - nearestIndices[nearestIndicesLen++] = i; - } - } - } - nearestIndices.length = nearestIndicesLen; - - return nearestIndices; - } - - /** - * Get raw data index. - * Do not initialize. - * Default `getRawIndex`. And it can be changed. - */ - getRawIndex: (idx: number) => number = getRawIndexWithoutIndices; - - /** - * Get raw data item - */ - getRawDataItem(idx: number): OptionDataItem { - if (!this._rawData.persistent) { - const val = []; - for (let i = 0; i < this.dimensions.length; i++) { - const dim = this.dimensions[i]; - val.push(this.get(dim, idx)); - } - return val; - } - else { - return this._rawData.getItem(this.getRawIndex(idx)); - } - } - - /** - * @return Never be null/undefined. `number` will be converted to string. Becuase: - * In most cases, name is used in display, where returning a string is more convenient. - * In other cases, name is used in query (see `indexOfName`), where we can keep the - * rule that name `2` equals to name `'2'`. - */ - getName(idx: number): string { - const rawIndex = this.getRawIndex(idx); - let name = this._nameList[rawIndex]; - if (name == null && this._nameDimIdx != null) { - name = getIdNameFromStore(this, this._nameDimIdx, this._nameOrdinalMeta, rawIndex); - } - if (name == null) { - name = ''; - } - return name; - } - - /** - * @return Never null/undefined. `number` will be converted to string. Becuase: - * In all cases having encountered at present, id is used in making diff comparison, which - * are usually based on hash map. We can keep the rule that the internal id are always string - * (treat `2` is the same as `'2'`) to make the related logic simple. - */ - getId(idx: number): string { - return getId(this, this.getRawIndex(idx)); - } - - /** - * Data iteration - * @param ctx default this - * @example - * list.each('x', function (x, idx) {}); - * list.each(['x', 'y'], function (x, y, idx) {}); - * list.each(function (idx) {}) - */ - each(cb: EachCb0, ctx?: Ctx, ctxCompat?: Ctx): void; - each(dims: DimensionLoose, cb: EachCb1, ctx?: Ctx, ctxCompat?: Ctx): void; - each(dims: [DimensionLoose], cb: EachCb1, ctx?: Ctx, ctxCompat?: Ctx): void; - each(dims: [DimensionLoose, DimensionLoose], cb: EachCb2, ctx?: Ctx, ctxCompat?: Ctx): void; - each(dims: ItrParamDims, cb: EachCb, ctx?: Ctx, ctxCompat?: Ctx): void; - each( - dims: ItrParamDims | EachCb, - cb: EachCb | Ctx, - ctx?: Ctx, - ctxCompat?: Ctx - ): void { - 'use strict'; - - if (!this._count) { - return; - } - - if (typeof dims === 'function') { - ctxCompat = ctx; - ctx = cb as Ctx; - cb = dims; - dims = []; - } - - // ctxCompat just for compat echarts3 - const fCtx = (ctx || ctxCompat || this) as CtxOrList; - - const dimNames = map(normalizeDimensions(dims), this.getDimension, this); - - if (__DEV__) { - validateDimensions(this, dimNames); - } - - const dimSize = dimNames.length; - const dimIndices = map(dimNames, (dimName) => { - return this._dimensionInfos[dimName].index; - }); - const storageArr = this._storageArr; - - for (let i = 0, len = this.count(); i < len; i++) { - const rawIdx = this.getRawIndex(i); - // Simple optimization - switch (dimSize) { - case 0: - (cb as EachCb0).call(fCtx, i); - break; - case 1: - (cb as EachCb1).call(fCtx, storageArr[dimIndices[0]][rawIdx], i); - break; - case 2: - (cb as EachCb2).call( - fCtx, storageArr[dimIndices[0]][rawIdx], storageArr[dimIndices[1]][rawIdx], i - ); - break; - default: - let k = 0; - const value = []; - for (; k < dimSize; k++) { - value[k] = storageArr[dimIndices[k]][rawIdx]; - } - // Index - value[k] = i; - (cb as EachCb).apply(fCtx, value); - } - } - } - - /** - * Data filter - */ - filterSelf(cb: FilterCb0, ctx?: Ctx, ctxCompat?: Ctx): this; - filterSelf(dims: DimensionLoose, cb: FilterCb1, ctx?: Ctx, ctxCompat?: Ctx): this; - filterSelf(dims: [DimensionLoose], cb: FilterCb1, ctx?: Ctx, ctxCompat?: Ctx): this; - filterSelf(dims: [DimensionLoose, DimensionLoose], cb: FilterCb2, ctx?: Ctx, ctxCompat?: Ctx): this; - filterSelf(dims: ItrParamDims, cb: FilterCb, ctx?: Ctx, ctxCompat?: Ctx): this; - filterSelf( - dims: ItrParamDims | FilterCb, - cb: FilterCb | Ctx, - ctx?: Ctx, - ctxCompat?: Ctx - ): List { - 'use strict'; - - if (!this._count) { - return; - } - - if (typeof dims === 'function') { - ctxCompat = ctx; - ctx = cb as Ctx; - cb = dims; - dims = []; - } - - // ctxCompat just for compat echarts3 - const fCtx = (ctx || ctxCompat || this) as CtxOrList; - - const dimNames = map( - normalizeDimensions(dims), this.getDimension, this - ); - - if (__DEV__) { - validateDimensions(this, dimNames); - } - - - const count = this.count(); - const Ctor = getIndicesCtor(this); - const newIndices = new Ctor(count); - const value = []; - const dimSize = dimNames.length; - - let offset = 0; - const dimIndices = map(dimNames, (dimName) => { - return this._dimensionInfos[dimName].index; - }); - const dim0 = dimIndices[0]; - const storageArr = this._storageArr; - - for (let i = 0; i < count; i++) { - let keep; - const rawIdx = this.getRawIndex(i); - // Simple optimization - if (dimSize === 0) { - keep = (cb as FilterCb0).call(fCtx, i); - } - else if (dimSize === 1) { - const val = storageArr[dim0][rawIdx]; - keep = (cb as FilterCb1).call(fCtx, val, i); - } - else { - let k = 0; - for (; k < dimSize; k++) { - value[k] = storageArr[dimIndices[k]][rawIdx]; - } - value[k] = i; - keep = (cb as FilterCb).apply(fCtx, value); - } - if (keep) { - newIndices[offset++] = rawIdx; - } - } - - // Set indices after filtered. - if (offset < count) { - this._indices = newIndices; - } - this._count = offset; - // Reset data extent - this._extent = {}; - - this.getRawIndex = this._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices; - - return this; - } - - /** - * Select data in range. (For optimization of filter) - * (Manually inline code, support 5 million data filtering in data zoom.) - */ - selectRange(range: {[dimName: string]: [number, number]}): List { - 'use strict'; - - const len = this._count; - - if (!len) { - return; - } - - const dimensions = []; - for (const dim in range) { - if (range.hasOwnProperty(dim)) { - dimensions.push(dim); - } - } - - if (__DEV__) { - validateDimensions(this, dimensions); - } - - const dimSize = dimensions.length; - if (!dimSize) { - return; - } - - const originalCount = this.count(); - const Ctor = getIndicesCtor(this); - const newIndices = new Ctor(originalCount); - - let offset = 0; - const dim0 = dimensions[0]; - const dimIndices = map(dimensions, (dimName) => { - return this._dimensionInfos[dimName].index; - }); - - const min = range[dim0][0]; - const max = range[dim0][1]; - const storageArr = this._storageArr; - - let quickFinished = false; - if (!this._indices) { - // Extreme optimization for common case. About 2x faster in chrome. - let idx = 0; - if (dimSize === 1) { - const dimStorage = storageArr[dimIndices[0]]; - for (let i = 0; i < len; i++) { - const val = dimStorage[i]; - // NaN will not be filtered. Consider the case, in line chart, empty - // value indicates the line should be broken. But for the case like - // scatter plot, a data item with empty value will not be rendered, - // but the axis extent may be effected if some other dim of the data - // item has value. Fortunately it is not a significant negative effect. - if ( - (val >= min && val <= max) || isNaN(val as any) - ) { - newIndices[offset++] = idx; - } - idx++; - } - quickFinished = true; - } - else if (dimSize === 2) { - const dimStorage = storageArr[dimIndices[0]]; - const dimStorage2 = storageArr[dimIndices[1]]; - const min2 = range[dimensions[1]][0]; - const max2 = range[dimensions[1]][1]; - for (let i = 0; i < len; i++) { - const val = dimStorage[i]; - const val2 = dimStorage2[i]; - // Do not filter NaN, see comment above. - if (( - (val >= min && val <= max) || isNaN(val as any) - ) - && ( - (val2 >= min2 && val2 <= max2) || isNaN(val2 as any) - ) - ) { - newIndices[offset++] = idx; - } - idx++; - } - quickFinished = true; - } - } - if (!quickFinished) { - if (dimSize === 1) { - for (let i = 0; i < originalCount; i++) { - const rawIndex = this.getRawIndex(i); - const val = storageArr[dimIndices[0]][rawIndex]; - // Do not filter NaN, see comment above. - if ( - (val >= min && val <= max) || isNaN(val as any) - ) { - newIndices[offset++] = rawIndex; - } - } - } - else { - for (let i = 0; i < originalCount; i++) { - let keep = true; - const rawIndex = this.getRawIndex(i); - for (let k = 0; k < dimSize; k++) { - const dimk = dimensions[k]; - const val = storageArr[dimIndices[k]][rawIndex]; - // Do not filter NaN, see comment above. - if (val < range[dimk][0] || val > range[dimk][1]) { - keep = false; - } - } - if (keep) { - newIndices[offset++] = this.getRawIndex(i); - } - } - } - } - - // Set indices after filtered. - if (offset < originalCount) { - this._indices = newIndices; - } - this._count = offset; - // Reset data extent - this._extent = {}; - - this.getRawIndex = this._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices; - - return this; - } - - /** - * Data mapping to a plain array - */ - mapArray>(cb: Cb, ctx?: Ctx, ctxCompat?: Ctx): ReturnType[]; - /* eslint-disable */ - mapArray>(dims: DimensionLoose, cb: Cb, ctx?: Ctx, ctxCompat?: Ctx): ReturnType[]; - mapArray>(dims: [DimensionLoose], cb: Cb, ctx?: Ctx, ctxCompat?: Ctx): ReturnType[]; - mapArray>(dims: [DimensionLoose, DimensionLoose], cb: Cb, ctx?: Ctx, ctxCompat?: Ctx): ReturnType[]; - mapArray>(dims: ItrParamDims, cb: Cb, ctx?: Ctx, ctxCompat?: Ctx): ReturnType[]; - /* eslint-enable */ - mapArray( - dims: ItrParamDims | MapArrayCb, - cb: MapArrayCb | Ctx, - ctx?: Ctx, - ctxCompat?: Ctx - ): any[] { - 'use strict'; - - if (typeof dims === 'function') { - ctxCompat = ctx; - ctx = cb as Ctx; - cb = dims; - dims = []; - } - - // ctxCompat just for compat echarts3 - ctx = (ctx || ctxCompat || this) as Ctx; - - const result: any[] = []; - this.each(dims, function () { - result.push(cb && (cb as MapArrayCb).apply(this, arguments)); - }, ctx); - return result; - } - - /** - * Data mapping to a new List with given dimensions - */ - map(dims: DimensionLoose, cb: MapCb1, ctx?: Ctx, ctxCompat?: Ctx): List; - map(dims: [DimensionLoose], cb: MapCb1, ctx?: Ctx, ctxCompat?: Ctx): List; - map(dims: [DimensionLoose, DimensionLoose], cb: MapCb2, ctx?: Ctx, ctxCompat?: Ctx): List; - map( - dims: ItrParamDims, - cb: MapCb, - ctx?: Ctx, - ctxCompat?: Ctx - ): List { - 'use strict'; - - // ctxCompat just for compat echarts3 - const fCtx = (ctx || ctxCompat || this) as CtxOrList; - - const dimNames = map( - normalizeDimensions(dims), this.getDimension, this - ); - - if (__DEV__) { - validateDimensions(this, dimNames); - } - - const list = cloneListForMapAndSample(this, dimNames); - const storage = list._storage; - - // Following properties are all immutable. - // So we can reference to the same value - list._indices = this._indices; - list.getRawIndex = list._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices; - - const tmpRetValue = []; - const dimSize = dimNames.length; - const dataCount = this.count(); - const values = []; - const rawExtent = list._rawExtent; - - for (let dataIndex = 0; dataIndex < dataCount; dataIndex++) { - for (let dimIndex = 0; dimIndex < dimSize; dimIndex++) { - values[dimIndex] = this.get(dimNames[dimIndex], dataIndex); - } - values[dimSize] = dataIndex; - - let retValue = cb && cb.apply(fCtx, values); - if (retValue != null) { - // a number or string (in oridinal dimension)? - if (typeof retValue !== 'object') { - tmpRetValue[0] = retValue; - retValue = tmpRetValue; - } - - const rawIndex = this.getRawIndex(dataIndex); - - for (let i = 0; i < retValue.length; i++) { - const dim = dimNames[i]; - const val = retValue[i]; - const rawExtentOnDim = rawExtent[dim]; - - const dimStore = storage[dim]; - if (dimStore) { - dimStore[rawIndex] = val; - } - - if (val < rawExtentOnDim[0]) { - rawExtentOnDim[0] = val as number; - } - if (val > rawExtentOnDim[1]) { - rawExtentOnDim[1] = val as number; - } - } - } - } - - return list; - } - - /** - * Large data down sampling on given dimension - * @param sampleIndex Sample index for name and id - */ - downSample( - dimension: DimensionName, - rate: number, - sampleValue: (frameValues: ArrayLike) => ParsedValueNumeric, - sampleIndex: (frameValues: ArrayLike, value: ParsedValueNumeric) => number - ): List { - const list = cloneListForMapAndSample(this, [dimension]); - const targetStorage = list._storage; - - const frameValues = []; - let frameSize = mathFloor(1 / rate); - - const dimStore = targetStorage[dimension]; - const len = this.count(); - const rawExtentOnDim = list._rawExtent[dimension]; - - const newIndices = new (getIndicesCtor(this))(len); - - let offset = 0; - for (let i = 0; i < len; i += frameSize) { - // Last frame - if (frameSize > len - i) { - frameSize = len - i; - frameValues.length = frameSize; - } - for (let k = 0; k < frameSize; k++) { - const dataIdx = this.getRawIndex(i + k); - frameValues[k] = dimStore[dataIdx]; - } - const value = sampleValue(frameValues); - const sampleFrameIdx = this.getRawIndex( - Math.min(i + sampleIndex(frameValues, value) || 0, len - 1) - ); - // Only write value on the filtered data - dimStore[sampleFrameIdx] = value; - - if (value < rawExtentOnDim[0]) { - rawExtentOnDim[0] = value; - } - if (value > rawExtentOnDim[1]) { - rawExtentOnDim[1] = value; - } - - newIndices[offset++] = sampleFrameIdx; - } - - list._count = offset; - list._indices = newIndices; - - list.getRawIndex = getRawIndexWithIndices; - - return list as List; - } - - /** - * Large data down sampling using largest-triangle-three-buckets - * @param {string} valueDimension - * @param {number} targetCount - */ - lttbDownSample( - valueDimension: DimensionName, - rate: number - ) { - const list = cloneListForMapAndSample(this, []); - const targetStorage = list._storage; - const dimStore = targetStorage[valueDimension]; - const len = this.count(); - const newIndices = new (getIndicesCtor(this))(len); - - let sampledIndex = 0; - - const frameSize = mathFloor(1 / rate); - - let currentRawIndex = this.getRawIndex(0); - let maxArea; - let area; - let nextRawIndex; - - // First frame use the first data. - newIndices[sampledIndex++] = currentRawIndex; - for (let i = 1; i < len - 1; i += frameSize) { - const nextFrameStart = Math.min(i + frameSize, len - 1); - const nextFrameEnd = Math.min(i + frameSize * 2, len); - - const avgX = (nextFrameEnd + nextFrameStart) / 2; - let avgY = 0; - - for (let idx = nextFrameStart; idx < nextFrameEnd; idx++) { - const rawIndex = this.getRawIndex(idx); - const y = dimStore[rawIndex] as number; - if (isNaN(y)) { - continue; - } - avgY += y as number; - } - avgY /= (nextFrameEnd - nextFrameStart); - - const frameStart = i; - const frameEnd = Math.min(i + frameSize, len); - - const pointAX = i - 1; - const pointAY = dimStore[currentRawIndex] as number; - - maxArea = -1; - - nextRawIndex = frameStart; - // Find a point from current frame that construct a triangel with largest area with previous selected point - // And the average of next frame. - for (let idx = frameStart; idx < frameEnd; idx++) { - const rawIndex = this.getRawIndex(idx); - const y = dimStore[rawIndex] as number; - if (isNaN(y)) { - continue; - } - // Calculate triangle area over three buckets - area = Math.abs((pointAX - avgX) * (y - pointAY) - - (pointAX - idx) * (avgY - pointAY) - ); - if (area > maxArea) { - maxArea = area; - nextRawIndex = rawIndex; // Next a is this b - } - } - - newIndices[sampledIndex++] = nextRawIndex; - - currentRawIndex = nextRawIndex; // This a is the next a (chosen b) - } - - // First frame use the last data. - newIndices[sampledIndex++] = this.getRawIndex(len - 1); - list._count = sampledIndex; - list._indices = newIndices; - - list.getRawIndex = getRawIndexWithIndices; - return list; - } - - - /** - * Get model of one data item. - */ - // TODO: Type of data item - getItemModel(idx: number): Model - > { - const hostModel = this.hostModel; - const dataItem = this.getRawDataItem(idx) as ModelOption; - return new Model(dataItem, hostModel, hostModel && hostModel.ecModel); - } - - /** - * Create a data differ - */ - diff(otherList: List): DataDiffer { - const thisList = this; - - return new DataDiffer( - otherList ? otherList.getIndices() : [], - this.getIndices(), - function (idx: number) { - return getId(otherList, idx); - }, - function (idx: number) { - return getId(thisList, idx); - } - ); - } - - /** - * Get visual property. - */ - getVisual(key: K): Visual[K] { - const visual = this._visual as Visual; - return visual && visual[key]; - } - - /** - * Set visual property - * - * @example - * setVisual('color', color); - * setVisual({ - * 'color': color - * }); - */ - setVisual(key: K, val: Visual[K]): void; - setVisual(kvObj: Partial): void; - setVisual(kvObj: string | Partial, val?: any): void { - this._visual = this._visual || {}; - if (isObject(kvObj)) { - zrUtil.extend(this._visual, kvObj); - } - else { - this._visual[kvObj as string] = val; - } - } - - /** - * Get visual property of single data item - */ - // eslint-disable-next-line - getItemVisual(idx: number, key: K): Visual[K] { - const itemVisual = this._itemVisuals[idx] as Visual; - const val = itemVisual && itemVisual[key]; - if (val == null) { - // Use global visual property - return this.getVisual(key); - } - return val; - } - - /** - * If exists visual property of single data item - */ - hasItemVisual() { - return this._itemVisuals.length > 0; - } - - /** - * Make sure itemVisual property is unique - */ - // TODO: use key to save visual to reduce memory. - ensureUniqueItemVisual(idx: number, key: K): Visual[K] { - const itemVisuals = this._itemVisuals; - let itemVisual = itemVisuals[idx] as Visual; - if (!itemVisual) { - itemVisual = itemVisuals[idx] = {} as Visual; - } - let val = itemVisual[key]; - if (val == null) { - val = this.getVisual(key); - - // TODO Performance? - if (zrUtil.isArray(val)) { - val = val.slice() as unknown as Visual[K]; - } - else if (isObject(val)) { - val = zrUtil.extend({}, val); - } - - itemVisual[key] = val; - } - return val; - } - /** - * Set visual property of single data item - * - * @param {number} idx - * @param {string|Object} key - * @param {*} [value] - * - * @example - * setItemVisual(0, 'color', color); - * setItemVisual(0, { - * 'color': color - * }); - */ - // eslint-disable-next-line - setItemVisual(idx: number, key: K, value: Visual[K]): void; - setItemVisual(idx: number, kvObject: Partial): void; - // eslint-disable-next-line - setItemVisual(idx: number, key: K | Partial, value?: Visual[K]): void { - const itemVisual = this._itemVisuals[idx] || {}; - this._itemVisuals[idx] = itemVisual; - - if (isObject(key)) { - zrUtil.extend(itemVisual, key); - } - else { - itemVisual[key as string] = value; - } - } - - /** - * Clear itemVisuals and list visual. - */ - clearAllVisual(): void { - this._visual = {}; - this._itemVisuals = []; - } - - /** - * Set layout property. - */ - setLayout(key: string, val: any): void; - setLayout(kvObj: Dictionary): void; - setLayout(key: string | Dictionary, val?: any): void { - if (isObject(key)) { - for (const name in key) { - if (key.hasOwnProperty(name)) { - this.setLayout(name, key[name]); - } - } - return; - } - this._layout[key] = val; - } - - /** - * Get layout property. - */ - getLayout(key: string): any { - return this._layout[key]; - } - - /** - * Get layout of single data item - */ - getItemLayout(idx: number): any { - return this._itemLayouts[idx]; - } - - /** - * Set layout of single data item - */ - setItemLayout( - idx: number, - layout: (M extends true ? Dictionary : any), - merge?: M - ): void { - this._itemLayouts[idx] = merge - ? zrUtil.extend(this._itemLayouts[idx] || {}, layout) - : layout; - } - - /** - * Clear all layout of single data item - */ - clearItemLayouts(): void { - this._itemLayouts.length = 0; - } - - /** - * Set graphic element relative to data. It can be set as null - */ - setItemGraphicEl(idx: number, el: Element): void { - const seriesIndex = this.hostModel && (this.hostModel as any).seriesIndex; - // Add data index and series index for indexing the data by element - // Useful in tooltip - setCommonECData(seriesIndex, this.dataType, idx, el); - - this._graphicEls[idx] = el; - } - - getItemGraphicEl(idx: number): Element { - return this._graphicEls[idx]; - } - - eachItemGraphicEl( - cb: (this: Ctx, el: Element, idx: number) => void, - context?: Ctx - ): void { - zrUtil.each(this._graphicEls, function (el, idx) { - if (el) { - cb && cb.call(context, el, idx); - } - }); - } - - /** - * Shallow clone a new list except visual and layout properties, and graph elements. - * New list only change the indices. - */ - cloneShallow(list?: List): List { - if (!list) { - const dimensionInfoList = map(this.dimensions, this.getDimensionInfo, this); - list = new List(dimensionInfoList, this.hostModel); - } - - // FIXME - list._storage = this._storage; - list._storageArr = this._storageArr; - - transferProperties(list, this); - - // Clone will not change the data extent and indices - if (this._indices) { - const Ctor = this._indices.constructor as DataArrayLikeConstructor; - if (Ctor === Array) { - const thisCount = this._indices.length; - list._indices = new Ctor(thisCount); - for (let i = 0; i < thisCount; i++) { - list._indices[i] = this._indices[i]; - } - } - else { - list._indices = new (Ctor as DataTypedArrayConstructor)(this._indices); - } - } - else { - list._indices = null; - } - list.getRawIndex = list._indices ? getRawIndexWithIndices : getRawIndexWithoutIndices; - - return list; - } - - /** - * Wrap some method to add more feature - */ - wrapMethod( - methodName: FunctionPropertyNames, - injectFunction: (...args: any) => any - ): void { - const originalMethod = this[methodName]; - if (typeof originalMethod !== 'function') { - return; - } - this.__wrappedMethods = this.__wrappedMethods || []; - this.__wrappedMethods.push(methodName); - this[methodName] = function () { - const res = (originalMethod as any).apply(this, arguments); - return injectFunction.apply(this, [res].concat(zrUtil.slice(arguments))); - }; - } - - - // ---------------------------------------------------------- - // A work around for internal method visiting private member. - // ---------------------------------------------------------- - private static internalField = (function () { - - defaultDimValueGetters = { - - arrayRows: getDimValueSimply, - - objectRows: function ( - this: List, dataItem: Dictionary, dimName: string, dataIndex: number, dimIndex: number - ): ParsedValue { - return parseDataValue(dataItem[dimName], this._dimensionInfos[dimName]); - }, - - keyedColumns: getDimValueSimply, - - original: function ( - this: List, dataItem: any, dimName: string, dataIndex: number, dimIndex: number - ): ParsedValue { - // Performance sensitive, do not use modelUtil.getDataItemValue. - // If dataItem is an plain object with no value field, the let `value` - // will be assigned with the object, but it will be tread correctly - // in the `convertValue`. - const value = dataItem && (dataItem.value == null ? dataItem : dataItem.value); - - // If any dataItem is like { value: 10 } - if (!this._rawData.pure && isDataItemOption(dataItem)) { - this.hasItemOption = true; - } - return parseDataValue( - (value instanceof Array) - ? value[dimIndex] - // If value is a single number or something else not array. - : value, - this._dimensionInfos[dimName] - ); - }, - - typedArray: function ( - this: List, dataItem: any, dimName: string, dataIndex: number, dimIndex: number - ): ParsedValue { - return dataItem[dimIndex]; - } - - }; - - function getDimValueSimply( - this: List, dataItem: any, dimName: string, dataIndex: number, dimIndex: number - ): ParsedValue { - return parseDataValue(dataItem[dimIndex], this._dimensionInfos[dimName]); - } - - prepareInvertedIndex = function (list: List): void { - const invertedIndicesMap = list._invertedIndicesMap; - zrUtil.each(invertedIndicesMap, function (invertedIndices, dim) { - const dimInfo = list._dimensionInfos[dim]; - - // Currently, only dimensions that has ordinalMeta can create inverted indices. - const ordinalMeta = dimInfo.ordinalMeta; - if (ordinalMeta) { - invertedIndices = invertedIndicesMap[dim] = new CtorInt32Array( - ordinalMeta.categories.length - ); - // The default value of TypedArray is 0. To avoid miss - // mapping to 0, we should set it as INDEX_NOT_FOUND. - for (let i = 0; i < invertedIndices.length; i++) { - invertedIndices[i] = INDEX_NOT_FOUND; - } - for (let i = 0; i < list._count; i++) { - // Only support the case that all values are distinct. - invertedIndices[list.get(dim, i) as number] = i; - } - } - }); - }; - - getIdNameFromStore = function ( - list: List, dimIdx: number, ordinalMeta: OrdinalMeta, rawIndex: number - ): string { - let val; - const chunk = list._storageArr[dimIdx]; - if (chunk) { - val = chunk[rawIndex]; - if (ordinalMeta && ordinalMeta.categories.length) { - val = ordinalMeta.categories[val as OrdinalNumber]; - } - } - return convertOptionIdName(val, null); - }; - - getIndicesCtor = function (list: List): DataArrayLikeConstructor { - // The possible max value in this._indicies is always this._rawCount despite of filtering. - return list._rawCount > 65535 ? CtorUint32Array : CtorUint16Array; - }; - - prepareStorage = function ( - storage: DataStorage, - dimInfo: DataDimensionInfo, - end: number, - append?: boolean - ): void { - const DataCtor = dataCtors[dimInfo.type]; - const dim = dimInfo.name; - - if (append) { - const oldStore = storage[dim]; - const oldLen = oldStore && oldStore.length; - if (!(oldLen === end)) { - const newStore = new DataCtor(end); - // The cost of the copy is probably inconsiderable - // within the initial chunkSize. - for (let j = 0; j < oldLen; j++) { - newStore[j] = oldStore[j]; - } - storage[dim] = newStore; - } - } - else { - storage[dim] = new DataCtor(end); - } - }; - - getRawIndexWithoutIndices = function (this: List, idx: number): number { - return idx; - }; - - getRawIndexWithIndices = function (this: List, idx: number): number { - if (idx < this._count && idx >= 0) { - return this._indices[idx]; - } - return -1; - }; - - /** - * @see the comment of `List['getId']`. - */ - getId = function (list: List, rawIndex: number): string { - let id = list._idList[rawIndex]; - if (id == null && list._idDimIdx != null) { - id = getIdNameFromStore(list, list._idDimIdx, list._idOrdinalMeta, rawIndex); - } - if (id == null) { - id = ID_PREFIX + rawIndex; - } - return id; - }; - - normalizeDimensions = function ( - dimensions: ItrParamDims - ): Array { - if (!zrUtil.isArray(dimensions)) { - dimensions = dimensions != null ? [dimensions] : []; - } - return dimensions; - }; - - validateDimensions = function (list: List, dims: DimensionName[]): void { - for (let i = 0; i < dims.length; i++) { - // stroage may be empty when no data, so use - // dimensionInfos to check. - if (!list._dimensionInfos[dims[i]]) { - console.error('Unkown dimension ' + dims[i]); - } - } - }; - - // Data in excludeDimensions is copied, otherwise transfered. - cloneListForMapAndSample = function ( - original: List, excludeDimensions: DimensionName[] - ): List { - const allDimensions = original.dimensions; - const list = new List( - map(allDimensions, original.getDimensionInfo, original), - original.hostModel - ); - // FIXME If needs stackedOn, value may already been stacked - transferProperties(list, original); - - const storage = list._storage = {} as DataStorage; - const originalStorage = original._storage; - const storageArr: DataValueChunk[] = list._storageArr = []; - - // Init storage - for (let i = 0; i < allDimensions.length; i++) { - const dim = allDimensions[i]; - if (originalStorage[dim]) { - // Notice that we do not reset invertedIndicesMap here, becuase - // there is no scenario of mapping or sampling ordinal dimension. - if (zrUtil.indexOf(excludeDimensions, dim) >= 0) { - storage[dim] = cloneChunk(originalStorage[dim]); - list._rawExtent[dim] = getInitialExtent(); - list._extent[dim] = null; - } - else { - // Direct reference for other dimensions - storage[dim] = originalStorage[dim]; - } - storageArr.push(storage[dim]); - } - } - return list; - }; - - function cloneChunk(originalChunk: DataValueChunk): DataValueChunk { - const Ctor = originalChunk.constructor; - // Only shallow clone is enough when Array. - return Ctor === Array - ? (originalChunk as Array).slice() - : new (Ctor as DataTypedArrayConstructor)(originalChunk as DataTypedArray); - } - - getInitialExtent = function (): [number, number] { - return [Infinity, -Infinity]; - }; - - transferProperties = function (target: List, source: List): void { - zrUtil.each( - TRANSFERABLE_PROPERTIES.concat(source.__wrappedMethods || []), - function (propName) { - if (source.hasOwnProperty(propName)) { - (target as any)[propName] = (source as any)[propName]; - } - } - ); - - target.__wrappedMethods = source.__wrappedMethods; - - zrUtil.each(CLONE_PROPERTIES, function (propName) { - (target as any)[propName] = zrUtil.clone((source as any)[propName]); - }); - - target._calculationInfo = zrUtil.extend({}, source._calculationInfo); - }; - - makeIdFromName = function (list: List, idx: number): void { - const nameList = list._nameList; - const idList = list._idList; - const nameDimIdx = list._nameDimIdx; - const idDimIdx = list._idDimIdx; - - let name = nameList[idx]; - let id = idList[idx]; - - if (name == null && nameDimIdx != null) { - nameList[idx] = name = getIdNameFromStore(list, nameDimIdx, list._nameOrdinalMeta, idx); - } - if (id == null && idDimIdx != null) { - idList[idx] = id = getIdNameFromStore(list, idDimIdx, list._idOrdinalMeta, idx); - } - if (id == null && name != null) { - const nameRepeatCount = list._nameRepeatCount; - const nmCnt = nameRepeatCount[name] = (nameRepeatCount[name] || 0) + 1; - id = name; - if (nmCnt > 1) { - id += '__ec__' + nmCnt; - } - idList[idx] = id; - } - }; - - })(); - -} - -interface List { - getLinkedData(dataType?: SeriesDataType): List; - getLinkedDataAll(): { data: List, type?: SeriesDataType }[]; -} - -export default List; diff --git a/src/data/OrdinalMeta.ts b/src/data/OrdinalMeta.ts index f2fd790c80..d56273678e 100644 --- a/src/data/OrdinalMeta.ts +++ b/src/data/OrdinalMeta.ts @@ -21,6 +21,7 @@ import {createHashMap, isObject, map, HashMap} from 'zrender/src/core/util'; import Model from '../model/Model'; import { OrdinalNumber, OrdinalRawValue } from '../util/types'; +let uidBase = 0; class OrdinalMeta { @@ -32,6 +33,8 @@ class OrdinalMeta { private _map: HashMap; + readonly uid: number; + constructor(opt: { categories?: OrdinalRawValue[], @@ -41,6 +44,7 @@ class OrdinalMeta { this.categories = opt.categories || []; this._needCollect = opt.needCollect; this._deduplication = opt.deduplication; + this.uid = ++uidBase; } static createByAxisModel(axisModel: Model): OrdinalMeta { diff --git a/src/data/SeriesData.ts b/src/data/SeriesData.ts new file mode 100644 index 0000000000..99b3c04dc2 --- /dev/null +++ b/src/data/SeriesData.ts @@ -0,0 +1,1508 @@ +/* +* 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. +*/ + +/* global Int32Array */ + + +import * as zrUtil from 'zrender/src/core/util'; +import {PathStyleProps} from 'zrender/src/graphic/Path'; +import Model from '../model/Model'; +import DataDiffer from './DataDiffer'; +import {DataProvider, DefaultDataProvider} from './helper/dataProvider'; +import {summarizeDimensions, DimensionSummary} from './helper/dimensionHelper'; +import SeriesDimensionDefine from './SeriesDimensionDefine'; +import {ArrayLike, Dictionary, FunctionPropertyNames} from 'zrender/src/core/types'; +import Element from 'zrender/src/Element'; +import { + DimensionIndex, DimensionName, DimensionLoose, OptionDataItem, + ParsedValue, ParsedValueNumeric, + ModelOption, SeriesDataType, OptionSourceData, SOURCE_FORMAT_TYPED_ARRAY, SOURCE_FORMAT_ORIGINAL, + DecalObject, + OrdinalNumber, + OrdinalRawValue +} from '../util/types'; +import {convertOptionIdName, isDataItemOption} from '../util/model'; +import { setCommonECData } from '../util/innerStore'; +import type Graph from './Graph'; +import type Tree from './Tree'; +import type { VisualMeta } from '../component/visualMap/VisualMapModel'; +import {isSourceInstance, Source} from './Source'; +import { LineStyleProps } from '../model/mixin/lineStyle'; +import DataStore, { DataStoreDimensionDefine, DimValueGetter } from './DataStore'; +import { isSeriesDataSchema, SeriesDataSchema } from './helper/SeriesDataSchema'; + +const isObject = zrUtil.isObject; +const map = zrUtil.map; + +const CtorInt32Array = typeof Int32Array === 'undefined' ? Array : Int32Array; + +// Use prefix to avoid index to be the same as otherIdList[idx], +// which will cause weird udpate animation. +const ID_PREFIX = 'e\0\0'; + +const INDEX_NOT_FOUND = -1; + +type NameRepeatCount = {[name: string]: number}; +type ItrParamDims = DimensionLoose | Array; +// If Ctx not specified, use List as Ctx +type CtxOrList = unknown extends Ctx ? SeriesData : Ctx; +type EachCb0 = (this: CtxOrList, idx: number) => void; +type EachCb1 = (this: CtxOrList, x: ParsedValue, idx: number) => void; +type EachCb2 = (this: CtxOrList, x: ParsedValue, y: ParsedValue, idx: number) => void; +type EachCb = (this: CtxOrList, ...args: any) => void; +type FilterCb0 = (this: CtxOrList, idx: number) => boolean; +type FilterCb1 = (this: CtxOrList, x: ParsedValue, idx: number) => boolean; +type FilterCb2 = (this: CtxOrList, x: ParsedValue, y: ParsedValue, idx: number) => boolean; +type FilterCb = (this: CtxOrList, ...args: any) => boolean; +type MapArrayCb0 = (this: CtxOrList, idx: number) => any; +type MapArrayCb1 = (this: CtxOrList, x: ParsedValue, idx: number) => any; +type MapArrayCb2 = (this: CtxOrList, x: ParsedValue, y: ParsedValue, idx: number) => any; +type MapArrayCb = (this: CtxOrList, ...args: any) => any; +type MapCb1 = (this: CtxOrList, x: ParsedValue, idx: number) => ParsedValue | ParsedValue[]; +type MapCb2 = (this: CtxOrList, x: ParsedValue, y: ParsedValue, idx: number) => + ParsedValue | ParsedValue[]; +type MapCb = (this: CtxOrList, ...args: any) => ParsedValue | ParsedValue[]; + +type SeriesDimensionDefineLoose = string | object | SeriesDimensionDefine; + +// `SeriesDimensionLoose` and `SeriesDimensionName` is the dimension that is used by coordinate +// system or declared in `series.encode`, which will be saved in `SeriesData`. Other dimension +// might not be saved in `SeriesData` for performance consideration. See `createDimension` for +// more details. +type SeriesDimensionLoose = DimensionLoose; +type SeriesDimensionName = DimensionName; +// type SeriesDimensionIndex = DimensionIndex; + + +const TRANSFERABLE_PROPERTIES = [ + 'hasItemOption', '_nameList', '_idList', '_invertedIndicesMap', + '_dimSummary', 'userOutput', + '_rawData', '_dimValueGetter', + '_nameDimIdx', '_idDimIdx', '_nameRepeatCount' +]; + +const CLONE_PROPERTIES = [ + '_approximateExtent' +]; + +export interface DefaultDataVisual { + style: PathStyleProps + // Draw type determined which prop should be set with encoded color. + // It's only available on the global visual. Use getVisual('drawType') to access it. + // It will be set in visual/style.ts module in the first priority. + drawType: 'fill' | 'stroke' + + symbol?: string + symbolSize?: number | number[] + symbolRotate?: number + symbolKeepAspect?: boolean + symbolOffset?: string | number | (string | number)[] + + liftZ?: number + // For legend. + legendIcon?: string + legendLineStyle?: LineStyleProps + + // visualMap will inject visualMeta data + visualMeta?: VisualMeta[] + + // If color is encoded from palette + colorFromPalette?: boolean + + decal?: DecalObject +} + +export interface DataCalculationInfo { + stackedDimension: DimensionName; + stackedByDimension: DimensionName; + isStackedByIndex: boolean; + stackedOverDimension: DimensionName; + stackResultDimension: DimensionName; + stackedOnSeries?: SERIES_MODEL; +} + +// ----------------------------- +// Internal method declarations: +// ----------------------------- +let prepareInvertedIndex: (data: SeriesData) => void; +let getId: (data: SeriesData, rawIndex: number) => string; +let getIdNameFromStore: (data: SeriesData, dimIdx: number, dataIdx: number) => string; +let normalizeDimensions: (dimensions: ItrParamDims) => Array; +let transferProperties: (target: SeriesData, source: SeriesData) => void; +let cloneListForMapAndSample: (original: SeriesData) => SeriesData; +let makeIdFromName: (data: SeriesData, idx: number) => void; + +class SeriesData< + HostModel extends Model = Model, + Visual extends DefaultDataVisual = DefaultDataVisual +> { + + readonly type = 'list'; + + /** + * Name of dimensions list of SeriesData. + * + * @caution Carefully use the index of this array. + * Becuase when DataStore is an extra high dimension(>30) dataset. We will only pick + * the used dimensions from DataStore to avoid performance issue. + */ + readonly dimensions: SeriesDimensionName[]; + + // Infomation of each data dimension, like data type. + private _dimInfos: Record; + + private _dimOmitted = false; + private _schema?: SeriesDataSchema; + /** + * @pending + * Actually we do not really need to convert dimensionIndex to dimensionName + * and do not need `_dimIdxToName` if we do everything internally based on dimension + * index rather than dimension name. + */ + private _dimIdxToName?: zrUtil.HashMap; + + readonly hostModel: HostModel; + + /** + * @readonly + */ + dataType: SeriesDataType; + + /** + * @readonly + * Host graph if List is used to store graph nodes / edges. + */ + graph?: Graph; + + /** + * @readonly + * Host tree if List is used to store tree ndoes. + */ + tree?: Tree; + + private _store: DataStore; + + private _nameList: string[] = []; + private _idList: string[] = []; + + // Models of data option is stored sparse for optimizing memory cost + // Never used yet (not used yet). + // private _optionModels: Model[] = []; + + // Global visual properties after visual coding + private _visual: Dictionary = {}; + + // Globel layout properties. + private _layout: Dictionary = {}; + + // Item visual properties after visual coding + private _itemVisuals: Dictionary[] = []; + + // Item layout properties after layout + private _itemLayouts: any[] = []; + + // Graphic elemnents + private _graphicEls: Element[] = []; + + // key: dim, value: extent + private _approximateExtent: Record = {}; + + private _dimSummary: DimensionSummary; + + private _invertedIndicesMap: Record>; + + private _calculationInfo: DataCalculationInfo = {} as DataCalculationInfo; + + // User output info of this data. + // DO NOT use it in other places! + // When preparing user params for user callbacks, we have + // to clone these inner data structures to prevent users + // from modifying them to effect built-in logic. And for + // performance consideration we make this `userOutput` to + // avoid clone them too many times. + userOutput: DimensionSummary['userOutput']; + + // Having detected that there is data item is non primitive type + // (in type `OptionDataItemObject`). + // Like `data: [ { value: xx, itemStyle: {...} }, ...]` + // At present it only happen in `SOURCE_FORMAT_ORIGINAL`. + hasItemOption: boolean = false; + + // id or name is used on dynamic data, mapping old and new items. + // When generating id from name, avoid repeat. + private _nameRepeatCount: NameRepeatCount; + private _nameDimIdx: number; + private _idDimIdx: number; + + private __wrappedMethods: string[]; + + // Methods that create a new list based on this list should be listed here. + // Notice that those method should `RETURN` the new list. + TRANSFERABLE_METHODS = ['cloneShallow', 'downSample', 'lttbDownSample', 'map'] as const; + // Methods that change indices of this list should be listed here. + CHANGABLE_METHODS = ['filterSelf', 'selectRange'] as const; + DOWNSAMPLE_METHODS = ['downSample', 'lttbDownSample'] as const; + + /** + * @param dimensionsInput.dimensions + * For example, ['someDimName', {name: 'someDimName', type: 'someDimType'}, ...]. + * Dimensions should be concrete names like x, y, z, lng, lat, angle, radius + */ + constructor( + dimensionsInput: SeriesDataSchema | SeriesDimensionDefineLoose[], + hostModel: HostModel + ) { + let dimensions: SeriesDimensionDefineLoose[]; + let assignStoreDimIdx = false; + if (isSeriesDataSchema(dimensionsInput)) { + dimensions = dimensionsInput.dimensions; + this._dimOmitted = dimensionsInput.isDimensionOmitted(); + this._schema = dimensionsInput; + } + else { + assignStoreDimIdx = true; + dimensions = dimensionsInput as SeriesDimensionDefineLoose[]; + } + + dimensions = dimensions || ['x', 'y']; + + const dimensionInfos: Dictionary = {}; + const dimensionNames = []; + const invertedIndicesMap: Dictionary = {}; + let needsHasOwn = false; + const emptyObj = {}; + + for (let i = 0; i < dimensions.length; i++) { + // Use the original dimensions[i], where other flag props may exists. + const dimInfoInput = dimensions[i]; + + const dimensionInfo: SeriesDimensionDefine = + zrUtil.isString(dimInfoInput) + ? new SeriesDimensionDefine({name: dimInfoInput}) + : !(dimInfoInput instanceof SeriesDimensionDefine) + ? new SeriesDimensionDefine(dimInfoInput) + : dimInfoInput; + + const dimensionName = dimensionInfo.name; + dimensionInfo.type = dimensionInfo.type || 'float'; + if (!dimensionInfo.coordDim) { + dimensionInfo.coordDim = dimensionName; + dimensionInfo.coordDimIndex = 0; + } + + const otherDims = dimensionInfo.otherDims = dimensionInfo.otherDims || {}; + dimensionNames.push(dimensionName); + dimensionInfos[dimensionName] = dimensionInfo; + if ((emptyObj as any)[dimensionName] != null) { + needsHasOwn = true; + } + + if (dimensionInfo.createInvertedIndices) { + invertedIndicesMap[dimensionName] = []; + } + if (otherDims.itemName === 0) { + this._nameDimIdx = i; + } + if (otherDims.itemId === 0) { + this._idDimIdx = i; + } + + if (__DEV__) { + zrUtil.assert(assignStoreDimIdx || dimensionInfo.storeDimIndex >= 0); + } + if (assignStoreDimIdx) { + dimensionInfo.storeDimIndex = i; + } + } + + this.dimensions = dimensionNames; + this._dimInfos = dimensionInfos; + this._initGetDimensionInfo(needsHasOwn); + + this.hostModel = hostModel; + + this._invertedIndicesMap = invertedIndicesMap; + + if (this._dimOmitted) { + const dimIdxToName = this._dimIdxToName = zrUtil.createHashMap(); + zrUtil.each(dimensionNames, dimName => { + dimIdxToName.set(dimensionInfos[dimName].storeDimIndex, dimName); + }); + } + } + + /** + * + * Get concrete dimension name by dimension name or dimension index. + * If input a dimension name, do not validate whether the dimension name exits. + * + * @caution + * @param dim Must make sure the dimension is `SeriesDimensionLoose`. + * Because only those dimensions will have auto-generated dimension names if not + * have a user-specified name, and other dimensions will get a return of null/undefined. + * + * @notice Becuause of this reason, should better use `getDimensionIndex` instead, for examples: + * ```js + * const val = data.getStore().get(data.getDimensionIndex(dim), dataIdx); + * ``` + * + * @return Concrete dim name. + */ + getDimension(dim: SeriesDimensionLoose): DimensionName { + let dimIdx = this._recognizeDimIndex(dim); + if (dimIdx == null) { + return dim as DimensionName; + } + dimIdx = dim as DimensionIndex; + + if (!this._dimOmitted) { + return this.dimensions[dimIdx]; + } + + // Retrieve from series dimension definition becuase it probably contains + // generated dimension name (like 'x', 'y'). + const dimName = this._dimIdxToName.get(dimIdx); + if (dimName != null) { + return dimName; + } + + const sourceDimDef = this._schema.getSourceDimension(dimIdx); + if (sourceDimDef) { + return sourceDimDef.name; + } + } + + /** + * Get dimension index in data store. Return -1 if not found. + * Can be used to index value from getRawValue. + */ + getDimensionIndex(dim: DimensionLoose): DimensionIndex { + const dimIdx = this._recognizeDimIndex(dim); + if (dimIdx != null) { + return dimIdx; + } + + if (dim == null) { + return -1; + } + + const dimInfo = this._getDimInfo(dim as DimensionName); + return dimInfo + ? dimInfo.storeDimIndex + : this._dimOmitted + ? this._schema.getSourceDimensionIndex(dim as DimensionName) + : -1; + } + + /** + * The meanings of the input parameter `dim`: + * + * + If dim is a number (e.g., `1`), it means the index of the dimension. + * For example, `getDimension(0)` will return 'x' or 'lng' or 'radius'. + * + If dim is a number-like string (e.g., `"1"`): + * + If there is the same concrete dim name defined in `series.dimensions` or `dataset.dimensions`, + * it means that concrete name. + * + If not, it will be converted to a number, which means the index of the dimension. + * (why? because of the backward compatbility. We have been tolerating number-like string in + * dimension setting, although now it seems that it is not a good idea.) + * For example, `visualMap[i].dimension: "1"` is the same meaning as `visualMap[i].dimension: 1`, + * if no dimension name is defined as `"1"`. + * + If dim is a not-number-like string, it means the concrete dim name. + * For example, it can be be default name `"x"`, `"y"`, `"z"`, `"lng"`, `"lat"`, `"angle"`, `"radius"`, + * or customized in `dimensions` property of option like `"age"`. + * + * @return recogonized `DimensionIndex`. Otherwise return null/undefined (means that dim is `DimensionName`). + */ + private _recognizeDimIndex(dim: DimensionLoose): DimensionIndex { + if (typeof dim === 'number' + // If being a number-like string but not being defined as a dimension name. + || ( + dim != null + && !isNaN(dim as any) + && !this._getDimInfo(dim) + && (!this._dimOmitted || this._schema.getSourceDimensionIndex(dim) < 0) + ) + ) { + return +dim; + } + } + + private _getStoreDimIndex(dim: DimensionLoose): DimensionIndex { + const dimIdx = this.getDimensionIndex(dim); + if (__DEV__) { + if (dimIdx == null) { + throw new Error('Unkown dimension ' + dim); + } + } + return dimIdx; + } + + /** + * Get type and calculation info of particular dimension + * @param dim + * Dimension can be concrete names like x, y, z, lng, lat, angle, radius + * Or a ordinal number. For example getDimensionInfo(0) will return 'x' or 'lng' or 'radius' + */ + getDimensionInfo(dim: SeriesDimensionLoose): SeriesDimensionDefine { + // Do not clone, because there may be categories in dimInfo. + return this._getDimInfo(this.getDimension(dim)); + } + + /** + * If `dimName` if from outside of `SeriesData`, + * use this method other than visit `this._dimInfos` directly. + */ + private _getDimInfo: (dimName: SeriesDimensionName) => SeriesDimensionDefine; + + private _initGetDimensionInfo(needsHasOwn: boolean): void { + const dimensionInfos = this._dimInfos; + this._getDimInfo = needsHasOwn + ? dimName => (dimensionInfos.hasOwnProperty(dimName) ? dimensionInfos[dimName] : undefined) + : dimName => dimensionInfos[dimName]; + } + + /** + * concrete dimension name list on coord. + */ + getDimensionsOnCoord(): SeriesDimensionName[] { + return this._dimSummary.dataDimsOnCoord.slice(); + } + + /** + * @param coordDim + * @param idx A coordDim may map to more than one data dim. + * If not specified, return the first dim not extra. + * @return concrete data dim. If not found, return null/undefined + */ + mapDimension(coordDim: SeriesDimensionName): SeriesDimensionName; + mapDimension(coordDim: SeriesDimensionName, idx: number): SeriesDimensionName; + mapDimension(coordDim: SeriesDimensionName, idx?: number): SeriesDimensionName { + const dimensionsSummary = this._dimSummary; + + if (idx == null) { + return dimensionsSummary.encodeFirstDimNotExtra[coordDim] as any; + } + + const dims = dimensionsSummary.encode[coordDim]; + return dims ? dims[idx as number] as any : null; + } + + mapDimensionsAll(coordDim: SeriesDimensionName): SeriesDimensionName[] { + const dimensionsSummary = this._dimSummary; + const dims = dimensionsSummary.encode[coordDim]; + return (dims || []).slice(); + } + + getStore() { + return this._store; + } + + /** + * Initialize from data + * @param data source or data or data store. + * @param nameList The name of a datum is used on data diff and + * default label/tooltip. + * A name can be specified in encode.itemName, + * or dataItem.name (only for series option data), + * or provided in nameList from outside. + */ + initData( + data: Source | OptionSourceData | DataStore | DataProvider, + nameList?: string[], + dimValueGetter?: DimValueGetter + ): void { + let store: DataStore; + if (data instanceof DataStore) { + store = data; + } + + if (!store) { + const dimensions = this.dimensions; + const provider = (isSourceInstance(data) || zrUtil.isArrayLike(data)) + ? new DefaultDataProvider(data as Source | OptionSourceData, dimensions.length) + : data as DataProvider; + store = new DataStore(); + const dimensionInfos: DataStoreDimensionDefine[] = map(dimensions, dimName => ({ + type: this._dimInfos[dimName].type, + property: dimName + })); + store.initData(provider, dimensionInfos, dimValueGetter); + } + + this._store = store; + + // Reset + this._nameList = (nameList || []).slice(); + this._idList = []; + this._nameRepeatCount = {}; + + this._doInit(0, store.count()); + + // Cache summary info for fast visit. See "dimensionHelper". + // Needs to be initialized after store is prepared. + this._dimSummary = summarizeDimensions(this, this._schema); + this.userOutput = this._dimSummary.userOutput; + } + + /** + * Caution: Can be only called on raw data (before `this._indices` created). + */ + appendData(data: ArrayLike): void { + const range = this._store.appendData(data); + this._doInit(range[0], range[1]); + } + /** + * Caution: Can be only called on raw data (before `this._indices` created). + * This method does not modify `rawData` (`dataProvider`), but only + * add values to store. + * + * The final count will be increased by `Math.max(values.length, names.length)`. + * + * @param values That is the SourceType: 'arrayRows', like + * [ + * [12, 33, 44], + * [NaN, 43, 1], + * ['-', 'asdf', 0] + * ] + * Each item is exaclty cooresponding to a dimension. + */ + appendValues(values: any[][], names?: string[]): void { + const {start, end} = this._store.appendValues(values, names.length); + const shouldMakeIdFromName = this._shouldMakeIdFromName(); + + this._updateOrdinalMeta(); + + if (names) { + for (let idx = start; idx < end; idx++) { + const sourceIdx = idx - start; + this._nameList[idx] = names[sourceIdx]; + if (shouldMakeIdFromName) { + makeIdFromName(this, idx); + } + } + } + } + + private _updateOrdinalMeta(): void { + const store = this._store; + const dimensions = this.dimensions; + for (let i = 0; i < dimensions.length; i++) { + const dimInfo = this._dimInfos[dimensions[i]]; + if (dimInfo.ordinalMeta) { + store.collectOrdinalMeta(dimInfo.storeDimIndex, dimInfo.ordinalMeta); + } + } + } + + private _shouldMakeIdFromName(): boolean { + const provider = this._store.getProvider(); + return this._idDimIdx == null + && provider.getSource().sourceFormat !== SOURCE_FORMAT_TYPED_ARRAY + && !provider.fillStorage; + } + + private _doInit(start: number, end: number): void { + if (start >= end) { + return; + } + + const store = this._store; + const provider = store.getProvider(); + + this._updateOrdinalMeta(); + + const nameList = this._nameList; + const idList = this._idList; + const sourceFormat = provider.getSource().sourceFormat; + const isFormatOriginal = sourceFormat === SOURCE_FORMAT_ORIGINAL; + + // Each data item is value + // [1, 2] + // 2 + // Bar chart, line chart which uses category axis + // only gives the 'y' value. 'x' value is the indices of category + // Use a tempValue to normalize the value to be a (x, y) value + // If dataItem is {name: ...} or {id: ...}, it has highest priority. + // This kind of ids and names are always stored `_nameList` and `_idList`. + if (isFormatOriginal && !provider.pure) { + const sharedDataItem = [] as OptionDataItem; + for (let idx = start; idx < end; idx++) { + // NOTICE: Try not to write things into dataItem + const dataItem = provider.getItem(idx, sharedDataItem); + if (!this.hasItemOption && isDataItemOption(dataItem)) { + this.hasItemOption = true; + } + if (dataItem) { + const itemName = (dataItem as any).name; + if (nameList[idx] == null && itemName != null) { + nameList[idx] = convertOptionIdName(itemName, null); + } + const itemId = (dataItem as any).id; + if (idList[idx] == null && itemId != null) { + idList[idx] = convertOptionIdName(itemId, null); + } + } + } + } + + if (this._shouldMakeIdFromName()) { + for (let idx = start; idx < end; idx++) { + makeIdFromName(this, idx); + } + } + + prepareInvertedIndex(this); + } + + /** + * PENDING: In fact currently this function is only used to short-circuit + * the calling of `scale.unionExtentFromData` when data have been filtered by modules + * like "dataZoom". `scale.unionExtentFromData` is used to calculate data extent for series on + * an axis, but if a "axis related data filter module" is used, the extent of the axis have + * been fixed and no need to calling `scale.unionExtentFromData` actually. + * But if we add "custom data filter" in future, which is not "axis related", this method may + * be still needed. + * + * Optimize for the scenario that data is filtered by a given extent. + * Consider that if data amount is more than hundreds of thousand, + * extent calculation will cost more than 10ms and the cache will + * be erased because of the filtering. + */ + getApproximateExtent(dim: SeriesDimensionLoose): [number, number] { + return this._approximateExtent[dim] || this._store.getDataExtent(this._getStoreDimIndex(dim)); + } + + /** + * Calculate extent on a filtered data might be time consuming. + * Approximate extent is only used for: calculte extent of filtered data outside. + */ + setApproximateExtent(extent: [number, number], dim: SeriesDimensionLoose): void { + dim = this.getDimension(dim); + this._approximateExtent[dim] = extent.slice() as [number, number]; + } + + getCalculationInfo>( + key: CALC_INFO_KEY + ): DataCalculationInfo[CALC_INFO_KEY] { + return this._calculationInfo[key]; + } + + /** + * @param key or k-v object + */ + setCalculationInfo( + key: DataCalculationInfo + ): void; + setCalculationInfo>( + key: CALC_INFO_KEY, + value: DataCalculationInfo[CALC_INFO_KEY] + ): void; + setCalculationInfo( + key: (keyof DataCalculationInfo) | DataCalculationInfo, + value?: DataCalculationInfo[keyof DataCalculationInfo] + ): void { + isObject(key) + ? zrUtil.extend(this._calculationInfo, key as object) + : ((this._calculationInfo as any)[key] = value); + } + + /** + * @return Never be null/undefined. `number` will be converted to string. Becuase: + * In most cases, name is used in display, where returning a string is more convenient. + * In other cases, name is used in query (see `indexOfName`), where we can keep the + * rule that name `2` equals to name `'2'`. + */ + getName(idx: number): string { + const rawIndex = this.getRawIndex(idx); + let name = this._nameList[rawIndex]; + if (name == null && this._nameDimIdx != null) { + name = getIdNameFromStore(this, this._nameDimIdx, rawIndex); + } + if (name == null) { + name = ''; + } + return name; + } + + private _getCategory(dimIdx: number, idx: number): OrdinalRawValue { + const ordinal = this._store.get(dimIdx, idx); + const ordinalMeta = this._store.getOrdinalMeta(dimIdx); + if (ordinalMeta) { + return ordinalMeta.categories[ordinal as OrdinalNumber]; + } + return ordinal; + } + + /** + * @return Never null/undefined. `number` will be converted to string. Becuase: + * In all cases having encountered at present, id is used in making diff comparison, which + * are usually based on hash map. We can keep the rule that the internal id are always string + * (treat `2` is the same as `'2'`) to make the related logic simple. + */ + getId(idx: number): string { + return getId(this, this.getRawIndex(idx)); + } + + count(): number { + return this._store.count(); + } + + /** + * Get value. Return NaN if idx is out of range. + * + * @notice Should better to use `data.getStore().get(dimIndex, dataIdx)` instead. + */ + get(dim: SeriesDimensionName, idx: number): ParsedValue { + const store = this._store; + const dimInfo = this._dimInfos[dim]; + if (dimInfo) { + return store.get(dimInfo.storeDimIndex, idx); + } + } + + /** + * @notice Should better to use `data.getStore().getByRawIndex(dimIndex, dataIdx)` instead. + */ + getByRawIndex(dim: SeriesDimensionName, rawIdx: number): ParsedValue { + const store = this._store; + const dimInfo = this._dimInfos[dim]; + if (dimInfo) { + return store.getByRawIndex(dimInfo.storeDimIndex, rawIdx); + } + } + + getIndices() { + return this._store.getIndices(); + } + + getDataExtent(dim: DimensionLoose): [number, number] { + return this._store.getDataExtent(this._getStoreDimIndex(dim)); + } + + getSum(dim: DimensionLoose): number { + return this._store.getSum(this._getStoreDimIndex(dim)); + } + + getMedian(dim: DimensionLoose): number { + return this._store.getMedian(this._getStoreDimIndex(dim)); + } + /** + * Get value for multi dimensions. + * @param dimensions If ignored, using all dimensions. + */ + getValues(idx: number): ParsedValue[]; + getValues(dimensions: readonly DimensionName[], idx: number): ParsedValue[]; + getValues(dimensions: readonly DimensionName[] | number, idx?: number): ParsedValue[] { + const store = this._store; + return zrUtil.isArray(dimensions) + ? store.getValues(map(dimensions, dim => this._getStoreDimIndex(dim)), idx) + : store.getValues(dimensions as number); + } + + /** + * If value is NaN. Inlcuding '-' + * Only check the coord dimensions. + */ + hasValue(idx: number): boolean { + const dataDimIndicesOnCoord = this._dimSummary.dataDimIndicesOnCoord; + for (let i = 0, len = dataDimIndicesOnCoord.length; i < len; i++) { + // Ordinal type originally can be string or number. + // But when an ordinal type is used on coord, it can + // not be string but only number. So we can also use isNaN. + if (isNaN(this._store.get(dataDimIndicesOnCoord[i], idx) as any)) { + return false; + } + } + return true; + } + + /** + * Retreive the index with given name + */ + indexOfName(name: string): number { + for (let i = 0, len = this._store.count(); i < len; i++) { + if (this.getName(i) === name) { + return i; + } + } + return -1; + } + + getRawIndex(idx: number): number { + return this._store.getRawIndex(idx); + } + + indexOfRawIndex(rawIndex: number): number { + return this._store.indexOfRawIndex(rawIndex); + } + + /** + * Only support the dimension which inverted index created. + * Do not support other cases until required. + * @param dim concrete dim + * @param value ordinal index + * @return rawIndex + */ + rawIndexOf(dim: SeriesDimensionName, value: OrdinalNumber): number { + const invertedIndices = dim && this._invertedIndicesMap[dim]; + if (__DEV__) { + if (!invertedIndices) { + throw new Error('Do not supported yet'); + } + } + const rawIndex = invertedIndices[value]; + if (rawIndex == null || isNaN(rawIndex)) { + return INDEX_NOT_FOUND; + } + return rawIndex; + } + + /** + * Retreive the index of nearest value + * @param dim + * @param value + * @param [maxDistance=Infinity] + * @return If and only if multiple indices has + * the same value, they are put to the result. + */ + indicesOfNearest(dim: DimensionLoose, value: number, maxDistance?: number): number[] { + return this._store.indicesOfNearest( + this._getStoreDimIndex(dim), + value, maxDistance + ); + } + /** + * Data iteration + * @param ctx default this + * @example + * list.each('x', function (x, idx) {}); + * list.each(['x', 'y'], function (x, y, idx) {}); + * list.each(function (idx) {}) + */ + each(cb: EachCb0, ctx?: Ctx, ctxCompat?: Ctx): void; + each(dims: DimensionLoose, cb: EachCb1, ctx?: Ctx): void; + each(dims: [DimensionLoose], cb: EachCb1, ctx?: Ctx): void; + each(dims: [DimensionLoose, DimensionLoose], cb: EachCb2, ctx?: Ctx): void; + each(dims: ItrParamDims, cb: EachCb, ctx?: Ctx): void; + each( + dims: ItrParamDims | EachCb, + cb: EachCb | Ctx, + ctx?: Ctx + ): void { + 'use strict'; + + if (typeof dims === 'function') { + ctx = cb as Ctx; + cb = dims; + dims = []; + } + + // ctxCompat just for compat echarts3 + const fCtx = (ctx || this) as CtxOrList; + + const dimIndices = map(normalizeDimensions(dims), this._getStoreDimIndex, this); + + this._store.each(dimIndices, (fCtx + ? zrUtil.bind(cb as any, fCtx as any) + : cb) as any + ); + } + /** + * Data filter + */ + filterSelf(cb: FilterCb0, ctx?: Ctx, ctxCompat?: Ctx): this; + filterSelf(dims: DimensionLoose, cb: FilterCb1, ctx?: Ctx): this; + filterSelf(dims: [DimensionLoose], cb: FilterCb1, ctx?: Ctx): this; + filterSelf(dims: [DimensionLoose, DimensionLoose], cb: FilterCb2, ctx?: Ctx): this; + filterSelf(dims: ItrParamDims, cb: FilterCb, ctx?: Ctx): this; + filterSelf( + dims: ItrParamDims | FilterCb, + cb: FilterCb | Ctx, + ctx?: Ctx + ): SeriesData { + 'use strict'; + + if (typeof dims === 'function') { + ctx = cb as Ctx; + cb = dims; + dims = []; + } + + // ctxCompat just for compat echarts3 + const fCtx = (ctx || this) as CtxOrList; + + const dimIndices = map(normalizeDimensions(dims), this._getStoreDimIndex, this); + + this._store = this._store.filter(dimIndices, (fCtx + ? zrUtil.bind(cb as any, fCtx as any) + : cb) as any + ); + + return this; + } + + /** + * Select data in range. (For optimization of filter) + * (Manually inline code, support 5 million data filtering in data zoom.) + */ + selectRange(range: Record): SeriesData { + 'use strict'; + + const innerRange: Record = {}; + const dims = zrUtil.keys(range); + const dimIndices: number[] = []; + zrUtil.each(dims, (dim) => { + const dimIdx = this._getStoreDimIndex(dim); + innerRange[dimIdx] = range[dim]; + dimIndices.push(dimIdx); + }); + + this._store = this._store.selectRange(innerRange); + return this; + } + + /** + * Data mapping to a plain array + */ + mapArray>(cb: Cb, ctx?: Ctx, ctxCompat?: Ctx): ReturnType[]; + /* eslint-disable max-len */ + mapArray>(dims: DimensionLoose, cb: Cb, ctx?: Ctx, ctxCompat?: Ctx): ReturnType[]; + mapArray>(dims: [DimensionLoose], cb: Cb, ctx?: Ctx, ctxCompat?: Ctx): ReturnType[]; + mapArray>(dims: [DimensionLoose, DimensionLoose], cb: Cb, ctx?: Ctx, ctxCompat?: Ctx): ReturnType[]; + mapArray>(dims: ItrParamDims, cb: Cb, ctx?: Ctx, ctxCompat?: Ctx): ReturnType[]; + /* eslint-enable max-len */ + mapArray( + dims: ItrParamDims | MapArrayCb, + cb: MapArrayCb | Ctx, + ctx?: Ctx + ): unknown[] { + 'use strict'; + + if (typeof dims === 'function') { + ctx = cb as Ctx; + cb = dims; + dims = []; + } + + // ctxCompat just for compat echarts3 + ctx = (ctx || this) as Ctx; + + const result: unknown[] = []; + this.each(dims, function () { + result.push(cb && (cb as MapArrayCb).apply(this, arguments)); + }, ctx); + return result; + } + + /** + * Data mapping to a new List with given dimensions + */ + map(dims: DimensionLoose, cb: MapCb1, ctx?: Ctx, ctxCompat?: Ctx): SeriesData; + map(dims: [DimensionLoose], cb: MapCb1, ctx?: Ctx, ctxCompat?: Ctx): SeriesData; + // eslint-disable-next-line max-len + map(dims: [DimensionLoose, DimensionLoose], cb: MapCb2, ctx?: Ctx, ctxCompat?: Ctx): SeriesData; + map( + dims: ItrParamDims, + cb: MapCb, + ctx?: Ctx, + ctxCompat?: Ctx + ): SeriesData { + 'use strict'; + + // ctxCompat just for compat echarts3 + const fCtx = (ctx || ctxCompat || this) as CtxOrList; + + const dimIndices = map( + normalizeDimensions(dims), this._getStoreDimIndex, this + ); + + const list = cloneListForMapAndSample(this); + list._store = this._store.map( + dimIndices, + fCtx ? zrUtil.bind(cb, fCtx) : cb + ); + return list; + } + + /** + * !!Danger: used on stack dimension only. + */ + modify(dims: DimensionLoose, cb: MapCb1, ctx?: Ctx, ctxCompat?: Ctx): void; + modify(dims: [DimensionLoose], cb: MapCb1, ctx?: Ctx, ctxCompat?: Ctx): void; + modify(dims: [DimensionLoose, DimensionLoose], cb: MapCb2, ctx?: Ctx, ctxCompat?: Ctx): void; + modify( + dims: ItrParamDims, + cb: MapCb, + ctx?: Ctx, + ctxCompat?: Ctx + ): void { + // ctxCompat just for compat echarts3 + const fCtx = (ctx || ctxCompat || this) as CtxOrList; + + if (__DEV__) { + zrUtil.each(normalizeDimensions(dims), dim => { + const dimInfo = this.getDimensionInfo(dim); + if (!dimInfo.isCalculationCoord) { + console.error('Danger: only stack dimension can be modified'); + } + }); + } + + const dimIndices = map( + normalizeDimensions(dims), this._getStoreDimIndex, this + ); + + // If do shallow clone here, if there are too many stacked series, + // it still cost lots of memory, becuase `_store.dimensions` are not shared. + // We should consider there probably be shallow clone happen in each sereis + // in consequent filter/map. + this._store.modify( + dimIndices, + fCtx ? zrUtil.bind(cb, fCtx) : cb + ); + } + + /** + * Large data down sampling on given dimension + * @param sampleIndex Sample index for name and id + */ + downSample( + dimension: DimensionLoose, + rate: number, + sampleValue: (frameValues: ArrayLike) => ParsedValueNumeric, + sampleIndex: (frameValues: ArrayLike, value: ParsedValueNumeric) => number + ): SeriesData { + const list = cloneListForMapAndSample(this); + list._store = this._store.downSample( + this._getStoreDimIndex(dimension), + rate, + sampleValue, + sampleIndex + ); + return list as SeriesData; + } + + /** + * Large data down sampling using largest-triangle-three-buckets + * @param {string} valueDimension + * @param {number} targetCount + */ + lttbDownSample( + valueDimension: DimensionLoose, + rate: number + ): SeriesData { + const list = cloneListForMapAndSample(this); + list._store = this._store.lttbDownSample( + this._getStoreDimIndex(valueDimension), + rate + ); + return list as SeriesData; + } + + getRawDataItem(idx: number) { + return this._store.getRawDataItem(idx); + } + + /** + * Get model of one data item. + */ + // TODO: Type of data item + getItemModel(idx: number): Model + > { + const hostModel = this.hostModel; + const dataItem = this.getRawDataItem(idx) as ModelOption; + return new Model(dataItem, hostModel, hostModel && hostModel.ecModel); + } + + /** + * Create a data differ + */ + diff(otherList: SeriesData): DataDiffer { + const thisList = this; + + return new DataDiffer( + otherList ? otherList.getStore().getIndices() : [], + this.getStore().getIndices(), + function (idx: number) { + return getId(otherList, idx); + }, + function (idx: number) { + return getId(thisList, idx); + } + ); + } + + /** + * Get visual property. + */ + getVisual(key: K): Visual[K] { + const visual = this._visual as Visual; + return visual && visual[key]; + } + + /** + * Set visual property + * + * @example + * setVisual('color', color); + * setVisual({ + * 'color': color + * }); + */ + setVisual(key: K, val: Visual[K]): void; + setVisual(kvObj: Partial): void; + setVisual(kvObj: string | Partial, val?: any): void { + this._visual = this._visual || {}; + if (isObject(kvObj)) { + zrUtil.extend(this._visual, kvObj); + } + else { + this._visual[kvObj as string] = val; + } + } + + /** + * Get visual property of single data item + */ + // eslint-disable-next-line + getItemVisual(idx: number, key: K): Visual[K] { + const itemVisual = this._itemVisuals[idx] as Visual; + const val = itemVisual && itemVisual[key]; + if (val == null) { + // Use global visual property + return this.getVisual(key); + } + return val; + } + + /** + * If exists visual property of single data item + */ + hasItemVisual() { + return this._itemVisuals.length > 0; + } + + /** + * Make sure itemVisual property is unique + */ + // TODO: use key to save visual to reduce memory. + ensureUniqueItemVisual(idx: number, key: K): Visual[K] { + const itemVisuals = this._itemVisuals; + let itemVisual = itemVisuals[idx] as Visual; + if (!itemVisual) { + itemVisual = itemVisuals[idx] = {} as Visual; + } + let val = itemVisual[key]; + if (val == null) { + val = this.getVisual(key); + + // TODO Performance? + if (zrUtil.isArray(val)) { + val = val.slice() as unknown as Visual[K]; + } + else if (isObject(val)) { + val = zrUtil.extend({}, val); + } + + itemVisual[key] = val; + } + return val; + } + /** + * Set visual property of single data item + * + * @param {number} idx + * @param {string|Object} key + * @param {*} [value] + * + * @example + * setItemVisual(0, 'color', color); + * setItemVisual(0, { + * 'color': color + * }); + */ + // eslint-disable-next-line + setItemVisual(idx: number, key: K, value: Visual[K]): void; + setItemVisual(idx: number, kvObject: Partial): void; + // eslint-disable-next-line + setItemVisual(idx: number, key: K | Partial, value?: Visual[K]): void { + const itemVisual = this._itemVisuals[idx] || {}; + this._itemVisuals[idx] = itemVisual; + + if (isObject(key)) { + zrUtil.extend(itemVisual, key); + } + else { + itemVisual[key as string] = value; + } + } + + /** + * Clear itemVisuals and list visual. + */ + clearAllVisual(): void { + this._visual = {}; + this._itemVisuals = []; + } + + /** + * Set layout property. + */ + setLayout(key: string, val: any): void; + setLayout(kvObj: Dictionary): void; + setLayout(key: string | Dictionary, val?: any): void { + if (isObject(key)) { + for (const name in key) { + if (key.hasOwnProperty(name)) { + this.setLayout(name, key[name]); + } + } + return; + } + this._layout[key] = val; + } + + /** + * Get layout property. + */ + getLayout(key: string): any { + return this._layout[key]; + } + + /** + * Get layout of single data item + */ + getItemLayout(idx: number): any { + return this._itemLayouts[idx]; + } + + /** + * Set layout of single data item + */ + setItemLayout( + idx: number, + layout: (M extends true ? Dictionary : any), + merge?: M + ): void { + this._itemLayouts[idx] = merge + ? zrUtil.extend(this._itemLayouts[idx] || {}, layout) + : layout; + } + + /** + * Clear all layout of single data item + */ + clearItemLayouts(): void { + this._itemLayouts.length = 0; + } + + /** + * Set graphic element relative to data. It can be set as null + */ + setItemGraphicEl(idx: number, el: Element): void { + const seriesIndex = this.hostModel && (this.hostModel as any).seriesIndex; + + setCommonECData(seriesIndex, this.dataType, idx, el); + + this._graphicEls[idx] = el; + } + + getItemGraphicEl(idx: number): Element { + return this._graphicEls[idx]; + } + + eachItemGraphicEl( + cb: (this: Ctx, el: Element, idx: number) => void, + context?: Ctx + ): void { + zrUtil.each(this._graphicEls, function (el, idx) { + if (el) { + cb && cb.call(context, el, idx); + } + }); + } + + /** + * Shallow clone a new list except visual and layout properties, and graph elements. + * New list only change the indices. + */ + cloneShallow(list?: SeriesData): SeriesData { + if (!list) { + list = new SeriesData( + this._schema + ? this._schema + : map(this.dimensions, this._getDimInfo, this), + this.hostModel + ); + } + + transferProperties(list, this); + list._store = this._store; + + return list; + } + + /** + * Wrap some method to add more feature + */ + wrapMethod( + methodName: FunctionPropertyNames, + injectFunction: (...args: any) => any + ): void { + const originalMethod = this[methodName]; + if (typeof originalMethod !== 'function') { + return; + } + this.__wrappedMethods = this.__wrappedMethods || []; + this.__wrappedMethods.push(methodName); + this[methodName] = function () { + const res = (originalMethod as any).apply(this, arguments); + return injectFunction.apply(this, [res].concat(zrUtil.slice(arguments))); + }; + } + + + // ---------------------------------------------------------- + // A work around for internal method visiting private member. + // ---------------------------------------------------------- + private static internalField = (function () { + + prepareInvertedIndex = function (data: SeriesData): void { + const invertedIndicesMap = data._invertedIndicesMap; + zrUtil.each(invertedIndicesMap, function (invertedIndices, dim) { + const dimInfo = data._dimInfos[dim]; + // Currently, only dimensions that has ordinalMeta can create inverted indices. + const ordinalMeta = dimInfo.ordinalMeta; + const store = data._store; + if (ordinalMeta) { + invertedIndices = invertedIndicesMap[dim] = new CtorInt32Array( + ordinalMeta.categories.length + ); + // The default value of TypedArray is 0. To avoid miss + // mapping to 0, we should set it as INDEX_NOT_FOUND. + for (let i = 0; i < invertedIndices.length; i++) { + invertedIndices[i] = INDEX_NOT_FOUND; + } + for (let i = 0; i < store.count(); i++) { + // Only support the case that all values are distinct. + invertedIndices[store.get(dimInfo.storeDimIndex, i) as number] = i; + } + } + }); + }; + + getIdNameFromStore = function ( + data: SeriesData, dimIdx: number, idx: number + ): string { + return convertOptionIdName(data._getCategory(dimIdx, idx), null); + }; + + /** + * @see the comment of `List['getId']`. + */ + getId = function (data: SeriesData, rawIndex: number): string { + let id = data._idList[rawIndex]; + if (id == null && data._idDimIdx != null) { + id = getIdNameFromStore(data, data._idDimIdx, rawIndex); + } + if (id == null) { + id = ID_PREFIX + rawIndex; + } + return id; + }; + + normalizeDimensions = function ( + dimensions: ItrParamDims + ): Array { + if (!zrUtil.isArray(dimensions)) { + dimensions = dimensions != null ? [dimensions] : []; + } + return dimensions; + }; + + /** + * Data in excludeDimensions is copied, otherwise transfered. + */ + cloneListForMapAndSample = function (original: SeriesData): SeriesData { + const list = new SeriesData( + original._schema + ? original._schema + : map(original.dimensions, original._getDimInfo, original), + original.hostModel + ); + // FIXME If needs stackedOn, value may already been stacked + transferProperties(list, original); + return list; + }; + + transferProperties = function (target: SeriesData, source: SeriesData): void { + zrUtil.each( + TRANSFERABLE_PROPERTIES.concat(source.__wrappedMethods || []), + function (propName) { + if (source.hasOwnProperty(propName)) { + (target as any)[propName] = (source as any)[propName]; + } + } + ); + + target.__wrappedMethods = source.__wrappedMethods; + + zrUtil.each(CLONE_PROPERTIES, function (propName) { + (target as any)[propName] = zrUtil.clone((source as any)[propName]); + }); + + target._calculationInfo = zrUtil.extend({}, source._calculationInfo); + }; + makeIdFromName = function (data: SeriesData, idx: number): void { + const nameList = data._nameList; + const idList = data._idList; + const nameDimIdx = data._nameDimIdx; + const idDimIdx = data._idDimIdx; + + let name = nameList[idx]; + let id = idList[idx]; + + if (name == null && nameDimIdx != null) { + nameList[idx] = name = getIdNameFromStore(data, nameDimIdx, idx); + } + if (id == null && idDimIdx != null) { + idList[idx] = id = getIdNameFromStore(data, idDimIdx, idx); + } + if (id == null && name != null) { + const nameRepeatCount = data._nameRepeatCount; + const nmCnt = nameRepeatCount[name] = (nameRepeatCount[name] || 0) + 1; + id = name; + if (nmCnt > 1) { + id += '__ec__' + nmCnt; + } + idList[idx] = id; + } + }; + })(); + +} + +interface SeriesData { + getLinkedData(dataType?: SeriesDataType): SeriesData; + getLinkedDataAll(): { data: SeriesData, type?: SeriesDataType }[]; +} + +export default SeriesData; diff --git a/src/data/DataDimensionInfo.ts b/src/data/SeriesDimensionDefine.ts similarity index 85% rename from src/data/DataDimensionInfo.ts rename to src/data/SeriesDimensionDefine.ts index 2857ef8562..3d4b628f21 100644 --- a/src/data/DataDimensionInfo.ts +++ b/src/data/SeriesDimensionDefine.ts @@ -21,11 +21,10 @@ import * as zrUtil from 'zrender/src/core/util'; import OrdinalMeta from './OrdinalMeta'; import { DataVisualDimensions, DimensionType } from '../util/types'; -class DataDimensionInfo { +class SeriesDimensionDefine { /** * Dimension type. The enumerable values are the key of - * `dataCtors` of `data/List`. * Optional. */ type?: DimensionType; @@ -46,6 +45,17 @@ class DataDimensionInfo { // See Series.ts#formatArrayValue tooltip?: boolean; + /** + * This dimension maps to the the dimension in dataStore by `storeDimIndex`. + * Notice the facts: + * 1. When there are too many dimensions in data store, seriesData only save the + * used store dimensions. + * 2. We use dimensionIndex but not name to reference store dimension + * becuause the dataset dimension definition might has no name specified by users, + * or names in sereis dimension definition might be different from dataset. + */ + storeDimIndex?: number; + /** * Which coordSys dimension this dimension mapped to. * A `coordDim` can be a "coordSysDim" that the coordSys required @@ -61,13 +71,6 @@ class DataDimensionInfo { * Mandatory. */ coordDimIndex?: number; - - /** - * This index of this dimension info in `data/List#_dimensionInfos`. - * Mandatory after added to `data/List`. - */ - index?: number; - /** * The format of `otherDims` is: * ```js @@ -126,7 +129,7 @@ class DataDimensionInfo { /** * @param opt All of the fields will be shallow copied. */ - constructor(opt?: object | DataDimensionInfo) { + constructor(opt?: object | SeriesDimensionDefine) { if (opt != null) { zrUtil.extend(this, opt); } @@ -134,4 +137,4 @@ class DataDimensionInfo { }; -export default DataDimensionInfo; +export default SeriesDimensionDefine; diff --git a/src/data/Source.ts b/src/data/Source.ts index bedebb14f7..a530a50811 100644 --- a/src/data/Source.ts +++ b/src/data/Source.ts @@ -32,7 +32,6 @@ import { DimensionName, OptionSourceHeader, DimensionDefinitionLoose, - OptionEncode, SOURCE_FORMAT_ARRAY_ROWS, SOURCE_FORMAT_OBJECT_ROWS, Dictionary, @@ -45,6 +44,7 @@ import { } from '../util/types'; import { DatasetOption } from '../component/dataset/install'; import { getDataItemValue } from '../util/model'; +import { BE_ORDINAL, guessOrdinal } from './helper/sourceHelper'; /** * [sourceFormat] @@ -119,13 +119,6 @@ class SourceImpl { */ readonly dimensionsDefine: DimensionDefinition[]; - /** - * encode definition in option. - * can be null/undefined. - * Might be specified outside. - */ - readonly encodeDefine: HashMap; - /** * Only make sense in `SOURCE_FORMAT_ARRAY_ROWS`. * That is the same as `sourceHeader: number`, @@ -146,8 +139,6 @@ class SourceImpl { */ readonly metaRawOption: SourceMetaRawOption; - // readonly frozen: boolean; - constructor(fields: { data: OptionSourceData, @@ -176,42 +167,22 @@ class SourceImpl { // Visit config this.seriesLayoutBy = fields.seriesLayoutBy || SERIES_LAYOUT_BY_COLUMN; this.startIndex = fields.startIndex || 0; - this.dimensionsDefine = fields.dimensionsDefine; this.dimensionsDetectedCount = fields.dimensionsDetectedCount; - this.encodeDefine = fields.encodeDefine; this.metaRawOption = fields.metaRawOption; - } - // There is performance issue in some browser like Safari, - // an also slower than clone in Chrome. - // So DO NOT use `Object.freeze`. - /** - * When expose the source to thrid-party transform, it probably better to - * freeze to make sure immutability. - * If a third-party transform modify the raw upstream data structure, it might bring about - * "uncertain effect" when using multiple transforms with different combinations. - * - * [Caveat] - * `OptionManager.ts` have perform `clone` in `setOption`. - * The original user input object should better not be frozen in case they - * make other usages. - */ - // freeze() { - // assert(sourceFormatCanBeExposed(this)); - // const data = this.data as OptionSourceDataArrayRows; - // if (this.frozen || !data || !isFunction(Object.freeze)) { - // return; - // } - // // @ts-ignore - // this.frozen = true; - // // PENDING: - // // There is a flaw that there might be non-primitive values like `Date`. - // // Is it worth handling that? - // for (let i = 0; i < data.length; i++) { - // Object.freeze(data[i]); - // } - // Object.freeze(data); - // } + const dimensionsDefine = this.dimensionsDefine = fields.dimensionsDefine; + + if (dimensionsDefine) { + for (let i = 0; i < dimensionsDefine.length; i++) { + const dim = dimensionsDefine[i]; + if (dim.type == null) { + if (guessOrdinal(this, i) === BE_ORDINAL.Must) { + dim.type = 'ordinal'; + } + } + } + } + } } @@ -219,12 +190,15 @@ export function isSourceInstance(val: unknown): val is Source { return val instanceof SourceImpl; } +/** + * Create a source from option. + * NOTE: Created source is immutable. Don't change any properties in it. + */ export function createSource( sourceData: OptionSourceData, thisMetaRawOption: SourceMetaRawOption, // can be null. If not provided, auto detect it from `sourceData`. - sourceFormat: SourceFormat, - encodeDefine: OptionEncode // can be null + sourceFormat: SourceFormat ): Source { sourceFormat = sourceFormat || detectSourceFormat(sourceData); const seriesLayoutBy = thisMetaRawOption.seriesLayoutBy; @@ -243,7 +217,6 @@ export function createSource( dimensionsDefine: determined.dimensionsDefine, startIndex: determined.startIndex, dimensionsDetectedCount: determined.dimensionsDetectedCount, - encodeDefine: makeEncodeDefine(encodeDefine), metaRawOption: clone(thisMetaRawOption) }); @@ -273,20 +246,10 @@ export function cloneSourceShallow(source: Source): Source { seriesLayoutBy: source.seriesLayoutBy, dimensionsDefine: clone(source.dimensionsDefine), startIndex: source.startIndex, - dimensionsDetectedCount: source.dimensionsDetectedCount, - encodeDefine: makeEncodeDefine(source.encodeDefine) + dimensionsDetectedCount: source.dimensionsDetectedCount }); } -function makeEncodeDefine( - encodeDefine: OptionEncode | HashMap -): HashMap { - // null means user not specify `series.encode`. - return encodeDefine - ? createHashMap(encodeDefine) - : null; -} - /** * Note: An empty array will be detected as `SOURCE_FORMAT_ARRAY_ROWS`. */ @@ -516,3 +479,8 @@ function arrayRowsTravelFirst( } } } + +export function shouldRetrieveDataByName(source: Source): boolean { + const sourceFormat = source.sourceFormat; + return sourceFormat === SOURCE_FORMAT_OBJECT_ROWS || sourceFormat === SOURCE_FORMAT_KEYED_COLUMNS; +} diff --git a/src/data/Tree.ts b/src/data/Tree.ts index d95dbe5adf..0389f6a9bf 100644 --- a/src/data/Tree.ts +++ b/src/data/Tree.ts @@ -23,9 +23,9 @@ import * as zrUtil from 'zrender/src/core/util'; import Model from '../model/Model'; -import linkList from './helper/linkList'; -import List from './List'; -import createDimensions from './helper/createDimensions'; +import linkSeriesData from './helper/linkSeriesData'; +import SeriesData from './SeriesData'; +import prepareSeriesDataSchema from './helper/createDimensions'; import { DimensionLoose, ParsedValue, OptionDataValue, OptionDataItemObject @@ -210,7 +210,7 @@ export class TreeNode { getValue(dimension?: DimensionLoose): ParsedValue { const data = this.hostTree.data; - return data.get(data.getDimension(dimension || 'value'), this.dataIndex); + return data.getStore().get(data.getDimensionIndex(dimension || 'value'), this.dataIndex); } setLayout(layout: any, merge?: boolean) { @@ -323,7 +323,7 @@ class Tree { root: TreeNode; - data: List; + data: SeriesData; hostModel: HostModel; @@ -415,7 +415,7 @@ class Tree { static createTree( dataRoot: T, hostModel: HostModel, - beforeLink?: (data: List) => void + beforeLink?: (data: SeriesData) => void ) { const tree = new Tree(hostModel); @@ -447,17 +447,17 @@ class Tree { tree.root.updateDepthAndHeight(0); - const dimensionsInfo = createDimensions(listData, { + const { dimensions } = prepareSeriesDataSchema(listData, { coordDimensions: ['value'], dimensionsCount: dimMax }); - const list = new List(dimensionsInfo, hostModel); + const list = new SeriesData(dimensions, hostModel); list.initData(listData); beforeLink && beforeLink(list); - linkList({ + linkSeriesData({ mainData: list, struct: tree, structAttr: 'tree' diff --git a/src/data/helper/SeriesDataSchema.ts b/src/data/helper/SeriesDataSchema.ts new file mode 100644 index 0000000000..67e9f1fc9a --- /dev/null +++ b/src/data/helper/SeriesDataSchema.ts @@ -0,0 +1,267 @@ +/* +* 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 { createHashMap, HashMap, isObject, retrieve2 } from 'zrender/src/core/util'; +import { makeInner } from '../../util/model'; +import { + DimensionDefinition, DimensionDefinitionLoose, DimensionIndex, DimensionName, DimensionType +} from '../../util/types'; +import { DataStoreDimensionDefine } from '../DataStore'; +import OrdinalMeta from '../OrdinalMeta'; +import SeriesDimensionDefine from '../SeriesDimensionDefine'; +import { shouldRetrieveDataByName, Source } from '../Source'; + +const inner = makeInner<{ + dimNameMap: HashMap; +}, Source>(); + +const dimTypeShort = { + float: 'f', int: 'i', ordinal: 'o', number: 'n', time: 't' +} as const; + +/** + * Represents the dimension requirement of a series. + * + * NOTICE: + * When there are too many dimensions in dataset and many series, only the used dimensions + * (i.e., used by coord sys and declared in `series.encode`) are add to `dimensionDefineList`. + * But users may query data by other unused dimension names. + * In this case, users can only query data if and only if they have defined dimension names + * via ec option, so we provide `getDimensionIndexFromSource`, which only query them from + * `source` dimensions. + */ +export class SeriesDataSchema { + + /** + * When there are too many dimensions, `dimensionDefineList` might only contain + * used dimensions. + * + * CAUTION: + * Should have been sorted by `storeDimIndex` asc. + * + * PENDING: + * The item can still be modified outsite. + * But MUST NOT add/remove item of this array. + */ + readonly dimensions: SeriesDimensionDefine[]; + + readonly source: Source; + + private _fullDimCount: number; + private _dimNameMap: ReturnType['dimNameMap']; + private _dimOmitted: boolean; + + constructor(opt: { + source: Source, + dimensions: SeriesDimensionDefine[], + fullDimensionCount: number, + dimensionOmitted: boolean + }) { + this.dimensions = opt.dimensions; + this._dimOmitted = opt.dimensionOmitted; + this.source = opt.source; + this._fullDimCount = opt.fullDimensionCount; + + this._updateDimOmitted(opt.dimensionOmitted); + } + + isDimensionOmitted(): boolean { + return this._dimOmitted; + } + + private _updateDimOmitted(dimensionOmitted: boolean): void { + this._dimOmitted = dimensionOmitted; + if (!dimensionOmitted) { + return; + } + if (!this._dimNameMap) { + this._dimNameMap = ensureSourceDimNameMap(this.source); + } + } + + /** + * @caution Can only be used when `dimensionOmitted: true`. + * + * Get index by user defined dimension name (i.e., not internal generate name). + * That is, get index from `dimensionsDefine`. + * If no `dimensionsDefine`, or no name get, return -1. + */ + getSourceDimensionIndex(dimName: DimensionName): DimensionIndex { + return retrieve2(this._dimNameMap.get(dimName), -1); + } + + /** + * @caution Can only be used when `dimensionOmitted: true`. + * + * Notice: may return `null`/`undefined` if user not specify dimension names. + */ + getSourceDimension(dimIndex: DimensionIndex): DimensionDefinition { + const dimensionsDefine = this.source.dimensionsDefine; + if (dimensionsDefine) { + return dimensionsDefine[dimIndex]; + } + } + + makeStoreSchema(): { + dimensions: DataStoreDimensionDefine[]; + hash: string + } { + const dimCount = this._fullDimCount; + const willRetrieveDataByName = shouldRetrieveDataByName(this.source); + const makeHashStrict = !shouldOmitUnusedDimensions(dimCount); + + // If source don't have dimensions or series don't omit unsed dimensions. + // Generate from seriesDimList directly + let dimHash = ''; + const dims: DataStoreDimensionDefine[] = []; + + for (let fullDimIdx = 0, seriesDimIdx = 0; fullDimIdx < dimCount; fullDimIdx++) { + let property: string; + let type: DimensionType; + let ordinalMeta: OrdinalMeta; + + const seriesDimDef = this.dimensions[seriesDimIdx]; + // The list has been sorted by `storeDimIndex` asc. + if (seriesDimDef && seriesDimDef.storeDimIndex === fullDimIdx) { + property = willRetrieveDataByName ? seriesDimDef.name : null; + type = seriesDimDef.type; + ordinalMeta = seriesDimDef.ordinalMeta; + + seriesDimIdx++; + } + else { + const sourceDimDef = this.getSourceDimension(fullDimIdx); + if (sourceDimDef) { + property = willRetrieveDataByName ? sourceDimDef.name : null; + type = sourceDimDef.type; + } + } + + dims.push({ property, type, ordinalMeta }); + + // If retrieving data by index, + // use to determine whether data can be shared. + // (Becuase in this case there might be no dimension name defined in dataset, but indices always exists). + // (indices are always 0, 1, 2, ..., so we can ignore them to shorten the hash). + // Otherwise if retrieving data by property name (like `data: [{aa: 123, bb: 765}, ...]`), + // use in hash. + if (willRetrieveDataByName + && property != null + // For data stack, we have make sure each series has its own dim on this store. + // So we do not add property to hash to make sure they can share this store. + && (!seriesDimDef || !seriesDimDef.isCalculationCoord) + ) { + dimHash += (makeHashStrict + // Use escape character '`' in case that property name contains '$'. + ? property.replace(/\`/g, '`1').replace(/\$/g, '`2') + // For better performance, when there are large dimensions, tolerant this defects that hardly meet. + : property + ); + } + dimHash += '$'; + dimHash += dimTypeShort[type] || 'f'; + + if (ordinalMeta) { + dimHash += ordinalMeta.uid; + } + + dimHash += '$'; + } + + // Source from endpoint(usually series) will be read differently + // when seriesLayoutBy or startIndex(which is affected by sourceHeader) are different. + // So we use this three props as key. + const source = this.source; + const hash = [ + source.seriesLayoutBy, + source.startIndex, + dimHash + ].join('$$'); + + return { + dimensions: dims, + hash: hash + }; + } + + makeOutputDimensionNames(): DimensionName[] { + const result = [] as DimensionName[]; + + for (let fullDimIdx = 0, seriesDimIdx = 0; fullDimIdx < this._fullDimCount; fullDimIdx++) { + let name: DimensionName; + const seriesDimDef = this.dimensions[seriesDimIdx]; + // The list has been sorted by `storeDimIndex` asc. + if (seriesDimDef && seriesDimDef.storeDimIndex === fullDimIdx) { + if (!seriesDimDef.isCalculationCoord) { + name = seriesDimDef.name; + } + seriesDimIdx++; + } + else { + const sourceDimDef = this.getSourceDimension(fullDimIdx); + if (sourceDimDef) { + name = sourceDimDef.name; + } + } + result.push(name); + } + + return result; + } + + appendCalculationDimension(dimDef: SeriesDimensionDefine): void { + this.dimensions.push(dimDef); + dimDef.isCalculationCoord = true; + this._fullDimCount++; + // If append dimension on a data store, consider the store + // might be shared by different series, series dimensions not + // really map to store dimensions. + this._updateDimOmitted(true); + } +} + +export function isSeriesDataSchema( + schema: any +): schema is SeriesDataSchema { + return schema instanceof SeriesDataSchema; +} + + +export function createDimNameMap(dimsDef: DimensionDefinitionLoose[]): HashMap { + const dataDimNameMap = createHashMap(); + for (let i = 0; i < (dimsDef || []).length; i++) { + const dimDefItemRaw = dimsDef[i]; + const userDimName = isObject(dimDefItemRaw) ? dimDefItemRaw.name : dimDefItemRaw; + if (userDimName != null && dataDimNameMap.get(userDimName) == null) { + dataDimNameMap.set(userDimName, i); + } + } + return dataDimNameMap; +} + +export function ensureSourceDimNameMap(source: Source): HashMap { + const innerSource = inner(source); + return innerSource.dimNameMap || ( + innerSource.dimNameMap = createDimNameMap(source.dimensionsDefine) + ); +} + +export function shouldOmitUnusedDimensions(dimCount: number): boolean { + return dimCount > 30; +} diff --git a/src/data/helper/completeDimensions.ts b/src/data/helper/completeDimensions.ts deleted file mode 100644 index 972dbc5930..0000000000 --- a/src/data/helper/completeDimensions.ts +++ /dev/null @@ -1,328 +0,0 @@ -/* -* Licensed to the Apache Software Foundation (ASF) under one -* or more contributor license agreements. See the NOTICE file -* distributed with this work for additional information -* regarding copyright ownership. The ASF licenses this file -* to you under the Apache License, Version 2.0 (the -* "License"); you may not use this file except in compliance -* with the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, -* software distributed under the License is distributed on an -* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -* KIND, either express or implied. See the License for the -* specific language governing permissions and limitations -* under the License. -*/ - -/** - * @deprecated - * Use `echarts/data/helper/createDimensions` instead. - */ - -import {createHashMap, each, isString, defaults, extend, isObject, clone, HashMap} from 'zrender/src/core/util'; -import {normalizeToArray} from '../../util/model'; -import {guessOrdinal, BE_ORDINAL} from './sourceHelper'; -import { createSourceFromSeriesDataOption, isSourceInstance, Source } from '../Source'; -import { - VISUAL_DIMENSIONS, DimensionDefinitionLoose, OptionSourceData, - EncodeDefaulter, OptionEncodeValue, OptionEncode, DimensionName, DimensionIndex, DataVisualDimensions -} from '../../util/types'; -import DataDimensionInfo from '../DataDimensionInfo'; -import List from '../List'; -import { CoordDimensionDefinition, CoordDimensionDefinitionLoose } from './createDimensions'; - -/** - * @see {module:echarts/test/ut/spec/data/completeDimensions} - * - * This method builds the relationship between: - * + "what the coord sys or series requires (see `sysDims`)", - * + "what the user defines (in `encode` and `dimensions`, see `opt.dimsDef` and `opt.encodeDef`)" - * + "what the data source provids (see `source`)". - * - * Some guess strategy will be adapted if user does not define something. - * If no 'value' dimension specified, the first no-named dimension will be - * named as 'value'. - * - * @param {Array.} sysDims Necessary dimensions, like ['x', 'y'], which - * provides not only dim template, but also default order. - * properties: 'name', 'type', 'displayName'. - * `name` of each item provides default coord name. - * [{dimsDef: [string|Object, ...]}, ...] dimsDef of sysDim item provides default dim name, and - * provide dims count that the sysDim required. - * [{ordinalMeta}] can be specified. - * @param {module:echarts/data/Source|Array|Object} source or data (for compatibal with pervious) - * @param {Object} [opt] - * @param {Array.} [opt.dimsDef] option.series.dimensions User defined dimensions - * For example: ['asdf', {name, type}, ...]. - * @param {Object|HashMap} [opt.encodeDef] option.series.encode {x: 2, y: [3, 1], tooltip: [1, 2], label: 3} - * @param {Function} [opt.encodeDefaulter] Called if no `opt.encodeDef` exists. - * If not specified, auto find the next available data dim. - * param source {module:data/Source} - * param dimCount {number} - * return {Object} encode Never be `null/undefined`. - * @param {string} [opt.generateCoord] Generate coord dim with the given name. - * If not specified, extra dim names will be: - * 'value', 'value0', 'value1', ... - * @param {number} [opt.generateCoordCount] By default, the generated dim name is `generateCoord`. - * If `generateCoordCount` specified, the generated dim names will be: - * `generateCoord` + 0, `generateCoord` + 1, ... - * can be Infinity, indicate that use all of the remain columns. - * @param {number} [opt.dimCount] If not specified, guess by the first data item. - * @return {Array.} - */ -function completeDimensions( - sysDims: CoordDimensionDefinitionLoose[], - source: Source | List | OptionSourceData, - opt: { - dimsDef?: DimensionDefinitionLoose[]; - encodeDef?: HashMap | OptionEncode; - dimCount?: number; - encodeDefaulter?: EncodeDefaulter; - generateCoord?: string; - generateCoordCount?: number; - } -): DataDimensionInfo[] { - if (!isSourceInstance(source)) { - source = createSourceFromSeriesDataOption(source as OptionSourceData); - } - - opt = opt || {}; - sysDims = (sysDims || []).slice(); - const dimsDef = (opt.dimsDef || []).slice(); - const dataDimNameMap = createHashMap(); - const coordDimNameMap = createHashMap(); - // let valueCandidate; - const result: DataDimensionInfo[] = []; - - const dimCount = getDimCount(source, sysDims, dimsDef, opt.dimCount); - - // Apply user defined dims (`name` and `type`) and init result. - for (let i = 0; i < dimCount; i++) { - const dimDefItemRaw = dimsDef[i]; - const dimDefItem = dimsDef[i] = extend( - {}, isObject(dimDefItemRaw) ? dimDefItemRaw : { name: dimDefItemRaw } - ); - const userDimName = dimDefItem.name; - const resultItem = result[i] = new DataDimensionInfo(); - // Name will be applied later for avoiding duplication. - if (userDimName != null && dataDimNameMap.get(userDimName) == null) { - // Only if `series.dimensions` is defined in option - // displayName, will be set, and dimension will be diplayed vertically in - // tooltip by default. - resultItem.name = resultItem.displayName = userDimName; - dataDimNameMap.set(userDimName, i); - } - dimDefItem.type != null && (resultItem.type = dimDefItem.type); - dimDefItem.displayName != null && (resultItem.displayName = dimDefItem.displayName); - } - - let encodeDef = opt.encodeDef; - if (!encodeDef && opt.encodeDefaulter) { - encodeDef = opt.encodeDefaulter(source, dimCount); - } - const encodeDefMap = createHashMap(encodeDef as any); - - // Set `coordDim` and `coordDimIndex` by `encodeDefMap` and normalize `encodeDefMap`. - encodeDefMap.each(function (dataDimsRaw, coordDim) { - const dataDims = normalizeToArray(dataDimsRaw as []).slice(); - - // Note: It is allowed that `dataDims.length` is `0`, e.g., options is - // `{encode: {x: -1, y: 1}}`. Should not filter anything in - // this case. - if (dataDims.length === 1 && !isString(dataDims[0]) && dataDims[0] < 0) { - encodeDefMap.set(coordDim, false); - return; - } - - const validDataDims = encodeDefMap.set(coordDim, []) as DimensionIndex[]; - each(dataDims, function (resultDimIdxOrName, idx) { - // The input resultDimIdx can be dim name or index. - const resultDimIdx = isString(resultDimIdxOrName) - ? dataDimNameMap.get(resultDimIdxOrName) - : resultDimIdxOrName; - if (resultDimIdx != null && resultDimIdx < dimCount) { - validDataDims[idx] = resultDimIdx; - applyDim(result[resultDimIdx], coordDim, idx); - } - }); - }); - - // Apply templetes and default order from `sysDims`. - let availDimIdx = 0; - each(sysDims, function (sysDimItemRaw) { - let coordDim: DimensionName; - let sysDimItemDimsDef: CoordDimensionDefinition['dimsDef']; - let sysDimItemOtherDims: CoordDimensionDefinition['otherDims']; - let sysDimItem: CoordDimensionDefinition; - if (isString(sysDimItemRaw)) { - coordDim = sysDimItemRaw; - sysDimItem = {} as CoordDimensionDefinition; - } - else { - sysDimItem = sysDimItemRaw; - coordDim = sysDimItem.name; - const ordinalMeta = sysDimItem.ordinalMeta; - sysDimItem.ordinalMeta = null; - sysDimItem = clone(sysDimItem); - sysDimItem.ordinalMeta = ordinalMeta; - // `coordDimIndex` should not be set directly. - sysDimItemDimsDef = sysDimItem.dimsDef; - sysDimItemOtherDims = sysDimItem.otherDims; - sysDimItem.name = sysDimItem.coordDim = sysDimItem.coordDimIndex = - sysDimItem.dimsDef = sysDimItem.otherDims = null; - } - - let dataDims = encodeDefMap.get(coordDim); - - // negative resultDimIdx means no need to mapping. - if (dataDims === false) { - return; - } - - dataDims = normalizeToArray(dataDims); - - // dimensions provides default dim sequences. - if (!dataDims.length) { - for (let i = 0; i < (sysDimItemDimsDef && sysDimItemDimsDef.length || 1); i++) { - while (availDimIdx < result.length && result[availDimIdx].coordDim != null) { - availDimIdx++; - } - availDimIdx < result.length && dataDims.push(availDimIdx++); - } - } - - // Apply templates. - each(dataDims, function (resultDimIdx, coordDimIndex) { - const resultItem = result[resultDimIdx]; - applyDim(defaults(resultItem, sysDimItem), coordDim, coordDimIndex); - if (resultItem.name == null && sysDimItemDimsDef) { - let sysDimItemDimsDefItem = sysDimItemDimsDef[coordDimIndex]; - !isObject(sysDimItemDimsDefItem) && (sysDimItemDimsDefItem = {name: sysDimItemDimsDefItem}); - resultItem.name = resultItem.displayName = sysDimItemDimsDefItem.name; - resultItem.defaultTooltip = sysDimItemDimsDefItem.defaultTooltip; - } - // FIXME refactor, currently only used in case: {otherDims: {tooltip: false}} - sysDimItemOtherDims && defaults(resultItem.otherDims, sysDimItemOtherDims); - }); - }); - - function applyDim(resultItem: DataDimensionInfo, coordDim: DimensionName, coordDimIndex: DimensionIndex) { - if (VISUAL_DIMENSIONS.get(coordDim as keyof DataVisualDimensions) != null) { - resultItem.otherDims[coordDim as keyof DataVisualDimensions] = coordDimIndex; - } - else { - resultItem.coordDim = coordDim; - resultItem.coordDimIndex = coordDimIndex; - coordDimNameMap.set(coordDim, true); - } - } - - // Make sure the first extra dim is 'value'. - const generateCoord = opt.generateCoord; - let generateCoordCount = opt.generateCoordCount; - const fromZero = generateCoordCount != null; - generateCoordCount = generateCoord ? (generateCoordCount || 1) : 0; - const extra = generateCoord || 'value'; - - // Set dim `name` and other `coordDim` and other props. - for (let resultDimIdx = 0; resultDimIdx < dimCount; resultDimIdx++) { - const resultItem = result[resultDimIdx] = result[resultDimIdx] || new DataDimensionInfo(); - const coordDim = resultItem.coordDim; - - if (coordDim == null) { - resultItem.coordDim = genName( - extra, coordDimNameMap, fromZero - ); - resultItem.coordDimIndex = 0; - if (!generateCoord || generateCoordCount <= 0) { - resultItem.isExtraCoord = true; - } - generateCoordCount--; - } - - resultItem.name == null && (resultItem.name = genName( - resultItem.coordDim, dataDimNameMap, false - )); - - if (resultItem.type == null - && ( - guessOrdinal(source, resultDimIdx) === BE_ORDINAL.Must - // Consider the case: - // { - // dataset: {source: [ - // ['2001', 123], - // ['2002', 456], - // ... - // ['The others', 987], - // ]}, - // series: {type: 'pie'} - // } - // The first colum should better be treated as a "ordinal" although it - // might not able to be detected as an "ordinal" by `guessOrdinal`. - || (resultItem.isExtraCoord - && (resultItem.otherDims.itemName != null - || resultItem.otherDims.seriesName != null - ) - ) - ) - ) { - resultItem.type = 'ordinal'; - } - } - - return result; -} - -// ??? TODO -// Originally detect dimCount by data[0]. Should we -// optimize it to only by sysDims and dimensions and encode. -// So only necessary dims will be initialized. -// But -// (1) custom series should be considered. where other dims -// may be visited. -// (2) sometimes user need to calcualte bubble size or use visualMap -// on other dimensions besides coordSys needed. -// So, dims that is not used by system, should be shared in storage? -function getDimCount( - source: Source, - sysDims: CoordDimensionDefinitionLoose[], - dimsDef: DimensionDefinitionLoose[], - optDimCount: number -): number { - // Note that the result dimCount should not small than columns count - // of data, otherwise `dataDimNameMap` checking will be incorrect. - let dimCount = Math.max( - source.dimensionsDetectedCount || 1, - sysDims.length, - dimsDef.length, - optDimCount || 0 - ); - each(sysDims, function (sysDimItem) { - let sysDimItemDimsDef; - if (isObject(sysDimItem) && (sysDimItemDimsDef = sysDimItem.dimsDef)) { - dimCount = Math.max(dimCount, sysDimItemDimsDef.length); - } - }); - return dimCount; -} - -function genName( - name: DimensionName, - map: HashMap, - fromZero: boolean -): DimensionName { - if (fromZero || map.get(name) != null) { - let i = 0; - while (map.get(name + i) != null) { - i++; - } - name += i; - } - map.set(name, true); - return name; -} - -export default completeDimensions; diff --git a/src/data/helper/createDimensions.ts b/src/data/helper/createDimensions.ts index 102104c5e0..cd44440aa7 100644 --- a/src/data/helper/createDimensions.ts +++ b/src/data/helper/createDimensions.ts @@ -17,20 +17,28 @@ * under the License. */ -/** - * Substitute `completeDimensions`. - * `completeDimensions` is to be deprecated. - */ -import completeDimensions from './completeDimensions'; import { DimensionDefinitionLoose, OptionEncode, OptionEncodeValue, - EncodeDefaulter, OptionSourceData, DimensionName, DimensionDefinition, DataVisualDimensions, DimensionIndex + EncodeDefaulter, + OptionSourceData, + DimensionName, + DimensionDefinition, + DataVisualDimensions, + DimensionIndex, + VISUAL_DIMENSIONS } from '../../util/types'; -import List from '../List'; -import DataDimensionInfo from '../DataDimensionInfo'; -import { HashMap } from 'zrender/src/core/util'; +import SeriesDimensionDefine from '../SeriesDimensionDefine'; +import { + createHashMap, defaults, each, extend, HashMap, isObject, isString +} from 'zrender/src/core/util'; import OrdinalMeta from '../OrdinalMeta'; -import { Source } from '../Source'; +import { createSourceFromSeriesDataOption, isSourceInstance, Source } from '../Source'; +import { CtorInt32Array } from '../DataStore'; +import { normalizeToArray } from '../../util/model'; +import { BE_ORDINAL, guessOrdinal } from './sourceHelper'; +import { + createDimNameMap, ensureSourceDimNameMap, SeriesDataSchema, shouldOmitUnusedDimensions +} from './SeriesDataSchema'; export interface CoordDimensionDefinition extends DimensionDefinition { @@ -42,35 +50,363 @@ export interface CoordDimensionDefinition extends DimensionDefinition { } export type CoordDimensionDefinitionLoose = CoordDimensionDefinition['name'] | CoordDimensionDefinition; -export type CreateDimensionsParams = { +export type PrepareSeriesDataSchemaParams = { coordDimensions?: CoordDimensionDefinitionLoose[], + /** + * Will use `source.dimensionsDefine` if not given. + */ dimensionsDefine?: DimensionDefinitionLoose[], + /** + * Will use `source.encodeDefine` if not given. + */ encodeDefine?: HashMap | OptionEncode, dimensionsCount?: number, + /** + * Make default encode if user not specified. + */ encodeDefaulter?: EncodeDefaulter, generateCoord?: string, - generateCoordCount?: number + generateCoordCount?: number, + + /** + * If be able to omit unused dimension + * Used to improve the performance on high dimension data. + */ + canOmitUnusedDimensions?: boolean }; /** - * @param opt.coordDimensions - * @param opt.dimensionsDefine By default `source.dimensionsDefine` Overwrite source define. - * @param opt.encodeDefine By default `source.encodeDefine` Overwrite source define. - * @param opt.encodeDefaulter Make default encode if user not specified. + * For outside usage compat (like echarts-gl are using it). + */ +export function createDimensions( + source: Source | OptionSourceData, + opt?: PrepareSeriesDataSchemaParams +): SeriesDimensionDefine[] { + return prepareSeriesDataSchema(source, opt).dimensions; +} + +/** + * This method builds the relationship between: + * + "what the coord sys or series requires (see `coordDimensions`)", + * + "what the user defines (in `encode` and `dimensions`, see `opt.dimensionsDefine` and `opt.encodeDefine`)" + * + "what the data source provids (see `source`)". + * + * Some guess strategy will be adapted if user does not define something. + * If no 'value' dimension specified, the first no-named dimension will be + * named as 'value'. + * + * @return The results are always sorted by `storeDimIndex` asc. */ -export default function createDimensions( +export default function prepareSeriesDataSchema( // TODO: TYPE completeDimensions type - source: Source | List | OptionSourceData, - opt?: CreateDimensionsParams -): DataDimensionInfo[] { + source: Source | OptionSourceData, + opt?: PrepareSeriesDataSchemaParams +): SeriesDataSchema { + if (!isSourceInstance(source)) { + source = createSourceFromSeriesDataOption(source as OptionSourceData); + } + opt = opt || {}; - return completeDimensions(opt.coordDimensions || [], source, { - // FIXME:TS detect whether source then call `.dimensionsDefine` and `.encodeDefine`? - dimsDef: opt.dimensionsDefine || (source as Source).dimensionsDefine, - encodeDef: opt.encodeDefine || (source as Source).encodeDefine, - dimCount: opt.dimensionsCount, - encodeDefaulter: opt.encodeDefaulter, - generateCoord: opt.generateCoord, - generateCoordCount: opt.generateCoordCount + + const sysDims = opt.coordDimensions || []; + const dimsDef = opt.dimensionsDefine || source.dimensionsDefine || []; + const coordDimNameMap = createHashMap(); + const resultList: SeriesDimensionDefine[] = []; + const dimCount = getDimCount(source, sysDims, dimsDef, opt.dimensionsCount); + + // Try to ignore unsed dimensions if sharing a high dimension datastore + // 30 is an experience value. + const omitUnusedDimensions = opt.canOmitUnusedDimensions && shouldOmitUnusedDimensions(dimCount); + + const isUsingSourceDimensionsDef = dimsDef === source.dimensionsDefine; + const dataDimNameMap = isUsingSourceDimensionsDef + ? ensureSourceDimNameMap(source) : createDimNameMap(dimsDef); + + let encodeDef = opt.encodeDefine; + if (!encodeDef && opt.encodeDefaulter) { + encodeDef = opt.encodeDefaulter(source, dimCount); + } + const encodeDefMap = createHashMap(encodeDef as any); + + const indicesMap = new CtorInt32Array(dimCount); + for (let i = 0; i < indicesMap.length; i++) { + indicesMap[i] = -1; + } + + function getResultItem(dimIdx: number) { + const idx = indicesMap[dimIdx]; + if (idx < 0) { + const dimDefItemRaw = dimsDef[dimIdx]; + const dimDefItem = isObject(dimDefItemRaw) ? dimDefItemRaw : { name: dimDefItemRaw }; + const resultItem = new SeriesDimensionDefine(); + const userDimName = dimDefItem.name; + if (userDimName != null && dataDimNameMap.get(userDimName) != null) { + // Only if `series.dimensions` is defined in option + // displayName, will be set, and dimension will be diplayed vertically in + // tooltip by default. + resultItem.name = resultItem.displayName = userDimName; + } + dimDefItem.type != null && (resultItem.type = dimDefItem.type); + dimDefItem.displayName != null && (resultItem.displayName = dimDefItem.displayName); + const newIdx = resultList.length; + indicesMap[dimIdx] = newIdx; + resultItem.storeDimIndex = dimIdx; + resultList.push(resultItem); + return resultItem; + } + return resultList[idx]; + } + + if (!omitUnusedDimensions) { + for (let i = 0; i < dimCount; i++) { + getResultItem(i); + } + } + + // Set `coordDim` and `coordDimIndex` by `encodeDefMap` and normalize `encodeDefMap`. + encodeDefMap.each(function (dataDimsRaw, coordDim) { + const dataDims = normalizeToArray(dataDimsRaw as []).slice(); + + // Note: It is allowed that `dataDims.length` is `0`, e.g., options is + // `{encode: {x: -1, y: 1}}`. Should not filter anything in + // this case. + if (dataDims.length === 1 && !isString(dataDims[0]) && dataDims[0] < 0) { + encodeDefMap.set(coordDim, false); + return; + } + + const validDataDims = encodeDefMap.set(coordDim, []) as DimensionIndex[]; + each(dataDims, function (resultDimIdxOrName, idx) { + // The input resultDimIdx can be dim name or index. + const resultDimIdx = isString(resultDimIdxOrName) + ? dataDimNameMap.get(resultDimIdxOrName) + : resultDimIdxOrName; + if (resultDimIdx != null && resultDimIdx < dimCount) { + validDataDims[idx] = resultDimIdx; + applyDim(getResultItem(resultDimIdx), coordDim, idx); + } + }); }); + + // Apply templetes and default order from `sysDims`. + let availDimIdx = 0; + each(sysDims, function (sysDimItemRaw) { + let coordDim: DimensionName; + let sysDimItemDimsDef: CoordDimensionDefinition['dimsDef']; + let sysDimItemOtherDims: CoordDimensionDefinition['otherDims']; + let sysDimItem: CoordDimensionDefinition; + if (isString(sysDimItemRaw)) { + coordDim = sysDimItemRaw; + sysDimItem = {} as CoordDimensionDefinition; + } + else { + sysDimItem = sysDimItemRaw; + coordDim = sysDimItem.name; + const ordinalMeta = sysDimItem.ordinalMeta; + sysDimItem.ordinalMeta = null; + sysDimItem = extend({}, sysDimItem); + sysDimItem.ordinalMeta = ordinalMeta; + // `coordDimIndex` should not be set directly. + sysDimItemDimsDef = sysDimItem.dimsDef; + sysDimItemOtherDims = sysDimItem.otherDims; + sysDimItem.name = sysDimItem.coordDim = sysDimItem.coordDimIndex = + sysDimItem.dimsDef = sysDimItem.otherDims = null; + } + + let dataDims = encodeDefMap.get(coordDim); + + // negative resultDimIdx means no need to mapping. + if (dataDims === false) { + return; + } + + dataDims = normalizeToArray(dataDims); + + // dimensions provides default dim sequences. + if (!dataDims.length) { + for (let i = 0; i < (sysDimItemDimsDef && sysDimItemDimsDef.length || 1); i++) { + while (availDimIdx < dimCount && getResultItem(availDimIdx).coordDim != null) { + availDimIdx++; + } + availDimIdx < dimCount && dataDims.push(availDimIdx++); + } + } + + // Apply templates. + each(dataDims, function (resultDimIdx, coordDimIndex) { + const resultItem = getResultItem(resultDimIdx); + // Coordinate system has a higher priority on dim type than source. + if (isUsingSourceDimensionsDef && sysDimItem.type != null) { + resultItem.type = sysDimItem.type; + } + applyDim(defaults(resultItem, sysDimItem), coordDim, coordDimIndex); + if (resultItem.name == null && sysDimItemDimsDef) { + let sysDimItemDimsDefItem = sysDimItemDimsDef[coordDimIndex]; + !isObject(sysDimItemDimsDefItem) && (sysDimItemDimsDefItem = { + name: sysDimItemDimsDefItem + }); + resultItem.name = resultItem.displayName = sysDimItemDimsDefItem.name; + resultItem.defaultTooltip = sysDimItemDimsDefItem.defaultTooltip; + } + // FIXME refactor, currently only used in case: {otherDims: {tooltip: false}} + sysDimItemOtherDims && defaults(resultItem.otherDims, sysDimItemOtherDims); + }); + }); + + function applyDim(resultItem: SeriesDimensionDefine, coordDim: DimensionName, coordDimIndex: DimensionIndex) { + if (VISUAL_DIMENSIONS.get(coordDim as keyof DataVisualDimensions) != null) { + resultItem.otherDims[coordDim as keyof DataVisualDimensions] = coordDimIndex; + } + else { + resultItem.coordDim = coordDim; + resultItem.coordDimIndex = coordDimIndex; + coordDimNameMap.set(coordDim, true); + } + } + + // Make sure the first extra dim is 'value'. + const generateCoord = opt.generateCoord; + let generateCoordCount = opt.generateCoordCount; + const fromZero = generateCoordCount != null; + generateCoordCount = generateCoord ? (generateCoordCount || 1) : 0; + const extra = generateCoord || 'value'; + + function ifNoNameFillWithCoordName(resultItem: SeriesDimensionDefine): void { + if (resultItem.name == null) { + // Duplication will be removed in the next step. + resultItem.name = resultItem.coordDim; + } + } + + // Set dim `name` and other `coordDim` and other props. + if (!omitUnusedDimensions) { + for (let resultDimIdx = 0; resultDimIdx < dimCount; resultDimIdx++) { + const resultItem = getResultItem(resultDimIdx); + const coordDim = resultItem.coordDim; + + if (coordDim == null) { + // TODO no need to generate coordDim for isExtraCoord? + resultItem.coordDim = genCoordDimName( + extra, coordDimNameMap, fromZero + ); + + resultItem.coordDimIndex = 0; + // Series specified generateCoord is using out. + if (!generateCoord || generateCoordCount <= 0) { + resultItem.isExtraCoord = true; + } + generateCoordCount--; + } + + ifNoNameFillWithCoordName(resultItem); + + if (resultItem.type == null + && ( + guessOrdinal(source, resultDimIdx) === BE_ORDINAL.Must + // Consider the case: + // { + // dataset: {source: [ + // ['2001', 123], + // ['2002', 456], + // ... + // ['The others', 987], + // ]}, + // series: {type: 'pie'} + // } + // The first colum should better be treated as a "ordinal" although it + // might not able to be detected as an "ordinal" by `guessOrdinal`. + || (resultItem.isExtraCoord + && (resultItem.otherDims.itemName != null + || resultItem.otherDims.seriesName != null + ) + ) + ) + ) { + resultItem.type = 'ordinal'; + } + } + } + else { + each(resultList, resultItem => { + // PENDING: guessOrdinal or let user specify type: 'ordinal' manually? + ifNoNameFillWithCoordName(resultItem); + }); + // Sort dimensions: there are some rule that use the last dim as label, + // and for some latter travel process easier. + resultList.sort((item0, item1) => item0.storeDimIndex - item1.storeDimIndex); + } + + removeDuplication(resultList); + + return new SeriesDataSchema({ + source, + dimensions: resultList, + fullDimensionCount: dimCount, + dimensionOmitted: omitUnusedDimensions + }); +} + +function removeDuplication(result: SeriesDimensionDefine[]) { + const duplicationMap = createHashMap(); + for (let i = 0; i < result.length; i++) { + const dim = result[i]; + const dimOriginalName = dim.name; + let count = duplicationMap.get(dimOriginalName) || 0; + if (count > 0) { + // Starts from 0. + dim.name = dimOriginalName + (count - 1); + } + count++; + duplicationMap.set(dimOriginalName, count); + } +} + +// ??? TODO +// Originally detect dimCount by data[0]. Should we +// optimize it to only by sysDims and dimensions and encode. +// So only necessary dims will be initialized. +// But +// (1) custom series should be considered. where other dims +// may be visited. +// (2) sometimes user need to calcualte bubble size or use visualMap +// on other dimensions besides coordSys needed. +// So, dims that is not used by system, should be shared in data store? +function getDimCount( + source: Source, + sysDims: CoordDimensionDefinitionLoose[], + dimsDef: DimensionDefinitionLoose[], + optDimCount?: number +): number { + // Note that the result dimCount should not small than columns count + // of data, otherwise `dataDimNameMap` checking will be incorrect. + let dimCount = Math.max( + source.dimensionsDetectedCount || 1, + sysDims.length, + dimsDef.length, + optDimCount || 0 + ); + each(sysDims, function (sysDimItem) { + let sysDimItemDimsDef; + if (isObject(sysDimItem) && (sysDimItemDimsDef = sysDimItem.dimsDef)) { + dimCount = Math.max(dimCount, sysDimItemDimsDef.length); + } + }); + return dimCount; +} + +function genCoordDimName( + name: DimensionName, + map: HashMap, + fromZero: boolean +) { + const mapData = map.data; + if (fromZero || mapData.hasOwnProperty(name)) { + let i = 0; + while (mapData.hasOwnProperty(name + i)) { + i++; + } + name += i; + } + map.set(name, true); + return name; } diff --git a/src/data/helper/dataProvider.ts b/src/data/helper/dataProvider.ts index 94a60fb871..37247edb2c 100644 --- a/src/data/helper/dataProvider.ts +++ b/src/data/helper/dataProvider.ts @@ -34,9 +34,9 @@ import { SERIES_LAYOUT_BY_COLUMN, SERIES_LAYOUT_BY_ROW, DimensionName, DimensionIndex, OptionSourceData, - DimensionIndexLoose, OptionDataItem, OptionDataValue, SourceFormat, SeriesLayoutBy, ParsedValue + OptionDataItem, OptionDataValue, SourceFormat, SeriesLayoutBy, ParsedValue, DimensionLoose, NullUndefined } from '../../util/types'; -import List from '../List'; +import SeriesData from '../SeriesData'; export interface DataProvider { /** @@ -136,7 +136,7 @@ export class DefaultDataProvider implements DataProvider { return 0; } - getItem(idx: number, out?: ArrayLike): OptionDataItem { + getItem(idx: number, out?: ArrayLike): OptionDataItem { return; } @@ -291,8 +291,11 @@ type RawSourceItemGetter = ( rawData: OptionSourceData, startIndex: number, dimsDef: { name?: DimensionName }[], - idx: number -) => OptionDataItem; + idx: number, + // Only used in SOURCE_FORMAT_ARRAY_ROWS + '_' + SERIES_LAYOUT_BY_ROW and SOURCE_FORMAT_KEYED_COLUMNS + // to avoid create a new [] if `out` is provided. + out?: ArrayLike +) => OptionDataItem | ArrayLike; const getItemSimply: RawSourceItemGetter = function ( rawData, startIndex, dimsDef, idx @@ -303,26 +306,26 @@ const getItemSimply: RawSourceItemGetter = function ( const rawSourceItemGetterMap: Dictionary = { [SOURCE_FORMAT_ARRAY_ROWS + '_' + SERIES_LAYOUT_BY_COLUMN]: function ( rawData, startIndex, dimsDef, idx - ): OptionDataValue[] { + ) { return (rawData as OptionDataValue[][])[idx + startIndex]; }, [SOURCE_FORMAT_ARRAY_ROWS + '_' + SERIES_LAYOUT_BY_ROW]: function ( - rawData, startIndex, dimsDef, idx - ): OptionDataValue[] { + rawData, startIndex, dimsDef, idx, out + ) { idx += startIndex; - const item = []; + const item = out || []; const data = rawData as OptionDataValue[][]; for (let i = 0; i < data.length; i++) { const row = data[i]; - item.push(row ? row[idx] : null); + item[i] = row ? row[idx] : null; } return item; }, [SOURCE_FORMAT_OBJECT_ROWS]: getItemSimply, [SOURCE_FORMAT_KEYED_COLUMNS]: function ( - rawData, startIndex, dimsDef, idx - ): OptionDataValue[] { - const item = []; + rawData, startIndex, dimsDef, idx, out + ) { + const item = out || []; for (let i = 0; i < dimsDef.length; i++) { const dimName = dimsDef[i].name; if (__DEV__) { @@ -331,7 +334,7 @@ const rawSourceItemGetterMap: Dictionary = { } } const col = (rawData as Dictionary)[dimName]; - item.push(col ? col[idx] : null); + item[i] = col ? col[idx] : null; } return item; }, @@ -402,42 +405,37 @@ export function getRawSourceDataCounter( } - -// TODO -// merge it to dataProvider? type RawSourceValueGetter = ( dataItem: OptionDataItem, dimIndex: DimensionIndex, - dimName: DimensionName - // If dimIndex is null/undefined, return OptionDataItem. - // Otherwise, return OptionDataValue. -) => OptionDataValue | OptionDataItem; + property: DimensionName +) => OptionDataValue; const getRawValueSimply = function ( - dataItem: ArrayLike, dimIndex: number, dimName: string -): OptionDataValue | ArrayLike { - return dimIndex != null ? dataItem[dimIndex] : dataItem; + dataItem: ArrayLike, dimIndex: number, property: string +): OptionDataValue { + return dataItem[dimIndex]; }; -const rawSourceValueGetterMap: {[sourceFormat: string]: RawSourceValueGetter} = { +const rawSourceValueGetterMap: Partial> = { [SOURCE_FORMAT_ARRAY_ROWS]: getRawValueSimply, [SOURCE_FORMAT_OBJECT_ROWS]: function ( - dataItem: Dictionary, dimIndex: number, dimName: string - ): OptionDataValue | Dictionary { - return dimIndex != null ? dataItem[dimName] : dataItem; + dataItem: Dictionary, dimIndex: number, property: string + ): OptionDataValue { + return dataItem[property]; }, [SOURCE_FORMAT_KEYED_COLUMNS]: getRawValueSimply, [SOURCE_FORMAT_ORIGINAL]: function ( - dataItem: OptionDataItem, dimIndex: number, dimName: string - ): OptionDataValue | OptionDataItem { + dataItem: OptionDataItem, dimIndex: number, property: string + ): OptionDataValue { // FIXME: In some case (markpoint in geo (geo-map.html)), // dataItem is {coord: [...]} const value = getDataItemValue(dataItem); - return (dimIndex == null || !(value instanceof Array)) + return !(value instanceof Array) ? value : value[dimIndex]; }, @@ -469,9 +467,10 @@ function getMethodMapKey(sourceFormat: SourceFormat, seriesLayoutBy: SeriesLayou // value may be 0.91000000001, which have brings trouble to display. // TODO: consider how to treat null/undefined/NaN when display? export function retrieveRawValue( - data: List, dataIndex: number, dim?: DimensionName | DimensionIndexLoose + data: SeriesData, dataIndex: number, // If dimIndex is null/undefined, return OptionDataItem. // Otherwise, return OptionDataValue. + dim?: DimensionLoose | NullUndefined ): OptionDataValue | OptionDataItem { if (!data) { return; @@ -484,17 +483,22 @@ export function retrieveRawValue( return; } - const sourceFormat = data.getProvider().getSource().sourceFormat; - let dimName; - let dimIndex; + const store = data.getStore(); + const sourceFormat = store.getSource().sourceFormat; - const dimInfo = data.getDimensionInfo(dim); - if (dimInfo) { - dimName = dimInfo.name; - dimIndex = dimInfo.index; - } + if (dim != null) { + const dimIndex = data.getDimensionIndex(dim); + const property = store.getDimensionProperty(dimIndex); - return getRawSourceValueGetter(sourceFormat)(dataItem, dimIndex, dimName); + return getRawSourceValueGetter(sourceFormat)(dataItem, dimIndex, property); + } + else { + let result = dataItem; + if (sourceFormat === SOURCE_FORMAT_ORIGINAL) { + result = getDataItemValue(dataItem); + } + return result; + } } @@ -510,12 +514,12 @@ export function retrieveRawValue( * @param dataIndex * @param attr like 'selected' */ -export function retrieveRawAttr(data: List, dataIndex: number, attr: string): any { +export function retrieveRawAttr(data: SeriesData, dataIndex: number, attr: string): any { if (!data) { return; } - const sourceFormat = data.getProvider().getSource().sourceFormat; + const sourceFormat = data.getStore().getSource().sourceFormat; if (sourceFormat !== SOURCE_FORMAT_ORIGINAL && sourceFormat !== SOURCE_FORMAT_OBJECT_ROWS diff --git a/src/data/helper/dataStackHelper.ts b/src/data/helper/dataStackHelper.ts index cef669caf3..deb3e3e918 100644 --- a/src/data/helper/dataStackHelper.ts +++ b/src/data/helper/dataStackHelper.ts @@ -18,11 +18,21 @@ */ import {each, isString} from 'zrender/src/core/util'; -import DataDimensionInfo from '../DataDimensionInfo'; +import SeriesDimensionDefine from '../SeriesDimensionDefine'; import SeriesModel from '../../model/Series'; -import List, { DataCalculationInfo } from '../List'; +import SeriesData, { DataCalculationInfo } from '../SeriesData'; import type { SeriesOption, SeriesStackOptionMixin, DimensionName } from '../../util/types'; - +import { isSeriesDataSchema, SeriesDataSchema } from './SeriesDataSchema'; +import DataStore from '../DataStore'; + +type EnableDataStackDimensionsInput = { + schema: SeriesDataSchema; + // If given, stack dimension will be ensured on this store. + // Otherwise, stack dimesnion will be appended at the tail, and should not + // be used on a shared store, but should create a brand new stroage later. + store?: DataStore; +}; +type EnableDataStackDimensionsInputLegacy = (SeriesDimensionDefine | string)[]; /** * Note that it is too complicated to support 3d stack by value @@ -30,8 +40,8 @@ import type { SeriesOption, SeriesStackOptionMixin, DimensionName } from '../../ * we just support that stacked by index. * * @param seriesModel - * @param dimensionInfoList The same as the input of . - * The input dimensionInfoList will be modified. + * @param dimensionsInput The same as the input of . + * The input will be modified. * @param opt * @param opt.stackedCoordDimension Specify a coord dimension if needed. * @param opt.byIndex=false @@ -46,8 +56,9 @@ import type { SeriesOption, SeriesStackOptionMixin, DimensionName } from '../../ */ export function enableDataStack( seriesModel: SeriesModel, - dimensionInfoList: (DataDimensionInfo | string)[], + dimensionsInput: EnableDataStackDimensionsInput | EnableDataStackDimensionsInputLegacy, opt?: { + // Backward compat stackedCoordDimension?: string byIndex?: boolean } @@ -63,18 +74,31 @@ export function enableDataStack( let byIndex = opt.byIndex; const stackedCoordDimension = opt.stackedCoordDimension; + let dimensionDefineList: EnableDataStackDimensionsInputLegacy; + let schema: SeriesDataSchema; + let store: DataStore; + + if (isLegacyDimensionsInput(dimensionsInput)) { + dimensionDefineList = dimensionsInput; + } + else { + schema = dimensionsInput.schema; + dimensionDefineList = schema.dimensions; + store = dimensionsInput.store; + } + // Compatibal: when `stack` is set as '', do not stack. const mayStack = !!(seriesModel && seriesModel.get('stack')); - let stackedByDimInfo: DataDimensionInfo; - let stackedDimInfo: DataDimensionInfo; + let stackedByDimInfo: SeriesDimensionDefine; + let stackedDimInfo: SeriesDimensionDefine; let stackResultDimension: string; let stackedOverDimension: string; - each(dimensionInfoList, function (dimensionInfo, index) { + each(dimensionDefineList, function (dimensionInfo, index) { if (isString(dimensionInfo)) { - dimensionInfoList[index] = dimensionInfo = { + dimensionDefineList[index] = dimensionInfo = { name: dimensionInfo as string - } as DataDimensionInfo; + } as SeriesDimensionDefine; } if (mayStack && !dimensionInfo.isExtraCoord) { @@ -104,8 +128,10 @@ export function enableDataStack( // might not be a good way. if (stackedDimInfo) { // Use a weird name that not duplicated with other names. - stackResultDimension = '__\0ecstackresult'; - stackedOverDimension = '__\0ecstackedover'; + // Also need to use seriesModel.id as postfix because different + // series may share same data store. The stack dimension needs to be distinguished. + stackResultDimension = '__\0ecstackresult_' + seriesModel.id; + stackedOverDimension = '__\0ecstackedover_' + seriesModel.id; // Create inverted index to fast query index by value. if (stackedByDimInfo) { @@ -116,33 +142,49 @@ export function enableDataStack( const stackedDimType = stackedDimInfo.type; let stackedDimCoordIndex = 0; - each(dimensionInfoList, function (dimensionInfo: DataDimensionInfo) { + each(dimensionDefineList, function (dimensionInfo: SeriesDimensionDefine) { if (dimensionInfo.coordDim === stackedDimCoordDim) { stackedDimCoordIndex++; } }); - dimensionInfoList.push({ + const stackedOverDimensionDefine: SeriesDimensionDefine = { name: stackResultDimension, coordDim: stackedDimCoordDim, coordDimIndex: stackedDimCoordIndex, type: stackedDimType, isExtraCoord: true, - isCalculationCoord: true - }); + isCalculationCoord: true, + storeDimIndex: dimensionDefineList.length + }; - stackedDimCoordIndex++; - - dimensionInfoList.push({ + const stackResultDimensionDefine: SeriesDimensionDefine = { name: stackedOverDimension, // This dimension contains stack base (generally, 0), so do not set it as // `stackedDimCoordDim` to avoid extent calculation, consider log scale. coordDim: stackedOverDimension, - coordDimIndex: stackedDimCoordIndex, + coordDimIndex: stackedDimCoordIndex + 1, type: stackedDimType, isExtraCoord: true, - isCalculationCoord: true - }); + isCalculationCoord: true, + storeDimIndex: dimensionDefineList.length + 1 + }; + + if (schema) { + if (store) { + stackedOverDimensionDefine.storeDimIndex = + store.ensureCalculationDimension(stackedOverDimension, stackedDimType); + stackResultDimensionDefine.storeDimIndex = + store.ensureCalculationDimension(stackResultDimension, stackedDimType); + } + + schema.appendCalculationDimension(stackedOverDimensionDefine); + schema.appendCalculationDimension(stackResultDimensionDefine); + } + else { + dimensionDefineList.push(stackedOverDimensionDefine); + dimensionDefineList.push(stackResultDimensionDefine); + } } return { @@ -154,18 +196,19 @@ export function enableDataStack( }; } -export function isDimensionStacked(data: List, stackedDim: string /*, stackedByDim*/): boolean { +function isLegacyDimensionsInput( + dimensionsInput: Parameters[1] +): dimensionsInput is EnableDataStackDimensionsInputLegacy { + return !isSeriesDataSchema((dimensionsInput as EnableDataStackDimensionsInput).schema); +} + +export function isDimensionStacked(data: SeriesData, stackedDim: string): boolean { // Each single series only maps to one pair of axis. So we do not need to // check stackByDim, whatever stacked by a dimension or stacked by index. return !!stackedDim && stackedDim === data.getCalculationInfo('stackedDimension'); - // && ( - // stackedByDim != null - // ? stackedByDim === data.getCalculationInfo('stackedByDimension') - // : data.getCalculationInfo('isStackedByIndex') - // ); } -export function getStackedDimension(data: List, targetDim: string): DimensionName { +export function getStackedDimension(data: SeriesData, targetDim: string): DimensionName { return isDimensionStacked(data, targetDim) ? data.getCalculationInfo('stackResultDimension') : targetDim; diff --git a/src/data/helper/dataValueHelper.ts b/src/data/helper/dataValueHelper.ts index 98e36c86d4..1c10798c76 100644 --- a/src/data/helper/dataValueHelper.ts +++ b/src/data/helper/dataValueHelper.ts @@ -18,7 +18,6 @@ */ import { ParsedValue, DimensionType } from '../../util/types'; -import OrdinalMeta from '../OrdinalMeta'; import { parseDate, numericToNumber } from '../../util/number'; import { createHashMap, trim, hasOwn } from 'zrender/src/core/util'; import { throwError } from '../../util/log'; @@ -40,18 +39,14 @@ export function parseDataValue( // will be parsed to NaN if do not set `type` as 'ordinal'. It has been // the logic in `List.ts` for long time. Follow the same way if you need // to get same result as List did from a raw value. - type?: DimensionType, - ordinalMeta?: OrdinalMeta + type?: DimensionType } ): ParsedValue { // Performance sensitive. const dimType = opt && opt.type; if (dimType === 'ordinal') { // If given value is a category string - const ordinalMeta = opt && opt.ordinalMeta; - return ordinalMeta - ? ordinalMeta.parseAndCollect(value) - : value; + return value; } if (dimType === 'time' diff --git a/src/data/helper/dimensionHelper.ts b/src/data/helper/dimensionHelper.ts index b38062dbf3..e4d9899ca0 100644 --- a/src/data/helper/dimensionHelper.ts +++ b/src/data/helper/dimensionHelper.ts @@ -18,11 +18,13 @@ */ -import {each, createHashMap, assert} from 'zrender/src/core/util'; -import List, { ListDimensionType } from '../List'; +import {each, createHashMap, assert, map} from 'zrender/src/core/util'; +import SeriesData from '../SeriesData'; import { - DimensionName, VISUAL_DIMENSIONS, DimensionType, DimensionUserOuput, DimensionUserOuputEncode, DimensionIndex + DimensionName, VISUAL_DIMENSIONS, DimensionType, DimensionIndex } from '../../util/types'; +import { DataStoreDimensionType } from '../DataStore'; +import { SeriesDataSchema } from './SeriesDataSchema'; export type DimensionSummaryEncode = { defaultedLabel: DimensionName[], @@ -37,21 +39,68 @@ export type DimensionSummary = { userOutput: DimensionUserOuput, // All of the data dim names that mapped by coordDim. dataDimsOnCoord: DimensionName[], + dataDimIndicesOnCoord: DimensionIndex[], encodeFirstDimNotExtra: {[coordDim: string]: DimensionName}, }; -export function summarizeDimensions(data: List): DimensionSummary { +export type DimensionUserOuputEncode = { + // index: coordDimIndex, value: dataDimIndex + [coordOrVisualDimName: string]: DimensionIndex[] +}; + +class DimensionUserOuput { + private _encode: DimensionUserOuputEncode; + private _cachedDimNames: DimensionName[]; + private _schema?: SeriesDataSchema; + + constructor( + encode: DimensionUserOuputEncode, + dimRequest?: SeriesDataSchema + ) { + this._encode = encode; + this._schema = dimRequest; + } + + get(): { + fullDimensions: DimensionName[]; + encode: DimensionUserOuputEncode; + } { + return { + // Do not generate full dimension name until fist used. + fullDimensions: this._getFullDimensionNames(), + encode: this._encode + }; + } + + /** + * Get all data store dimension names. + * Theoretically a series data store is defined both by series and used dataset (if any). + * If some dimensions are omitted for performance reason in `this.dimensions`, + * the dimension name may not be auto-generated if user does not specify a dimension name. + * In this case, the dimension name is `null`/`undefined`. + */ + private _getFullDimensionNames(): DimensionName[] { + if (!this._cachedDimNames) { + this._cachedDimNames = this._schema + ? this._schema.makeOutputDimensionNames() + : []; + } + return this._cachedDimNames; + } +}; + + +export function summarizeDimensions( + data: SeriesData, + schema?: SeriesDataSchema +): DimensionSummary { const summary: DimensionSummary = {} as DimensionSummary; const encode = summary.encode = {} as DimensionSummaryEncode; const notExtraCoordDimMap = createHashMap<1, DimensionName>(); let defaultedLabel = [] as DimensionName[]; let defaultedTooltip = [] as DimensionName[]; - // See the comment of `List.js#userOutput`. - const userOutput = summary.userOutput = { - dimensionNames: data.dimensions.slice(), - encode: {} - }; + const userOutputEncode = {} as DimensionUserOuputEncode; each(data.dimensions, function (dimName) { const dimItem = data.getDimensionInfo(dimName); @@ -78,7 +127,8 @@ export function summarizeDimensions(data: List): DimensionSummary { // User output encode do not contain generated coords. // And it only has index. User can use index to retrieve value from the raw item array. - getOrCreateEncodeArr(userOutput.encode, coordDim)[coordDimIndex] = dimItem.index; + getOrCreateEncodeArr(userOutputEncode, coordDim)[coordDimIndex] = + data.getDimensionIndex(dimItem.name); } if (dimItem.defaultTooltip) { defaultedTooltip.push(dimName); @@ -107,6 +157,9 @@ export function summarizeDimensions(data: List): DimensionSummary { }); summary.dataDimsOnCoord = dataDimsOnCoord; + summary.dataDimIndicesOnCoord = map( + dataDimsOnCoord, dimName => data.getDimensionInfo(dimName).storeDimIndex + ); summary.encodeFirstDimNotExtra = encodeFirstDimNotExtra; const encodeLabel = encode.label; @@ -127,6 +180,8 @@ export function summarizeDimensions(data: List): DimensionSummary { encode.defaultedLabel = defaultedLabel; encode.defaultedTooltip = defaultedTooltip; + summary.userOutput = new DimensionUserOuput(userOutputEncode, schema); + return summary; } @@ -140,7 +195,7 @@ function getOrCreateEncodeArr( } // FIXME:TS should be type `AxisType` -export function getDimensionTypeByAxis(axisType: string): ListDimensionType { +export function getDimensionTypeByAxis(axisType: string): DataStoreDimensionType { return axisType === 'category' ? 'ordinal' : axisType === 'time' diff --git a/src/data/helper/linkList.ts b/src/data/helper/linkList.ts index a7cecc484c..61a926bd8b 100644 --- a/src/data/helper/linkList.ts +++ b/src/data/helper/linkList.ts @@ -17,170 +17,6 @@ * under the License. */ - -/** - * Link lists and struct (graph or tree) - */ - -import { curry, each, assert, extend, map, keys } from 'zrender/src/core/util'; -import List from '../List'; -import { makeInner } from '../../util/model'; -import { SeriesDataType } from '../../util/types'; - -// That is: { dataType: data }, -// like: { node: nodeList, edge: edgeList }. -// Should contain mainData. -type Datas = { [key in SeriesDataType]?: List }; -type StructReferDataAttr = 'data' | 'edgeData'; -type StructAttr = 'tree' | 'graph'; - -const inner = makeInner<{ - datas: Datas; - mainData: List; -}, List>(); - - -// Caution: -// In most case, either list or its shallow clones (see list.cloneShallow) -// is active in echarts process. So considering heap memory consumption, -// we do not clone tree or graph, but share them among list and its shallow clones. -// But in some rare case, we have to keep old list (like do animation in chart). So -// please take care that both the old list and the new list share the same tree/graph. - -type LinkListOpt = { - mainData: List; - // For example, instance of Graph or Tree. - struct: { - update: () => void; - } & { - [key in StructReferDataAttr]?: List - }; - // Will designate: `mainData[structAttr] = struct;` - structAttr: StructAttr; - datas?: Datas; - // { dataType: attr }, - // Will designate: `struct[datasAttr[dataType]] = list;` - datasAttr?: { [key in SeriesDataType]?: StructReferDataAttr }; -}; - -function linkList(opt: LinkListOpt): void { - const mainData = opt.mainData; - let datas = opt.datas; - - if (!datas) { - datas = { main: mainData }; - opt.datasAttr = { main: 'data' }; - } - opt.datas = opt.mainData = null; - - linkAll(mainData, datas, opt); - - // Porxy data original methods. - each(datas, function (data: List) { - each(mainData.TRANSFERABLE_METHODS, function (methodName) { - data.wrapMethod(methodName, curry(transferInjection, opt)); - }); - }); - - // Beyond transfer, additional features should be added to `cloneShallow`. - mainData.wrapMethod('cloneShallow', curry(cloneShallowInjection, opt)); - - // Only mainData trigger change, because struct.update may trigger - // another changable methods, which may bring about dead lock. - each(mainData.CHANGABLE_METHODS, function (methodName) { - mainData.wrapMethod(methodName, curry(changeInjection, opt)); - }); - - // Make sure datas contains mainData. - assert(datas[mainData.dataType] === mainData); -} - -function transferInjection(this: List, opt: LinkListOpt, res: List): unknown { - if (isMainData(this)) { - // Transfer datas to new main data. - const datas = extend({}, inner(this).datas); - datas[this.dataType] = res; - linkAll(res, datas, opt); - } - else { - // Modify the reference in main data to point newData. - linkSingle(res, this.dataType, inner(this).mainData, opt); - } - return res; -} - -function changeInjection(opt: LinkListOpt, res: unknown): unknown { - opt.struct && opt.struct.update(); - return res; -} - -function cloneShallowInjection(opt: LinkListOpt, res: List): List { - // cloneShallow, which brings about some fragilities, may be inappropriate - // to be exposed as an API. So for implementation simplicity we can make - // the restriction that cloneShallow of not-mainData should not be invoked - // outside, but only be invoked here. - each(inner(res).datas, function (data: List, dataType) { - data !== res && linkSingle(data.cloneShallow(), dataType, res, opt); - }); - return res; -} - -/** - * Supplement method to List. - * - * @public - * @param [dataType] If not specified, return mainData. - */ -function getLinkedData(this: List, dataType?: SeriesDataType): List { - const mainData = inner(this).mainData; - return (dataType == null || mainData == null) - ? mainData - : inner(mainData).datas[dataType]; -} - -/** - * Get list of all linked data - */ -function getLinkedDataAll(this: List): { - data: List, - type?: SeriesDataType -}[] { - const mainData = inner(this).mainData; - return (mainData == null) - ? [{ data: mainData }] - : map(keys(inner(mainData).datas), function (type) { - return { - type, - data: inner(mainData).datas[type] - }; - }); -} - -function isMainData(data: List): boolean { - return inner(data).mainData === data; -} - -function linkAll(mainData: List, datas: Datas, opt: LinkListOpt): void { - inner(mainData).datas = {}; - each(datas, function (data: List, dataType) { - linkSingle(data, dataType, mainData, opt); - }); -} - -function linkSingle(data: List, dataType: SeriesDataType, mainData: List, opt: LinkListOpt): void { - inner(mainData).datas[dataType] = data; - inner(data).mainData = mainData; - - data.dataType = dataType; - - if (opt.struct) { - data[opt.structAttr] = opt.struct as any; - opt.struct[opt.datasAttr[dataType]] = data; - } - - // Supplement method. - data.getLinkedData = getLinkedData; - data.getLinkedDataAll = getLinkedDataAll; -} - -export default linkList; +// TODO: this module is only for compatibility with echarts-gl +import linkSeriesData from './linkSeriesData'; +export default linkSeriesData; \ No newline at end of file diff --git a/src/data/helper/linkSeriesData.ts b/src/data/helper/linkSeriesData.ts new file mode 100644 index 0000000000..2b767cf427 --- /dev/null +++ b/src/data/helper/linkSeriesData.ts @@ -0,0 +1,186 @@ +/* +* 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. +*/ + + +/** + * Link lists and struct (graph or tree) + */ + +import { curry, each, assert, extend, map, keys } from 'zrender/src/core/util'; +import SeriesData from '../SeriesData'; +import { makeInner } from '../../util/model'; +import { SeriesDataType } from '../../util/types'; + +// That is: { dataType: data }, +// like: { node: nodeList, edge: edgeList }. +// Should contain mainData. +type Datas = { [key in SeriesDataType]?: SeriesData }; +type StructReferDataAttr = 'data' | 'edgeData'; +type StructAttr = 'tree' | 'graph'; + +const inner = makeInner<{ + datas: Datas; + mainData: SeriesData; +}, SeriesData>(); + + +// Caution: +// In most case, either seriesData or its shallow clones (see seriesData.cloneShallow) +// is active in echarts process. So considering heap memory consumption, +// we do not clone tree or graph, but share them among seriesData and its shallow clones. +// But in some rare case, we have to keep old seriesData (like do animation in chart). So +// please take care that both the old seriesData and the new seriesData share the same tree/graph. + +type LinkSeriesDataOpt = { + mainData: SeriesData; + // For example, instance of Graph or Tree. + struct: { + update: () => void; + } & { + [key in StructReferDataAttr]?: SeriesData + }; + // Will designate: `mainData[structAttr] = struct;` + structAttr: StructAttr; + datas?: Datas; + // { dataType: attr }, + // Will designate: `struct[datasAttr[dataType]] = list;` + datasAttr?: { [key in SeriesDataType]?: StructReferDataAttr }; +}; + +function linkSeriesData(opt: LinkSeriesDataOpt): void { + const mainData = opt.mainData; + let datas = opt.datas; + + if (!datas) { + datas = { main: mainData }; + opt.datasAttr = { main: 'data' }; + } + opt.datas = opt.mainData = null; + + linkAll(mainData, datas, opt); + + // Porxy data original methods. + each(datas, function (data: SeriesData) { + each(mainData.TRANSFERABLE_METHODS, function (methodName) { + data.wrapMethod(methodName, curry(transferInjection, opt)); + }); + }); + + // Beyond transfer, additional features should be added to `cloneShallow`. + mainData.wrapMethod('cloneShallow', curry(cloneShallowInjection, opt)); + + // Only mainData trigger change, because struct.update may trigger + // another changable methods, which may bring about dead lock. + each(mainData.CHANGABLE_METHODS, function (methodName) { + mainData.wrapMethod(methodName, curry(changeInjection, opt)); + }); + + // Make sure datas contains mainData. + assert(datas[mainData.dataType] === mainData); +} + +function transferInjection(this: SeriesData, opt: LinkSeriesDataOpt, res: SeriesData): unknown { + if (isMainData(this)) { + // Transfer datas to new main data. + const datas = extend({}, inner(this).datas); + datas[this.dataType] = res; + linkAll(res, datas, opt); + } + else { + // Modify the reference in main data to point newData. + linkSingle(res, this.dataType, inner(this).mainData, opt); + } + return res; +} + +function changeInjection(opt: LinkSeriesDataOpt, res: unknown): unknown { + opt.struct && opt.struct.update(); + return res; +} + +function cloneShallowInjection(opt: LinkSeriesDataOpt, res: SeriesData): SeriesData { + // cloneShallow, which brings about some fragilities, may be inappropriate + // to be exposed as an API. So for implementation simplicity we can make + // the restriction that cloneShallow of not-mainData should not be invoked + // outside, but only be invoked here. + each(inner(res).datas, function (data: SeriesData, dataType) { + data !== res && linkSingle(data.cloneShallow(), dataType, res, opt); + }); + return res; +} + +/** + * Supplement method to List. + * + * @public + * @param [dataType] If not specified, return mainData. + */ +function getLinkedData(this: SeriesData, dataType?: SeriesDataType): SeriesData { + const mainData = inner(this).mainData; + return (dataType == null || mainData == null) + ? mainData + : inner(mainData).datas[dataType]; +} + +/** + * Get list of all linked data + */ +function getLinkedDataAll(this: SeriesData): { + data: SeriesData, + type?: SeriesDataType +}[] { + const mainData = inner(this).mainData; + return (mainData == null) + ? [{ data: mainData }] + : map(keys(inner(mainData).datas), function (type) { + return { + type, + data: inner(mainData).datas[type] + }; + }); +} + +function isMainData(data: SeriesData): boolean { + return inner(data).mainData === data; +} + +function linkAll(mainData: SeriesData, datas: Datas, opt: LinkSeriesDataOpt): void { + inner(mainData).datas = {}; + each(datas, function (data: SeriesData, dataType) { + linkSingle(data, dataType, mainData, opt); + }); +} + +function linkSingle(data: SeriesData, dataType: SeriesDataType, mainData: SeriesData, opt: LinkSeriesDataOpt): void { + inner(mainData).datas[dataType] = data; + inner(data).mainData = mainData; + + data.dataType = dataType; + + if (opt.struct) { + data[opt.structAttr] = opt.struct as any; + opt.struct[opt.datasAttr[dataType]] = data; + } + + // Supplement method. + data.getLinkedData = getLinkedData; + data.getLinkedDataAll = getLinkedDataAll; +} + +export default linkSeriesData; diff --git a/src/data/helper/sourceManager.ts b/src/data/helper/sourceManager.ts index bf3f1ece88..019b317f4e 100644 --- a/src/data/helper/sourceManager.ts +++ b/src/data/helper/sourceManager.ts @@ -19,18 +19,25 @@ import { DatasetModel } from '../../component/dataset/install'; import SeriesModel from '../../model/Series'; -import { setAsPrimitive, map, isTypedArray, assert, each, retrieve2 } from 'zrender/src/core/util'; +import { + setAsPrimitive, map, isTypedArray, assert, each, retrieve2 +} from 'zrender/src/core/util'; import { SourceMetaRawOption, Source, createSource, cloneSourceShallow } from '../Source'; import { SeriesEncodableModel, OptionSourceData, SOURCE_FORMAT_TYPED_ARRAY, SOURCE_FORMAT_ORIGINAL, - SourceFormat, SeriesLayoutBy, OptionSourceHeader, DimensionDefinitionLoose + SourceFormat, SeriesLayoutBy, OptionSourceHeader, + DimensionDefinitionLoose, Dictionary } from '../../util/types'; import { querySeriesUpstreamDatasetModel, queryDatasetUpstreamDatasetModels } from './sourceHelper'; import { applyDataTransform } from './transform'; +import DataStore, { DataStoreDimensionDefine } from '../DataStore'; +import { DefaultDataProvider } from './dataProvider'; +import { SeriesDataSchema } from './SeriesDataSchema'; +type DataStoreMap = Dictionary; /** * [REQUIREMENT_MEMO]: @@ -131,11 +138,15 @@ export class SourceManager { // Cached source. Do not repeat calculating if not dirty. private _sourceList: Source[] = []; + private _storeList: DataStoreMap[] = []; + // version sign of each upstream source manager. private _upstreamSignList: string[] = []; private _versionSignBase = 0; + private _dirty = true; + constructor(sourceHost: DatasetModel | SeriesModel) { this._sourceHost = sourceHost; } @@ -145,6 +156,8 @@ export class SourceManager { */ dirty() { this._setLocalSource([], []); + this._storeList = []; + this._dirty = true; } private _setLocalSource( @@ -175,11 +188,13 @@ export class SourceManager { // cache the result source to prevent from repeating transform. if (this._isDirty()) { this._createSource(); + this._dirty = false; } } private _createSource(): void { this._setLocalSource([], []); + const sourceHost = this._sourceHost; const upSourceMgrList = this._getUpstreamSourceManagers(); @@ -211,30 +226,25 @@ export class SourceManager { } // See [REQUIREMENT_MEMO], merge settings on series and parent dataset if it is root. - const newMetaRawOption = this._getSourceMetaRawOption(); - const upMetaRawOption = upSource ? upSource.metaRawOption : null; - const seriesLayoutBy = retrieve2( - newMetaRawOption.seriesLayoutBy, - upMetaRawOption ? upMetaRawOption.seriesLayoutBy : null - ); - const sourceHeader = retrieve2( - newMetaRawOption.sourceHeader, - upMetaRawOption ? upMetaRawOption.sourceHeader : null - ); + const newMetaRawOption = this._getSourceMetaRawOption() || {} as SourceMetaRawOption; + const upMetaRawOption = upSource && upSource.metaRawOption || {} as SourceMetaRawOption; + const seriesLayoutBy = retrieve2(newMetaRawOption.seriesLayoutBy, upMetaRawOption.seriesLayoutBy) || null; + const sourceHeader = retrieve2(newMetaRawOption.sourceHeader, upMetaRawOption.sourceHeader) || null; // Note here we should not use `upSource.dimensionsDefine`. Consider the case: // `upSource.dimensionsDefine` is detected by `seriesLayoutBy: 'column'`, // but series need `seriesLayoutBy: 'row'`. - const dimensions = retrieve2( - newMetaRawOption.dimensions, - upMetaRawOption ? upMetaRawOption.dimensions : null - ); - - resultSourceList = [createSource( + const dimensions = retrieve2(newMetaRawOption.dimensions, upMetaRawOption.dimensions); + + // We share source with dataset as much as possible + // to avoid extra memroy cost of high dimensional data. + const needsCreateSource = seriesLayoutBy !== upMetaRawOption.seriesLayoutBy + || !!sourceHeader !== !!upMetaRawOption.sourceHeader + || dimensions; + resultSourceList = needsCreateSource ? [createSource( data, { seriesLayoutBy, sourceHeader, dimensions }, - sourceFormat, - seriesModel.get('encode', true) - )]; + sourceFormat + )] : []; } else { const datasetModel = sourceHost as DatasetModel; @@ -251,8 +261,6 @@ export class SourceManager { resultSourceList = [createSource( sourceData, this._getSourceMetaRawOption(), - null, - // Note: dataset option does not have `encode`. null )]; upstreamSignList = []; @@ -322,8 +330,7 @@ export class SourceManager { } private _isDirty(): boolean { - const sourceList = this._sourceList; - if (!sourceList.length) { + if (this._dirty) { return true; } @@ -346,8 +353,73 @@ export class SourceManager { * @param sourceIndex By defualt 0, means "main source". * Most cases there is only one source. */ - getSource(sourceIndex?: number) { - return this._sourceList[sourceIndex || 0]; + getSource(sourceIndex?: number): Source { + sourceIndex = sourceIndex || 0; + const source = this._sourceList[sourceIndex]; + if (!source) { + // Series may share source instance with dataset. + const upSourceMgrList = this._getUpstreamSourceManagers(); + return upSourceMgrList[0] + && upSourceMgrList[0].getSource(sourceIndex); + } + return source; + } + + /** + * + * Get a data store which can be shared across series. + * Only available for series. + * + * @param seriesDimRequest Dimensions that are generated in series. + * Should have been sorted by `storeDimIndex` asc. + */ + getSharedDataStore(seriesDimRequest: SeriesDataSchema): DataStore { + if (__DEV__) { + assert(isSeries(this._sourceHost), 'Can only call getDataStore on series source manager.'); + } + const schema = seriesDimRequest.makeStoreSchema(); + return this._innerGetDataStore( + schema.dimensions, seriesDimRequest.source, schema.hash + ); + } + + private _innerGetDataStore( + storeDims: DataStoreDimensionDefine[], + seriesSource: Source, + sourceReadKey: string + ): DataStore | undefined { + // TODO Can use other sourceIndex? + const sourceIndex = 0; + + const storeList = this._storeList; + + let cachedStoreMap = storeList[sourceIndex]; + + if (!cachedStoreMap) { + cachedStoreMap = storeList[sourceIndex] = {}; + } + + let cachedStore = cachedStoreMap[sourceReadKey]; + if (!cachedStore) { + const upSourceMgr = this._getUpstreamSourceManagers()[0]; + + if (isSeries(this._sourceHost) && upSourceMgr) { + cachedStore = upSourceMgr._innerGetDataStore( + storeDims, seriesSource, sourceReadKey + ); + } + else { + cachedStore = new DataStore(); + // Always create store from source of series. + cachedStore.initData( + new DefaultDataProvider(seriesSource, storeDims.length), + storeDims + ); + } + cachedStoreMap[sourceReadKey] = cachedStore; + } + + return cachedStore; } /** diff --git a/src/data/helper/transform.ts b/src/data/helper/transform.ts index 43625cd917..78c1a70754 100644 --- a/src/data/helper/transform.ts +++ b/src/data/helper/transform.ts @@ -533,7 +533,6 @@ function applySingleDataTransform( return createSource( result.data, resultMetaRawOption, - null, null ); }); diff --git a/src/echarts.ts b/src/echarts.ts index 5c3e5d2a46..2c4df0d8e7 100644 --- a/src/echarts.ts +++ b/src/echarts.ts @@ -27,7 +27,7 @@ import {install as DatasetComponent} from './component/dataset/install'; // Default to have canvas renderer and dataset for compitatble reason. use([CanvasRenderer, DatasetComponent]); -// Compatitable with the following code +// TODO: Compatitable with the following code // import echarts from 'echarts/lib/echarts' export default { init() { diff --git a/src/export/api.ts b/src/export/api.ts index 1d84814ada..d7f7cf0e33 100644 --- a/src/export/api.ts +++ b/src/export/api.ts @@ -24,6 +24,7 @@ import ComponentView, { ComponentViewConstructor } from '../view/Component'; import SeriesModel, { SeriesModelConstructor } from '../model/Series'; import ChartView, { ChartViewConstructor } from '../view/Chart'; +import SeriesData from '../data/SeriesData'; // Provide utilities API in echarts. It will be in echarts namespace. // Like echarts.util, echarts.graphic @@ -53,7 +54,8 @@ export * as util from './api/util'; export {default as env} from 'zrender/src/core/env'; //////////////// Export for Exension Usage //////////////// -export {default as List} from '../data/List'; +// export {SeriesData}; +export {SeriesData as List}; // TODO: Compatitable with exists echarts-gl code export {default as Model} from '../model/Model'; export {default as Axis} from '../coord/Axis'; diff --git a/src/export/api/helper.ts b/src/export/api/helper.ts index 07d6cef2c7..3d63a7b140 100644 --- a/src/export/api/helper.ts +++ b/src/export/api/helper.ts @@ -22,7 +22,7 @@ */ import * as zrUtil from 'zrender/src/core/util'; -import createListFromArray from '../../chart/helper/createListFromArray'; +import createSeriesData from '../../chart/helper/createSeriesData'; // import createGraphFromNodeEdge from './chart/helper/createGraphFromNodeEdge'; import * as axisHelper from '../../coord/axisHelper'; import {AxisModelCommonMixin} from '../../coord/axisModelCommonMixin'; @@ -43,7 +43,7 @@ import { DisplayState, TextCommonOption } from '../../util/types'; * Create a muti dimension List structure from seriesModel. */ export function createList(seriesModel: SeriesModel) { - return createListFromArray(seriesModel.getSource(), seriesModel); + return createSeriesData(null, seriesModel); } // export function createGraph(seriesModel) { @@ -54,7 +54,7 @@ export function createList(seriesModel: SeriesModel) { export {getLayoutRect}; -export {default as createDimensions} from '../../data/helper/createDimensions'; +export {createDimensions} from '../../data/helper/createDimensions'; export const dataStack = { isDimensionStacked: isDimensionStacked, diff --git a/src/label/labelStyle.ts b/src/label/labelStyle.ts index def6b19f1b..606aa948f1 100644 --- a/src/label/labelStyle.ts +++ b/src/label/labelStyle.ts @@ -38,7 +38,7 @@ import { isFunction, retrieve2, extend, keys, trim } from 'zrender/src/core/util import { SPECIAL_STATES, DISPLAY_STATES } from '../util/states'; import { deprecateReplaceLog } from '../util/log'; import { makeInner, interpolateRawValues } from '../util/model'; -import List from '../data/List'; +import SeriesData from '../data/SeriesData'; import { initProps, updateProps } from '../util/graphic'; import { getECData } from '../util/innerStore'; @@ -688,7 +688,7 @@ export function setLabelValueAnimation( export function animateLabelValue( textEl: ZRText, dataIndex: number, - data: List, + data: SeriesData, animatableModel: Model, labelFetcher: SetLabelStyleOpt['labelFetcher'] ) { diff --git a/src/layout/barGrid.ts b/src/layout/barGrid.ts index edf64007aa..4a7c90887f 100644 --- a/src/layout/barGrid.ts +++ b/src/layout/barGrid.ts @@ -158,9 +158,10 @@ function getValueAxesMinGaps(barSeries: BarSeriesModel[]) { const data = seriesModel.getData(); const key = baseAxis.dim + '_' + baseAxis.index; - const dim = data.mapDimension(baseAxis.dim); - for (let i = 0, cnt = data.count(); i < cnt; ++i) { - const value = data.get(dim, i) as number; + const dimIdx = data.getDimensionIndex(data.mapDimension(baseAxis.dim)); + const store = data.getStore(); + for (let i = 0, cnt = store.count(); i < cnt; ++i) { + const value = store.get(dimIdx, i) as number; if (!axisValues[key]) { // No previous data for the axis axisValues[key] = [value]; @@ -472,14 +473,17 @@ export function layout(seriesType: string, ecModel: GlobalModel) { const valueDim = data.mapDimension(valueAxis.dim); const baseDim = data.mapDimension(baseAxis.dim); - const stacked = isDimensionStacked(data, valueDim /*, baseDim*/); + const stacked = isDimensionStacked(data, valueDim); const isValueAxisH = valueAxis.isHorizontal(); const valueAxisStart = getValueAxisStart(baseAxis, valueAxis, stacked); - for (let idx = 0, len = data.count(); idx < len; idx++) { - const value = data.get(valueDim, idx); - const baseValue = data.get(baseDim, idx) as number; + const store = data.getStore(); + const valueDimIdx = data.getDimensionIndex(valueDim); + const baseDimIdx = data.getDimensionIndex(baseDim); + for (let idx = 0, len = store.count(); idx < len; idx++) { + const value = store.get(valueDimIdx, idx); + const baseValue = store.get(baseDimIdx, idx) as number; const sign = value >= 0 ? 'p' : 'n' as 'p' | 'n'; let baseCoord = valueAxisStart; @@ -563,8 +567,8 @@ export const largeLayout: StageHandler = { const coordLayout = cartesian.master.getRect(); const baseAxis = cartesian.getBaseAxis(); const valueAxis = cartesian.getOtherAxis(baseAxis); - const valueDim = data.mapDimension(valueAxis.dim); - const baseDim = data.mapDimension(baseAxis.dim); + const valueDimI = data.getDimensionIndex(data.mapDimension(valueAxis.dim)); + const baseDimI = data.getDimensionIndex(data.mapDimension(baseAxis.dim)); const valueAxisHorizontal = valueAxis.isHorizontal(); const valueDimIdx = valueAxisHorizontal ? 0 : 1; @@ -586,10 +590,11 @@ export const largeLayout: StageHandler = { const valuePair = []; let pointsOffset = 0; let idxOffset = 0; + const store = data.getStore(); while ((dataIndex = params.next()) != null) { - valuePair[valueDimIdx] = data.get(valueDim, dataIndex); - valuePair[1 - valueDimIdx] = data.get(baseDim, dataIndex); + valuePair[valueDimIdx] = store.get(valueDimI, dataIndex); + valuePair[1 - valueDimIdx] = store.get(baseDimI, dataIndex); coord = cartesian.dataToPoint(valuePair, null); // Data index might not be in order, depends on `progressiveChunkMode`. diff --git a/src/layout/points.ts b/src/layout/points.ts index 8946852ef1..bde24f1535 100644 --- a/src/layout/points.ts +++ b/src/layout/points.ts @@ -56,11 +56,9 @@ export default function pointsLayout(seriesType: string, forceStoreInTypedArray? dims[1] = stackResultDim; } - const dimInfo0 = data.getDimensionInfo(dims[0]); - const dimInfo1 = data.getDimensionInfo(dims[1]); - - const dimIdx0 = dimInfo0 && dimInfo0.index; - const dimIdx1 = dimInfo1 && dimInfo1.index; + const store = data.getStore(); + const dimIdx0 = data.getDimensionIndex(dims[0]); + const dimIdx1 = data.getDimensionIndex(dims[1]); return dimLen && { progress(params, data) { @@ -74,13 +72,13 @@ export default function pointsLayout(seriesType: string, forceStoreInTypedArray? let point; if (dimLen === 1) { - const x = data.getByDimIdx(dimIdx0, i) as ParsedValueNumeric; + const x = store.get(dimIdx0, i) as ParsedValueNumeric; // NOTE: Make sure the second parameter is null to use default strategy. point = coordSys.dataToPoint(x, null, tmpOut); } else { - tmpIn[0] = data.getByDimIdx(dimIdx0, i) as ParsedValueNumeric; - tmpIn[1] = data.getByDimIdx(dimIdx1, i) as ParsedValueNumeric; + tmpIn[0] = store.get(dimIdx0, i) as ParsedValueNumeric; + tmpIn[1] = store.get(dimIdx1, i) as ParsedValueNumeric; // Let coordinate system to handle the NaN data. point = coordSys.dataToPoint(tmpIn, null, tmpOut); } diff --git a/src/model/Series.ts b/src/model/Series.ts index cf49226a2f..a11bc5c6de 100644 --- a/src/model/Series.ts +++ b/src/model/Series.ts @@ -23,7 +23,12 @@ import * as modelUtil from '../util/model'; import { DataHost, DimensionName, StageHandlerProgressParams, SeriesOption, ZRColor, BoxLayoutOptionMixin, - ScaleDataValue, Dictionary, OptionDataItemObject, SeriesDataType, + ScaleDataValue, + Dictionary, + OptionDataItemObject, + SeriesDataType, + SeriesEncodeOptionMixin, + OptionEncodeValue, ColorBy } from '../util/types'; import ComponentModel, { ComponentModelConstructor } from './Component'; @@ -41,7 +46,7 @@ import { CoordinateSystem } from '../coord/CoordinateSystem'; import { ExtendableConstructor, mountExtend, Constructor } from '../util/clazz'; import { PipelineContext, SeriesTaskContext, GeneralTask, OverallTask, SeriesTask } from '../core/Scheduler'; import LegendVisualProvider from '../visual/LegendVisualProvider'; -import List from '../data/List'; +import SeriesData from '../data/SeriesData'; import Axis from '../coord/Axis'; import type { BrushCommonSelectorsForSeries, BrushSelectableArea } from '../component/brush/selector'; import makeStyleMapper from './mixin/makeStyleMapper'; @@ -53,12 +58,12 @@ import {Group} from '../util/graphic'; import {LegendIconParams} from '../component/legend/LegendModel'; const inner = modelUtil.makeInner<{ - data: List - dataBeforeProcessed: List + data: SeriesData + dataBeforeProcessed: SeriesData sourceManager: SourceManager }, SeriesModel>(); -function getSelectionKey(data: List, dataIndex: number): string { +function getSelectionKey(data: SeriesData, dataIndex: number): string { return data.getName(dataIndex) || data.getId(dataIndex); } @@ -106,7 +111,7 @@ interface SeriesModel { */ brushSelector( dataIndex: number, - data: List, + data: SeriesData, selectors: BrushCommonSelectorsForSeries, area: BrushSelectableArea ): boolean; @@ -218,7 +223,7 @@ class SeriesModel extends ComponentMode // dataBeforeProcessed by cloneShallow), cloneShallow will // cause data.graph.data !== data when using // module:echarts/data/Graph or module:echarts/data/Tree. - // See module:echarts/data/helper/linkList + // See module:echarts/data/helper/linkSeriesData // Theoretically, it is unreasonable to call `seriesModel.getData()` in the model // init or merge stage, because the data can be restored. So we do not `restoreData` @@ -312,7 +317,7 @@ class SeriesModel extends ComponentMode * Init a data structure from data related option in series * Must be overriden. */ - getInitialData(option: Opt, ecModel: GlobalModel): List { + getInitialData(option: Opt, ecModel: GlobalModel): SeriesData { return; } @@ -333,23 +338,23 @@ class SeriesModel extends ComponentMode * data in the stream procedure. So we fetch data from upstream * each time `task.perform` called. */ - getData(dataType?: SeriesDataType): List { + getData(dataType?: SeriesDataType): SeriesData { const task = getCurrentTask(this); if (task) { const data = task.context.data; - return (dataType == null ? data : data.getLinkedData(dataType)) as List; + return (dataType == null ? data : data.getLinkedData(dataType)) as SeriesData; } else { // When series is not alive (that may happen when click toolbox // restore or setOption with not merge mode), series data may // be still need to judge animation or something when graphic // elements want to know whether fade out. - return inner(this).data as List; + return inner(this).data as SeriesData; } } getAllData(): ({ - data: List, + data: SeriesData, type?: SeriesDataType })[] { const mainData = this.getData(); @@ -358,7 +363,7 @@ class SeriesModel extends ComponentMode : [{ data: mainData }]; } - setData(data: List): void { + setData(data: SeriesData): void { const task = getCurrentTask(this); if (task) { const context = task.context; @@ -383,14 +388,25 @@ class SeriesModel extends ComponentMode inner(this).data = data; } + getEncode() { + const encode = (this as Model).get('encode', true); + if (encode) { + return zrUtil.createHashMap(encode); + } + } + + getSourceManager(): SourceManager { + return inner(this).sourceManager; + } + getSource(): Source { - return inner(this).sourceManager.getSource(); + return this.getSourceManager().getSource(); } /** * Get data before processed */ - getRawData(): List { + getRawData(): SeriesData { return inner(this).dataBeforeProcessed; } @@ -548,7 +564,6 @@ class SeriesModel extends ComponentMode return true; } - // NOTE: don't support define universalTransition in global option yet. const universalTransitionOpt = this.option.universalTransition; // Quick reject if (!universalTransitionOpt) { @@ -563,7 +578,7 @@ class SeriesModel extends ComponentMode return universalTransitionOpt && universalTransitionOpt.enabled; } - private _innerSelect(data: List, innerDataIndices: number[]) { + private _innerSelect(data: SeriesData, innerDataIndices: number[]) { const selectedMode = this.option.selectedMode; const len = innerDataIndices.length; if (!selectedMode || !len) { @@ -592,7 +607,7 @@ class SeriesModel extends ComponentMode } } - private _initSelectedMapFromData(data: List) { + private _initSelectedMapFromData(data: SeriesData) { // Ignore select info in data if selectedMap exists. // NOTE It's only for legacy usage. edge data is not supported. if (this.option.selectedMap) { @@ -683,13 +698,13 @@ function dataTaskProgress(param: StageHandlerProgressParams, context: SeriesTask } // TODO refactor -function wrapData(data: List, seriesModel: SeriesModel): void { - zrUtil.each([...data.CHANGABLE_METHODS, ...data.DOWNSAMPLE_METHODS], function (methodName) { +function wrapData(data: SeriesData, seriesModel: SeriesModel): void { + zrUtil.each(zrUtil.concatArray(data.CHANGABLE_METHODS, data.DOWNSAMPLE_METHODS), function (methodName) { data.wrapMethod(methodName as any, zrUtil.curry(onDataChange, seriesModel)); }); } -function onDataChange(this: List, seriesModel: SeriesModel, newList: List): List { +function onDataChange(this: SeriesData, seriesModel: SeriesModel, newList: SeriesData): SeriesData { const task = getCurrentTask(seriesModel); if (task) { // Consider case: filter, selectRange diff --git a/src/model/mixin/dataFormat.ts b/src/model/mixin/dataFormat.ts index 4e364537a9..d634a7b8f7 100644 --- a/src/model/mixin/dataFormat.ts +++ b/src/model/mixin/dataFormat.ts @@ -35,7 +35,7 @@ import { } from '../../util/types'; import GlobalModel from '../Global'; import { TooltipMarkupBlockFragment } from '../../component/tooltip/tooltipMarkup'; -import { makePrintable } from '../../util/log'; +import { error, makePrintable } from '../../util/log'; const DIMENSION_LABEL_REG = /\{@(.+?)\}/g; @@ -70,7 +70,7 @@ export class DataFormatMixin { const borderColor = style && style.stroke as ColorString; const mainType = this.mainType; const isSeries = mainType === 'series'; - const userOutput = data.userOutput; + const userOutput = data.userOutput && data.userOutput.get(); return { componentType: mainType, @@ -87,7 +87,7 @@ export class DataFormatMixin { value: rawValue, color: color, borderColor: borderColor, - dimensionNames: userOutput ? userOutput.dimensionNames : null, + dimensionNames: userOutput ? userOutput.fullDimensions : null, encode: userOutput ? userOutput.encode : null, // Param name list for mapping `a`, `b`, `c`, `d`, `e` @@ -149,16 +149,23 @@ export class DataFormatMixin { // Do not support '}' in dim name util have to. return str.replace(DIMENSION_LABEL_REG, function (origin, dimStr: string) { const len = dimStr.length; - const dimLoose: DimensionLoose = (dimStr.charAt(0) === '[' && dimStr.charAt(len - 1) === ']') - ? +dimStr.slice(1, len - 1) // Also support: '[]' => 0 - : dimStr; + + let dimLoose: DimensionLoose = dimStr; + if (dimLoose.charAt(0) === '[' && dimLoose.charAt(len - 1) === ']') { + dimLoose = +dimLoose.slice(1, len - 1); // Also support: '[]' => 0 + if (__DEV__) { + if (isNaN(dimLoose)) { + error(`Invalide label formatter: @${dimStr}, only support @[0], @[1], @[2], ...`); + } + } + } let val = retrieveRawValue(data, dataIndex, dimLoose) as OptionDataValue; if (extendParams && zrUtil.isArray(extendParams.interpolatedValue)) { - const dimInfo = data.getDimensionInfo(dimLoose); - if (dimInfo) { - val = extendParams.interpolatedValue[dimInfo.index]; + const dimIndex = data.getDimensionIndex(dimLoose); + if (dimIndex >= 0) { + val = extendParams.interpolatedValue[dimIndex]; } } diff --git a/src/processor/dataStack.ts b/src/processor/dataStack.ts index e9559cbad2..42f7779ba6 100644 --- a/src/processor/dataStack.ts +++ b/src/processor/dataStack.ts @@ -20,19 +20,22 @@ import {createHashMap, each} from 'zrender/src/core/util'; import GlobalModel from '../model/Global'; import SeriesModel from '../model/Series'; -import { SeriesOption, SeriesStackOptionMixin, DimensionName } from '../util/types'; -import List from '../data/List'; +import { SeriesOption, SeriesStackOptionMixin } from '../util/types'; +import SeriesData, { DataCalculationInfo } from '../data/SeriesData'; import { addSafe } from '../util/number'; -interface StackInfo { - stackedDimension: DimensionName - isStackedByIndex: boolean - stackedByDimension: DimensionName - stackResultDimension: DimensionName - stackedOverDimension: DimensionName - data: List +type StackInfo = Pick< + DataCalculationInfo, + 'stackedDimension' + | 'isStackedByIndex' + | 'stackedByDimension' + | 'stackResultDimension' + | 'stackedOverDimension' +> & { + data: SeriesData seriesModel: SeriesModel -} +}; + // (1) [Caution]: the logic is correct based on the premises: // data processing stage is blocked in stream. // See @@ -87,7 +90,7 @@ function calculateStack(stackInfoList: StackInfo[]) { // Should not write on raw data, because stack series model list changes // depending on legend selection. - const newData = targetData.map(dims, function (v0, v1, dataIndex) { + targetData.modify(dims, function (v0, v1, dataIndex) { let sum = targetData.get(targetStackInfo.stackedDimension, dataIndex) as number; // Consider `connectNulls` of line area, if value is NaN, stackedOver @@ -141,9 +144,5 @@ function calculateStack(stackInfoList: StackInfo[]) { return resultVal; }); - - (targetData.hostModel as SeriesModel).setData(newData); - // Update for consequent calculation - targetStackInfo.data = newData; }); } diff --git a/src/scale/Log.ts b/src/scale/Log.ts index 11a1c84fa7..f8c3258b49 100644 --- a/src/scale/Log.ts +++ b/src/scale/Log.ts @@ -24,7 +24,7 @@ import * as scaleHelper from './helper'; // Use some method of IntervalScale import IntervalScale from './Interval'; -import List from '../data/List'; +import SeriesData from '../data/SeriesData'; import { DimensionName, ScaleTick } from '../util/types'; const scaleProto = Scale.prototype; @@ -118,7 +118,7 @@ class LogScale extends Scale { scaleProto.unionExtent.call(this, extent); } - unionExtentFromData(data: List, dim: DimensionName): void { + unionExtentFromData(data: SeriesData, dim: DimensionName): void { // TODO // filter value that <= 0 this.unionExtent(data.getApproximateExtent(dim)); diff --git a/src/scale/Ordinal.ts b/src/scale/Ordinal.ts index 76f515a0c5..7f23c2d4cc 100644 --- a/src/scale/Ordinal.ts +++ b/src/scale/Ordinal.ts @@ -26,7 +26,7 @@ import Scale from './Scale'; import OrdinalMeta from '../data/OrdinalMeta'; -import List from '../data/List'; +import SeriesData from '../data/SeriesData'; import * as scaleHelper from './helper'; import { OrdinalRawValue, @@ -263,7 +263,7 @@ class OrdinalScale extends Scale { return this._extent[1] - this._extent[0] + 1; } - unionExtentFromData(data: List, dim: DimensionLoose) { + unionExtentFromData(data: SeriesData, dim: DimensionLoose) { this.unionExtent(data.getApproximateExtent(dim)); } diff --git a/src/scale/Scale.ts b/src/scale/Scale.ts index 701013c5d9..afae3f46b7 100644 --- a/src/scale/Scale.ts +++ b/src/scale/Scale.ts @@ -20,7 +20,7 @@ import * as clazzUtil from '../util/clazz'; import { Dictionary } from 'zrender/src/core/types'; -import List from '../data/List'; +import SeriesData from '../data/SeriesData'; import { DimensionName, ScaleDataValue, @@ -91,7 +91,7 @@ abstract class Scale = Dictionary> /** * Set extent from data */ - unionExtentFromData(data: List, dim: DimensionName | DimensionLoose): void { + unionExtentFromData(data: SeriesData, dim: DimensionName | DimensionLoose): void { this.unionExtent(data.getApproximateExtent(dim)); } diff --git a/src/util/model.ts b/src/util/model.ts index b640ec6017..9f78c1583e 100644 --- a/src/util/model.ts +++ b/src/util/model.ts @@ -32,7 +32,7 @@ import { import env from 'zrender/src/core/env'; import GlobalModel from '../model/Global'; import ComponentModel, {ComponentModelConstructor} from '../model/Component'; -import List from '../data/List'; +import SeriesData from '../data/SeriesData'; import { ComponentOption, ComponentMainType, @@ -686,7 +686,7 @@ export function compressBatches( * each of which can be Array or primary type. * @return dataIndex If not found, return undefined/null. */ -export function queryDataIndex(data: List, payload: Payload & { +export function queryDataIndex(data: SeriesData, payload: Payload & { dataIndexInside?: number | number[] dataIndex?: number | number[] name?: string | string[] @@ -1032,7 +1032,7 @@ export function groupData( * Other cases do not supported. */ export function interpolateRawValues( - data: List, + data: SeriesData, precision: number | 'auto', sourceValue: InterpolatableValue, targetValue: InterpolatableValue, @@ -1070,7 +1070,7 @@ export function interpolateRawValues( for (let i = 0; i < length; ++i) { const info = data.getDimensionInfo(i); // Don't interpolate ordinal dims - if (info.type === 'ordinal') { + if (info && info.type === 'ordinal') { // In init, there is no `sourceValue`, but should better not to get undefined result. interpolated[i] = (percent < 1 && leftArr ? leftArr : rightArr)[i] as number; } diff --git a/src/util/states.ts b/src/util/states.ts index aa78811d33..ac5e01c890 100644 --- a/src/util/states.ts +++ b/src/util/states.ts @@ -41,7 +41,7 @@ import { import { extend, indexOf, isArrayLike, isObject, keys, isArray, each } from 'zrender/src/core/util'; import { getECData } from './innerStore'; import * as colorTool from 'zrender/src/tool/color'; -import List from '../data/List'; +import SeriesData from '../data/SeriesData'; import SeriesModel from '../model/Series'; import { CoordinateSystemMaster, CoordinateSystem } from '../coord/CoordinateSystem'; import { queryDataIndex, makeInner } from './model'; @@ -422,7 +422,7 @@ export function blurSeries( const ecModel = api.getModel(); blurScope = blurScope || 'coordinateSystem'; - function leaveBlurOfIndices(data: List, dataIndices: ArrayLike) { + function leaveBlurOfIndices(data: SeriesData, dataIndices: ArrayLike) { for (let i = 0; i < dataIndices.length; i++) { const itemEl = data.getItemGraphicEl(dataIndices[i]); itemEl && leaveBlur(itemEl); diff --git a/src/util/types.ts b/src/util/types.ts index 82a691fee0..ab1a6b1b2b 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -33,7 +33,7 @@ import ExtensionAPI from '../core/ExtensionAPI'; import SeriesModel from '../model/Series'; import { createHashMap, HashMap } from 'zrender/src/core/util'; import { TaskPlanCallbackReturn, TaskProgressParams } from '../core/task'; -import List, {ListDimensionType} from '../data/List'; +import SeriesData from '../data/SeriesData'; import { Dictionary, ElementEventName, ImageLike, TextAlign, TextVerticalAlign } from 'zrender/src/core/types'; import { PatternObject } from 'zrender/src/graphic/Pattern'; import { TooltipMarker } from './format'; @@ -47,6 +47,8 @@ import { ImageStyleProps } from 'zrender/src/graphic/Image'; import ZRText, { TextStyleProps } from 'zrender/src/graphic/Text'; import { Source } from '../data/Source'; import Model from '../model/Model'; +import { DataStoreDimensionType } from '../data/DataStore'; +import { DimensionUserOuputEncode } from '../data/helper/dimensionHelper'; @@ -57,6 +59,7 @@ import Model from '../model/Model'; export {Dictionary}; export type RendererType = 'canvas' | 'svg'; +export type NullUndefined = null | undefined; export type LayoutOrient = 'vertical' | 'horizontal'; export type HorizontalAlign = 'left' | 'center' | 'right'; @@ -128,7 +131,7 @@ export interface ECElement extends Element { } export interface DataHost { - getData(dataType?: SeriesDataType): List; + getData(dataType?: SeriesDataType): SeriesData; } export interface DataModel extends Model, DataHost, DataFormatMixin {} @@ -300,8 +303,8 @@ export interface StageHandlerInternal extends StageHandler { export type StageHandlerProgressParams = TaskProgressParams; export interface StageHandlerProgressExecutor { - dataEach?: (data: List, idx: number) => void; - progress?: (params: StageHandlerProgressParams, data: List) => void; + dataEach?: (data: SeriesData, idx: number) => void; + progress?: (params: StageHandlerProgressParams, data: SeriesData) => void; } export type StageHandlerPlanReturn = TaskPlanCallbackReturn; export interface StageHandlerPlan { @@ -355,11 +358,11 @@ export type OrdinalSortInfo = { /** * `OptionDataValue` is the primitive value in `series.data` or `dataset.source`. * `OptionDataValue` are parsed (see `src/data/helper/dataValueHelper.parseDataValue`) - * into `ParsedValue` and stored into `data/List` storage. + * into `ParsedValue` and stored into `data/SeriesData` storage. * Note: * (1) The term "parse" does not mean `src/scale/Scale['parse']`. * (2) If a category dimension is not mapped to any axis, its raw value will NOT be - * parsed to `OrdinalNumber` but keep the original `OrdinalRawValue` in `src/data/List` storage. + * parsed to `OrdinalNumber` but keep the original `OrdinalRawValue` in `src/data/SeriesData` storage. */ export type ParsedValue = ParsedValueNumeric | OrdinalRawValue; export type ParsedValueNumeric = number | OrdinalNumber; @@ -414,7 +417,7 @@ export type DimensionIndex = number; export type DimensionIndexLoose = DimensionIndex | string; export type DimensionName = string; export type DimensionLoose = DimensionName | DimensionIndexLoose; -export type DimensionType = ListDimensionType; +export type DimensionType = DataStoreDimensionType; export const VISUAL_DIMENSIONS = createHashMap([ 'tooltip', 'label', 'itemName', 'itemId', 'itemGroupId', 'seriesName' @@ -428,15 +431,12 @@ export interface DataVisualDimensions { label?: DimensionIndex; itemName?: DimensionIndex; itemId?: DimensionIndex; - // Group id is used for linking the aggregate relationship between two set of data. - // Which is useful in prepresenting the transition key of drilldown/up animation. - // Or hover linking. itemGroupId?: DimensionIndex; seriesName?: DimensionIndex; } export type DimensionDefinition = { - type?: ListDimensionType, + type?: DataStoreDimensionType, name?: DimensionName, displayName?: string }; @@ -650,6 +650,11 @@ export interface OptionEncodeVisualDimensions { itemId?: OptionEncodeValue; seriesName?: OptionEncodeValue; // Notice: `value` is coordDim, not nonCoordDim. + + // Group id is used for linking the aggregate relationship between two set of data. + // Which is useful in prepresenting the transition key of drilldown/up animation. + // Or hover linking. + itemGroupId?: OptionEncodeValue; } export interface OptionEncode extends OptionEncodeVisualDimensions { [coordDim: string]: OptionEncodeValue | undefined @@ -688,16 +693,6 @@ export interface CallbackDataParams { $vars: string[]; } export type InterpolatableValue = ParsedValue | ParsedValue[]; -export type DimensionUserOuputEncode = { - [coordOrVisualDimName: string]: - // index: coordDimIndex, value: dataDimIndex - DimensionIndex[] -}; -export type DimensionUserOuput = { - // The same as `data.dimensions` - dimensionNames: DimensionName[] - encode: DimensionUserOuputEncode -}; export type DecalDashArrayX = number | (number | number[])[]; export type DecalDashArrayY = number | number[]; diff --git a/src/view/Chart.ts b/src/view/Chart.ts index eb9db4a2d1..8f32838f3f 100644 --- a/src/view/Chart.ts +++ b/src/view/Chart.ts @@ -34,7 +34,7 @@ import { StageHandlerPlanReturn, DisplayState, StageHandlerProgressParams, ECElementEvent } from '../util/types'; import { SeriesTaskContext, SeriesTask } from '../core/Scheduler'; -import List from '../data/List'; +import SeriesData from '../data/SeriesData'; const inner = modelUtil.makeInner<{ updateMethod: keyof ChartView @@ -206,7 +206,7 @@ function elSetState(el: Element, state: DisplayState, highlightDigit: number) { } } -function toggleHighlight(data: List, payload: Payload, state: DisplayState) { +function toggleHighlight(data: SeriesData, payload: Payload, state: DisplayState) { const dataIndex = modelUtil.queryDataIndex(data, payload); const highlightDigit = (payload && payload.highlightKey != null) diff --git a/src/visual/LegendVisualProvider.ts b/src/visual/LegendVisualProvider.ts index c62297eadd..c4ff79a3f8 100644 --- a/src/visual/LegendVisualProvider.ts +++ b/src/visual/LegendVisualProvider.ts @@ -1,4 +1,4 @@ -import List from '../data/List'; +import SeriesData from '../data/SeriesData'; /* * Licensed to the Apache Software Foundation (ASF) under one @@ -26,14 +26,14 @@ import List from '../data/List'; */ class LegendVisualProvider { - private _getDataWithEncodedVisual: () => List; - private _getRawData: () => List; + private _getDataWithEncodedVisual: () => SeriesData; + private _getRawData: () => SeriesData; constructor( // Function to get data after filtered. It stores all the encoding info - getDataWithEncodedVisual: () => List, + getDataWithEncodedVisual: () => SeriesData, // Function to get raw data before filtered. - getRawData: () => List + getRawData: () => SeriesData ) { this._getDataWithEncodedVisual = getDataWithEncodedVisual; this._getRawData = getRawData; diff --git a/src/visual/aria.ts b/src/visual/aria.ts index baa1e8fdd1..9d2afa691f 100644 --- a/src/visual/aria.ts +++ b/src/visual/aria.ts @@ -24,7 +24,7 @@ import GlobalModel from '../model/Global'; import Model from '../model/Model'; import SeriesModel from '../model/Series'; import {makeInner} from '../util/model'; -import {Dictionary, DecalObject, InnerDecalObject, AriaOption, SeriesOption} from '../util/types'; +import {Dictionary, DecalObject, InnerDecalObject, AriaOption} from '../util/types'; import {LocaleOption} from '../core/locale'; import { getDecalFromPalette } from '../model/mixin/palette'; import type {TitleOption} from '../component/title/install'; diff --git a/src/visual/commonVisualTypes.ts b/src/visual/commonVisualTypes.ts index e279a4cfd2..7b812141ad 100644 --- a/src/visual/commonVisualTypes.ts +++ b/src/visual/commonVisualTypes.ts @@ -17,7 +17,7 @@ * under the License. */ -import { DefaultDataVisual } from '../data/List'; +import { DefaultDataVisual } from '../data/SeriesData'; export interface LineDataVisual extends DefaultDataVisual { fromSymbol: string diff --git a/src/visual/helper.ts b/src/visual/helper.ts index e419a9a524..5c60f6f99d 100644 --- a/src/visual/helper.ts +++ b/src/visual/helper.ts @@ -24,10 +24,10 @@ * In the List module storage: * 'style', 'symbol', 'symbolSize'... */ -import List from '../data/List'; +import SeriesData from '../data/SeriesData'; -export function getItemVisualFromData(data: List, dataIndex: number, key: string) { +export function getItemVisualFromData(data: SeriesData, dataIndex: number, key: string) { switch (key) { case 'color': const style = data.getItemVisual(dataIndex, 'style'); @@ -45,7 +45,7 @@ export function getItemVisualFromData(data: List, dataIndex: number, key: string } } -export function getVisualFromData(data: List, key: string) { +export function getVisualFromData(data: SeriesData, key: string) { switch (key) { case 'color': const style = data.getVisual('style'); @@ -63,7 +63,7 @@ export function getVisualFromData(data: List, key: string) { } } -export function setItemVisualFromData(data: List, dataIndex: number, key: string, value: any) { +export function setItemVisualFromData(data: SeriesData, dataIndex: number, key: string, value: any) { switch (key) { case 'color': // Make sure not sharing style object. diff --git a/src/visual/symbol.ts b/src/visual/symbol.ts index 1e17689c5a..019d536177 100644 --- a/src/visual/symbol.ts +++ b/src/visual/symbol.ts @@ -28,7 +28,7 @@ import { SymbolRotateCallback, SymbolOffsetCallback } from '../util/types'; -import List from '../data/List'; +import SeriesData from '../data/SeriesData'; import SeriesModel from '../model/Series'; import GlobalModel from '../model/Global'; @@ -91,7 +91,7 @@ const seriesSymbolTask: StageHandler = { return; } - function dataEach(data: List, idx: number) { + function dataEach(data: SeriesData, idx: number) { const rawValue = seriesModel.getRawValue(idx); const params = seriesModel.getDataParams(idx); hasSymbolTypeCallback && data.setItemVisual( @@ -133,7 +133,7 @@ const dataSymbolTask: StageHandler = { const data = seriesModel.getData(); - function dataEach(data: List, idx: number) { + function dataEach(data: SeriesData, idx: number) { const itemModel = data.getItemModel(idx); const itemSymbolType = itemModel.getShallow('symbol', true); const itemSymbolSize = itemModel.getShallow('symbolSize', true); diff --git a/src/visual/visualSolution.ts b/src/visual/visualSolution.ts index accd72dd0f..33fffb5ee8 100644 --- a/src/visual/visualSolution.ts +++ b/src/visual/visualSolution.ts @@ -28,9 +28,10 @@ import { BuiltinVisualProperty, ParsedValue, DimensionLoose, - StageHandlerProgressExecutor + StageHandlerProgressExecutor, + DimensionIndex } from '../util/types'; -import List from '../data/List'; +import SeriesData from '../data/SeriesData'; import { getItemVisualFromData, setItemVisualFromData } from './helper'; const each = zrUtil.each; @@ -135,7 +136,7 @@ export function replaceVisualOption( export function applyVisual( stateList: readonly VisualState[], visualMappings: VisualMappingCollection, - data: List, + data: SeriesData, getValueState: (this: Scope, valueOrIndex: ParsedValue | number) => VisualState, scope?: Scope, dimension?: DimensionLoose @@ -209,9 +210,9 @@ export function incrementalApplyVisual( return { progress: function progress(params, data) { - let dimName: string; + let dimIndex: DimensionIndex; if (dim != null) { - dimName = data.getDimension(dim); + dimIndex = data.getDimensionIndex(dim); } function getVisual(key: string) { @@ -223,6 +224,7 @@ export function incrementalApplyVisual( } let dataIndex: number; + const store = data.getStore(); while ((dataIndex = params.next()) != null) { const rawDataItem = data.getRawDataItem(dataIndex); @@ -233,7 +235,7 @@ export function incrementalApplyVisual( } const value = dim != null - ? data.get(dimName, dataIndex) + ? store.get(dimIndex, dataIndex) : dataIndex; const valueState = getValueState(value); diff --git a/test/bar-stack.html b/test/bar-stack.html index 4c47b61ac3..51ce177f2f 100644 --- a/test/bar-stack.html +++ b/test/bar-stack.html @@ -35,78 +35,10 @@ -
- - - - - +
+
+
@@ -510,5 +442,289 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/test/dataset-case.html b/test/dataset-case.html new file mode 100644 index 0000000000..577401433e --- /dev/null +++ b/test/dataset-case.html @@ -0,0 +1,746 @@ + + + + + + + + + + + + + + + + + + + + + +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/test/dataset-performance.html b/test/dataset-performance.html new file mode 100644 index 0000000000..25e12f41eb --- /dev/null +++ b/test/dataset-performance.html @@ -0,0 +1,220 @@ + + + + + + + + + + + + + + + + + + + + + +
+
+ + + + + + + + + + + + + + diff --git a/test/lib/reset.css b/test/lib/reset.css index ca257f4832..6a2ddce4ed 100644 --- a/test/lib/reset.css +++ b/test/lib/reset.css @@ -82,6 +82,7 @@ pre.test-print-object { font-family: Menlo, Monaco, 'Courier New', monospace; } .test-chart { + position: relative; height: 400px; } diff --git a/test/lib/testHelper.js b/test/lib/testHelper.js index e0b8f28f8e..7d4908475c 100644 --- a/test/lib/testHelper.js +++ b/test/lib/testHelper.js @@ -312,6 +312,10 @@ * @param checkFn {Function} param: a function `assert`. */ testHelper.printAssert = function (chartOrDomId, checkerFn) { + if (!chartOrDomId) { + return; + } + var hostDOMEl; var chart; if (typeof chartOrDomId === 'string') { diff --git a/test/runTest/actions/__meta__.json b/test/runTest/actions/__meta__.json index 2a8d5a552c..28341e7bd1 100644 --- a/test/runTest/actions/__meta__.json +++ b/test/runTest/actions/__meta__.json @@ -26,7 +26,7 @@ "bar-polar-null-data-radial": 1, "bar-polar-stack": 1, "bar-race2": 2, - "bar-stack": 1, + "bar-stack": 3, "bar-start": 1, "bar-width": 3, "bar2": 3, @@ -61,6 +61,7 @@ "custom-shape-morphing": 1, "custom-text-content": 6, "dataSelect": 7, + "dataset-case": 6, "dataZoom-action": 4, "dataZoom-axes": 4, "dataZoom-axis-type": 3, diff --git a/test/runTest/actions/bar-stack.json b/test/runTest/actions/bar-stack.json index 575ec17a80..9ee7d3da98 100644 --- a/test/runTest/actions/bar-stack.json +++ b/test/runTest/actions/bar-stack.json @@ -1 +1 @@ -[{"name":"Action 1","ops":[{"type":"mousedown","time":307,"x":645,"y":74},{"type":"mouseup","time":381,"x":645,"y":74},{"time":382,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":476,"x":645,"y":75},{"type":"mousemove","time":684,"x":641,"y":93},{"type":"mousedown","time":734,"x":641,"y":93},{"type":"mouseup","time":818,"x":641,"y":93},{"time":819,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":884,"x":642,"y":97},{"type":"mousemove","time":1084,"x":643,"y":117},{"type":"mousedown","time":1184,"x":643,"y":117},{"type":"mouseup","time":1283,"x":643,"y":117},{"time":1284,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":1341,"x":643,"y":117},{"type":"mousemove","time":1542,"x":645,"y":137},{"type":"mousemove","time":1751,"x":645,"y":140},{"type":"mousedown","time":1851,"x":645,"y":140},{"type":"mouseup","time":1926,"x":645,"y":140},{"time":1927,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":1952,"x":645,"y":140},{"type":"mousemove","time":2152,"x":646,"y":169},{"type":"mousedown","time":2324,"x":646,"y":169},{"type":"mouseup","time":2418,"x":646,"y":169},{"time":2419,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2475,"x":647,"y":170},{"type":"mousemove","time":2675,"x":647,"y":186},{"type":"mousedown","time":2826,"x":647,"y":187},{"type":"mouseup","time":2895,"x":647,"y":187},{"time":2896,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2935,"x":647,"y":190},{"type":"mousemove","time":3138,"x":644,"y":217},{"type":"mousedown","time":3368,"x":644,"y":217},{"type":"mouseup","time":3456,"x":644,"y":217},{"time":3457,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3509,"x":644,"y":218},{"type":"mousemove","time":3709,"x":647,"y":232},{"type":"mousedown","time":3772,"x":647,"y":232},{"type":"mouseup","time":3850,"x":647,"y":232},{"time":3851,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3876,"x":647,"y":233},{"type":"mousemove","time":4077,"x":644,"y":263},{"type":"mousedown","time":4192,"x":644,"y":263},{"type":"mouseup","time":4274,"x":644,"y":263},{"time":4275,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4297,"x":644,"y":264},{"type":"mousemove","time":4504,"x":643,"y":286},{"type":"mousedown","time":4744,"x":643,"y":286},{"type":"mousemove","time":4791,"x":643,"y":286},{"type":"mouseup","time":4840,"x":643,"y":286},{"time":4841,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4991,"x":642,"y":304},{"type":"mousemove","time":5191,"x":642,"y":313},{"type":"mousedown","time":5239,"x":642,"y":313},{"type":"mouseup","time":5476,"x":642,"y":313},{"time":5477,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5492,"x":642,"y":325},{"type":"mousemove","time":5692,"x":646,"y":329},{"type":"mousemove","time":5892,"x":649,"y":314},{"type":"mousemove","time":6092,"x":643,"y":325},{"type":"mousemove","time":6292,"x":643,"y":329},{"type":"mousedown","time":6310,"x":643,"y":329},{"type":"mouseup","time":6359,"x":643,"y":329},{"time":6360,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6493,"x":641,"y":344},{"type":"mousemove","time":6694,"x":640,"y":352},{"type":"mousedown","time":6810,"x":640,"y":352},{"type":"mousemove","time":6879,"x":640,"y":352},{"type":"mouseup","time":6888,"x":640,"y":352},{"time":6889,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7081,"x":640,"y":353},{"type":"mousedown","time":7694,"x":640,"y":353},{"type":"mouseup","time":7792,"x":640,"y":353},{"time":7793,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7806,"x":640,"y":353},{"type":"mousemove","time":8010,"x":641,"y":339},{"type":"mousemove","time":8210,"x":642,"y":327},{"type":"mousedown","time":8459,"x":642,"y":327},{"type":"mouseup","time":8535,"x":642,"y":327},{"time":8536,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8595,"x":642,"y":327},{"type":"mousemove","time":8795,"x":646,"y":301},{"type":"mousemove","time":8995,"x":646,"y":248},{"type":"mousemove","time":9201,"x":621,"y":84},{"type":"mousemove","time":9412,"x":640,"y":76},{"type":"mousedown","time":9595,"x":643,"y":74},{"type":"mousemove","time":9613,"x":643,"y":74},{"type":"mouseup","time":9668,"x":643,"y":74},{"time":9669,"delay":400,"type":"screenshot-auto"}],"scrollY":0,"scrollX":0,"timestamp":1568018408672}] \ No newline at end of file +[{"name":"Action 1","ops":[{"type":"mousedown","time":307,"x":645,"y":74},{"type":"mouseup","time":381,"x":645,"y":74},{"time":382,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":476,"x":645,"y":75},{"type":"mousemove","time":684,"x":641,"y":93},{"type":"mousedown","time":734,"x":641,"y":93},{"type":"mouseup","time":818,"x":641,"y":93},{"time":819,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":884,"x":642,"y":97},{"type":"mousemove","time":1084,"x":643,"y":117},{"type":"mousedown","time":1184,"x":643,"y":117},{"type":"mouseup","time":1283,"x":643,"y":117},{"time":1284,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":1341,"x":643,"y":117},{"type":"mousemove","time":1542,"x":645,"y":137},{"type":"mousemove","time":1751,"x":645,"y":140},{"type":"mousedown","time":1851,"x":645,"y":140},{"type":"mouseup","time":1926,"x":645,"y":140},{"time":1927,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":1952,"x":645,"y":140},{"type":"mousemove","time":2152,"x":646,"y":169},{"type":"mousedown","time":2324,"x":646,"y":169},{"type":"mouseup","time":2418,"x":646,"y":169},{"time":2419,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2475,"x":647,"y":170},{"type":"mousemove","time":2675,"x":647,"y":186},{"type":"mousedown","time":2826,"x":647,"y":187},{"type":"mouseup","time":2895,"x":647,"y":187},{"time":2896,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2935,"x":647,"y":190},{"type":"mousemove","time":3138,"x":644,"y":217},{"type":"mousedown","time":3368,"x":644,"y":217},{"type":"mouseup","time":3456,"x":644,"y":217},{"time":3457,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3509,"x":644,"y":218},{"type":"mousemove","time":3709,"x":647,"y":232},{"type":"mousedown","time":3772,"x":647,"y":232},{"type":"mouseup","time":3850,"x":647,"y":232},{"time":3851,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3876,"x":647,"y":233},{"type":"mousemove","time":4077,"x":644,"y":263},{"type":"mousedown","time":4192,"x":644,"y":263},{"type":"mouseup","time":4274,"x":644,"y":263},{"time":4275,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4297,"x":644,"y":264},{"type":"mousemove","time":4504,"x":643,"y":286},{"type":"mousedown","time":4744,"x":643,"y":286},{"type":"mousemove","time":4791,"x":643,"y":286},{"type":"mouseup","time":4840,"x":643,"y":286},{"time":4841,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4991,"x":642,"y":304},{"type":"mousemove","time":5191,"x":642,"y":313},{"type":"mousedown","time":5239,"x":642,"y":313},{"type":"mouseup","time":5476,"x":642,"y":313},{"time":5477,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5492,"x":642,"y":325},{"type":"mousemove","time":5692,"x":646,"y":329},{"type":"mousemove","time":5892,"x":649,"y":314},{"type":"mousemove","time":6092,"x":643,"y":325},{"type":"mousemove","time":6292,"x":643,"y":329},{"type":"mousedown","time":6310,"x":643,"y":329},{"type":"mouseup","time":6359,"x":643,"y":329},{"time":6360,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6493,"x":641,"y":344},{"type":"mousemove","time":6694,"x":640,"y":352},{"type":"mousedown","time":6810,"x":640,"y":352},{"type":"mousemove","time":6879,"x":640,"y":352},{"type":"mouseup","time":6888,"x":640,"y":352},{"time":6889,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7081,"x":640,"y":353},{"type":"mousedown","time":7694,"x":640,"y":353},{"type":"mouseup","time":7792,"x":640,"y":353},{"time":7793,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7806,"x":640,"y":353},{"type":"mousemove","time":8010,"x":641,"y":339},{"type":"mousemove","time":8210,"x":642,"y":327},{"type":"mousedown","time":8459,"x":642,"y":327},{"type":"mouseup","time":8535,"x":642,"y":327},{"time":8536,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8595,"x":642,"y":327},{"type":"mousemove","time":8795,"x":646,"y":301},{"type":"mousemove","time":8995,"x":646,"y":248},{"type":"mousemove","time":9201,"x":621,"y":84},{"type":"mousemove","time":9412,"x":640,"y":76},{"type":"mousedown","time":9595,"x":643,"y":74},{"type":"mousemove","time":9613,"x":643,"y":74},{"type":"mouseup","time":9668,"x":643,"y":74},{"time":9669,"delay":400,"type":"screenshot-auto"}],"scrollY":0,"scrollX":0,"timestamp":1568018408672},{"name":"Action 2","ops":[{"type":"mousemove","time":176,"x":607,"y":352},{"type":"mousemove","time":377,"x":611,"y":239},{"type":"mousemove","time":585,"x":304,"y":122},{"type":"mousemove","time":743,"x":299,"y":121},{"type":"mousemove","time":943,"x":101,"y":125},{"type":"mousemove","time":1149,"x":70,"y":138},{"type":"mousedown","time":1194,"x":67,"y":138},{"type":"mouseup","time":1333,"x":67,"y":138},{"time":1334,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":1371,"x":67,"y":138},{"type":"mousemove","time":1559,"x":68,"y":138},{"type":"mousemove","time":1759,"x":97,"y":140},{"type":"mousedown","time":1900,"x":112,"y":140},{"type":"mousemove","time":1959,"x":112,"y":140},{"type":"mouseup","time":2032,"x":112,"y":140},{"time":2033,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2125,"x":111,"y":140},{"type":"mousemove","time":2325,"x":65,"y":140},{"type":"mousemove","time":2525,"x":51,"y":140},{"type":"mousemove","time":2734,"x":51,"y":140},{"type":"mousemove","time":2759,"x":51,"y":140},{"type":"mousemove","time":2960,"x":303,"y":158},{"type":"mousemove","time":3172,"x":347,"y":181},{"type":"mousedown","time":3318,"x":355,"y":183},{"type":"mousemove","time":3384,"x":355,"y":182},{"type":"mouseup","time":3450,"x":355,"y":182},{"time":3451,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3692,"x":355,"y":182},{"type":"mousemove","time":3892,"x":390,"y":173},{"type":"mousedown","time":3951,"x":391,"y":174},{"type":"mouseup","time":4100,"x":391,"y":174},{"time":4101,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4124,"x":391,"y":174},{"type":"mousemove","time":4527,"x":393,"y":174},{"type":"mousemove","time":4726,"x":422,"y":162},{"type":"mousemove","time":4928,"x":409,"y":175},{"type":"mousemove","time":5130,"x":355,"y":175},{"type":"mousedown","time":5197,"x":353,"y":174},{"type":"mouseup","time":5329,"x":353,"y":174},{"time":5330,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5357,"x":353,"y":174},{"type":"mousemove","time":5457,"x":353,"y":174},{"type":"mousemove","time":5665,"x":435,"y":170},{"type":"mousedown","time":5880,"x":458,"y":169},{"type":"mousemove","time":5899,"x":458,"y":169},{"type":"mouseup","time":6004,"x":458,"y":169},{"time":6005,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6121,"x":457,"y":169},{"type":"mousemove","time":6321,"x":430,"y":169},{"type":"mousedown","time":6497,"x":402,"y":168},{"type":"mousemove","time":6532,"x":401,"y":168},{"type":"mouseup","time":6613,"x":401,"y":168},{"time":6614,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6737,"x":393,"y":168},{"type":"mousemove","time":6937,"x":366,"y":168},{"type":"mousemove","time":7138,"x":419,"y":168},{"type":"mousemove","time":7347,"x":443,"y":168},{"type":"mousedown","time":7369,"x":444,"y":168},{"type":"mouseup","time":7487,"x":444,"y":168},{"time":7488,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7563,"x":444,"y":168},{"type":"mousemove","time":7604,"x":444,"y":169},{"type":"mousemove","time":7804,"x":415,"y":169},{"type":"mousemove","time":8004,"x":405,"y":168},{"type":"mousemove","time":8206,"x":366,"y":174},{"type":"mousemove","time":8406,"x":359,"y":175},{"type":"mousemove","time":8607,"x":345,"y":176},{"type":"mousemove","time":8813,"x":343,"y":176},{"type":"mousemove","time":9037,"x":343,"y":175},{"type":"mousemove","time":9337,"x":343,"y":174},{"type":"mousemove","time":9547,"x":343,"y":171},{"type":"mousemove","time":12239,"x":343,"y":171}],"scrollY":493,"scrollX":0,"timestamp":1629615854239},{"name":"Action 3","ops":[{"type":"mousemove","time":120,"x":110,"y":154},{"type":"mousemove","time":325,"x":71,"y":167},{"type":"mousemove","time":533,"x":47,"y":198},{"type":"mousemove","time":733,"x":48,"y":196},{"type":"mousemove","time":933,"x":49,"y":193},{"type":"mousedown","time":1090,"x":49,"y":193},{"type":"mouseup","time":1223,"x":49,"y":193},{"time":1224,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":1400,"x":50,"y":193},{"type":"mousemove","time":1609,"x":115,"y":194},{"type":"mousedown","time":1779,"x":125,"y":199},{"type":"mousemove","time":1823,"x":125,"y":199},{"type":"mouseup","time":1941,"x":125,"y":199},{"time":1942,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2149,"x":125,"y":199},{"type":"mousemove","time":2349,"x":101,"y":201},{"type":"mousemove","time":2549,"x":75,"y":200},{"type":"mousemove","time":2750,"x":66,"y":197},{"type":"mousemove","time":2959,"x":63,"y":195},{"type":"mousedown","time":3792,"x":63,"y":195},{"type":"mouseup","time":3880,"x":63,"y":195},{"time":3881,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3999,"x":65,"y":195},{"type":"mousemove","time":4199,"x":268,"y":230},{"type":"mousemove","time":4399,"x":341,"y":245},{"type":"mousemove","time":4599,"x":327,"y":223},{"type":"mousedown","time":4761,"x":355,"y":217},{"type":"mousemove","time":4810,"x":355,"y":217},{"type":"mouseup","time":4843,"x":355,"y":217},{"time":4844,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5083,"x":359,"y":217},{"type":"mousedown","time":5276,"x":403,"y":219},{"type":"mousemove","time":5298,"x":403,"y":219},{"type":"mouseup","time":5361,"x":403,"y":219},{"time":5362,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5499,"x":430,"y":219},{"type":"mousedown","time":5659,"x":449,"y":220},{"type":"mousemove","time":5699,"x":449,"y":220},{"type":"mouseup","time":5796,"x":449,"y":220},{"time":5797,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5883,"x":446,"y":220},{"type":"mousemove","time":6083,"x":412,"y":220},{"type":"mousemove","time":6283,"x":410,"y":221},{"type":"mousedown","time":6311,"x":410,"y":221},{"type":"mouseup","time":6460,"x":410,"y":221},{"time":6461,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6570,"x":412,"y":221},{"type":"mousemove","time":6776,"x":458,"y":221},{"type":"mousedown","time":6977,"x":449,"y":221},{"type":"mousemove","time":6986,"x":448,"y":221},{"type":"mouseup","time":7065,"x":448,"y":221},{"time":7066,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7202,"x":429,"y":221},{"type":"mousemove","time":7414,"x":357,"y":219},{"type":"mousedown","time":7498,"x":344,"y":221},{"type":"mouseup","time":7593,"x":344,"y":221},{"time":7594,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7633,"x":344,"y":221},{"type":"mousemove","time":7824,"x":337,"y":221},{"type":"mousemove","time":8026,"x":123,"y":181},{"type":"mousemove","time":8243,"x":122,"y":179},{"type":"mousemove","time":8457,"x":118,"y":195},{"type":"mousedown","time":8523,"x":118,"y":195},{"type":"mouseup","time":8663,"x":118,"y":195},{"time":8664,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8696,"x":118,"y":195},{"type":"mousemove","time":8822,"x":117,"y":195},{"type":"mousemove","time":9024,"x":78,"y":197},{"type":"mousemove","time":9230,"x":57,"y":199},{"type":"mousemove","time":9447,"x":56,"y":199},{"type":"mousemove","time":9872,"x":56,"y":199}],"scrollY":845,"scrollX":0,"timestamp":1629615870890}] \ No newline at end of file diff --git a/test/runTest/actions/dataset-case.json b/test/runTest/actions/dataset-case.json new file mode 100644 index 0000000000..23e8b4027f --- /dev/null +++ b/test/runTest/actions/dataset-case.json @@ -0,0 +1 @@ +[{"name":"Action 1","ops":[{"type":"mousemove","time":420,"x":111,"y":360},{"type":"mousemove","time":628,"x":77,"y":398},{"type":"mousemove","time":836,"x":66,"y":390},{"type":"mousedown","time":868,"x":66,"y":390},{"type":"mouseup","time":1012,"x":66,"y":390},{"time":1013,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":1037,"x":66,"y":390},{"type":"mousemove","time":1120,"x":66,"y":392},{"type":"mousemove","time":1328,"x":66,"y":412},{"type":"mousedown","time":1380,"x":66,"y":413},{"type":"mouseup","time":1500,"x":66,"y":413},{"time":1501,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":1544,"x":66,"y":413},{"type":"mousemove","time":2003,"x":66,"y":417},{"type":"mousemove","time":2204,"x":61,"y":459},{"type":"mousemove","time":2412,"x":60,"y":471},{"type":"mousedown","time":2628,"x":59,"y":468},{"type":"mousemove","time":2644,"x":59,"y":468},{"type":"mouseup","time":2695,"x":59,"y":468},{"time":2696,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2936,"x":59,"y":467},{"type":"mousemove","time":3136,"x":61,"y":439},{"type":"mousedown","time":3201,"x":61,"y":438},{"type":"mouseup","time":3312,"x":61,"y":438},{"time":3313,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3346,"x":61,"y":438},{"type":"mousemove","time":3403,"x":61,"y":439},{"type":"mousemove","time":3610,"x":61,"y":493},{"type":"mousedown","time":3728,"x":61,"y":495},{"type":"mousemove","time":3829,"x":61,"y":495},{"type":"mouseup","time":3845,"x":61,"y":495},{"time":3846,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4086,"x":61,"y":495},{"type":"mousedown","time":4228,"x":62,"y":489},{"type":"mousemove","time":4293,"x":62,"y":489},{"type":"mouseup","time":4331,"x":62,"y":489},{"time":4332,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4535,"x":62,"y":491},{"type":"mousedown","time":4711,"x":62,"y":505},{"type":"mousemove","time":4736,"x":62,"y":505},{"type":"mouseup","time":4827,"x":62,"y":505},{"time":4828,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5137,"x":62,"y":505},{"type":"mousemove","time":5344,"x":71,"y":488},{"type":"mousedown","time":5486,"x":72,"y":484},{"type":"mousemove","time":5560,"x":72,"y":484},{"type":"mouseup","time":5613,"x":72,"y":484},{"time":5614,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5785,"x":72,"y":483},{"type":"mousemove","time":5995,"x":72,"y":463},{"type":"mousedown","time":6003,"x":72,"y":463},{"type":"mouseup","time":6111,"x":72,"y":463},{"time":6112,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6220,"x":72,"y":462},{"type":"mousemove","time":6428,"x":72,"y":443},{"type":"mousedown","time":6553,"x":72,"y":438},{"type":"mousemove","time":6645,"x":72,"y":438},{"type":"mouseup","time":6693,"x":72,"y":438},{"time":6694,"delay":400,"type":"screenshot-auto"}],"scrollY":0,"scrollX":0,"timestamp":1629397351306},{"name":"Action 2","ops":[{"type":"mousedown","time":500,"x":99,"y":302},{"type":"mouseup","time":667,"x":99,"y":302},{"time":668,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":827,"x":99,"y":303},{"type":"mousemove","time":1035,"x":97,"y":325},{"type":"mousedown","time":1188,"x":96,"y":328},{"type":"mousemove","time":1316,"x":96,"y":328},{"type":"mouseup","time":1323,"x":96,"y":328},{"time":1324,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":1409,"x":96,"y":328},{"type":"mousemove","time":1609,"x":96,"y":297},{"type":"mousedown","time":1802,"x":96,"y":282},{"type":"mousemove","time":1818,"x":96,"y":282},{"type":"mouseup","time":1919,"x":96,"y":282},{"time":1920,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":1975,"x":96,"y":282},{"type":"mousemove","time":2176,"x":96,"y":340},{"type":"mousemove","time":2376,"x":95,"y":350},{"type":"mousedown","time":2502,"x":95,"y":352},{"type":"mousemove","time":2586,"x":95,"y":352},{"type":"mouseup","time":2602,"x":95,"y":352},{"time":2603,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2843,"x":95,"y":352},{"type":"mousemove","time":3043,"x":82,"y":392},{"type":"mousedown","time":3085,"x":82,"y":392},{"type":"mouseup","time":3185,"x":82,"y":392},{"time":3186,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3243,"x":82,"y":392},{"type":"mousemove","time":3409,"x":82,"y":391},{"type":"mousedown","time":3552,"x":84,"y":383},{"type":"mousemove","time":3610,"x":84,"y":383},{"type":"mouseup","time":3655,"x":84,"y":383},{"time":3656,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3859,"x":84,"y":383},{"type":"mousemove","time":4059,"x":83,"y":401},{"type":"mousedown","time":4152,"x":83,"y":402},{"type":"mouseup","time":4256,"x":83,"y":402},{"time":4257,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4278,"x":83,"y":402},{"type":"mousemove","time":4576,"x":83,"y":400},{"type":"mousemove","time":4783,"x":86,"y":389},{"type":"mousedown","time":4859,"x":86,"y":389},{"type":"mouseup","time":4966,"x":86,"y":389},{"time":4967,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5178,"x":86,"y":388},{"type":"mousedown","time":5384,"x":86,"y":377},{"type":"mousemove","time":5400,"x":86,"y":377},{"type":"mouseup","time":5491,"x":86,"y":377},{"time":5492,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5776,"x":86,"y":373},{"type":"mousemove","time":5976,"x":89,"y":358},{"type":"mousedown","time":6005,"x":89,"y":357},{"type":"mouseup","time":6118,"x":89,"y":357},{"time":6119,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6187,"x":89,"y":357},{"type":"mousemove","time":6260,"x":89,"y":356},{"type":"mousemove","time":6460,"x":89,"y":336},{"type":"mousedown","time":6520,"x":89,"y":334},{"type":"mouseup","time":6653,"x":89,"y":334},{"time":6654,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6681,"x":89,"y":334},{"type":"mousemove","time":6810,"x":89,"y":334},{"type":"mousemove","time":7010,"x":89,"y":306},{"type":"mousedown","time":7104,"x":89,"y":305},{"type":"mousemove","time":7219,"x":89,"y":305},{"type":"mouseup","time":7227,"x":89,"y":305},{"time":7228,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7459,"x":89,"y":304},{"type":"mousemove","time":7669,"x":91,"y":283},{"type":"mousedown","time":7690,"x":91,"y":283},{"type":"mouseup","time":7809,"x":91,"y":283},{"time":7810,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7862,"x":91,"y":285},{"type":"mousemove","time":8062,"x":89,"y":383},{"type":"mousemove","time":8270,"x":83,"y":418},{"type":"mousemove","time":8476,"x":86,"y":405},{"type":"mousedown","time":8518,"x":86,"y":405},{"type":"mouseup","time":8634,"x":86,"y":405},{"time":8635,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8678,"x":86,"y":405},{"type":"mousemove","time":8859,"x":87,"y":405},{"type":"mousemove","time":9060,"x":162,"y":341},{"type":"mousemove","time":9270,"x":162,"y":341}],"scrollY":642,"scrollX":0,"timestamp":1629397363631},{"name":"Action 3","ops":[{"type":"mousemove","time":246,"x":109,"y":267},{"type":"mousedown","time":428,"x":108,"y":260},{"type":"mousemove","time":452,"x":108,"y":260},{"type":"mouseup","time":555,"x":108,"y":260},{"time":556,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":645,"x":108,"y":262},{"type":"mousemove","time":853,"x":108,"y":279},{"type":"mousedown","time":871,"x":108,"y":279},{"type":"mouseup","time":973,"x":108,"y":279},{"time":974,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":1070,"x":108,"y":279},{"type":"mousemove","time":1231,"x":108,"y":281},{"type":"mousemove","time":1441,"x":92,"y":333},{"type":"mousedown","time":1473,"x":92,"y":333},{"type":"mouseup","time":1622,"x":92,"y":333},{"time":1623,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":1745,"x":92,"y":333},{"type":"mousemove","time":1945,"x":94,"y":312},{"type":"mousedown","time":1989,"x":94,"y":311},{"type":"mouseup","time":2095,"x":94,"y":311},{"time":2096,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2154,"x":94,"y":311},{"type":"mousemove","time":2329,"x":94,"y":311},{"type":"mousemove","time":2539,"x":100,"y":278},{"type":"mousedown","time":2577,"x":100,"y":276},{"type":"mouseup","time":2721,"x":100,"y":276},{"time":2722,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2754,"x":100,"y":276},{"type":"mousemove","time":2896,"x":100,"y":277},{"type":"mousedown","time":3022,"x":100,"y":279},{"type":"mousemove","time":3105,"x":100,"y":279},{"type":"mouseup","time":3138,"x":100,"y":279},{"time":3139,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3379,"x":100,"y":280},{"type":"mousemove","time":3579,"x":88,"y":340},{"type":"mousemove","time":3779,"x":85,"y":352},{"type":"mousedown","time":3907,"x":83,"y":361},{"type":"mousemove","time":3988,"x":83,"y":361},{"type":"mouseup","time":4021,"x":83,"y":361},{"time":4022,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4162,"x":83,"y":363},{"type":"mousemove","time":4362,"x":83,"y":388},{"type":"mousedown","time":4421,"x":83,"y":388},{"type":"mouseup","time":4587,"x":83,"y":388},{"time":4588,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4796,"x":83,"y":387},{"type":"mousemove","time":5006,"x":94,"y":350},{"type":"mousedown","time":5095,"x":97,"y":342},{"type":"mousemove","time":5221,"x":97,"y":342},{"type":"mouseup","time":5289,"x":97,"y":342},{"time":5290,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5394,"x":97,"y":343},{"type":"mousemove","time":5599,"x":97,"y":348},{"type":"mousedown","time":5624,"x":97,"y":348},{"type":"mouseup","time":5756,"x":97,"y":348},{"time":5757,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5929,"x":97,"y":349},{"type":"mousemove","time":6131,"x":97,"y":354},{"type":"mousedown","time":6159,"x":97,"y":354},{"type":"mouseup","time":6258,"x":97,"y":354},{"time":6259,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6340,"x":97,"y":354},{"type":"mousemove","time":6497,"x":97,"y":357},{"type":"mousemove","time":6707,"x":86,"y":381},{"type":"mousedown","time":6772,"x":86,"y":382},{"type":"mouseup","time":6873,"x":86,"y":382},{"time":6874,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6924,"x":86,"y":382},{"type":"mousemove","time":7081,"x":86,"y":379},{"type":"mousemove","time":7289,"x":94,"y":320},{"type":"mousemove","time":7496,"x":97,"y":313},{"type":"mousedown","time":7517,"x":97,"y":313},{"type":"mouseup","time":7670,"x":97,"y":313},{"time":7671,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7879,"x":97,"y":312},{"type":"mousemove","time":8078,"x":102,"y":287},{"type":"mousedown","time":8157,"x":102,"y":286},{"type":"mousemove","time":8279,"x":102,"y":286},{"type":"mouseup","time":8309,"x":102,"y":286},{"time":8310,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8397,"x":102,"y":285},{"type":"mousemove","time":8609,"x":102,"y":268},{"type":"mousedown","time":8662,"x":102,"y":266},{"type":"mouseup","time":8792,"x":102,"y":266},{"time":8793,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8825,"x":102,"y":266}],"scrollY":1197,"scrollX":0,"timestamp":1629397376601},{"name":"Action 4","ops":[{"type":"mousemove","time":549,"x":116,"y":368},{"type":"mousemove","time":749,"x":40,"y":337},{"type":"mousemove","time":959,"x":18,"y":334},{"type":"mousemove","time":1171,"x":28,"y":336},{"type":"mousemove","time":1374,"x":33,"y":376},{"type":"mousemove","time":1582,"x":33,"y":378},{"type":"mousemove","time":1793,"x":34,"y":328},{"type":"mousemove","time":1994,"x":37,"y":295},{"type":"mousemove","time":2201,"x":179,"y":306},{"type":"mousemove","time":2411,"x":277,"y":333},{"type":"mousemove","time":2611,"x":278,"y":333},{"type":"mousemove","time":2811,"x":286,"y":333},{"type":"mousemove","time":3011,"x":288,"y":402},{"type":"mousemove","time":3211,"x":290,"y":416},{"type":"mousemove","time":3420,"x":290,"y":419},{"type":"mousemove","time":3478,"x":290,"y":419},{"type":"mousemove","time":3692,"x":570,"y":342},{"type":"mousemove","time":3894,"x":583,"y":361},{"type":"mousemove","time":4094,"x":581,"y":368},{"type":"mousemove","time":4294,"x":579,"y":231},{"type":"mousemove","time":4494,"x":576,"y":226},{"type":"mousemove","time":4694,"x":187,"y":264},{"type":"mousemove","time":4894,"x":109,"y":232},{"type":"mousemove","time":5103,"x":47,"y":259},{"type":"mousemove","time":5311,"x":34,"y":274},{"type":"mousemove","time":5524,"x":30,"y":274},{"type":"mousemove","time":5737,"x":30,"y":273},{"type":"mousedown","time":5888,"x":30,"y":273},{"type":"mousemove","time":5897,"x":30,"y":277},{"type":"mousemove","time":6097,"x":28,"y":314},{"type":"mousemove","time":6312,"x":30,"y":328},{"type":"mouseup","time":6523,"x":30,"y":326},{"time":6524,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6534,"x":32,"y":322},{"type":"mousemove","time":6738,"x":36,"y":272},{"type":"mousemove","time":6955,"x":35,"y":270},{"type":"mousemove","time":7188,"x":34,"y":268},{"type":"mousedown","time":7390,"x":34,"y":268},{"type":"mousemove","time":7399,"x":34,"y":270},{"type":"mousemove","time":7601,"x":32,"y":312},{"type":"mousemove","time":7806,"x":33,"y":334},{"type":"mousemove","time":8012,"x":34,"y":340},{"type":"mouseup","time":8190,"x":34,"y":340},{"time":8191,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8200,"x":33,"y":343},{"type":"mousemove","time":8407,"x":27,"y":375},{"type":"mousemove","time":8621,"x":27,"y":376},{"type":"mousedown","time":8670,"x":27,"y":376},{"type":"mousemove","time":8682,"x":27,"y":374},{"type":"mousemove","time":8898,"x":28,"y":344},{"type":"mousemove","time":9110,"x":31,"y":316},{"type":"mousemove","time":9312,"x":34,"y":286},{"type":"mousemove","time":9527,"x":34,"y":284},{"type":"mouseup","time":9704,"x":34,"y":284},{"time":9705,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":9713,"x":34,"y":290},{"type":"mousemove","time":9922,"x":31,"y":356},{"type":"mousemove","time":10193,"x":32,"y":343},{"type":"mousemove","time":10405,"x":32,"y":337},{"type":"mousedown","time":10724,"x":32,"y":337},{"type":"mousemove","time":10734,"x":32,"y":339},{"type":"mousemove","time":10951,"x":32,"y":437},{"type":"mousemove","time":11172,"x":32,"y":451},{"type":"mouseup","time":11245,"x":32,"y":451},{"time":11246,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":11255,"x":34,"y":447},{"type":"mousemove","time":11455,"x":68,"y":373},{"type":"mousedown","time":11538,"x":68,"y":371},{"type":"mouseup","time":11636,"x":68,"y":371},{"time":11637,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":11672,"x":68,"y":371}],"scrollY":1549,"scrollX":0,"timestamp":1629397393090},{"name":"Action 5","ops":[{"type":"mousemove","time":204,"x":43,"y":319},{"type":"mousemove","time":404,"x":30,"y":318},{"type":"mousemove","time":604,"x":28,"y":316},{"type":"mousemove","time":814,"x":30,"y":315},{"type":"mousedown","time":1047,"x":30,"y":315},{"type":"mousemove","time":1056,"x":30,"y":316},{"type":"mousemove","time":1260,"x":33,"y":342},{"type":"mousemove","time":1470,"x":36,"y":364},{"type":"mousemove","time":1681,"x":36,"y":369},{"type":"mouseup","time":1731,"x":36,"y":369},{"time":1732,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":1887,"x":34,"y":421},{"type":"mousemove","time":2087,"x":33,"y":423},{"type":"mousedown","time":2197,"x":33,"y":423},{"type":"mousemove","time":2209,"x":33,"y":418},{"type":"mousemove","time":2421,"x":30,"y":378},{"type":"mousemove","time":2636,"x":32,"y":322},{"type":"mousemove","time":2848,"x":34,"y":311},{"type":"mouseup","time":2885,"x":34,"y":311},{"time":2886,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":3055,"x":24,"y":392},{"type":"mousemove","time":3264,"x":23,"y":407},{"type":"mousemove","time":3471,"x":23,"y":402},{"type":"mousemove","time":3671,"x":24,"y":399},{"type":"mousemove","time":3879,"x":24,"y":399},{"type":"mousedown","time":3985,"x":24,"y":399},{"type":"mousemove","time":3995,"x":24,"y":402},{"type":"mousemove","time":4202,"x":28,"y":493},{"type":"mousemove","time":4418,"x":28,"y":496},{"type":"mouseup","time":4463,"x":28,"y":496},{"time":4464,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":4475,"x":37,"y":486},{"type":"mousemove","time":4682,"x":101,"y":418}],"scrollY":1991,"scrollX":0,"timestamp":1629397408600},{"name":"Action 6","ops":[{"type":"mousemove","time":532,"x":127,"y":195},{"type":"mousemove","time":732,"x":129,"y":188},{"type":"mousemove","time":932,"x":130,"y":180},{"type":"mousemove","time":1132,"x":130,"y":180},{"type":"mousedown","time":1477,"x":130,"y":180},{"type":"mouseup","time":1614,"x":130,"y":180},{"time":1615,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":2849,"x":130,"y":180},{"type":"mousemove","time":3049,"x":275,"y":178},{"type":"mousemove","time":3249,"x":302,"y":181},{"type":"mousedown","time":3376,"x":303,"y":181},{"type":"mousemove","time":3459,"x":303,"y":181},{"type":"mouseup","time":3544,"x":303,"y":181},{"time":3545,"delay":400,"type":"screenshot-auto"},{"type":"mousedown","time":4749,"x":303,"y":181},{"type":"mouseup","time":4927,"x":303,"y":181},{"time":4928,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":5250,"x":302,"y":181},{"type":"mousemove","time":5450,"x":237,"y":192},{"type":"mousemove","time":5650,"x":221,"y":184},{"type":"mousedown","time":5725,"x":221,"y":183},{"type":"mousemove","time":5858,"x":221,"y":183},{"type":"mouseup","time":5878,"x":221,"y":183},{"time":5879,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":6282,"x":228,"y":183},{"type":"mousemove","time":6482,"x":348,"y":214},{"type":"mousedown","time":6664,"x":355,"y":216},{"type":"mousemove","time":6696,"x":355,"y":216},{"type":"mouseup","time":6760,"x":355,"y":216},{"time":6761,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":7199,"x":355,"y":216},{"type":"mousemove","time":7400,"x":374,"y":211},{"type":"mousemove","time":7610,"x":377,"y":211},{"type":"mousedown","time":8180,"x":377,"y":211},{"type":"mouseup","time":8293,"x":377,"y":211},{"time":8294,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8466,"x":383,"y":211},{"type":"mousemove","time":8666,"x":424,"y":211},{"type":"mousedown","time":8732,"x":425,"y":211},{"type":"mouseup","time":8860,"x":425,"y":211},{"time":8861,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":8887,"x":425,"y":211},{"type":"mousemove","time":9166,"x":424,"y":211},{"type":"mousemove","time":9366,"x":364,"y":215},{"type":"mousedown","time":9377,"x":363,"y":215},{"type":"mouseup","time":9511,"x":363,"y":215},{"time":9512,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":9580,"x":363,"y":215},{"type":"mousemove","time":9616,"x":364,"y":215},{"type":"mousemove","time":9816,"x":424,"y":215},{"type":"mousemove","time":10025,"x":434,"y":215},{"type":"mousedown","time":10048,"x":434,"y":215},{"type":"mouseup","time":10178,"x":434,"y":215},{"time":10179,"delay":400,"type":"screenshot-auto"},{"type":"mousemove","time":10282,"x":434,"y":215},{"type":"mousemove","time":10483,"x":394,"y":215},{"type":"mousedown","time":10612,"x":374,"y":214},{"type":"mousemove","time":10693,"x":374,"y":214},{"type":"mouseup","time":10745,"x":374,"y":214},{"time":10746,"delay":400,"type":"screenshot-auto"}],"scrollY":2475,"scrollX":0,"timestamp":1629397417622}] \ No newline at end of file diff --git a/test/runTest/blacklist.js b/test/runTest/blacklist.js index ba638ed9fb..3bedb0cf4c 100644 --- a/test/runTest/blacklist.js +++ b/test/runTest/blacklist.js @@ -39,7 +39,9 @@ module.exports.blacklist = [ // This case will have timeout 'visualMap-performance1.html', - 'lines-stream-not-large.html' + 'lines-stream-not-large.html', + + 'dataset-performance.html' ]; diff --git a/test/sankey.html b/test/sankey.html index 777255da74..1536192b22 100644 --- a/test/sankey.html +++ b/test/sankey.html @@ -25,37 +25,34 @@ + + + + -
+ + +
+
+ + + + + + + + + + + \ No newline at end of file diff --git a/test/timeline-event.html b/test/timeline-event.html index c46e6275c6..621afb4840 100644 --- a/test/timeline-event.html +++ b/test/timeline-event.html @@ -23,7 +23,7 @@ - + diff --git a/test/ut/jest.config.js b/test/ut/jest.config.js index 9946de2a14..5ca8031953 100644 --- a/test/ut/jest.config.js +++ b/test/ut/jest.config.js @@ -17,6 +17,9 @@ * under the License. */ +const { pathsToModuleNameMapper } = require('ts-jest/utils'); +const { compilerOptions } = require('./tsconfig') + module.exports = { preset: 'ts-jest', testEnvironment: 'jsdom', @@ -44,5 +47,8 @@ module.exports = { '**/spec/model/*.test.ts', '**/spec/scale/*.test.ts', '**/spec/util/*.test.ts' - ] + ], + moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, { + prefix: '/' + }) }; diff --git a/test/ut/spec/data/List.test.ts b/test/ut/spec/data/List.test.ts deleted file mode 100644 index c136afc31b..0000000000 --- a/test/ut/spec/data/List.test.ts +++ /dev/null @@ -1,557 +0,0 @@ - -/* -* Licensed to the Apache Software Foundation (ASF) under one -* or more contributor license agreements. See the NOTICE file -* distributed with this work for additional information -* regarding copyright ownership. The ASF licenses this file -* to you under the Apache License, Version 2.0 (the -* "License"); you may not use this file except in compliance -* with the License. You may obtain a copy of the License at -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, -* software distributed under the License is distributed on an -* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -* KIND, either express or implied. See the License for the -* specific language governing permissions and limitations -* under the License. -*/ - -/* global Float32Array */ - -import List from '../../../../src/data/List'; -import Model from '../../../../src/model/Model'; -import { createSourceFromSeriesDataOption, Source, createSource } from '../../../../src/data/Source'; -import { OptionDataItemObject, OptionDataValue, SOURCE_FORMAT_ARRAY_ROWS } from '../../../../src/util/types'; -import DataDimensionInfo from '../../../../src/data/DataDimensionInfo'; -import OrdinalMeta from '../../../../src/data/OrdinalMeta'; - - -const ID_PREFIX = 'e\0\0'; -const NAME_REPEAT_PREFIX = '__ec__'; - - -describe('List', function () { - - describe('Data Manipulation', function () { - - it('initData 1d', function () { - const list = new List(['x', 'y'], new Model()); - list.initData([10, 20, 30]); - expect(list.get('x', 0)).toEqual(10); - expect(list.get('x', 1)).toEqual(20); - expect(list.get('x', 2)).toEqual(30); - expect(list.get('y', 1)).toEqual(20); - }); - - it('initData 2d', function () { - const list = new List(['x', 'y'], new Model()); - list.initData([[10, 15], [20, 25], [30, 35]]); - expect(list.get('x', 1)).toEqual(20); - expect(list.get('y', 1)).toEqual(25); - }); - - it('initData 2d yx', function () { - const list = new List(['y', 'x'], new Model()); - list.initData([[10, 15], [20, 25], [30, 35]]); - expect(list.get('x', 1)).toEqual(25); - expect(list.get('y', 1)).toEqual(20); - }); - - it('Data with option 1d', function () { - const list = new List(['x', 'y'], new Model()); - list.initData([ - 1, - { - value: 2, - somProp: 'foo' - } as OptionDataItemObject - ]); - expect(list.getItemModel(1).get('somProp' as any)).toEqual('foo'); - expect(list.getItemModel(0).get('somProp' as any)).toBeNull(); - }); - - it('Empty data', function () { - const list = new List(['x', 'y'], new Model()); - list.initData([1, '-']); - expect(list.get('y', 1)).toBeNaN(); - }); - - it('getRawValue', function () { - const list1 = new List(['x', 'y'], new Model()); - // here construct a new list2 because if we only use one list - // to call initData() twice, list._chunkCount will be accumulated - // to 1 instead of 0. - const list2 = new List(['x', 'y'], new Model()); - - list1.initData([1, 2, 3]); - expect(list1.getItemModel(1).option).toEqual(2); - - list2.initData([[10, 15], [20, 25], [30, 35]]); - expect(list2.getItemModel(1).option).toEqual([20, 25]); - }); - - it('indexOfRawIndex', function () { - const list = new List(['x'], new Model()); - list.initData([]); - expect(list.indexOfRawIndex(1)).toEqual(-1); - - const list1 = new List(['x'], new Model()); - list1.initData([0]); - expect(list1.indexOfRawIndex(0)).toEqual(0); - expect(list1.indexOfRawIndex(1)).toEqual(-1); - - const list2 = new List(['x'], new Model()); - list2.initData([0, 1, 2, 3]); - expect(list2.indexOfRawIndex(1)).toEqual(1); - expect(list2.indexOfRawIndex(2)).toEqual(2); - expect(list2.indexOfRawIndex(5)).toEqual(-1); - - const list3 = new List(['x'], new Model()); - list3.initData([0, 1, 2, 3, 4]); - expect(list3.indexOfRawIndex(2)).toEqual(2); - expect(list3.indexOfRawIndex(3)).toEqual(3); - expect(list3.indexOfRawIndex(5)).toEqual(-1); - - list3.filterSelf(function (idx) { - return idx >= 2; - }); - expect(list3.indexOfRawIndex(2)).toEqual(0); - }); - - it('getDataExtent', function () { - const list = new List(['x', 'y'], new Model()); - list.initData([1, 2, 3]); - expect(list.getDataExtent('x')).toEqual([1, 3]); - expect(list.getDataExtent('y')).toEqual([1, 3]); - }); - - it('Data types', function () { - const list = new List([{ - name: 'x', - type: 'int' - }, { - name: 'y', - type: 'float' - }], new Model()); - list.initData([[1.1, 1.1]]); - expect(list.get('x', 0)).toEqual(1); - expect(list.get('y', 0)).toBeCloseTo(1.1, 5); - }); - - it('map', function () { - const list = new List(['x', 'y'], new Model()); - list.initData([[10, 15], [20, 25], [30, 35]]); - expect(list.map(['x', 'y'], function (x: number, y: number) { - return [x + 2, y + 2]; - }).mapArray('x', function (x) { - return x; - })).toEqual([12, 22, 32]); - }); - - it('mapArray', function () { - const list = new List(['x', 'y'], new Model()); - list.initData([[10, 15], [20, 25], [30, 35]]); - expect(list.mapArray(['x', 'y'], function (x, y) { - return [x, y]; - })).toEqual([[10, 15], [20, 25], [30, 35]]); - }); - - it('filterSelf', function () { - const list = new List(['x', 'y'], new Model()); - list.initData([[10, 15], [20, 25], [30, 35]]); - expect(list.filterSelf(['x', 'y'], function (x, y) { - return x < 30 && x > 10; - }).mapArray('x', function (x) { - return x; - })).toEqual([20]); - }); - - it('dataProvider', function () { - const list = new List(['x', 'y'], new Model()); - const typedArray = new Float32Array([10, 10, 20, 20]); - const source = createSourceFromSeriesDataOption(typedArray); - list.initData({ - count: function (): number { - return typedArray.length / 2; - }, - getItem: function (idx: number): number[] { - return [typedArray[idx * 2], typedArray[idx * 2 + 1]]; - }, - getSource: function (): Source { - return source; - } - }); - expect(list.mapArray(['x', 'y'], function (x, y) { - return [x, y]; - })).toEqual([[10, 10], [20, 20]]); - expect(list.getRawDataItem(0)).toEqual([10, 10]); - expect(list.getItemModel(0).option).toEqual([10, 10]); - }); - }); - - describe('Data read', function () { - it('indicesOfNearest', function () { - const list = new List(['value'], new Model()); - // ---- index: 0 1 2 3 4 5 6 7 - list.initData([10, 20, 30, 35, 40, 40, 35, 50]); - - expect(list.indicesOfNearest('value', 24.5)).toEqual([1]); - expect(list.indicesOfNearest('value', 25)).toEqual([1]); - expect(list.indicesOfNearest('value', 25.5)).toEqual([2]); - expect(list.indicesOfNearest('value', 25.5)).toEqual([2]); - expect(list.indicesOfNearest('value', 41)).toEqual([4, 5]); - expect(list.indicesOfNearest('value', 39)).toEqual([4, 5]); - expect(list.indicesOfNearest('value', 41)).toEqual([4, 5]); - expect(list.indicesOfNearest('value', 36)).toEqual([3, 6]); - - expect(list.indicesOfNearest('value', 50.6, 0.5)).toEqual([]); - expect(list.indicesOfNearest('value', 50.5, 0.5)).toEqual([7]); - }); - }); - - describe('id_and_name', function () { - - function makeOneByOneChecker(list: List) { - let getIdDataIndex = 0; - let getNameDataIndex = 0; - - return { - idEqualsTo: function (expectedId: string): void { - expect(list.getId(getIdDataIndex)).toEqual(expectedId); - getIdDataIndex++; - }, - nameEqualsTo: function (expectedName: string): void { - expect(list.getName(getNameDataIndex)).toEqual(expectedName); - getNameDataIndex++; - }, - currGetIdDataIndex: function (): number { - return getIdDataIndex; - }, - currGetNameDataIndex: function (): number { - return getNameDataIndex; - } - }; - } - - describe('only_name_declared', function () { - - function doChecks(list: List) { - const oneByOne = makeOneByOneChecker(list); - - oneByOne.idEqualsTo('a'); - oneByOne.idEqualsTo('b'); - oneByOne.idEqualsTo(`b${NAME_REPEAT_PREFIX}2`); - oneByOne.idEqualsTo('c'); - oneByOne.idEqualsTo(`${ID_PREFIX}4`); - oneByOne.idEqualsTo(`c${NAME_REPEAT_PREFIX}2`); - oneByOne.idEqualsTo('d'); - oneByOne.idEqualsTo(`c${NAME_REPEAT_PREFIX}3`); - - oneByOne.nameEqualsTo('a'); - oneByOne.nameEqualsTo('b'); - oneByOne.nameEqualsTo('b'); - oneByOne.nameEqualsTo('c'); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo('c'); - oneByOne.nameEqualsTo('d'); - oneByOne.nameEqualsTo('c'); - } - - it('sourceFormatOriginal', function () { - const list = new List(['x', 'y'], new Model()); - list.initData([ - { value: 10, name: 'a' }, - { value: 20, name: 'b' }, - { value: 30, name: 'b' }, - { value: 40, name: 'c' }, - { value: 50 }, // name not declared - { value: 60, name: 'c' }, - { value: 70, name: 'd' }, - { value: 80, name: 'c' } - ]); - - doChecks(list); - }); - - it('sourceFormatArrayRows', function () { - const list = new List( - [ - 'x', - { name: 'q', type: 'ordinal', otherDims: { itemName: 0 } } - ], - new Model() - ); - const source = createSource( - [ - [ 10, 'a' ], - [ 20, 'b' ], - [ 30, 'b' ], - [ 40, 'c' ], - [ 50, null ], - [ 60, 'c' ], - [ 70, 'd' ], - [ 80, 'c' ] - ], - { - seriesLayoutBy: 'column', - sourceHeader: 0, - dimensions: null - }, - SOURCE_FORMAT_ARRAY_ROWS, - { - itemName: 1 - } - ); - list.initData(source); - - doChecks(list); - }); - }); - - - describe('id_name_declared_sourceFormat_original', function () { - - it('sourceFormatOriginal', function () { - const list = new List(['x'], new Model()); - const oneByOne = makeOneByOneChecker(list); - - list.initData([ - { value: 0, id: 'myId_10' }, - { value: 10, id: 555 }, // numeric id. - { value: 20, id: '666%' }, - { value: 30, id: 'myId_good', name: 'b' }, - { value: 40, name: 'b' }, - { value: 50, id: null }, - { value: 60, id: undefined }, - { value: 70, id: NaN }, - { value: 80, id: '' }, - { value: 90, name: 'b' }, - { value: 100 }, - { value: 110, id: 'myId_better' }, - { value: 120, id: 'myId_better' } // duplicated id. - ]); - - oneByOne.idEqualsTo('myId_10'); - oneByOne.idEqualsTo('555'); - oneByOne.idEqualsTo('666%'); - oneByOne.idEqualsTo('myId_good'); - oneByOne.idEqualsTo('b'); - oneByOne.idEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); - oneByOne.idEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); - oneByOne.idEqualsTo('NaN'); - oneByOne.idEqualsTo(''); - oneByOne.idEqualsTo(`b${NAME_REPEAT_PREFIX}2`); - oneByOne.idEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); - oneByOne.idEqualsTo('myId_better'); - oneByOne.idEqualsTo('myId_better'); - - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo('b'); - oneByOne.nameEqualsTo('b'); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo('b'); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo(''); - - list.appendData([ - { value: 200, id: 'myId_best' }, - { value: 210, id: 999 }, // numeric id. - { value: 220, id: '777px' }, - { value: 230, name: 'b' }, - { value: 240 } - ]); - - oneByOne.idEqualsTo('myId_best'); - oneByOne.idEqualsTo('999'); - oneByOne.idEqualsTo('777px'); - oneByOne.idEqualsTo(`b${NAME_REPEAT_PREFIX}3`); - oneByOne.idEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); - - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo('b'); - oneByOne.nameEqualsTo(''); - - list.appendValues( - [ - [300], - [310], - [320] - ], - [ - 'b', - 'c', - null - ] - ); - - oneByOne.idEqualsTo(`b${NAME_REPEAT_PREFIX}4`); - oneByOne.idEqualsTo('c'); - oneByOne.idEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); - - oneByOne.nameEqualsTo('b'); - oneByOne.nameEqualsTo('c'); - oneByOne.nameEqualsTo(''); - }); - - }); - - describe('id_name_declared_sourceFormat_arrayRows', function () { - - function makeChecker(list: List) { - const oneByOne = makeOneByOneChecker(list); - return { - checkAfterInitData() { - oneByOne.idEqualsTo('myId_10'); - oneByOne.idEqualsTo('555'); - oneByOne.idEqualsTo('666%'); - oneByOne.idEqualsTo('myId_good'); - oneByOne.idEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); - oneByOne.idEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); - oneByOne.idEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); - oneByOne.idEqualsTo('NaN'); - oneByOne.idEqualsTo(''); - oneByOne.idEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); - oneByOne.idEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); - oneByOne.idEqualsTo('myId_better'); - oneByOne.idEqualsTo('myId_better'); - - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo('b'); - oneByOne.nameEqualsTo('b'); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo('b'); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo(''); - }, - checkAfterAppendData() { - oneByOne.idEqualsTo('myId_best'); - oneByOne.idEqualsTo('999'); - oneByOne.idEqualsTo('777px'); - oneByOne.idEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); - oneByOne.idEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); - - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo(''); - oneByOne.nameEqualsTo('b'); - oneByOne.nameEqualsTo(''); - }, - checkAfterAppendValues() { - oneByOne.idEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); - oneByOne.idEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); - oneByOne.idEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); - - oneByOne.nameEqualsTo('b'); - oneByOne.nameEqualsTo('c'); - oneByOne.nameEqualsTo(''); - } - }; - } - - it('no_ordinalMeta', function () { - testArrayRowsInSource([ - { name: 'x', type: 'number' }, - { name: 'p', type: 'ordinal', otherDims: { itemId: 0 } }, - { name: 'q', type: 'ordinal', otherDims: { itemName: 0 } } - ]); - }); - - it('has_ordinalMeta', function () { - const ordinalMetaP = new OrdinalMeta({ - categories: [], - needCollect: true, - deduplication: true - }); - const ordinalMetaQ = new OrdinalMeta({ - categories: [], - needCollect: true, - deduplication: true - }); - testArrayRowsInSource([ - { name: 'x', type: 'number' }, - { name: 'p', type: 'ordinal', otherDims: { itemId: 0 }, ordinalMeta: ordinalMetaP }, - { name: 'q', type: 'ordinal', otherDims: { itemName: 0 }, ordinalMeta: ordinalMetaQ } - ]); - }); - - function testArrayRowsInSource(dimensionsInfo: DataDimensionInfo[]): void { - const list = new List(dimensionsInfo, new Model()); - const checker = makeChecker(list); - - const source = createSource( - [ - [0, 'myId_10', null], - [10, 555, null], // numeric id. - [20, '666%', null], - [30, 'myId_good', 'b'], - [40, null, 'b'], - [50, null, null], - [60, undefined, null], - [70, NaN, null], - [80, '', null], - [90, null, 'b'], - [100, null, null], - [110, 'myId_better', null], - [120, 'myId_better', null] // duplicated id. - ], - { - seriesLayoutBy: 'column', - sourceHeader: 0, - dimensions: null - }, - SOURCE_FORMAT_ARRAY_ROWS, - { - itemId: 1, - itemName: 2 - } - ); - list.initData(source); - - checker.checkAfterInitData(); - - list.appendData([ - [ 200, 'myId_best', null ], - [ 210, 999, null ], // numeric id. - [ 220, '777px', null], - [ 230, null, 'b' ], - [ 240, null, null ] - ]); - - checker.checkAfterAppendData(); - - list.appendValues( - [ - [300], - [310], - [320] - ], - [ - 'b', - 'c', - null - ] - ); - - checker.checkAfterAppendValues(); - } - - }); - - }); -}); diff --git a/test/ut/spec/data/SeriesData.test.ts b/test/ut/spec/data/SeriesData.test.ts new file mode 100644 index 0000000000..564772b310 --- /dev/null +++ b/test/ut/spec/data/SeriesData.test.ts @@ -0,0 +1,585 @@ + +/* +* 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. +*/ + +/* global Float32Array */ + +import SeriesData from '@/src/data/SeriesData'; +import Model from '@/src/model/Model'; +import { createSourceFromSeriesDataOption, Source, createSource } from '@/src/data/Source'; +import { OptionDataItemObject, + OptionDataValue, + SOURCE_FORMAT_ARRAY_ROWS, + SOURCE_FORMAT_ORIGINAL } from '@/src/util/types'; +import SeriesDimensionDefine from '@/src/data/SeriesDimensionDefine'; +import OrdinalMeta from '@/src/data/OrdinalMeta'; +import DataStore from '@/src/data/DataStore'; +import { DefaultDataProvider } from '@/src/data/helper/dataProvider'; +import { SeriesDataSchema } from '@/src/data/helper/SeriesDataSchema'; + + +const ID_PREFIX = 'e\0\0'; +const NAME_REPEAT_PREFIX = '__ec__'; + + +describe('SeriesData', function () { + + describe('Data Manipulation', function () { + + it('initData 1d', function () { + const data = new SeriesData(['x', 'y'], new Model()); + data.initData([10, 20, 30]); + expect(data.get('x', 0)).toEqual(10); + expect(data.get('x', 1)).toEqual(20); + expect(data.get('x', 2)).toEqual(30); + expect(data.get('y', 1)).toEqual(20); + }); + + it('initData 2d', function () { + const data = new SeriesData(['x', 'y'], new Model()); + data.initData([[10, 15], [20, 25], [30, 35]]); + expect(data.get('x', 1)).toEqual(20); + expect(data.get('y', 1)).toEqual(25); + }); + + it('initData 2d yx', function () { + const data = new SeriesData(['y', 'x'], new Model()); + data.initData([[10, 15], [20, 25], [30, 35]]); + expect(data.get('x', 1)).toEqual(25); + expect(data.get('y', 1)).toEqual(20); + }); + + it('Data with option 1d', function () { + const data = new SeriesData(['x', 'y'], new Model()); + data.initData([ + 1, + { + value: 2, + somProp: 'foo' + } as OptionDataItemObject + ]); + expect(data.getItemModel(1).get('somProp' as any)).toEqual('foo'); + expect(data.getItemModel(0).get('somProp' as any)).toBeNull(); + }); + + it('Empty data', function () { + const data = new SeriesData(['x', 'y'], new Model()); + data.initData([1, '-']); + expect(data.get('y', 1)).toBeNaN(); + }); + + it('getRawValue', function () { + const data1 = new SeriesData(['x', 'y'], new Model()); + // here construct a new data2 because if we only use one data + // to call initData() twice, data._chunkCount will be accumulated + // to 1 instead of 0. + const data2 = new SeriesData(['x', 'y'], new Model()); + + data1.initData([1, 2, 3]); + expect(data1.getItemModel(1).option).toEqual(2); + + data2.initData([[10, 15], [20, 25], [30, 35]]); + expect(data2.getItemModel(1).option).toEqual([20, 25]); + }); + + it('indexOfRawIndex', function () { + const data = new SeriesData(['x'], new Model()); + data.initData([]); + expect(data.indexOfRawIndex(1)).toEqual(-1); + + const data1 = new SeriesData(['x'], new Model()); + data1.initData([0]); + expect(data1.indexOfRawIndex(0)).toEqual(0); + expect(data1.indexOfRawIndex(1)).toEqual(-1); + + const data2 = new SeriesData(['x'], new Model()); + data2.initData([0, 1, 2, 3]); + expect(data2.indexOfRawIndex(1)).toEqual(1); + expect(data2.indexOfRawIndex(2)).toEqual(2); + expect(data2.indexOfRawIndex(5)).toEqual(-1); + + const data3 = new SeriesData(['x'], new Model()); + data3.initData([0, 1, 2, 3, 4]); + expect(data3.indexOfRawIndex(2)).toEqual(2); + expect(data3.indexOfRawIndex(3)).toEqual(3); + expect(data3.indexOfRawIndex(5)).toEqual(-1); + + data3.filterSelf(function (idx) { + return idx >= 2; + }); + expect(data3.indexOfRawIndex(2)).toEqual(0); + }); + + it('getDataExtent', function () { + const data = new SeriesData(['x', 'y'], new Model()); + data.initData([1, 2, 3]); + expect(data.getDataExtent('x')).toEqual([1, 3]); + expect(data.getDataExtent('y')).toEqual([1, 3]); + }); + + it('Data types', function () { + const data = new SeriesData([{ + name: 'x', + type: 'int' + }, { + name: 'y', + type: 'float' + }], new Model()); + data.initData([[1.1, 1.1]]); + expect(data.get('x', 0)).toEqual(1); + expect(data.get('y', 0)).toBeCloseTo(1.1, 5); + }); + + it('map', function () { + const data = new SeriesData(['x', 'y'], new Model()); + data.initData([[10, 15], [20, 25], [30, 35]]); + expect(data.map(['x', 'y'], function (x: number, y: number) { + return [x + 2, y + 2]; + }).mapArray('x', function (x) { + return x; + })).toEqual([12, 22, 32]); + }); + + it('mapArray', function () { + const data = new SeriesData(['x', 'y'], new Model()); + data.initData([[10, 15], [20, 25], [30, 35]]); + expect(data.mapArray(['x', 'y'], function (x, y) { + return [x, y]; + })).toEqual([[10, 15], [20, 25], [30, 35]]); + }); + + it('filterSelf', function () { + const data = new SeriesData(['x', 'y'], new Model()); + data.initData([[10, 15], [20, 25], [30, 35]]); + expect(data.filterSelf(['x', 'y'], function (x, y) { + return x < 30 && x > 10; + }).mapArray('x', function (x) { + return x; + })).toEqual([20]); + }); + + it('dataProvider', function () { + const data = new SeriesData(['x', 'y'], new Model()); + const typedArray = new Float32Array([10, 10, 20, 20]); + const source = createSourceFromSeriesDataOption(typedArray); + data.initData({ + count: function (): number { + return typedArray.length / 2; + }, + getItem: function (idx: number): number[] { + return [typedArray[idx * 2], typedArray[idx * 2 + 1]]; + }, + getSource: function (): Source { + return source; + } + }); + expect(data.mapArray(['x', 'y'], function (x, y) { + return [x, y]; + })).toEqual([[10, 10], [20, 20]]); + expect(data.getRawDataItem(0)).toEqual([10, 10]); + expect(data.getItemModel(0).option).toEqual([10, 10]); + }); + }); + + describe('Data store', function () { + it('should guess ordinal correctly', function () { + const source = createSource([['A', 15], ['B', 25], ['C', 35]], { + dimensions: ['A', 'B'], + seriesLayoutBy: null, + sourceHeader: false + }, SOURCE_FORMAT_ORIGINAL); + expect(source.dimensionsDefine[0].type).toEqual('ordinal'); + }); + + function createStore() { + const provider = new DefaultDataProvider([['A', 15], ['B', 25], ['C', 35]]); + const store = new DataStore(); + store.initData(provider, [{type: 'ordinal'}, {type: 'float'}]); + return store; + } + + + it('SeriesData can still get other dims value from store when only part of dims are given.', function () { + const source = createSource( + [['A', 15, 20, 'cat'], ['B', 25, 30, 'mouse'], ['C', 35, 40, 'dog']], + { + dimensions: null, + seriesLayoutBy: null, + sourceHeader: false + }, + SOURCE_FORMAT_ARRAY_ROWS + ); + const store = new DataStore(); + store.initData(new DefaultDataProvider(source), [ + {type: 'ordinal'}, {type: 'float'}, {type: 'float'}, {type: 'ordinal'} + ]); + const schema = new SeriesDataSchema({ + source: source, + dimensions: [ + { type: 'float', name: 'dim1', storeDimIndex: 1 }, + { type: 'ordinal', name: 'dim3', storeDimIndex: 3 } + ], + fullDimensionCount: 2, + dimensionOmitted: true + }); + const data = new SeriesData(schema, null); + data.initData(store); + // Store should be the same. + expect(data.getStore()).toBe(store); + // Get self dim + expect(data.get('dim1', 0)).toEqual(15); + expect(data.get('dim1', 1)).toEqual(25); + // Get other dim + expect(data.getStore().get(0, 0)).toEqual('A'); + expect(data.getStore().get(0, 1)).toEqual('B'); + expect(data.getStore().get(2, 0)).toEqual(20); + expect(data.getStore().get(2, 1)).toEqual(30); + // Get all + expect(data.getValues(['dim3', 'dim1'], 0)).toEqual(['cat', 15]); + expect(data.getValues(1)).toEqual(['B', 25, 30, 'mouse']); + }); + + it('SeriesData#cloneShallow should share store', function () { + const store = createStore(); + const dims = [{ type: 'float', name: 'dim2' }]; + const data = new SeriesData(dims, null); + data.initData(store); + const data2 = data.cloneShallow(); + expect(data2.getStore()).toBe(data.getStore()); + }); + }); + + describe('Data read', function () { + it('indicesOfNearest', function () { + const data = new SeriesData(['value'], new Model()); + // ---- index: 0 1 2 3 4 5 6 7 + data.initData([10, 20, 30, 35, 40, 40, 35, 50]); + + expect(data.indicesOfNearest('value', 24.5)).toEqual([1]); + expect(data.indicesOfNearest('value', 25)).toEqual([1]); + expect(data.indicesOfNearest('value', 25.5)).toEqual([2]); + expect(data.indicesOfNearest('value', 25.5)).toEqual([2]); + expect(data.indicesOfNearest('value', 41)).toEqual([4, 5]); + expect(data.indicesOfNearest('value', 39)).toEqual([4, 5]); + expect(data.indicesOfNearest('value', 41)).toEqual([4, 5]); + expect(data.indicesOfNearest('value', 36)).toEqual([3, 6]); + + expect(data.indicesOfNearest('value', 50.6, 0.5)).toEqual([]); + expect(data.indicesOfNearest('value', 50.5, 0.5)).toEqual([7]); + }); + }); + + describe('id_and_name', function () { + + function makeOneByOneChecker(list: SeriesData) { + let getIdDataIndex = 0; + let getNameDataIndex = 0; + + return { + nextIdEqualsTo: function (expectedId: string): void { + expect(list.getId(getIdDataIndex)).toEqual(expectedId); + getIdDataIndex++; + }, + nextNameEqualsTo: function (expectedName: string): void { + expect(list.getName(getNameDataIndex)).toEqual(expectedName); + getNameDataIndex++; + }, + currGetIdDataIndex: function (): number { + return getIdDataIndex; + }, + currGetNameDataIndex: function (): number { + return getNameDataIndex; + } + }; + } + + describe('only_name_declared', function () { + + function doChecks(list: SeriesData) { + const oneByOne = makeOneByOneChecker(list); + + oneByOne.nextIdEqualsTo('a'); + oneByOne.nextIdEqualsTo('b'); + oneByOne.nextIdEqualsTo(`b${NAME_REPEAT_PREFIX}2`); + oneByOne.nextIdEqualsTo('c'); + oneByOne.nextIdEqualsTo(`${ID_PREFIX}4`); + oneByOne.nextIdEqualsTo(`c${NAME_REPEAT_PREFIX}2`); + oneByOne.nextIdEqualsTo('d'); + oneByOne.nextIdEqualsTo(`c${NAME_REPEAT_PREFIX}3`); + + oneByOne.nextNameEqualsTo('a'); + oneByOne.nextNameEqualsTo('b'); + oneByOne.nextNameEqualsTo('b'); + oneByOne.nextNameEqualsTo('c'); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo('c'); + oneByOne.nextNameEqualsTo('d'); + oneByOne.nextNameEqualsTo('c'); + } + + it('sourceFormatOriginal', function () { + const list = new SeriesData(['x', 'y'], new Model()); + list.initData([ + { value: 10, name: 'a' }, + { value: 20, name: 'b' }, + { value: 30, name: 'b' }, + { value: 40, name: 'c' }, + { value: 50 }, // name not declared + { value: 60, name: 'c' }, + { value: 70, name: 'd' }, + { value: 80, name: 'c' } + ]); + + doChecks(list); + }); + + it('sourceFormatArrayRows', function () { + const list = new SeriesData( + [ + 'x', + { name: 'q', type: 'ordinal', otherDims: { itemName: 0 } } + ], + new Model() + ); + const source = createSource( + [ + [ 10, 'a' ], + [ 20, 'b' ], + [ 30, 'b' ], + [ 40, 'c' ], + [ 50, null ], + [ 60, 'c' ], + [ 70, 'd' ], + [ 80, 'c' ] + ], + { + seriesLayoutBy: 'column', + sourceHeader: 0, + dimensions: null + }, + SOURCE_FORMAT_ARRAY_ROWS + ); + list.initData(source); + + doChecks(list); + }); + }); + + + describe('id_name_declared_sourceFormat_original', function () { + + it('sourceFormatOriginal', function () { + const list = new SeriesData(['x'], new Model()); + const oneByOne = makeOneByOneChecker(list); + + list.initData([ + { value: 0, id: 'myId_10' }, + { value: 10, id: 555 }, // numeric id. + { value: 20, id: '666%' }, + { value: 30, id: 'myId_good', name: 'b' }, + { value: 40, name: 'b' }, + { value: 50, id: null }, + { value: 60, id: undefined }, + { value: 70, id: NaN }, + { value: 80, id: '' }, + { value: 90, name: 'b' }, + { value: 100 }, + { value: 110, id: 'myId_better' }, + { value: 120, id: 'myId_better' } // duplicated id. + ]); + + oneByOne.nextIdEqualsTo('myId_10'); + oneByOne.nextIdEqualsTo('555'); + oneByOne.nextIdEqualsTo('666%'); + oneByOne.nextIdEqualsTo('myId_good'); + oneByOne.nextIdEqualsTo('b'); + oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); + oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); + oneByOne.nextIdEqualsTo('NaN'); + oneByOne.nextIdEqualsTo(''); + oneByOne.nextIdEqualsTo(`b${NAME_REPEAT_PREFIX}2`); + oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); + oneByOne.nextIdEqualsTo('myId_better'); + oneByOne.nextIdEqualsTo('myId_better'); + + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo('b'); + oneByOne.nextNameEqualsTo('b'); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo('b'); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo(''); + + list.appendData([ + { value: 200, id: 'myId_best' }, + { value: 210, id: 999 }, // numeric id. + { value: 220, id: '777px' }, + { value: 230, name: 'b' }, + { value: 240 } + ]); + + oneByOne.nextIdEqualsTo('myId_best'); + oneByOne.nextIdEqualsTo('999'); + oneByOne.nextIdEqualsTo('777px'); + oneByOne.nextIdEqualsTo(`b${NAME_REPEAT_PREFIX}3`); + oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); + + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo('b'); + oneByOne.nextNameEqualsTo(''); + + list.appendValues([], ['b', 'c', null]); + + oneByOne.nextIdEqualsTo(`b${NAME_REPEAT_PREFIX}4`); + oneByOne.nextIdEqualsTo('c'); + oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); + + oneByOne.nextNameEqualsTo('b'); + oneByOne.nextNameEqualsTo('c'); + oneByOne.nextNameEqualsTo(''); + }); + + }); + + describe('id_name_declared_sourceFormat_arrayRows', function () { + + it('no_ordinalMeta', function () { + testArrayRowsInSource([ + { name: 'x', type: 'number' }, + { name: 'p', type: 'ordinal', otherDims: { itemId: 0 } }, + { name: 'q', type: 'ordinal', otherDims: { itemName: 0 } } + ]); + }); + + it('has_ordinalMeta', function () { + const ordinalMetaP = new OrdinalMeta({ + categories: [], + needCollect: true, + deduplication: true + }); + const ordinalMetaQ = new OrdinalMeta({ + categories: [], + needCollect: true, + deduplication: true + }); + testArrayRowsInSource([ + { name: 'x', type: 'number' }, + { name: 'p', type: 'ordinal', otherDims: { itemId: 0 }, ordinalMeta: ordinalMetaP }, + { name: 'q', type: 'ordinal', otherDims: { itemName: 0 }, ordinalMeta: ordinalMetaQ } + ]); + }); + + function testArrayRowsInSource(dimensionsInfo: SeriesDimensionDefine[]): void { + const list = new SeriesData(dimensionsInfo, new Model()); + const oneByOne = makeOneByOneChecker(list); + + const source = createSource( + [ + [0, 'myId_10', null], + [10, 555, null], // numeric id. + [20, '666%', null], + [30, 'myId_good', 'b'], + [40, null, 'b'], + [50, null, null], + [60, undefined, null], + [70, NaN, null], + [80, '', null], + [90, null, 'b'], + [100, null, null], + [110, 'myId_better', null], + [120, 'myId_better', null] // duplicated id. + ], + { + seriesLayoutBy: 'column', + sourceHeader: 0, + dimensions: null + }, + SOURCE_FORMAT_ARRAY_ROWS + ); + list.initData(source); + oneByOne.nextIdEqualsTo('myId_10'); + oneByOne.nextIdEqualsTo('555'); + oneByOne.nextIdEqualsTo('666%'); + oneByOne.nextIdEqualsTo('myId_good'); + oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); + oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); + oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); + oneByOne.nextIdEqualsTo('NaN'); + oneByOne.nextIdEqualsTo(''); + oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); + oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); + oneByOne.nextIdEqualsTo('myId_better'); + oneByOne.nextIdEqualsTo('myId_better'); + + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo('b'); + oneByOne.nextNameEqualsTo('b'); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo('b'); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo(''); + + list.appendData([ + [ 200, 'myId_best', null ], + [ 210, 999, null ], // numeric id. + [ 220, '777px', null], + [ 230, null, 'b' ], + [ 240, null, null ] + ]); + + oneByOne.nextIdEqualsTo('myId_best'); + oneByOne.nextIdEqualsTo('999'); + oneByOne.nextIdEqualsTo('777px'); + oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); + oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); + + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo(''); + oneByOne.nextNameEqualsTo('b'); + oneByOne.nextNameEqualsTo(''); + + list.appendValues([], ['b', 'c', null]); + + oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); + oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); + oneByOne.nextIdEqualsTo(`${ID_PREFIX}${oneByOne.currGetIdDataIndex()}`); + + oneByOne.nextNameEqualsTo('b'); + oneByOne.nextNameEqualsTo('c'); + oneByOne.nextNameEqualsTo(''); + } + + }); + }); +}); diff --git a/test/ut/spec/data/completeDimensions.test.ts b/test/ut/spec/data/createDimensions.test.ts similarity index 63% rename from test/ut/spec/data/completeDimensions.test.ts rename to test/ut/spec/data/createDimensions.test.ts index 24efc4b382..5bb809badd 100644 --- a/test/ut/spec/data/completeDimensions.test.ts +++ b/test/ut/spec/data/createDimensions.test.ts @@ -18,29 +18,27 @@ */ -import completeDimensions from '../../../../src/data/helper/completeDimensions'; -import { createSource } from '../../../../src/data/Source'; -import { SOURCE_FORMAT_ARRAY_ROWS, SERIES_LAYOUT_BY_COLUMN } from '../../../../src/util/types'; - -type ParametersOfCompleteDimensions = Parameters; - -describe('completeDimensions', function () { - - function doCompleteDimensions( - sysDims: ParametersOfCompleteDimensions[0], - data: ParametersOfCompleteDimensions[1], - opt: ParametersOfCompleteDimensions[2] - ) { - const result = completeDimensions(sysDims, data, opt); - if (result) { - for (let i = 0; i < result.length; i++) { - const item = result[i]; - if (item && item.hasOwnProperty('dimsDef') && (item as any).dimsDef == null) { - delete (item as any).dimsDef; - } +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'; + +type ParametersOfCreateDimensions = Parameters; + +describe('createDimensions', function () { + + function doCreateDimensions( + source: ParametersOfCreateDimensions[0], + opt: ParametersOfCreateDimensions[1] + ): SeriesDimensionDefine[] { + const result = createDimensions(source, opt); + for (let i = 0; i < result.dimensions.length; i++) { + const item = result.dimensions[i]; + if (item && item.hasOwnProperty('dimsDef') && (item as any).dimsDef == null) { + delete (item as any).dimsDef; } } - return result; + return result.dimensions; } it('namesMoreThanDimCount', function () { @@ -77,12 +75,12 @@ describe('completeDimensions', function () { sourceHeader: 0, dimensions: void 0 }, - SOURCE_FORMAT_ARRAY_ROWS, - null + SOURCE_FORMAT_ARRAY_ROWS ); const opt = { - 'dimsDef': [ + 'coordDimensions': sysDims, + 'dimensionsDefine': [ { 'name': 'date', 'displayName': 'date' @@ -128,7 +126,7 @@ describe('completeDimensions', function () { 'displayName': 'sma9' } ], - 'encodeDef': { + 'encodeDefine': { 'x': 'date', 'y': [ 'haOpen', @@ -143,10 +141,10 @@ describe('completeDimensions', function () { 'close' ] }, - 'dimCount': 5 + 'dimensionsCount': 5 }; - const result: unknown = [ + const result: SeriesDimensionDefine[] = [ { 'otherDims': { 'tooltip': false, @@ -157,7 +155,7 @@ describe('completeDimensions', function () { 'coordDim': 'x', 'coordDimIndex': 0, 'type': 'ordinal', - 'ordinalMeta': undefined + 'storeDimIndex': 0 }, { 'otherDims': { @@ -167,7 +165,8 @@ describe('completeDimensions', function () { 'name': 'open', 'coordDim': 'value', 'coordDimIndex': 0, - 'isExtraCoord': true + 'isExtraCoord': true, + 'storeDimIndex': 1 }, { 'otherDims': { @@ -177,7 +176,8 @@ describe('completeDimensions', function () { 'name': 'high', 'coordDim': 'value0', 'coordDimIndex': 0, - 'isExtraCoord': true + 'isExtraCoord': true, + 'storeDimIndex': 2 }, { 'otherDims': { @@ -187,7 +187,8 @@ describe('completeDimensions', function () { 'name': 'low', 'coordDim': 'value1', 'coordDimIndex': 0, - 'isExtraCoord': true + 'isExtraCoord': true, + 'storeDimIndex': 3 }, { 'otherDims': { @@ -197,7 +198,8 @@ describe('completeDimensions', function () { 'name': 'close', 'coordDim': 'value2', 'coordDimIndex': 0, - 'isExtraCoord': true + 'isExtraCoord': true, + 'storeDimIndex': 4 }, { 'otherDims': {}, @@ -205,7 +207,8 @@ describe('completeDimensions', function () { 'name': 'volume', 'coordDim': 'value3', 'coordDimIndex': 0, - 'isExtraCoord': true + 'isExtraCoord': true, + 'storeDimIndex': 5 }, { 'otherDims': {}, @@ -214,7 +217,7 @@ describe('completeDimensions', function () { 'coordDim': 'y', 'coordDimIndex': 0, 'type': 'float', - 'ordinalMeta': undefined + 'storeDimIndex': 6 }, { 'otherDims': {}, @@ -223,7 +226,7 @@ describe('completeDimensions', function () { 'coordDim': 'y', 'coordDimIndex': 3, 'type': 'float', - 'ordinalMeta': undefined + 'storeDimIndex': 7 }, { 'otherDims': {}, @@ -232,7 +235,7 @@ describe('completeDimensions', function () { 'coordDim': 'y', 'coordDimIndex': 2, 'type': 'float', - 'ordinalMeta': undefined + 'storeDimIndex': 8 }, { 'otherDims': {}, @@ -241,7 +244,7 @@ describe('completeDimensions', function () { 'coordDim': 'y', 'coordDimIndex': 1, 'type': 'float', - 'ordinalMeta': undefined + 'storeDimIndex': 9 }, { 'otherDims': {}, @@ -249,135 +252,153 @@ describe('completeDimensions', function () { 'name': 'sma9', 'coordDim': 'value4', 'coordDimIndex': 0, - 'isExtraCoord': true + 'isExtraCoord': true, + 'storeDimIndex': 10 } ]; - expect(doCompleteDimensions(sysDims, source, opt)).toEqual(result); + expect(doCreateDimensions(source, opt)).toEqual(result.map(a => new SeriesDimensionDefine(a))); }); it('differentData', function () { function doTest( - sysDims: ParametersOfCompleteDimensions[0], - data: ParametersOfCompleteDimensions[1], - opt: ParametersOfCompleteDimensions[2], - result: unknown + source: ParametersOfCreateDimensions[0], + opt: ParametersOfCreateDimensions[1], + result: SeriesDimensionDefine[] ) { - expect(doCompleteDimensions(sysDims, data, opt)).toEqual(result); + expect(doCreateDimensions(source, opt)).toEqual(result.map(a => new SeriesDimensionDefine(a))); } // test dimcount - doTest(['x', 'y'], [], null, [ + doTest([], { coordDimensions: ['x', 'y']}, [ { 'otherDims': {}, 'coordDim': 'x', 'coordDimIndex': 0, - 'name': 'x' + 'name': 'x', + 'storeDimIndex': 0 }, { 'otherDims': {}, 'coordDim': 'y', 'coordDimIndex': 0, - 'name': 'y' + 'name': 'y', + 'storeDimIndex': 1 } ]); - doTest(['x', 'y'], [12], null, [ + doTest([12], { coordDimensions: ['x', 'y']}, [ { 'otherDims': {}, 'coordDim': 'x', 'coordDimIndex': 0, - 'name': 'x' + 'name': 'x', + 'storeDimIndex': 0 }, { 'otherDims': {}, 'coordDim': 'y', 'coordDimIndex': 0, - 'name': 'y' + 'name': 'y', + 'storeDimIndex': 1 } ]); - doTest(['x', 'y'], [12, 4], null, [ + doTest([12, 4], { coordDimensions: ['x', 'y']}, [ { 'otherDims': {}, 'coordDim': 'x', 'coordDimIndex': 0, - 'name': 'x' + 'name': 'x', + 'storeDimIndex': 0 }, { 'otherDims': {}, 'coordDim': 'y', 'coordDimIndex': 0, - 'name': 'y' + 'name': 'y', + 'storeDimIndex': 1 } ]); - doTest(['x'], [[32, 55]], null, [ + doTest([[32, 55]], { coordDimensions: ['x']}, [ { 'otherDims': {}, 'coordDim': 'x', 'coordDimIndex': 0, - 'name': 'x' + 'name': 'x', + 'storeDimIndex': 0 } ]); - doTest(['x', 'y', 'z'], [[32, 55]], null, [ + doTest([[32, 55]], { coordDimensions: ['x', 'y', 'z']}, [ { 'otherDims': {}, 'coordDim': 'x', 'coordDimIndex': 0, - 'name': 'x' + 'name': 'x', + 'storeDimIndex': 0 }, { 'otherDims': {}, 'coordDim': 'y', 'coordDimIndex': 0, - 'name': 'y' + 'name': 'y', + 'storeDimIndex': 1 }, { 'otherDims': {}, 'coordDim': 'z', 'coordDimIndex': 0, - 'name': 'z' + 'name': 'z', + 'storeDimIndex': 2 } ]); - doTest(['x'], [[32, 55], [99, 11]], null, [ + doTest([[32, 55], [99, 11]], { coordDimensions: ['x']}, [ { 'otherDims': {}, 'coordDim': 'x', 'coordDimIndex': 0, - 'name': 'x' + 'name': 'x', + 'storeDimIndex': 0 } ]); - doTest(['x', 'y'], [[32, 55], [99, 11]], {dimCount: 4}, [ + doTest([[32, 55], [99, 11]], { + dimensionsCount: 4, + coordDimensions: ['x', 'y'] + }, [ { 'otherDims': {}, 'coordDim': 'x', 'coordDimIndex': 0, - 'name': 'x' + 'name': 'x', + 'storeDimIndex': 0 }, { 'otherDims': {}, 'coordDim': 'y', 'coordDimIndex': 0, - 'name': 'y' + 'name': 'y', + 'storeDimIndex': 1 }, { 'otherDims': {}, 'coordDim': 'value', 'coordDimIndex': 0, 'isExtraCoord': true, - 'name': 'value' + 'name': 'value', + 'storeDimIndex': 2 }, { 'otherDims': {}, 'coordDim': 'value0', 'coordDimIndex': 0, 'isExtraCoord': true, - 'name': 'value0' + 'name': 'value0', + 'storeDimIndex': 3 } ]); }); @@ -391,12 +412,11 @@ describe('completeDimensions', function () { it('differentSysDims', function () { function doTest( - sysDims: ParametersOfCompleteDimensions[0], - data: ParametersOfCompleteDimensions[1], - opt: ParametersOfCompleteDimensions[2], - result: unknown + source: ParametersOfCreateDimensions[0], + opt: ParametersOfCreateDimensions[1], + result: SeriesDimensionDefine[] ) { - expect(doCompleteDimensions(sysDims, data, opt)).toEqual(result); + expect(doCreateDimensions(source, opt)).toEqual(result.map(a => new SeriesDimensionDefine(a))); } const data = [ @@ -405,41 +425,43 @@ describe('completeDimensions', function () { ]; doTest( - ['x', 'y'], data, null, + data, { coordDimensions: ['x', 'y'] }, [ { 'otherDims': {}, 'coordDim': 'x', 'coordDimIndex': 0, 'name': 'x', - 'type': 'ordinal' + 'type': 'ordinal', + 'storeDimIndex': 0 }, { 'otherDims': {}, 'coordDim': 'y', 'coordDimIndex': 0, - 'name': 'y' + 'name': 'y', + 'storeDimIndex': 1 } ] ); doTest( - ['value'], data, null, + data, { coordDimensions: ['value'] }, [ { 'otherDims': {}, 'coordDim': 'value', 'coordDimIndex': 0, 'name': 'value', - 'type': 'ordinal' + 'type': 'ordinal', + 'storeDimIndex': 0 } ] ); doTest( - [{name: 'time', type: 'time' as const}, 'value'], data, - null, + { coordDimensions: [{name: 'time', type: 'time' as const}, 'value'] }, [ { 'otherDims': {}, @@ -447,28 +469,32 @@ describe('completeDimensions', function () { 'type': 'time', 'coordDimIndex': 0, 'ordinalMeta': undefined, - 'coordDim': 'time' + 'coordDim': 'time', + 'storeDimIndex': 0 }, { 'otherDims': {}, 'coordDim': 'value', 'coordDimIndex': 0, - 'name': 'value' + 'name': 'value', + 'storeDimIndex': 1 } ] ); doTest( - [{ - name: 'y', - otherDims: { - tooltip: false - }, - dimsDef: ['base'] - }, { - name: 'x', - dimsDef: ['open', 'close'] - }], data, {}, + data, { + coordDimensions: [{ + name: 'y', + otherDims: { + tooltip: false + }, + dimsDef: ['base'] + }, { + name: 'x', + dimsDef: ['open', 'close'] + }] + }, [ { 'otherDims': { @@ -480,7 +506,8 @@ describe('completeDimensions', function () { 'coordDim': 'y', 'type': 'ordinal', 'displayName': 'base', - 'ordinalMeta': undefined + 'ordinalMeta': undefined, + 'storeDimIndex': 0 }, { 'otherDims': {}, @@ -489,24 +516,26 @@ describe('completeDimensions', function () { 'defaultTooltip': undefined, 'coordDimIndex': 0, 'coordDim': 'x', - 'displayName': 'open' + 'displayName': 'open', + 'storeDimIndex': 1 } ] ); doTest( - [{ - name: 'y', - otherDims: { - tooltip: false - }, - dimsDef: ['base'] - }, { - name: 'x', - dimsDef: ['open', 'close'] - }], data, { - dimsDef: ['基础', '打开', '关闭'], - encodeDef: { + data, { + dimensionsDefine: ['基础', '打开', '关闭'], + coordDimensions: [{ + name: 'y', + otherDims: { + tooltip: false + }, + dimsDef: ['base'] + }, { + name: 'x', + dimsDef: ['open', 'close'] + }], + encodeDefine: { tooltip: [1, 2, 0] } }, @@ -520,7 +549,8 @@ describe('completeDimensions', function () { 'ordinalMeta': undefined, 'coordDimIndex': 0, 'coordDim': 'y', - 'type': 'ordinal' + 'type': 'ordinal', + 'storeDimIndex': 0 }, { 'otherDims': { @@ -530,7 +560,8 @@ describe('completeDimensions', function () { 'displayName': '打开', 'coordDimIndex': 0, 'ordinalMeta': undefined, - 'coordDim': 'x' + 'coordDim': 'x', + 'storeDimIndex': 1 }, { 'otherDims': { @@ -540,24 +571,26 @@ describe('completeDimensions', function () { 'displayName': '关闭', 'ordinalMeta': undefined, 'coordDimIndex': 1, - 'coordDim': 'x' + 'coordDim': 'x', + 'storeDimIndex': 2 } ] ); doTest( - [{ - name: 'y', - otherDims: { - tooltip: false - }, - dimsDef: ['base'] - }, { - name: 'x', - dimsDef: ['open', 'close'] - }], data, { - dimsDef: ['基础', null, '关闭'], - encodeDef: { + data, { + coordDimensions: [{ + name: 'y', + otherDims: { + tooltip: false + }, + dimsDef: ['base'] + }, { + name: 'x', + dimsDef: ['open', 'close'] + }], + dimensionsDefine: ['基础', null, '关闭'], + encodeDefine: { x: [0, 4] } }, @@ -569,7 +602,8 @@ describe('completeDimensions', function () { 'coordDimIndex': 0, 'coordDim': 'x', 'ordinalMeta': undefined, - 'type': 'ordinal' + 'type': 'ordinal', + 'storeDimIndex': 0 }, { 'otherDims': { @@ -580,7 +614,8 @@ describe('completeDimensions', function () { 'ordinalMeta': undefined, 'defaultTooltip': undefined, 'coordDimIndex': 0, - 'coordDim': 'y' + 'coordDim': 'y', + 'storeDimIndex': 1 }, { 'otherDims': {}, @@ -588,7 +623,8 @@ describe('completeDimensions', function () { 'displayName': '关闭', 'coordDimIndex': 0, 'isExtraCoord': true, - 'coordDim': 'value' + 'coordDim': 'value', + 'storeDimIndex': 2 } ] ); @@ -605,18 +641,20 @@ describe('completeDimensions', function () { it('dimsDef', function () { function doTest( - sysDims: ParametersOfCompleteDimensions[0], - data: ParametersOfCompleteDimensions[1], - opt: ParametersOfCompleteDimensions[2], - result: unknown + source: ParametersOfCreateDimensions[0], + opt: ParametersOfCreateDimensions[1], + result: SeriesDimensionDefine[] ) { - expect(doCompleteDimensions(sysDims, data, opt)).toEqual(result); + expect(doCreateDimensions(source, opt)).toEqual(result.map(a => new SeriesDimensionDefine(a))); } const data = [['iw', 332, 4434, 323, 59], ['vrr', 44, 11, 144, 55]]; doTest( - ['x', 'y', 'value'], data, - {dimsDef: ['挨克思', null, '歪溜']}, + data, + { + dimensionsDefine: ['挨克思', null, '歪溜'], + coordDimensions: ['x', 'y', 'value'] + }, [ { 'otherDims': {}, @@ -624,27 +662,33 @@ describe('completeDimensions', function () { 'name': '挨克思', 'type': 'ordinal', 'coordDim': 'x', - 'coordDimIndex': 0 + 'coordDimIndex': 0, + 'storeDimIndex': 0 }, { 'otherDims': {}, 'coordDim': 'y', 'coordDimIndex': 0, - 'name': 'y' + 'name': 'y', + 'storeDimIndex': 1 }, { 'otherDims': {}, 'displayName': '歪溜', 'name': '歪溜', 'coordDim': 'value', - 'coordDimIndex': 0 + 'coordDimIndex': 0, + 'storeDimIndex': 2 } ] ); doTest( - ['x', 'y', 'value'], data, - {dimsDef: ['挨克思', null, {type: 'ordinal' as const}]}, // no name but only type + data, + { + dimensionsDefine: ['挨克思', null, {type: 'ordinal' as const}], + coordDimensions: ['x', 'y', 'value'] + }, // no name but only type [ { 'otherDims': {}, @@ -652,27 +696,33 @@ describe('completeDimensions', function () { 'name': '挨克思', 'type': 'ordinal', 'coordDim': 'x', - 'coordDimIndex': 0 + 'coordDimIndex': 0, + 'storeDimIndex': 0 }, { 'otherDims': {}, 'coordDim': 'y', 'coordDimIndex': 0, - 'name': 'y' + 'name': 'y', + 'storeDimIndex': 1 }, { 'otherDims': {}, 'name': 'value', 'coordDim': 'value', 'type': 'ordinal', - 'coordDimIndex': 0 + 'coordDimIndex': 0, + 'storeDimIndex': 2 } ] ); doTest( - [{name: 'time', type: 'time' as const}, 'value'], data, - {dimsDef: [{name: '泰亩', type: 'ordinal'}, {name: '歪溜', type: 'float'}]}, + data, + { + dimensionsDefine: [{name: '泰亩', type: 'ordinal'}, {name: '歪溜', type: 'float'}], + coordDimensions: [{name: 'time', type: 'time' as const}, 'value'] + }, [ { 'otherDims': {}, @@ -681,7 +731,8 @@ describe('completeDimensions', function () { 'type': 'ordinal', 'ordinalMeta': undefined, 'coordDimIndex': 0, - 'coordDim': 'time' + 'coordDim': 'time', + 'storeDimIndex': 0 }, { 'otherDims': {}, @@ -689,7 +740,38 @@ describe('completeDimensions', function () { 'name': '歪溜', 'type': 'float', 'coordDim': 'value', - 'coordDimIndex': 0 + 'coordDimIndex': 0, + 'storeDimIndex': 1 + } + ] + ); + + // Duplicate name + doTest( + data, + { + dimensionsDefine: [{name: '泰亩', type: 'ordinal'}, {name: '泰亩', type: 'float'}], + coordDimensions: [{name: 'time', type: 'time' as const}, 'value'] + }, + [ + { + 'otherDims': {}, + 'displayName': '泰亩', + 'name': '泰亩', + 'type': 'ordinal', + 'ordinalMeta': undefined, + 'coordDimIndex': 0, + 'coordDim': 'time', + 'storeDimIndex': 0 + }, + { + 'otherDims': {}, + 'displayName': '泰亩', + 'name': '泰亩0', + 'type': 'float', + 'coordDim': 'value', + 'coordDimIndex': 0, + 'storeDimIndex': 1 } ] ); @@ -705,20 +787,19 @@ describe('completeDimensions', function () { it('encodeDef', function () { function doTest( - sysDims: ParametersOfCompleteDimensions[0], - data: ParametersOfCompleteDimensions[1], - opt: ParametersOfCompleteDimensions[2], - result: unknown + source: ParametersOfCreateDimensions[0], + opt: ParametersOfCreateDimensions[1], + result: SeriesDimensionDefine[] ) { - expect(doCompleteDimensions(sysDims, data, opt)).toEqual(result); + expect(doCreateDimensions(source, opt)).toEqual(result.map(a => new SeriesDimensionDefine(a))); } const data = [['iw', 332, 4434, 323, 'd8', 59], ['vrr', 44, 11, 144, '-', 55]]; doTest( - null, data, + data, { - encodeDef: { + encodeDefine: { x: 2, y: [1, 4], tooltip: 2, @@ -732,16 +813,17 @@ describe('completeDimensions', function () { 'coordDimIndex': 0, 'name': 'value', 'isExtraCoord': true, - 'type': 'ordinal' + 'type': 'ordinal', + 'storeDimIndex': 0 } ] ); doTest( - null, data, + data, { - dimsDef: ['挨克思', null, '歪溜'], - encodeDef: { + dimensionsDefine: ['挨克思', null, '歪溜'], + encodeDefine: { x: 2, y: [1, 4], tooltip: 2, @@ -756,13 +838,15 @@ describe('completeDimensions', function () { 'type': 'ordinal', 'coordDim': 'value', 'coordDimIndex': 0, - 'isExtraCoord': true + 'isExtraCoord': true, + 'storeDimIndex': 0 }, { 'otherDims': {}, 'coordDim': 'y', 'coordDimIndex': 0, - 'name': 'y' + 'name': 'y', + 'storeDimIndex': 1 }, { 'otherDims': { @@ -771,16 +855,18 @@ describe('completeDimensions', function () { 'displayName': '歪溜', 'name': '歪溜', 'coordDim': 'x', - 'coordDimIndex': 0 + 'coordDimIndex': 0, + 'storeDimIndex': 2 } ] ); doTest( - ['x', {name: 'y', type: 'time' as const}, 'z'], data, + data, { - dimsDef: ['挨克思', null, '歪溜'], - encodeDef: { + dimensionsDefine: ['挨克思', null, '歪溜'], + coordDimensions: ['x', {name: 'y', type: 'time' as const}, 'z'], + encodeDefine: { x: 2, y: [1, 4], tooltip: 2, @@ -794,7 +880,8 @@ describe('completeDimensions', function () { 'name': '挨克思', 'type': 'ordinal', 'coordDim': 'z', - 'coordDimIndex': 0 + 'coordDimIndex': 0, + 'storeDimIndex': 0 }, { 'otherDims': {}, @@ -802,7 +889,8 @@ describe('completeDimensions', function () { 'coordDimIndex': 0, 'name': 'y', 'type': 'time', - 'ordinalMeta': undefined + 'ordinalMeta': undefined, + 'storeDimIndex': 1 }, { 'otherDims': { @@ -811,17 +899,19 @@ describe('completeDimensions', function () { 'displayName': '歪溜', 'name': '歪溜', 'coordDim': 'x', - 'coordDimIndex': 0 + 'coordDimIndex': 0, + 'storeDimIndex': 2 } ] ); doTest( - [{name: 'time', type: 'time' as const}, 'value'], data, + data, { // dimsDef type 'ordinal' has higher priority then sysDims type 'time'. - dimsDef: [{name: '泰亩', type: 'ordinal'}, {name: '歪溜', type: 'float'}], - encodeDef: { + dimensionsDefine: [{name: '泰亩', type: 'ordinal'}, {name: '歪溜', type: 'float'}], + coordDimensions: [{name: 'time', type: 'time' as const}, 'value'], + encodeDefine: { tooltip: 2 } }, @@ -833,7 +923,8 @@ describe('completeDimensions', function () { 'type': 'ordinal', 'ordinalMeta': undefined, 'coordDimIndex': 0, - 'coordDim': 'time' + 'coordDim': 'time', + 'storeDimIndex': 0 }, { 'otherDims': {}, @@ -841,17 +932,19 @@ describe('completeDimensions', function () { 'name': '歪溜', 'type': 'float', 'coordDim': 'value', - 'coordDimIndex': 0 + 'coordDimIndex': 0, + 'storeDimIndex': 1 } ] ); doTest( - [{name: 'time', type: 'time' as const}, 'value'], data, + data, { // dimsDef type 'ordinal' has higher priority then sysDims type 'time'. - dimsDef: [{name: '泰亩', type: 'ordinal'}, {name: '歪溜', type: 'float'}], - encodeDef: { + dimensionsDefine: [{name: '泰亩', type: 'ordinal'}, {name: '歪溜', type: 'float'}], + coordDimensions: [{name: 'time', type: 'time' as const}, 'value'], + encodeDefine: { tooltip: 2 } }, @@ -863,7 +956,8 @@ describe('completeDimensions', function () { 'type': 'ordinal', 'ordinalMeta': undefined, 'coordDimIndex': 0, - 'coordDim': 'time' + 'coordDim': 'time', + 'storeDimIndex': 0 }, { 'otherDims': {}, @@ -871,7 +965,8 @@ describe('completeDimensions', function () { 'name': '歪溜', 'type': 'float', 'coordDim': 'value', - 'coordDimIndex': 0 + 'coordDimIndex': 0, + 'storeDimIndex': 1 } ] ); diff --git a/test/ut/spec/data/dataTransform.test.ts b/test/ut/spec/data/dataTransform.test.ts index 316064fb30..04cc20eae3 100644 --- a/test/ut/spec/data/dataTransform.test.ts +++ b/test/ut/spec/data/dataTransform.test.ts @@ -17,10 +17,10 @@ * under the License. */ -import { EChartsType } from '../../../../src/echarts'; +import { EChartsType } from '@/src/echarts'; import { createChart, removeChart, getECModel } from '../../core/utHelper'; -import { EChartsOption } from '../../../../src/export/option'; -import { retrieveRawValue } from '../../../../src/data/helper/dataProvider'; +import { EChartsOption } from '@/src/export/option'; +import { retrieveRawValue } from '@/src/data/helper/dataProvider'; describe('dataTransform', function () { diff --git a/test/ut/spec/data/dataValueHelper.test.ts b/test/ut/spec/data/dataValueHelper.test.ts index 729395cb0c..a1d971a4ed 100644 --- a/test/ut/spec/data/dataValueHelper.test.ts +++ b/test/ut/spec/data/dataValueHelper.test.ts @@ -18,7 +18,7 @@ */ -import * as dataValueHelper from '../../../../src/data/helper/dataValueHelper'; +import * as dataValueHelper from '@/src/data/helper/dataValueHelper'; const NO_SUCH_CASE = 'NO_SUCH_CASE'; diff --git a/test/ut/spec/model/Global.test.ts b/test/ut/spec/model/Global.test.ts index 632f7f1cef..a115b99a41 100755 --- a/test/ut/spec/model/Global.test.ts +++ b/test/ut/spec/model/Global.test.ts @@ -18,13 +18,13 @@ * under the License. */ -import { EChartsType } from '../../../../src/echarts'; +import { EChartsType } from '@/src/echarts'; import { createChart, getECModel } from '../../core/utHelper'; -import { ComponentMainType, ParsedValue } from '../../../../src/util/types'; -import SeriesModel from '../../../../src/model/Series'; -import ComponentModel from '../../../../src/model/Component'; -import ChartView from '../../../../src/view/Chart'; -import { EChartsOption } from '../../../../src/export/option'; +import { ComponentMainType, ParsedValue } from '@/src/util/types'; +import SeriesModel from '@/src/model/Series'; +import ComponentModel from '@/src/model/Component'; +import ChartView from '@/src/view/Chart'; +import { EChartsOption } from '@/src/export/option'; type OriginModelView = { model: SeriesModel; diff --git a/test/ut/spec/model/componentDependency.test.ts b/test/ut/spec/model/componentDependency.test.ts index 31e2f6b55f..b86cf90981 100755 --- a/test/ut/spec/model/componentDependency.test.ts +++ b/test/ut/spec/model/componentDependency.test.ts @@ -18,8 +18,8 @@ * under the License. */ -import ComponentModel, { ComponentModelConstructor } from '../../../../src/model/Component'; -import { ComponentMainType } from '../../../../src/util/types'; +import ComponentModel, { ComponentModelConstructor } from '@/src/model/Component'; +import { ComponentMainType } from '@/src/util/types'; const componentModelConstructor = ComponentModel as ComponentModelConstructor; diff --git a/test/ut/spec/model/componentMissing.test.ts b/test/ut/spec/model/componentMissing.test.ts index 67e19322b3..bfbe58a810 100644 --- a/test/ut/spec/model/componentMissing.test.ts +++ b/test/ut/spec/model/componentMissing.test.ts @@ -18,18 +18,18 @@ * under the License. */ -import { init, use, EChartsType } from '../../../../src/export/core'; +import { init, use, EChartsType } from '@/src/export/core'; import { PieChart -} from '../../../../src/export/charts'; +} from '@/src/export/charts'; import { TitleComponent -} from '../../../../src/export/components'; +} from '@/src/export/components'; import { CanvasRenderer -} from '../../../../src/export/renderers'; +} from '@/src/export/renderers'; use([PieChart, TitleComponent, CanvasRenderer]); -import { EChartsOption } from '../../../../src/export/option'; +import { EChartsOption } from '@/src/export/option'; function createChart(theme?: object): EChartsType { diff --git a/test/ut/spec/model/timelineMediaOptions.test.ts b/test/ut/spec/model/timelineMediaOptions.test.ts index d0bf40245f..0b2d436443 100755 --- a/test/ut/spec/model/timelineMediaOptions.test.ts +++ b/test/ut/spec/model/timelineMediaOptions.test.ts @@ -18,13 +18,13 @@ * under the License. */ -import { EChartsType } from '../../../../src/echarts'; -import SeriesModel from '../../../../src/model/Series'; -import { ParsedValue } from '../../../../src/util/types'; -import { LegendOption } from '../../../../src/component/legend/LegendModel'; -import TimelineModel from '../../../../src/component/timeline/TimelineModel'; +import { EChartsType } from '@/src/echarts'; +import SeriesModel from '@/src/model/Series'; +import { ParsedValue } from '@/src/util/types'; +import { LegendOption } from '@/src/component/legend/LegendModel'; +import TimelineModel from '@/src/component/timeline/TimelineModel'; import { createChart, getECModel } from '../../core/utHelper'; -import { EChartsOption } from '../../../../src/export/option'; +import { EChartsOption } from '@/src/export/option'; describe('timelineMediaOptions', function () { diff --git a/test/ut/spec/scale/interval.test.ts b/test/ut/spec/scale/interval.test.ts index aec5999e0d..67965d7792 100755 --- a/test/ut/spec/scale/interval.test.ts +++ b/test/ut/spec/scale/interval.test.ts @@ -19,11 +19,11 @@ */ import { createChart, getECModel } from '../../core/utHelper'; -import { EChartsType } from '../../../../src/echarts'; -import CartesianAxisModel from '../../../../src/coord/cartesian/AxisModel'; -import IntervalScale from '../../../../src/scale/Interval'; -import { intervalScaleNiceTicks } from '../../../../src/scale/helper'; -import { getPrecisionSafe } from '../../../../src/util/number'; +import { EChartsType } from '@/src/echarts'; +import CartesianAxisModel from '@/src/coord/cartesian/AxisModel'; +import IntervalScale from '@/src/scale/Interval'; +import { intervalScaleNiceTicks } from '@/src/scale/helper'; +import { getPrecisionSafe } from '@/src/util/number'; describe('scale_interval', function () { diff --git a/test/ut/spec/series/custom.test.ts b/test/ut/spec/series/custom.test.ts index e6083fdcd6..ec0af5769d 100644 --- a/test/ut/spec/series/custom.test.ts +++ b/test/ut/spec/series/custom.test.ts @@ -17,10 +17,10 @@ * under the License. */ -import { EChartsType } from '../../../../src/echarts'; +import { EChartsType } from '@/src/echarts'; import { createChart } from '../../core/utHelper'; -import { ZRColor } from '../../../../src/util/types'; -import { CustomSeriesRenderItemAPI, CustomSeriesRenderItemParams } from '../../../../src/chart/custom/CustomSeries'; +import { ZRColor } from '@/src/util/types'; +import { CustomSeriesRenderItemAPI, CustomSeriesRenderItemParams } from '@/src/chart/custom/CustomSeries'; describe('custom_series', function () { diff --git a/test/ut/spec/util/graphic.test.ts b/test/ut/spec/util/graphic.test.ts index 5a3b1962d1..f6856ffe02 100755 --- a/test/ut/spec/util/graphic.test.ts +++ b/test/ut/spec/util/graphic.test.ts @@ -21,7 +21,7 @@ import { subPixelOptimize, subPixelOptimizeLine, subPixelOptimizeRect } from 'zrender/src/graphic/helper/subPixelOptimize'; -import { lineLineIntersect } from '../../../../src/util/graphic'; +import { lineLineIntersect } from '@/src/util/graphic'; describe('util/graphic', function () { diff --git a/test/ut/spec/util/layout.test.ts b/test/ut/spec/util/layout.test.ts index 4c43ceb17a..7006229685 100644 --- a/test/ut/spec/util/layout.test.ts +++ b/test/ut/spec/util/layout.test.ts @@ -19,8 +19,8 @@ */ // import { Dictionary } from 'zrender/src/core/types'; -import { mergeLayoutParam } from '../../../../src/util/layout'; -import { BoxLayoutOptionMixin } from '../../../../src/util/types'; +import { mergeLayoutParam } from '@/src/util/layout'; +import { BoxLayoutOptionMixin } from '@/src/util/types'; describe('util/number', function () { diff --git a/test/ut/spec/util/model.test.ts b/test/ut/spec/util/model.test.ts index bde88823b0..40f7890324 100755 --- a/test/ut/spec/util/model.test.ts +++ b/test/ut/spec/util/model.test.ts @@ -18,7 +18,7 @@ * under the License. */ -import { compressBatches } from '../../../../src/util/model'; +import { compressBatches } from '@/src/util/model'; describe('util/model', function () { diff --git a/test/ut/spec/util/number.test.ts b/test/ut/spec/util/number.test.ts index e211ea832f..52d872130c 100755 --- a/test/ut/spec/util/number.test.ts +++ b/test/ut/spec/util/number.test.ts @@ -22,7 +22,7 @@ import { linearMap, parseDate, reformIntervals, getPrecisionSafe, getPrecision, getPercentWithPrecision, quantityExponent, quantity, nice, isNumeric, numericToNumber, addSafe -} from '../../../../src/util/number'; +} from '@/src/util/number'; describe('util/number', function () { diff --git a/test/ut/tsconfig.json b/test/ut/tsconfig.json index 0cdbc3d770..63885f0a1e 100644 --- a/test/ut/tsconfig.json +++ b/test/ut/tsconfig.json @@ -6,7 +6,12 @@ "noImplicitThis": true, "strictBindCallApply": true, - "esModuleInterop": true + "esModuleInterop": true, + + "baseUrl": "./", + "paths": { + "@/*": ["../../*"] + } }, "include": [ "**/*.ts"