LoCs / echarts-5.5.0-rc.2 /src /data /helper /SeriesDataSchema.ts
ZoeDuan's picture
Upload 1382 files
4bb817b verified
raw
history blame
9.92 kB
/*
* 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;
}