blob: 1d79f15c69375b51985f6db37bac9de753a9ddfc [file]
/*
* 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 {each, isString} from 'zrender/src/core/util';
import SeriesDimensionDefine from '../SeriesDimensionDefine';
import SeriesModel from '../../model/Series';
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 dimension will be appended at the tail, and should not
// be used on a shared store, but should create a brand new storage later.
store?: DataStore;
};
type EnableDataStackDimensionsInputLegacy = (SeriesDimensionDefine | string)[];
/**
* Note that it is too complicated to support 3d stack by value
* (have to create two-dimension inverted index), so in 3d case
* we just support that stacked by index.
*
* Stack is calculated in `src/processor/dataStack.ts`.
*
* @param seriesModel
* @param dimensionsInput The same as the input of <module:echarts/data/SeriesData>.
* The input will be modified.
* @param opt
* @param opt.stackedCoordDimension Specify a coord dimension if needed.
* @param opt.byIndex=false
* @return calculationInfo
* {
* stackedDimension: string
* stackedByDimension: string
* isStackedByIndex: boolean
* stackedOverDimension: string
* stackResultDimension: string
* }
*/
export function enableDataStack(
seriesModel: SeriesModel<SeriesOption & SeriesStackOptionMixin>,
dimensionsInput: EnableDataStackDimensionsInput | EnableDataStackDimensionsInputLegacy,
opt?: {
// Backward compat
stackedCoordDimension?: string
byIndex?: boolean
}
): Pick<
DataCalculationInfo<unknown>,
'stackedDimension'
| 'stackedByDimension'
| 'isStackedByIndex'
| 'stackedOverDimension'
| 'stackResultDimension'
> {
opt = opt || {};
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;
}
// compatible: when `stack` is set as '', do not stack.
const mayStack = !!(seriesModel && seriesModel.get('stack'));
let stackedByDimInfo: SeriesDimensionDefine;
let stackedDimInfo: SeriesDimensionDefine;
let stackResultDimension: string;
let stackedOverDimension: string;
let allDimTypesAreNotOrdinalAndTime = true;
function dimTypeIsNotOrdinalAndTime(dimensionInfo: SeriesDimensionDefine): boolean {
return dimensionInfo.type !== 'ordinal' && dimensionInfo.type !== 'time';
}
each(dimensionDefineList, function (dimensionInfo, index) {
if (isString(dimensionInfo)) {
dimensionDefineList[index] = dimensionInfo = {
name: dimensionInfo as string
} as SeriesDimensionDefine;
}
if (!dimTypeIsNotOrdinalAndTime(dimensionInfo)) {
allDimTypesAreNotOrdinalAndTime = false;
}
});
each(dimensionDefineList, function (dimensionInfo: SeriesDimensionDefine, index) {
if (mayStack && !dimensionInfo.isExtraCoord) {
// Find the first ordinal dimension as the stackedByDimInfo.
if (!byIndex && !stackedByDimInfo && dimensionInfo.ordinalMeta) {
stackedByDimInfo = dimensionInfo;
}
// Find the first stackable dimension as the stackedDimInfo.
if (!stackedDimInfo
&& dimTypeIsNotOrdinalAndTime(dimensionInfo)
// FIXME:
// This rule MUST be consistent with `Cartesian2D['getBaseAxis']` and `Polar['getBaseAxis']`
// Need refactor - merge them!
// See comments in `Cartesian2D['getBaseAxis']` for details.
&& (!allDimTypesAreNotOrdinalAndTime
|| (
dimensionInfo.coordDim !== 'x'
&& dimensionInfo.coordDim !== 'angle'
)
)
&& (!stackedCoordDimension || stackedCoordDimension === dimensionInfo.coordDim)
) {
stackedDimInfo = dimensionInfo;
}
}
});
if (stackedDimInfo && !byIndex && !stackedByDimInfo) {
// "Stack by data index" makes sense only if users provide carefully constructed data - any
// mismatch or absence of data items can cause incorrect results. By "Stack by data index"
// is more performant - no hashmap is required.
// PENDING:
// Compatible with previous design, non-category axis ("value"/"log"/"time" axis) can only
// stack by index. "Stack by value" will not be supported until concrete requirements arise.
byIndex = true;
}
if (stackedDimInfo) {
// Use a weird name that not duplicated with other names.
// 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) {
stackedByDimInfo.createInvertedIndices = true;
}
const stackedDimCoordDim = stackedDimInfo.coordDim;
const stackedDimType = stackedDimInfo.type;
let stackedDimCoordIndex = 0;
each(dimensionDefineList, function (dimensionInfo: SeriesDimensionDefine) {
if (dimensionInfo.coordDim === stackedDimCoordDim) {
stackedDimCoordIndex++;
}
});
const stackedOverDimensionDefine: SeriesDimensionDefine = {
name: stackResultDimension,
coordDim: stackedDimCoordDim,
coordDimIndex: stackedDimCoordIndex,
type: stackedDimType,
isExtraCoord: true,
isCalculationCoord: true,
storeDimIndex: dimensionDefineList.length
};
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 + 1,
type: stackedDimType,
isExtraCoord: 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 {
stackedDimension: stackedDimInfo && stackedDimInfo.name,
stackedByDimension: stackedByDimInfo && stackedByDimInfo.name,
isStackedByIndex: byIndex,
stackedOverDimension: stackedOverDimension,
stackResultDimension: stackResultDimension
};
}
function isLegacyDimensionsInput(
dimensionsInput: Parameters<typeof enableDataStack>[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');
}
export function getStackedDimension(data: SeriesData, targetDim: string): DimensionName {
return isDimensionStacked(data, targetDim)
? data.getCalculationInfo('stackResultDimension')
: targetDim;
}