You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@echarts.apache.org by su...@apache.org on 2020/09/07 16:00:00 UTC
[incubator-echarts] branch next updated: ts: (1) add more strict
type check for id, name from option. (2) add more strict conversion for id,
name from option.
This is an automated email from the ASF dual-hosted git repository.
sushuang pushed a commit to branch next
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git
The following commit(s) were added to refs/heads/next by this push:
new 99ab1d7 ts: (1) add more strict type check for id, name from option. (2) add more strict conversion for id, name from option.
99ab1d7 is described below
commit 99ab1d7a2f1413ce38dd55d3808c4e434fe49ba8
Author: 100pah <su...@gmail.com>
AuthorDate: Mon Sep 7 23:59:12 2020 +0800
ts: (1) add more strict type check for id, name from option. (2) add more strict conversion for id, name from option.
---
src/chart/helper/createGraphFromNodeEdge.ts | 3 +-
src/chart/helper/whiskerBoxCommon.ts | 1 -
src/chart/sankey/SankeySeries.ts | 2 +-
src/component/timeline/TimelineModel.ts | 10 ++--
src/data/List.ts | 75 ++++++++++++++++++++++-------
src/data/Tree.ts | 20 ++++----
src/data/helper/dataStackHelper.ts | 18 +++----
src/util/model.ts | 31 +++++++++---
src/util/types.ts | 12 +++--
9 files changed, 115 insertions(+), 57 deletions(-)
diff --git a/src/chart/helper/createGraphFromNodeEdge.ts b/src/chart/helper/createGraphFromNodeEdge.ts
index 16206de..6e07d27 100644
--- a/src/chart/helper/createGraphFromNodeEdge.ts
+++ b/src/chart/helper/createGraphFromNodeEdge.ts
@@ -30,6 +30,7 @@ import {
OptionDataItemObject
} from '../../util/types';
import SeriesModel from '../../model/Series';
+import { convertOptionIdName } from '../../util/model';
export default function (
nodes: OptionSourceDataOriginal<OptionDataValue, OptionDataItemObject<OptionDataValue>>,
@@ -59,7 +60,7 @@ export default function (
if (graph.addEdge(source, target, linkCount)) {
validEdges.push(link);
linkNameList.push(zrUtil.retrieve(
- link.id != null ? link.id + '' : null,
+ convertOptionIdName(link.id, null),
source + ' > ' + target
));
linkCount++;
diff --git a/src/chart/helper/whiskerBoxCommon.ts b/src/chart/helper/whiskerBoxCommon.ts
index d1f6976..679360d 100644
--- a/src/chart/helper/whiskerBoxCommon.ts
+++ b/src/chart/helper/whiskerBoxCommon.ts
@@ -25,7 +25,6 @@ import type { SeriesOption, SeriesOnCartesianOptionMixin, LayoutOrient } from '.
import type GlobalModel from '../../model/Global';
import type SeriesModel from '../../model/Series';
import type CartesianAxisModel from '../../coord/cartesian/AxisModel';
-import type DataDimensionInfo from '../../data/DataDimensionInfo';
import type List from '../../data/List';
import type Axis2D from '../../coord/cartesian/Axis2D';
import { CoordDimensionDefinition } from '../../data/helper/createDimensions';
diff --git a/src/chart/sankey/SankeySeries.ts b/src/chart/sankey/SankeySeries.ts
index 28d5e6f..3b6e361 100644
--- a/src/chart/sankey/SankeySeries.ts
+++ b/src/chart/sankey/SankeySeries.ts
@@ -38,7 +38,7 @@ import GlobalModel from '../../model/Global';
import List from '../../data/List';
import { LayoutRect } from '../../util/layout';
import { createTooltipMarkup } from '../../component/tooltip/tooltipMarkup';
-import { defaultSeriesFormatTooltip } from '../../component/tooltip/seriesFormatTooltip';
+
type FocusNodeAdjacency = boolean | 'inEdges' | 'outEdges' | 'allEdges';
diff --git a/src/component/timeline/TimelineModel.ts b/src/component/timeline/TimelineModel.ts
index 4598abd..7160d14 100644
--- a/src/component/timeline/TimelineModel.ts
+++ b/src/component/timeline/TimelineModel.ts
@@ -19,7 +19,6 @@
import ComponentModel from '../../model/Component';
import List from '../../data/List';
-import * as modelUtil from '../../util/model';
import {
ComponentOption,
BoxLayoutOptionMixin,
@@ -38,6 +37,7 @@ import {
import Model from '../../model/Model';
import GlobalModel, { GlobalModelSetOptionOpts } from '../../model/Global';
import { each, isObject, clone, isString } from 'zrender/src/core/util';
+import { convertOptionIdName, getDataItemValue } from '../../util/model';
export interface TimelineControlStyle extends ItemStyleOption {
@@ -247,7 +247,7 @@ class TimelineModel extends ComponentModel<TimelineOption> {
if (axisType === 'category') {
processedDataArr = [];
each(dataArr, function (item, index) {
- let value = modelUtil.getDataItemValue(item);
+ const value = convertOptionIdName(getDataItemValue(item), '');
let newItem;
if (isObject(item)) {
@@ -260,11 +260,7 @@ class TimelineModel extends ComponentModel<TimelineOption> {
processedDataArr.push(newItem);
- if (!isString(value) && (value == null || isNaN(value as number))) {
- value = '';
- }
-
- names.push(value + '');
+ names.push(value);
});
}
else {
diff --git a/src/data/List.ts b/src/data/List.ts
index e1e5dba..1f0d0a8 100644
--- a/src/data/List.ts
+++ b/src/data/List.ts
@@ -33,9 +33,10 @@ import {ArrayLike, Dictionary, FunctionPropertyNames} from 'zrender/src/core/typ
import Element from 'zrender/src/Element';
import {
DimensionIndex, DimensionName, DimensionLoose, OptionDataItem,
- ParsedValue, ParsedValueNumeric, OrdinalNumber, DimensionUserOuput, ModelOption, SeriesDataType
+ ParsedValue, ParsedValueNumeric, OrdinalNumber, DimensionUserOuput,
+ ModelOption, SeriesDataType, OrdinalRawValue
} from '../util/types';
-import {isDataItemOption} from '../util/model';
+import {isDataItemOption, convertOptionIdName} from '../util/model';
import { getECData } from '../util/ecData';
import { PathStyleProps } from 'zrender/src/graphic/Path';
import type Graph from './Graph';
@@ -144,12 +145,21 @@ export interface DefaultDataVisual {
colorFromPalette?: boolean
}
+export interface DataCalculationInfo<SERIES_MODEL> {
+ stackedDimension: string;
+ stackedByDimension: string;
+ isStackedByIndex: boolean;
+ stackedOverDimension: string;
+ stackResultDimension: string;
+ stackedOnSeries?: SERIES_MODEL;
+}
+
// -----------------------------
// Internal method declarations:
// -----------------------------
let defaultDimValueGetters: {[sourceFormat: string]: DimValueGetter};
let prepareInvertedIndex: (list: List) => void;
-let getRawValueFromStore: (list: List, dimIndex: number, rawIndex: number) => any;
+let getRawValueFromStore: (list: List, dimIndex: number, rawIndex: number) => ParsedValue | OrdinalRawValue;
let getIndicesCtor: (list: List) => DataArrayLikeConstructor;
let prepareChunks: (
storage: DataStorage, dimInfo: DataDimensionInfo, chunkSize: number, chunkCount: number, end: number
@@ -245,7 +255,7 @@ class List<
private _invertedIndicesMap: {[dimName: string]: ArrayLike<number>};
- private _calculationInfo: {[key: string]: any} = {};
+ private _calculationInfo: DataCalculationInfo<HostModel> = {} as DataCalculationInfo<HostModel>;
// User output info of this data.
// DO NOT use it in other places!
@@ -628,7 +638,7 @@ class List<
// ??? FIXME not check by pure but sourceFormat?
// TODO refactor these logic.
if (!rawData.pure) {
- let name: any = nameList[idx];
+ let name: string = nameList[idx];
if (dataItem && name == null) {
// If dataItem is {name: ...}, it has highest priority.
@@ -636,24 +646,26 @@ class List<
if ((dataItem as any).name != null) {
// There is no other place to persistent dataItem.name,
// so save it to nameList.
- nameList[idx] = name = (dataItem as any).name;
+ nameList[idx] = name = convertOptionIdName((dataItem as any).name, null);
}
else if (nameDimIdx != null) {
const nameDim = dimensions[nameDimIdx];
const nameDimChunk = storage[nameDim][chunkIndex];
if (nameDimChunk) {
- name = nameDimChunk[chunkOffset];
const ordinalMeta = dimensionInfoMap[nameDim].ordinalMeta;
- if (ordinalMeta && ordinalMeta.categories.length) {
- name = ordinalMeta.categories[name];
- }
+ name = convertOptionIdName(
+ (ordinalMeta && ordinalMeta.categories.length)
+ ? ordinalMeta.categories[nameDimChunk[chunkOffset] as number]
+ : nameDimChunk[chunkOffset],
+ null
+ );
}
}
}
// Try using the id in option
// id or name is used on dynamical data, mapping old and new items.
- let id = dataItem == null ? null : (dataItem as any).id;
+ let id: string = dataItem == null ? null : convertOptionIdName((dataItem as any).id, null);
if (id == null && name != null) {
// Use name as id and add counter to avoid same name
@@ -908,17 +920,29 @@ class List<
this._approximateExtent[dim] = extent.slice() as [number, number];
}
- getCalculationInfo(key: string): any {
+ getCalculationInfo<CALC_INFO_KEY extends keyof DataCalculationInfo<HostModel>>(
+ key: CALC_INFO_KEY
+ ): DataCalculationInfo<HostModel>[CALC_INFO_KEY] {
return this._calculationInfo[key];
}
/**
* @param key or k-v object
*/
- setCalculationInfo(key: string | object, value?: any) {
+ setCalculationInfo(
+ key: DataCalculationInfo<HostModel>
+ ): void;
+ setCalculationInfo<CALC_INFO_KEY extends keyof DataCalculationInfo<HostModel>>(
+ key: CALC_INFO_KEY,
+ value: DataCalculationInfo<HostModel>[CALC_INFO_KEY]
+ ): void;
+ setCalculationInfo(
+ key: (keyof DataCalculationInfo<HostModel>) | DataCalculationInfo<HostModel>,
+ value?: DataCalculationInfo<HostModel>[keyof DataCalculationInfo<HostModel>]
+ ): void {
isObject(key)
? zrUtil.extend(this._calculationInfo, key as object)
- : (this._calculationInfo[key] = value);
+ : ((this._calculationInfo as any)[key] = value);
}
/**
@@ -1140,13 +1164,25 @@ class List<
}
}
+ /**
+ * @return Never be null/undefined. `number` will be converted to string. Becuase:
+ * In most cases, name is used in display, where returning a string is more convenient.
+ * In other cases, name is used in query (see `indexOfName`), where we can keep the
+ * rule that name `2` equals to name `'2'`.
+ */
getName(idx: number): string {
const rawIndex = this.getRawIndex(idx);
return this._nameList[rawIndex]
- || getRawValueFromStore(this, this._nameDimIdx, rawIndex)
+ || convertOptionIdName(getRawValueFromStore(this, this._nameDimIdx, rawIndex), '')
|| '';
}
+ /**
+ * @return Never null/undefined. `number` will be converted to string. Becuase:
+ * In all cases having encountered at present, id is used in making diff comparison, which
+ * are usually based on hash map. We can keep the rule that the internal id are always string
+ * (treat `2` is the same as `'2'`) to make the related logic simple.
+ */
getId(idx: number): string {
return getId(this, this.getRawIndex(idx));
}
@@ -1981,7 +2017,9 @@ class List<
});
};
- getRawValueFromStore = function (list: List, dimIndex: number, rawIndex: number): any {
+ getRawValueFromStore = function (
+ list: List, dimIndex: number, rawIndex: number
+ ): ParsedValue | OrdinalRawValue {
let val;
if (dimIndex != null) {
const chunkSize = list._chunkSize;
@@ -2043,10 +2081,13 @@ class List<
return -1;
};
+ /**
+ * @see the comment of `List['getId']`.
+ */
getId = function (list: List, rawIndex: number): string {
let id = list._idList[rawIndex];
if (id == null) {
- id = getRawValueFromStore(list, list._idDimIdx, rawIndex);
+ id = convertOptionIdName(getRawValueFromStore(list, list._idDimIdx, rawIndex), null);
}
if (id == null) {
// FIXME Check the usage in graph, should not use prefix.
diff --git a/src/data/Tree.ts b/src/data/Tree.ts
index bf5b8fb..a42591a 100644
--- a/src/data/Tree.ts
+++ b/src/data/Tree.ts
@@ -26,8 +26,12 @@ import Model from '../model/Model';
import linkList from './helper/linkList';
import List from './List';
import createDimensions from './helper/createDimensions';
-import { DimensionLoose, ParsedValue } from '../util/types';
+import {
+ DimensionLoose, ParsedValue, OptionId, OptionDataValue,
+ OptionDataItemObject
+} from '../util/types';
import { Dictionary } from 'zrender/src/core/types';
+import { convertOptionIdName } from '../util/model';
type TreeTraverseOrder = 'preorder' | 'postorder';
type TreeTraverseCallback<Ctx> = (this: Ctx, node: TreeNode) => boolean | void;
@@ -36,10 +40,8 @@ type TreeTraverseOption = {
attr?: 'children' | 'viewChildren'
};
-interface TreeNodeData {
- name?: string
- value?: any
- children?: TreeNodeData[]
+interface TreeNodeOption extends Pick<OptionDataItemObject<OptionDataValue>, 'name' | 'value'> {
+ children?: TreeNodeOption[];
}
export class TreeNode {
@@ -405,7 +407,7 @@ class Tree<HostModel extends Model = Model, LevelOption = any, LeavesOption = an
* ]
* }
*/
- static createTree<T extends TreeNodeData, HostModel extends Model, LevelOption>(
+ static createTree<T extends TreeNodeOption, HostModel extends Model, LevelOption>(
dataRoot: T,
hostModel: HostModel,
treeOptions?: {
@@ -415,18 +417,18 @@ class Tree<HostModel extends Model = Model, LevelOption = any, LeavesOption = an
) {
const tree = new Tree(hostModel, treeOptions && treeOptions.levels);
- const listData: TreeNodeData[] = [];
+ const listData: TreeNodeOption[] = [];
let dimMax = 1;
buildHierarchy(dataRoot);
- function buildHierarchy(dataNode: TreeNodeData, parentNode?: TreeNode) {
+ function buildHierarchy(dataNode: TreeNodeOption, parentNode?: TreeNode) {
const value = dataNode.value;
dimMax = Math.max(dimMax, zrUtil.isArray(value) ? value.length : 1);
listData.push(dataNode);
- const node = new TreeNode(dataNode.name, tree);
+ const node = new TreeNode(convertOptionIdName(dataNode.name, ''), tree);
parentNode
? addChild(node, parentNode)
: (tree.root = node);
diff --git a/src/data/helper/dataStackHelper.ts b/src/data/helper/dataStackHelper.ts
index 5682435..cef669c 100644
--- a/src/data/helper/dataStackHelper.ts
+++ b/src/data/helper/dataStackHelper.ts
@@ -20,16 +20,9 @@
import {each, isString} from 'zrender/src/core/util';
import DataDimensionInfo from '../DataDimensionInfo';
import SeriesModel from '../../model/Series';
-import List from '../List';
+import List, { DataCalculationInfo } from '../List';
import type { SeriesOption, SeriesStackOptionMixin, DimensionName } from '../../util/types';
-interface DataStackResult {
- stackedDimension: string
- stackedByDimension: string
- isStackedByIndex: boolean
- stackedOverDimension: string
- stackResultDimension: string
-}
/**
* Note that it is too complicated to support 3d stack by value
@@ -58,7 +51,14 @@ export function enableDataStack(
stackedCoordDimension?: string
byIndex?: boolean
}
-): DataStackResult {
+): Pick<
+ DataCalculationInfo<unknown>,
+ 'stackedDimension'
+ | 'stackedByDimension'
+ | 'isStackedByIndex'
+ | 'stackedOverDimension'
+ | 'stackResultDimension'
+> {
opt = opt || {};
let byIndex = opt.byIndex;
const stackedCoordDimension = opt.stackedCoordDimension;
diff --git a/src/util/model.ts b/src/util/model.ts
index dffc341..c09a154 100644
--- a/src/util/model.ts
+++ b/src/util/model.ts
@@ -41,7 +41,9 @@ import {
OptionDataItem,
OptionDataValue,
TooltipRenderMode,
- Payload
+ Payload,
+ OptionId,
+ OptionName
} from './types';
import { Dictionary } from 'zrender/src/core/types';
import SeriesModel from '../model/Series';
@@ -150,7 +152,7 @@ export function isDataItemOption(dataItem: OptionDataItem): boolean {
// number id will not be converted to string in option.
// number id will be converted to string in component instance id.
export interface MappingExistingItem {
- id?: string | number;
+ id?: OptionId;
name?: string;
};
/**
@@ -499,7 +501,11 @@ function makeIdAndName(
});
}
-function keyExistAndEqual(attr: 'id' | 'name', obj1: MappingExistingItem, obj2: MappingExistingItem): boolean {
+function keyExistAndEqual(
+ attr: 'id' | 'name',
+ obj1: { id?: OptionId, name?: OptionName },
+ obj2: { id?: OptionId, name?: OptionName }
+): boolean {
const key1 = obj1[attr];
const key2 = obj2[attr];
// See `MappingExistingItem`. `id` and `name` trade string equals to number.
@@ -509,13 +515,22 @@ function keyExistAndEqual(attr: 'id' | 'name', obj1: MappingExistingItem, obj2:
/**
* @return return null if not exist.
*/
-function makeComparableKey(val: string | number): string {
+function makeComparableKey(val: unknown): string {
if (__DEV__) {
if (val == null) {
throw new Error();
}
}
- return val + '';
+ return convertOptionIdName(val, '');
+}
+
+export function convertOptionIdName(idOrName: unknown, defaultValue: string): string {
+ const type = typeof idOrName;
+ return type === 'string'
+ ? idOrName as string
+ : (type === 'number' || isStringSafe(idOrName))
+ ? idOrName + ''
+ : defaultValue;
}
export function validateIdOrName(idOrName: unknown) {
@@ -542,7 +557,7 @@ export function isNameSpecified(componentModel: ComponentModel): boolean {
* @param {Object} cmptOption
* @return {boolean}
*/
-export function isComponentIdInternal(cmptOption: MappingExistingItem): boolean {
+export function isComponentIdInternal(cmptOption: { id?: MappingExistingItem['id'] }): boolean {
return cmptOption
&& cmptOption.id != null
&& makeComparableKey(cmptOption.id).indexOf(INTERNAL_COMPONENT_ID_PREFIX) === 0;
@@ -733,8 +748,8 @@ let innerUniqueIndex = getRandomIdBase();
* The priority is: index > id > name, the same with `ecModel.queryComponents`.
*/
export type ModelFinderIndexQuery = number | number[] | 'all' | 'none' | false;
-export type ModelFinderIdQuery = number | number[] | string | string[];
-export type ModelFinderNameQuery = number | number[] | string | string[];
+export type ModelFinderIdQuery = OptionId | OptionId[];
+export type ModelFinderNameQuery = OptionId | OptionId[];
export type ModelFinder = string | ModelFinderObject;
export type ModelFinderObject = {
seriesIndex?: ModelFinderIndexQuery, seriesId?: ModelFinderIdQuery, seriesName?: ModelFinderNameQuery
diff --git a/src/util/types.ts b/src/util/types.ts
index c33f0c7..d57e6b3 100644
--- a/src/util/types.ts
+++ b/src/util/types.ts
@@ -517,11 +517,15 @@ export type OptionDataItem =
| OptionDataItemObject<OptionDataValue>;
// Only for `SOURCE_FORMAT_KEYED_ORIGINAL`
export type OptionDataItemObject<T> = {
- id?: string | number;
- name?: string;
+ id?: OptionId;
+ name?: OptionName;
value?: T[] | T;
selected?: boolean;
};
+// Compat number because it is usually used and not easy to
+// restrict it in practise.
+export type OptionId = string | number;
+export type OptionName = string | number;
export interface GraphEdgeItemObject<
VAL extends OptionDataValue
> extends OptionDataItemObject<VAL> {
@@ -1247,8 +1251,8 @@ export interface CommonAxisPointerOption {
export interface ComponentOption {
type?: string;
- id?: string;
- name?: string;
+ id?: OptionId;
+ name?: OptionName;
z?: number;
zlevel?: number;
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org