Spaces:
Running
Running
/* | |
* 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<DimensionIndex, DimensionName>; | |
}, 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<typeof inner>['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 <index, type, ordinalMeta> to determine whether data can be shared. | |
// (Because 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 <property, type, ordinalMeta> 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<DimensionIndex, DimensionName> { | |
const dataDimNameMap = createHashMap<DimensionIndex, DimensionName>(); | |
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<DimensionIndex, DimensionName> { | |
const innerSource = inner(source); | |
return innerSource.dimNameMap || ( | |
innerSource.dimNameMap = createDimNameMap(source.dimensionsDefine) | |
); | |
} | |
export function shouldOmitUnusedDimensions(dimCount: number): boolean { | |
return dimCount > 30; | |
} | |