| /* |
| * 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; |
| } |