diff --git a/src/chart/helper/createSeriesData.ts b/src/chart/helper/createSeriesData.ts index 04872e9201..8fa209c1e3 100644 --- a/src/chart/helper/createSeriesData.ts +++ b/src/chart/helper/createSeriesData.ts @@ -159,7 +159,6 @@ function createSeriesData( const data = new SeriesData(dimInfoList, seriesModel); data.setCalculationInfo(stackCalculationInfo); - console.log(stackCalculationInfo) const dimValueGetter = firstCategoryDimIndex != null @@ -171,11 +170,19 @@ function createSeriesData( : this.defaultDimValueGetter(itemOpt, dimName, dataIndex, dimIndex); } : null; + let storage; + if (!isOriginalSource) { + storage = sourceManager.getSharedDataStorage(dimInfoList); + if (stackCalculationInfo.stackedOverDimension) { + storage.appendDimension(stackCalculationInfo.stackedOverDimension, 'float'); + storage.appendDimension(stackCalculationInfo.stackResultDimension, 'float'); + } + } data.hasItemOption = false; data.initData( // Try to reuse the data storage in sourceManager if using dataset. - isOriginalSource ? source : sourceManager.getDataStorage(dimInfoList) + isOriginalSource ? source : storage , null, dimValueGetter); return data; diff --git a/src/data/DataStorage.ts b/src/data/DataStorage.ts index e8d0affefe..6d214ce128 100644 --- a/src/data/DataStorage.ts +++ b/src/data/DataStorage.ts @@ -200,28 +200,48 @@ class DataStorage { }) as DataStorageDimensionDefine); const dimensionsIdxMap: Dictionary = {}; - let prefix = ''; + let needsHasOwn = false; // Needs to add prefix if key is used in object prototype for (let i = 0; i < dimensions.length; i++) { const name = dimensions[i].name; if ((emptyObj as any)[name] != null) { - prefix = '$$'; + needsHasOwn = true; + break; } } for (let i = 0; i < dimensions.length; i++) { const dim = dimensions[i]; const name = dim.name; - dimensionsIdxMap[prefix + name] = i; + dimensionsIdxMap[name] = i; } - // We use different functions because it may be a hotspot code. - this.getDimensionIndex = prefix ? function (dim) { - return dimensionsIdxMap[prefix + dim]; - } : function (dim) { - return dimensionsIdxMap[dim]; + const updateGetDimensionIndex = () => { + this.getDimensionIndex = needsHasOwn ? function (dim) { + return dimensionsIdxMap.hasOwnProperty(dim) ? dimensionsIdxMap[dim] : undefined; + } : function (dim) { + return dimensionsIdxMap[dim]; + }; }; + this.appendDimension = (dimName, dimType) => { + if (!needsHasOwn && (emptyObj as any)[dimName] != null) { + needsHasOwn = true; + updateGetDimensionIndex(); + } + const dimensions = this._dimensions; + const idx = dimensions.length; + dimensions.push({ + name: dimName, + type: dimType + }); + this._chunks.push(new dataCtors[dimType || 'float'](this._rawCount)); + this._rawExtent.push(getInitialExtent()); + dimensionsIdxMap[dimName] = idx; + }; + + updateGetDimensionIndex(); + this._initDataFromProvider(0, provider.count()); } @@ -234,6 +254,7 @@ class DataStorage { } getDimensionIndex: (dim: DimensionName) => number; + appendDimension: (dim: DimensionName, type: DataStorageDimensionType) => void; getDimensionCount() { return this._dimensions.length; @@ -833,12 +854,27 @@ class DataStorage { map(dims: DimensionIndex[], cb: MapCb): DataStorage { // TODO only clone picked chunks. const target = this.clone(dims); - const targetChunks = target._chunks; + this._updateDims(target, dims, cb); + return target; + } + /** + * Danger only can be used in SeriesData. + */ + modify(dims: DimensionIndex[], cb: MapCb) { + this._updateDims(this, dims, cb); + } + + _updateDims( + target: DataStorage, + dims: DimensionIndex[], + cb: MapCb + ) { + const targetChunks = target._chunks; const tmpRetValue = []; const dimSize = dims.length; - const dataCount = this.count(); + const dataCount = target.count(); const values = []; const rawExtent = target._rawExtent; @@ -847,7 +883,7 @@ class DataStorage { } for (let dataIndex = 0; dataIndex < dataCount; dataIndex++) { - const rawIndex = this.getRawIndex(dataIndex); + const rawIndex = target.getRawIndex(dataIndex); for (let k = 0; k < dimSize; k++) { values[k] = targetChunks[dims[k]][rawIndex]; @@ -881,8 +917,6 @@ class DataStorage { } } } - - return target; } /** diff --git a/src/data/SeriesData.ts b/src/data/SeriesData.ts index d02ffccac9..d39eba2b32 100644 --- a/src/data/SeriesData.ts +++ b/src/data/SeriesData.ts @@ -321,7 +321,13 @@ class SeriesData< } private _getStoreDimIndex(dim: DimensionLoose): DimensionIndex { - return this._store.getDimensionIndex(this.getDimension(dim)); + const dimIdx = this._store.getDimensionIndex(this.getDimension(dim)); + if (__DEV__) { + if (dimIdx == null) { + throw new Error('Unkown dimension ' + dim); + } + } + return dimIdx; } /** @@ -389,14 +395,7 @@ class SeriesData< const dimensions = this.dimensions; const dimensionInfos = map(dimensions, dimName => this._dimensionInfos[dimName]); if (data instanceof DataStorage) { - if (data.canUse(dimensionInfos)) { - store = data; - } - // Sync failed - else { - // Needs to recreate data storage if it's not match the given dimension. - data = data.getSource(); - } + store = data; } if (!store) { @@ -764,10 +763,6 @@ class SeriesData< const dimIndices = map(normalizeDimensions(dims), this._getStoreDimIndex, this); - if (__DEV__) { - validateDimensions(this, dimIndices); - } - this._store.each(dimIndices, (fCtx ? zrUtil.bind(cb as any, fCtx as any) : cb) as any @@ -799,10 +794,6 @@ class SeriesData< const dimIndices = map(normalizeDimensions(dims), this._getStoreDimIndex, this); - if (__DEV__) { - validateDimensions(this, dimIndices); - } - // Clone first this._store = this._store.clone(); this._store.filterSelf(dimIndices, (fCtx @@ -829,10 +820,6 @@ class SeriesData< dimIndices.push(dimIdx); }); - if (__DEV__) { - validateDimensions(this, dimIndices); - } - this._store = this._store.clone(); this._store.selectRange(innerRange); return this; @@ -893,10 +880,6 @@ class SeriesData< normalizeDimensions(dims), this._getStoreDimIndex, this ); - if (__DEV__) { - validateDimensions(this, dimIndices); - } - const list = cloneListForMapAndSample(this); list._store = this._store.map(dimIndices, (fCtx ? zrUtil.bind(cb as any, fCtx as any) @@ -905,6 +888,41 @@ class SeriesData< 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; + /* eslint-disable-next-line */ + modify(dims: [DimensionLoose, DimensionLoose], cb: MapCb2, ctx?: Ctx, ctxCompat?: Ctx): void; + modify( + dims: ItrParamDims, + cb: MapCb, + ctx?: Ctx, + ctxCompat?: Ctx + ) { + // 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 + ); + + this._store.modify(dimIndices, (fCtx + ? zrUtil.bind(cb as any, fCtx as any) + : cb) as any + ); + } + /** * Large data down sampling on given dimension * @param sampleIndex Sample index for name and id @@ -1207,13 +1225,13 @@ class SeriesData< // ---------------------------------------------------------- private static internalField = (function () { - prepareInvertedIndex = function (list: SeriesData): void { - const invertedIndicesMap = list._invertedIndicesMap; + prepareInvertedIndex = function (data: SeriesData): void { + const invertedIndicesMap = data._invertedIndicesMap; zrUtil.each(invertedIndicesMap, function (invertedIndices, dim) { - const dimInfo = list._dimensionInfos[dim]; + const dimInfo = data._dimensionInfos[dim]; // Currently, only dimensions that has ordinalMeta can create inverted indices. const ordinalMeta = dimInfo.ordinalMeta; - const store = list._store; + const store = data._store; const dimIdx = store.getDimensionIndex(dim); if (ordinalMeta) { invertedIndices = invertedIndicesMap[dim] = new CtorInt32Array( @@ -1233,18 +1251,18 @@ class SeriesData< }; getIdNameFromStore = function ( - list: SeriesData, dimIdx: number, idx: number + data: SeriesData, dimIdx: number, idx: number ): string { - return convertOptionIdName(list._getCategory(dimIdx, idx), null); + return convertOptionIdName(data._getCategory(dimIdx, idx), null); }; /** * @see the comment of `List['getId']`. */ - getId = function (list: SeriesData, rawIndex: number): string { - let id = list._idList[rawIndex]; - if (id == null && list._idDimIdx != null) { - id = getIdNameFromStore(list, list._idDimIdx, rawIndex); + 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; @@ -1261,15 +1279,6 @@ class SeriesData< return dimensions; }; - validateDimensions = function (list: SeriesData, dims: DimensionIndex[]): void { - for (let i = 0; i < dims.length; i++) { - // stroage may be empty when no data, so use - // dimensionInfos to check. - if (!list.dimensions[dims[i]]) { - console.error('Unkown dimension ' + dims[i]); - } - } - }; // Data in excludeDimensions is copied, otherwise transfered. cloneListForMapAndSample = function (original: SeriesData): SeriesData { diff --git a/src/data/helper/dataStackHelper.ts b/src/data/helper/dataStackHelper.ts index a25f4c3076..6ec95d3cc7 100644 --- a/src/data/helper/dataStackHelper.ts +++ b/src/data/helper/dataStackHelper.ts @@ -104,8 +104,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 storage. 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) { diff --git a/src/data/helper/sourceManager.ts b/src/data/helper/sourceManager.ts index c1285df6dd..5425ec34d8 100644 --- a/src/data/helper/sourceManager.ts +++ b/src/data/helper/sourceManager.ts @@ -364,11 +364,13 @@ export class SourceManager { } /** + * + * Get a data storage which can be shared across series. * Only available for series. * * @param dimensions Dimensions that are generated in series. */ - getDataStorage(seriesDims: SeriesDimensionDefine[]): DataStorage { + getSharedDataStorage(seriesDims: SeriesDimensionDefine[]): DataStorage { if (__DEV__) { assert(isSeries(this._sourceHost), 'Can only call getDataStorage on series source manager.'); } diff --git a/src/processor/dataStack.ts b/src/processor/dataStack.ts index 88cdc6839e..0d285c12aa 100644 --- a/src/processor/dataStack.ts +++ b/src/processor/dataStack.ts @@ -87,7 +87,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 +141,5 @@ function calculateStack(stackInfoList: StackInfo[]) { return resultVal; }); - - (targetData.hostModel as SeriesModel).setData(newData); - // Update for consequent calculation - targetStackInfo.data = newData; }); } diff --git a/test/ut/spec/data/SeriesData.test.ts b/test/ut/spec/data/SeriesData.test.ts index 4be6b3ceea..a87abc2c30 100644 --- a/test/ut/spec/data/SeriesData.test.ts +++ b/test/ut/spec/data/SeriesData.test.ts @@ -23,8 +23,11 @@ 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 DataDimensionInfo from '@/src/data/DataDimensionInfo'; +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 DataStorage from '@/src/data/DataStorage'; import { DefaultDataProvider } from '@/src/data/helper/dataProvider'; @@ -210,30 +213,6 @@ describe('SeriesData', function () { return store; } - it('should use storage if dimensions types are same', function () { - const store = createStore(); - const data = new SeriesData([{type: 'ordinal', name: 'dim0'}, {type: 'float', name: 'dim1'}], null); - data.initData(store); - expect(data.getStorage()).toBe(store); - }); - it('should recreate storage if dimensions types not compatitable', function () { - const store = createStore(); - const dims = [{ type: 'float', name: 'dim0' }, { type: 'float', name: 'dim1'}]; - const data = new SeriesData(dims, null); - data.initData(store); - expect(data.getStorage()).not.toBe(store); - // Can reuse now - const data2 = new SeriesData(dims, null); - data2.initData(data.getStorage()); - expect(data2.getStorage()).toBe(data.getStorage()); - }); - it('should recreate storage if dimensions name not exits', function () { - const store = createStore(); - const dims = [{ type: 'float', name: 'dim2' }]; - const data = new SeriesData(dims, null); - data.initData(store); - expect(data.getStorage()).not.toBe(store); - }); it('SeriesData can still get other dims value from storage when only part of dims are given.', function () { const provider = new DefaultDataProvider([['A', 15, 20], ['B', 25, 30], ['C', 35, 40]]); @@ -497,7 +476,7 @@ describe('SeriesData', function () { ]); }); - function testArrayRowsInSource(dimensionsInfo: DataDimensionInfo[]): void { + function testArrayRowsInSource(dimensionsInfo: SeriesDimensionDefine[]): void { const list = new SeriesData(dimensionsInfo, new Model()); const oneByOne = makeOneByOneChecker(list);