From 99ab1d7a2f1413ce38dd55d3808c4e434fe49ba8 Mon Sep 17 00:00:00 2001 From: 100pah Date: Mon, 7 Sep 2020 23:59:12 +0800 Subject: [PATCH] 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 16206dee4..6e07d2768 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>, @@ -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 d1f6976b2..679360dae 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 28d5e6f68..3b6e3613e 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 4598abddb..7160d14e9 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 { 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 { 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 e1e5dba88..1f0d0a81b 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 { + 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}; - private _calculationInfo: {[key: string]: any} = {}; + private _calculationInfo: DataCalculationInfo = {} as DataCalculationInfo; // 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>( + key: CALC_INFO_KEY + ): DataCalculationInfo[CALC_INFO_KEY] { return this._calculationInfo[key]; } /** * @param key or k-v object */ - setCalculationInfo(key: string | object, value?: any) { + setCalculationInfo( + key: DataCalculationInfo + ): void; + setCalculationInfo>( + key: CALC_INFO_KEY, + value: DataCalculationInfo[CALC_INFO_KEY] + ): void; + setCalculationInfo( + key: (keyof DataCalculationInfo) | DataCalculationInfo, + value?: DataCalculationInfo[keyof DataCalculationInfo] + ): 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 bf5b8fbce..a42591aca 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 = (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, 'name' | 'value'> { + children?: TreeNodeOption[]; } export class TreeNode { @@ -405,7 +407,7 @@ class Tree( + static createTree( dataRoot: T, hostModel: HostModel, treeOptions?: { @@ -415,18 +417,18 @@ class Tree, + '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 dffc34193..c09a154b6 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 c33f0c728..d57e6b3c9 100644 --- a/src/util/types.ts +++ b/src/util/types.ts @@ -517,11 +517,15 @@ export type OptionDataItem = | OptionDataItemObject; // Only for `SOURCE_FORMAT_KEYED_ORIGINAL` export type OptionDataItemObject = { - 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 { @@ -1247,8 +1251,8 @@ export interface CommonAxisPointerOption { export interface ComponentOption { type?: string; - id?: string; - name?: string; + id?: OptionId; + name?: OptionName; z?: number; zlevel?: number; -- GitLab