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/07/17 11:39:04 UTC
[incubator-echarts] 05/16: fix: fix replaceMerge in feature
"restore" and add test cases.
This is an automated email from the ASF dual-hosted git repository.
sushuang pushed a commit to branch remove-component
in repository https://gitbox.apache.org/repos/asf/incubator-echarts.git
commit fa7cd46ab3ee1f421d418d07caf6587c0b51c344
Author: 100pah <su...@gmail.com>
AuthorDate: Wed Jul 8 22:57:10 2020 +0800
fix: fix replaceMerge in feature "restore" and add test cases.
---
src/component/graphic.ts | 3 +-
src/model/Global.ts | 58 ++++-----
src/model/OptionManager.ts | 138 ++++++++++++++-------
src/util/model.ts | 137 ++++++++++++++++-----
test/option-replaceMerge.html | 271 ++++++++++++++++++++++++++++++++++++++++--
5 files changed, 491 insertions(+), 116 deletions(-)
diff --git a/src/component/graphic.ts b/src/component/graphic.ts
index 1996918..4079ed3 100644
--- a/src/component/graphic.ts
+++ b/src/component/graphic.ts
@@ -142,8 +142,7 @@ const GraphicModel = echarts.extendComponentModel({
const flattenedList = [];
this._flatten(newList, flattenedList);
- const mappingResult = modelUtil.mappingToExistsInNormalMerge(existList, flattenedList);
- modelUtil.makeIdAndName(mappingResult);
+ const mappingResult = modelUtil.mappingToExists(existList, flattenedList, 'normalMerge');
// Clear elOptionsToUpdate
const elOptionsToUpdate = this._elOptionsToUpdate = [];
diff --git a/src/model/Global.ts b/src/model/Global.ts
index fe9edae..5f5555e 100644
--- a/src/model/Global.ts
+++ b/src/model/Global.ts
@@ -176,6 +176,15 @@ class GlobalModel extends Model<ECUnitOption> {
this.restoreData();
}
+ // By design, if `setOption(option2)` at the second time, and `option2` is a `ECUnitOption`,
+ // it should better not have the same props with `MediaUnit['option']`.
+ // Becuase either `option2` or `MediaUnit['option']` will be always merged to "current option"
+ // rather than original "baseOption". If they both override a prop, the result might be
+ // unexpected when media state changed after `setOption` called.
+ // If we really need to modify a props in each `MediaUnit['option']`, use the full version
+ // (`{baseOption, media}`) in `setOption`.
+ // For `timeline`, the case is the same.
+
if (!type || type === 'recreate' || type === 'timeline') {
const timelineOption = optionManager.getTimelineOption(this);
if (timelineOption) {
@@ -220,7 +229,7 @@ class GlobalModel extends Model<ECUnitOption> {
return;
}
- if (!(ComponentModel as ComponentModelConstructor).hasClass(mainType)) {
+ if (!ComponentModel.hasClass(mainType)) {
// globalSettingTask.dirty();
option[mainType] = option[mainType] == null
? clone(componentOption)
@@ -247,20 +256,12 @@ class GlobalModel extends Model<ECUnitOption> {
const newCmptOptionList = modelUtil.normalizeToArray(newOption[mainType]);
const oldCmptList = componentsMap.get(mainType);
- const mapResult = replaceMergeMainTypeMap && replaceMergeMainTypeMap.get(mainType)
- ? modelUtil.mappingToExistsInReplaceMerge(oldCmptList, newCmptOptionList)
- : modelUtil.mappingToExistsInNormalMerge(oldCmptList, newCmptOptionList);
-
- modelUtil.makeIdAndName(mapResult);
+ const mergeMode = (replaceMergeMainTypeMap && replaceMergeMainTypeMap.get(mainType))
+ ? 'replaceMerge' : 'normalMerge';
+ const mappingResult = modelUtil.mappingToExists(oldCmptList, newCmptOptionList, mergeMode);
// Set mainType and complete subType.
- each(mapResult, function (item) {
- const opt = item.newOption;
- if (isObject(opt)) {
- item.keyInfo.mainType = mainType;
- item.keyInfo.subType = determineSubType(mainType, opt, item.existing);
- }
- });
+ modelUtil.setComponentTypeToKeyInfo(mappingResult, mainType, ComponentModel as ComponentModelConstructor);
// Set it before the travel, in case that `this._componentsMap` is
// used in some `init` or `merge` of components.
@@ -271,7 +272,7 @@ class GlobalModel extends Model<ECUnitOption> {
const cmptsByMainType = [] as ComponentModel[];
let cmptsCountByMainType = 0;
- each(mapResult, function (resultItem, index) {
+ each(mappingResult, function (resultItem, index) {
let componentModel = resultItem.existing;
const newCmptOption = resultItem.newOption;
@@ -309,6 +310,7 @@ class GlobalModel extends Model<ECUnitOption> {
componentModel = new ComponentModelClass(
newCmptOption, this, this, extraOpt
);
+ // Assign `keyInfo`
extend(componentModel, extraOpt);
if (resultItem.brandNew) {
componentModel.__requireNewView = true;
@@ -357,9 +359,9 @@ class GlobalModel extends Model<ECUnitOption> {
getOption(): ECUnitOption {
const option = clone(this.option);
- each(option, function (opts, mainType) {
- if ((ComponentModel as ComponentModelConstructor).hasClass(mainType)) {
- opts = modelUtil.normalizeToArray(opts);
+ each(option, function (optInMainType, mainType) {
+ if (ComponentModel.hasClass(mainType)) {
+ const opts = modelUtil.normalizeToArray(optInMainType);
// Inner cmpts need to be removed.
// Inner cmpts might not be at last since ec5.0, but still
// compatible for users: if inner cmpt at last, splice the returned array.
@@ -739,8 +741,6 @@ class GlobalModel extends Model<ECUnitOption> {
};
initBase = function (ecModel: GlobalModel, baseOption: ECUnitOption): void {
- baseOption = baseOption;
-
// Using OPTION_INNER_KEY to mark that this option can not be used outside,
// i.e. `chart.setOption(chart.getModel().option);` is forbiden.
ecModel.option = {} as ECUnitOption;
@@ -827,7 +827,7 @@ function mergeTheme(option: ECUnitOption, theme: ThemeOption): void {
}
// If it is component model mainType, the model handles that merge later.
// otherwise, merge them here.
- if (!(ComponentModel as ComponentModelConstructor).hasClass(name)) {
+ if (!ComponentModel.hasClass(name)) {
if (typeof themeItem === 'object') {
option[name] = !option[name]
? clone(themeItem)
@@ -842,22 +842,6 @@ function mergeTheme(option: ECUnitOption, theme: ThemeOption): void {
});
}
-function determineSubType(
- mainType: ComponentMainType,
- newCmptOption: ComponentOption,
- existComponent: {subType: ComponentSubType} | ComponentModel
-): ComponentSubType {
- const subType = newCmptOption.type
- ? newCmptOption.type
- : existComponent
- ? existComponent.subType
- // Use determineSubType only when there is no existComponent.
- : (ComponentModel as ComponentModelConstructor).determineSubType(mainType, newCmptOption);
-
- // tooltip, markline, markpoint may always has no subType
- return subType;
-}
-
function queryByIdOrName<T extends { id?: string, name?: string }>(
attr: 'id' | 'name',
idOrName: string | string[],
@@ -888,7 +872,7 @@ function normalizeReplaceMergeInput(opts: GlobalModelSetOptionOpts): InnerSetOpt
opts && each(modelUtil.normalizeToArray(opts.replaceMerge), function (mainType) {
if (__DEV__) {
assert(
- (ComponentModel as ComponentModelConstructor).hasClass(mainType),
+ ComponentModel.hasClass(mainType),
'"' + mainType + '" is not valid component main type in "replaceMerge"'
);
}
diff --git a/src/model/OptionManager.ts b/src/model/OptionManager.ts
index ee195e8..fd4a6fc 100644
--- a/src/model/OptionManager.ts
+++ b/src/model/OptionManager.ts
@@ -22,17 +22,18 @@
*/
-import * as zrUtil from 'zrender/src/core/util';
-import * as modelUtil from '../util/model';
import ComponentModel, { ComponentModelConstructor } from './Component';
import ExtensionAPI from '../ExtensionAPI';
-import { OptionPreprocessor, MediaQuery, ECUnitOption, MediaUnit, ECOption } from '../util/types';
+import {
+ OptionPreprocessor, MediaQuery, ECUnitOption, MediaUnit, ECOption, SeriesOption, ComponentOption
+} from '../util/types';
import GlobalModel, { InnerSetOptionOpts } from './Global';
-
-const each = zrUtil.each;
-const clone = zrUtil.clone;
-const map = zrUtil.map;
-const merge = zrUtil.merge;
+import {
+ MappingExistingItem, normalizeToArray, setComponentTypeToKeyInfo, mappingToExists
+} from '../util/model';
+import {
+ each, clone, map, merge, isTypedArray, setAsPrimitive, HashMap, createHashMap, extend
+} from 'zrender/src/core/util';
const QUERY_REG = /^(min|max)?(.+)$/;
@@ -43,6 +44,9 @@ interface ParsedRawOption {
mediaList: MediaUnit[];
}
+// Key: mainType
+type FakeComponentsMap = HashMap<(MappingExistingItem & { subType: string })[]>;
+
/**
* TERM EXPLANATIONS:
* See `ECOption` and `ECUnitOption` in `src/util/types.ts`.
@@ -65,6 +69,8 @@ class OptionManager {
private _optionBackup: ParsedRawOption;
+ private _fakeCmptsMap: FakeComponentsMap;
+
private _newBaseOption: ECUnitOption;
// timeline.notMerge is not supported in ec3. Firstly there is rearly
@@ -87,8 +93,8 @@ class OptionManager {
): void {
if (rawOption) {
// That set dat primitive is dangerous if user reuse the data when setOption again.
- zrUtil.each(modelUtil.normalizeToArray((rawOption as ECUnitOption).series), function (series) {
- series && series.data && zrUtil.isTypedArray(series.data) && zrUtil.setAsPrimitive(series.data);
+ each(normalizeToArray((rawOption as ECUnitOption).series), function (series: SeriesOption) {
+ series && series.data && isTypedArray(series.data) && setAsPrimitive(series.data);
});
}
@@ -101,28 +107,36 @@ class OptionManager {
// If some property is set in timeline options or media option but
// not set in baseOption, a warning should be given.
- const oldOptionBackup = this._optionBackup;
+ const optionBackup = this._optionBackup;
const newParsedOption = parseRawOption(
- rawOption, optionPreprocessorFuncs, !oldOptionBackup
+ rawOption, optionPreprocessorFuncs, !optionBackup
);
this._newBaseOption = newParsedOption.baseOption;
// For setOption at second time (using merge mode);
- if (oldOptionBackup) {
- // Only baseOption can be merged.
- mergeOption(oldOptionBackup.baseOption, newParsedOption.baseOption, opt);
+ if (optionBackup) {
+ // The first merge is delayed, becuase in most cases, users do not call `setOption` twice.
+ let fakeCmptsMap = this._fakeCmptsMap;
+ if (!fakeCmptsMap) {
+ fakeCmptsMap = this._fakeCmptsMap = createHashMap();
+ mergeToBackupOption(fakeCmptsMap, null, optionBackup.baseOption, null);
+ }
+
+ mergeToBackupOption(
+ fakeCmptsMap, optionBackup.baseOption, newParsedOption.baseOption, opt
+ );
// For simplicity, timeline options and media options do not support merge,
// that is, if you `setOption` twice and both has timeline options, the latter
// timeline opitons will not be merged to the formers, but just substitude them.
if (newParsedOption.timelineOptions.length) {
- oldOptionBackup.timelineOptions = newParsedOption.timelineOptions;
+ optionBackup.timelineOptions = newParsedOption.timelineOptions;
}
if (newParsedOption.mediaList.length) {
- oldOptionBackup.mediaList = newParsedOption.mediaList;
+ optionBackup.mediaList = newParsedOption.mediaList;
}
if (newParsedOption.mediaDefault) {
- oldOptionBackup.mediaDefault = newParsedOption.mediaDefault;
+ optionBackup.mediaDefault = newParsedOption.mediaDefault;
}
}
else {
@@ -133,14 +147,14 @@ class OptionManager {
mountOption(isRecreate: boolean): ECUnitOption {
const optionBackup = this._optionBackup;
- this._timelineOptions = map(optionBackup.timelineOptions, clone);
- this._mediaList = map(optionBackup.mediaList, clone);
- this._mediaDefault = clone(optionBackup.mediaDefault);
+ this._timelineOptions = optionBackup.timelineOptions;
+ this._mediaList = optionBackup.mediaList;
+ this._mediaDefault = optionBackup.mediaDefault;
this._currentMediaIndices = [];
return clone(isRecreate
// this._optionBackup.baseOption, which is created at the first `setOption`
- // called, and is merged into every new option by inner method `mergeOption`
+ // called, and is merged into every new option by inner method `mergeToBackupOption`
// each time `setOption` called, can be only used in `isRecreate`, because
// its reliability is under suspicion. In other cases option merge is
// performed by `model.mergeOption`.
@@ -263,7 +277,7 @@ function parseRawOption(
// Preprocess.
each([baseOption].concat(timelineOptions)
- .concat(zrUtil.map(mediaList, function (media) {
+ .concat(map(mediaList, function (media) {
return media.option;
})),
function (option) {
@@ -295,7 +309,7 @@ function applyMediaQuery(query: MediaQuery, ecWidth: number, ecHeight: number):
let applicatable = true;
- zrUtil.each(query, function (value: number, attr) {
+ each(query, function (value: number, attr) {
const matched = attr.match(QUERY_REG);
if (!matched || !matched[1] || !matched[2]) {
@@ -359,33 +373,73 @@ function indicesEquals(indices1: number[], indices2: number[]): boolean {
* When "resotre" action triggered, model from `componentActionModel` will be discarded
* instead of recreating the "ecModel" from the "_optionBackup".
*/
-function mergeOption(
- oldOption: ECUnitOption, newOption: ECUnitOption, opt: InnerSetOptionOpts
+function mergeToBackupOption(
+ fakeCmptsMap: FakeComponentsMap,
+ // `tarOption` Can be null/undefined, means init
+ tarOption: ECUnitOption,
+ newOption: ECUnitOption,
+ // Can be null/undefined
+ opt: InnerSetOptionOpts
): void {
newOption = newOption || {} as ECUnitOption;
+ const notInit = !!tarOption;
- each(newOption, function (newCptOpt, mainType) {
- if (newCptOpt == null) {
+ each(newOption, function (newOptsInMainType, mainType) {
+ if (newOptsInMainType == null) {
return;
}
- let oldCptOpt = oldOption[mainType];
-
- if (!(ComponentModel as ComponentModelConstructor).hasClass(mainType)) {
- oldOption[mainType] = merge(oldCptOpt, newCptOpt, true);
+ if (!ComponentModel.hasClass(mainType)) {
+ if (tarOption) {
+ tarOption[mainType] = merge(tarOption[mainType], newOptsInMainType, true);
+ }
}
else {
- newCptOpt = modelUtil.normalizeToArray(newCptOpt);
- oldCptOpt = modelUtil.normalizeToArray(oldCptOpt);
-
- const mapResult = opt.replaceMergeMainTypeMap.get(mainType)
- ? modelUtil.mappingToExistsInReplaceMerge(oldCptOpt, newCptOpt)
- : modelUtil.mappingToExistsInNormalMerge(oldCptOpt, newCptOpt);
+ const oldTarOptsInMainType = notInit ? normalizeToArray(tarOption[mainType]) : null;
+ const oldFakeCmptsInMainType = fakeCmptsMap.get(mainType) || [];
+ const resultTarOptsInMainType = notInit ? (tarOption[mainType] = [] as ComponentOption[]) : null;
+ const resultFakeCmptsInMainType = fakeCmptsMap.set(mainType, []);
+
+ const mappingResult = mappingToExists(
+ oldFakeCmptsInMainType,
+ normalizeToArray(newOptsInMainType),
+ (opt && opt.replaceMergeMainTypeMap.get(mainType)) ? 'replaceMerge' : 'normalMerge'
+ );
+ setComponentTypeToKeyInfo(mappingResult, mainType, ComponentModel as ComponentModelConstructor);
+
+ each(mappingResult, function (resultItem, index) {
+ // The same logic as `Global.ts#_mergeOption`.
+ let fakeCmpt = resultItem.existing;
+ const newOption = resultItem.newOption;
+ const keyInfo = resultItem.keyInfo;
+ let fakeCmptOpt;
+
+ if (!newOption) {
+ fakeCmptOpt = oldTarOptsInMainType[index];
+ }
+ else {
+ if (fakeCmpt && fakeCmpt.subType === keyInfo.subType) {
+ fakeCmpt.name = keyInfo.name;
+ if (notInit) {
+ fakeCmptOpt = merge(oldTarOptsInMainType[index], newOption, true);
+ }
+ }
+ else {
+ fakeCmpt = extend({}, keyInfo);
+ if (notInit) {
+ fakeCmptOpt = clone(newOption);
+ }
+ }
+ }
- oldOption[mainType] = map(mapResult, function (item) {
- return (item.newOption && item.existing)
- ? merge(item.existing, item.newOption, true)
- : (item.existing || item.newOption);
+ if (fakeCmpt) {
+ notInit && resultTarOptsInMainType.push(fakeCmptOpt);
+ resultFakeCmptsInMainType.push(fakeCmpt);
+ }
+ else {
+ notInit && resultTarOptsInMainType.push(void 0);
+ resultFakeCmptsInMainType.push(void 0);
+ }
});
}
});
diff --git a/src/util/model.ts b/src/util/model.ts
index 95cb000..826c23f 100644
--- a/src/util/model.ts
+++ b/src/util/model.ts
@@ -30,7 +30,7 @@ import {
} from 'zrender/src/core/util';
import env from 'zrender/src/core/env';
import GlobalModel, { QueryConditionKindB } from '../model/Global';
-import ComponentModel from '../model/Component';
+import ComponentModel, {ComponentModelConstructor} from '../model/Component';
import List from '../data/List';
import {
ComponentOption,
@@ -142,7 +142,14 @@ export function isDataItemOption(dataItem: OptionDataItem): boolean {
// && !(dataItem[0] && isObject(dataItem[0]) && !(dataItem[0] instanceof Array));
}
-type MappingExistItem = {id?: string, name?: string} | ComponentModel;
+// Compatible with previous definition: id could be number (but not recommanded).
+// number and number-like string are trade the same when compare.
+// 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;
+ name?: string;
+};
/**
* The array `MappingResult<T>[]` exactly represents the content of the result
* components array after merge.
@@ -150,7 +157,7 @@ type MappingExistItem = {id?: string, name?: string} | ComponentModel;
* Items will not be `null`/`undefined` even if the corresponding `existings` will be removed.
*/
type MappingResult<T> = MappingResultItem<T>[];
-interface MappingResultItem<T> {
+interface MappingResultItem<T extends MappingExistingItem = MappingExistingItem> {
// Existing component instance.
existing?: T;
// The mapped new component option.
@@ -160,15 +167,26 @@ interface MappingResultItem<T> {
brandNew?: boolean;
// id?: string;
// name?: string;
- // keyInfo for new component option.
+ // keyInfo for new component.
+ // All of them will be assigned to a created component instance.
keyInfo?: {
- name?: string,
- id?: string,
- mainType?: ComponentMainType,
- subType?: ComponentSubType
+ name: string,
+ id: string,
+ mainType: ComponentMainType,
+ subType: ComponentSubType
};
}
+export function mappingToExists<T extends MappingExistingItem>(
+ existings: T[],
+ newCmptOptions: ComponentOption[],
+ mode: 'normalMerge' | 'replaceMerge'
+): MappingResult<T> {
+ return mode === 'replaceMerge'
+ ? mappingToExistsInReplaceMerge(existings, newCmptOptions)
+ : mappingToExistsInNormalMerge(existings, newCmptOptions);
+}
+
/**
* Mapping to existings for merge.
* The mapping result (merge result) will keep the order of the existing
@@ -179,7 +197,7 @@ interface MappingResultItem<T> {
*
* @return See the comment of <MappingResult>.
*/
-export function mappingToExistsInNormalMerge<T extends MappingExistItem>(
+function mappingToExistsInNormalMerge<T extends MappingExistingItem>(
existings: T[],
newCmptOptions: ComponentOption[]
): MappingResult<T> {
@@ -205,9 +223,8 @@ export function mappingToExistsInNormalMerge<T extends MappingExistItem>(
for (let i = 0; i < result.length; i++) {
const existing = result[i].existing;
if (!result[i].newOption // Consider name: two map to one.
- && cmptOption.id != null
&& existing
- && existing.id === cmptOption.id + ''
+ && keyExistAndEqual('id', existing, cmptOption)
) {
result[i].newOption = cmptOption;
newCmptOptions[index] = null;
@@ -221,10 +238,9 @@ export function mappingToExistsInNormalMerge<T extends MappingExistItem>(
// Can not match when both ids existing but different.
&& existing
&& (existing.id == null || cmptOption.id == null)
- && cmptOption.name != null
&& !isIdInner(cmptOption)
&& !isIdInner(existing)
- && existing.name === cmptOption.name + ''
+ && keyExistAndEqual('name', existing, cmptOption)
) {
result[i].newOption = cmptOption;
newCmptOptions[index] = null;
@@ -235,6 +251,8 @@ export function mappingToExistsInNormalMerge<T extends MappingExistItem>(
mappingByIndexFinally(newCmptOptions, result, false);
+ makeIdAndName(result);
+
return result;
}
@@ -250,7 +268,7 @@ export function mappingToExistsInNormalMerge<T extends MappingExistItem>(
*
* @return See the comment of <MappingResult>.
*/
-export function mappingToExistsInReplaceMerge<T extends MappingExistItem>(
+function mappingToExistsInReplaceMerge<T extends MappingExistingItem>(
existings: T[],
newCmptOptions: ComponentOption[]
): MappingResult<T> {
@@ -272,7 +290,10 @@ export function mappingToExistsInReplaceMerge<T extends MappingExistItem>(
innerExisting = existing;
}
// Input with inner id is allowed for convenience of some internal usage.
- existingIdIdxMap.set(existing.id, index);
+ // When `existing` is rawOption (see `OptionManager`#`mergeOption`), id might be empty.
+ if (existing.id != null) {
+ existingIdIdxMap.set(existing.id, index);
+ }
}
result.push({ existing: innerExisting });
}
@@ -283,7 +304,10 @@ export function mappingToExistsInReplaceMerge<T extends MappingExistItem>(
newCmptOptions[index] = null;
return;
}
- const optionId = cmptOption.id + '';
+ if (cmptOption.id == null) {
+ return;
+ }
+ const optionId = makeComparableKey(cmptOption.id);
const existingIdx = existingIdIdxMap.get(optionId);
if (existingIdx != null) {
if (__DEV__) {
@@ -303,12 +327,14 @@ export function mappingToExistsInReplaceMerge<T extends MappingExistItem>(
mappingByIndexFinally(newCmptOptions, result, true);
+ makeIdAndName(result);
+
// The array `result` MUST NOT contain elided items, otherwise the
// forEach will ommit those items and result in incorrect result.
return result;
}
-function mappingByIndexFinally<T extends MappingExistItem>(
+function mappingByIndexFinally<T extends MappingExistingItem>(
newCmptOptions: ComponentOption[],
mappingResult: MappingResult<T>,
allBrandNew: boolean
@@ -331,7 +357,11 @@ function mappingByIndexFinally<T extends MappingExistItem>(
// not be merged to the existings with different id. Because id should not be overwritten.
// (3) Name can be overwritten, because axis use name as 'show label text'.
&& (
- (cmptOption.id != null && resultItem.existing)
+ (
+ cmptOption.id != null
+ && resultItem.existing
+ && !keyExistAndEqual('id', cmptOption, resultItem.existing)
+ )
|| resultItem.newOption
|| isIdInner(resultItem.existing)
)
@@ -354,8 +384,8 @@ function mappingByIndexFinally<T extends MappingExistItem>(
* Make id and name for mapping result (result of mappingToExists)
* into `keyInfo` field.
*/
-export function makeIdAndName(
- mapResult: MappingResult<MappingExistItem>
+function makeIdAndName(
+ mapResult: MappingResult<MappingExistingItem>
): void {
// We use this id to hash component models and view instances
// in echarts. id can be specified by user, or auto generated.
@@ -385,7 +415,7 @@ export function makeIdAndName(
);
opt && opt.id != null && idMap.set(opt.id, item);
- !item.keyInfo && (item.keyInfo = {});
+ !item.keyInfo && (item.keyInfo = {} as MappingResultItem['keyInfo']);
});
// Make name and id.
@@ -403,7 +433,7 @@ export function makeIdAndName(
// only in that case: setOption with 'not merge mode' and view
// instance will be recreated, which can be accepted.
keyInfo.name = opt.name != null
- ? opt.name + ''
+ ? makeComparableKey(opt.name)
: existing
? existing.name
// Avoid diffferent series has the same name,
@@ -411,10 +441,10 @@ export function makeIdAndName(
: DUMMY_COMPONENT_NAME_PREFIX + index;
if (existing) {
- keyInfo.id = existing.id;
+ keyInfo.id = makeComparableKey(existing.id);
}
else if (opt.id != null) {
- keyInfo.id = opt.id + '';
+ keyInfo.id = makeComparableKey(opt.id);
}
else {
// Consider this situatoin:
@@ -433,6 +463,25 @@ export function makeIdAndName(
});
}
+function keyExistAndEqual(attr: 'id' | 'name', obj1: MappingExistingItem, obj2: MappingExistingItem): boolean {
+ const key1 = obj1[attr];
+ const key2 = obj2[attr];
+ // See `MappingExistingItem`. `id` and `name` trade number-like string equals to number.
+ return key1 != null && key2 != null && key1 + '' === key2 + '';
+}
+
+/**
+ * @return return null if not exist.
+ */
+function makeComparableKey(val: string | number): string {
+ if (__DEV__) {
+ if (val == null) {
+ throw new Error();
+ }
+ }
+ return val + '';
+}
+
export function isNameSpecified(componentModel: ComponentModel): boolean {
const name = componentModel.name;
// Is specified when `indexOf` get -1 or > 0.
@@ -444,12 +493,46 @@ export function isNameSpecified(componentModel: ComponentModel): boolean {
* @param {Object} cmptOption
* @return {boolean}
*/
-export function isIdInner(cmptOption: ComponentOption): boolean {
+export function isIdInner(cmptOption: MappingExistingItem): boolean {
return cmptOption
- && cmptOption.id
- && (cmptOption.id + '').indexOf('\0_ec_\0') === 0;
+ && cmptOption.id != null
+ && makeComparableKey(cmptOption.id).indexOf('\0_ec_\0') === 0;
+}
+
+
+export function setComponentTypeToKeyInfo(
+ mappingResult: MappingResult<MappingExistingItem & { subType?: ComponentSubType }>,
+ mainType: ComponentMainType,
+ componentModelCtor: ComponentModelConstructor
+): void {
+ // Set mainType and complete subType.
+ each(mappingResult, function (item) {
+ const newOption = item.newOption;
+ if (isObject(newOption)) {
+ item.keyInfo.mainType = mainType;
+ item.keyInfo.subType = determineSubType(mainType, newOption, item.existing, componentModelCtor);
+ }
+ });
}
+function determineSubType(
+ mainType: ComponentMainType,
+ newCmptOption: ComponentOption,
+ existComponent: { subType?: ComponentSubType },
+ componentModelCtor: ComponentModelConstructor
+): ComponentSubType {
+ const subType = newCmptOption.type
+ ? newCmptOption.type
+ : existComponent
+ ? existComponent.subType
+ // Use determineSubType only when there is no existComponent.
+ : (componentModelCtor as ComponentModelConstructor).determineSubType(mainType, newCmptOption);
+
+ // tooltip, markline, markpoint may always has no subType
+ return subType;
+}
+
+
type BatchItem = {
seriesId: string,
dataIndex: number[]
diff --git a/test/option-replaceMerge.html b/test/option-replaceMerge.html
index 2513ec2..30dc848 100644
--- a/test/option-replaceMerge.html
+++ b/test/option-replaceMerge.html
@@ -46,6 +46,8 @@ under the License.
<div id="main_normalMerge_add_find_hole"></div>
<div id="main_replaceMerge_inner_and_other_cmpt_not_effect"></div>
<div id="main_replaceMerge_remove_all"></div>
+ <div id="main_replaceMerge_reproduce_by_getOption_src"></div>
+ <div id="main_replaceMerge_reproduce_by_getOption_tar"></div>
@@ -322,17 +324,39 @@ under the License.
<script>
require(['echarts'], function (echarts) {
- var option = makeBasicOption();
+ function makeInitOption() {
+ var option = makeBasicOption();
+
+ option.toolbox = {
+ left: 'center',
+ top: 25,
+ feature: {
+ magicType: {
+ show: true,
+ type: ['line', 'bar']
+ },
+ dataZoom: {},
+ restore: {},
+ dataView: {}
+ }
+ };
+
+ return option;
+ }
var chart = testHelper.create(echarts, 'main_replaceMerge_add_find_hole', {
title: [
'**replaceMerge**: add (find the first hole)',
- 'click the buttons one by one from left to right',
- 'should show **TWO checked: Pass**'
+ '<1> click the buttons one by one from left to right',
+ 'should show **TWO checked: Pass**',
+ '<2> use **toolbox.dataZoom**, then click btns again, should be OK',
+ '<3> use **toolbox.magicType**, then click btns again, should be OK',
+ '<4> use **toolbox.dataView**, then click btns again, should be OK',
+ '<5> use **toolbox.restore**, then click btns again, should be OK',
],
- option: option,
+ option: makeInitOption(),
buttons: [{
- text: 'setOption_remove',
+ text: 'remove some',
onclick: function () {
chart.setOption({
series: [{
@@ -345,7 +369,7 @@ under the License.
}, {replaceMerge: 'series'});
}
}, {
- text: 'check after click setOption_remove',
+ text: 'then check',
onclick: function () {
testHelper.printAssert(chart, function (assert) {
var seriesModels = chart.getModel().getSeries();
@@ -368,10 +392,22 @@ under the License.
assert(chart.getModel().getSeriesByIndex(1) == null);
assert(chart.getModel().getComponent('series', 1) == null);
+
+ var eachModelResult = [];
+ var eachIndexResult = [];
+ chart.getModel().eachComponent('series', function (seriesModel, seriesIndex) {
+ eachModelResult.push(seriesModel);
+ eachIndexResult.push(seriesIndex);
+ });
+ assert(eachModelResult.length === 3 && eachIndexResult.length === 3);
+ for (var i = 0; i < 3; i++) {
+ assert(eachModelResult[i] === seriesModels[i]);
+ assert(eachIndexResult[i] === seriesModels[i].componentIndex);
+ }
});
}
}, {
- text: 'setOption_replaceMerge',
+ text: 'replaceMerge',
onclick: function () {
chart.setOption({
series: [{
@@ -392,7 +428,7 @@ under the License.
}, {replaceMerge: 'series'});
}
}, {
- text: 'check after click setOption_replaceMerge',
+ text: 'then check',
onclick: function () {
testHelper.printAssert(chart, function (assert) {
var seriesModels = chart.getModel().getSeries();
@@ -420,8 +456,25 @@ under the License.
assert(chart.getModel().getSeriesByIndex(1).id == 'm');
assert(chart.getModel().getComponent('series', 1).id == 'm');
+
+ var eachModelResult = [];
+ var eachIndexResult = [];
+ chart.getModel().eachComponent('series', function (seriesModel, seriesIndex) {
+ eachModelResult.push(seriesModel);
+ eachIndexResult.push(seriesIndex);
+ });
+ assert(eachModelResult.length === 5 && eachIndexResult.length === 5);
+ for (var i = 0; i < 5; i++) {
+ assert(eachModelResult[i] === seriesModels[i]);
+ assert(eachIndexResult[i] === seriesModels[i].componentIndex);
+ }
});
}
+ }, {
+ text: 'reset all',
+ onclick: function () {
+ chart.setOption(makeInitOption(), true);
+ }
}],
height: 300
});
@@ -656,6 +709,208 @@ under the License.
+ <script>
+ require(['echarts'], function (echarts) {
+ function makeInitOption() {
+ var option = {
+ grid: [{
+ right: '55%',
+ bottom: 30
+ }, {
+ id: 'grid-r',
+ left: '55%',
+ bottom: 30
+ }],
+ xAxis: [{
+ type: 'category',
+ gridIndex: 0,
+ }, {
+ id: 'xAxis-r',
+ type: 'category',
+ gridIndex: 1,
+ }],
+ yAxis: [{
+ gridIndex: 0
+ }, {
+ id: 'yAxis-r',
+ gridIndex: 1
+ }],
+ legend: {},
+ tooltip: {},
+ // dataZoom: [{
+ // type: 'slider'
+ // }, {
+ // type: 'inside'
+ // }],
+ toolbox: {
+ left: 'center',
+ top: 25,
+ feature: {
+ magicType: {
+ show: true,
+ type: ['line', 'bar']
+ },
+ dataZoom: {},
+ restore: {},
+ dataView: {}
+ }
+ },
+ series: [{
+ id: 'a',
+ name: 'aa',
+ type: 'line',
+ data: [['a11', 22], ['a33', 44]]
+ }, {
+ id: 'b',
+ name: 'bb',
+ type: 'line',
+ data: [['a11', 55], ['a33', 77]],
+ xAxisIndex: 1,
+ yAxisIndex: 1,
+ }, {
+ id: 'c',
+ name: 'cc',
+ type: 'line',
+ data: [['a11', 66], ['a33', 100]]
+ }, {
+ name: 'no_id',
+ type: 'line',
+ data: [['a11', 130], ['a33', 160]]
+ }]
+ };
+
+ return option;
+ }
+
+ var chartSrc = testHelper.create(echarts, 'main_replaceMerge_reproduce_by_getOption_src', {
+ title: [
+ '**replaceMerge**: reproduce via getOption',
+ 'click the buttons one by one from left to right',
+ 'should show **TWO checked: Pass**',
+ 'The chart reproduced below should be **the same**'
+ ],
+ option: makeInitOption(),
+ buttons: [{
+ text: 'remove left grid',
+ onclick: function () {
+ chartSrc.setOption({
+ grid: {
+ id: 'grid-r'
+ },
+ xAxis: {
+ id: 'xAxis-r'
+ },
+ yAxis: {
+ id: 'yAxis-r'
+ },
+ series: [{
+ id: 'b'
+ }]
+ }, { replaceMerge: ['series', 'grid', 'xAxis', 'yAxis'] });
+ }
+ }, {
+ text: 'reproduce',
+ onclick: function () {
+ testHelper.printAssert(chartSrc, function (assert) {
+ var seriesModels = chartSrc.getModel().getSeries();
+ assert(seriesModels.length === 3);
+ assert(seriesModels[0].componentIndex === 0);
+ assert(seriesModels[1].componentIndex === 2);
+ assert(seriesModels[2].componentIndex === 3);
+ assert(seriesModels[0].id === 'a');
+ assert(seriesModels[1].id === 'c');
+ assert(seriesModels[2].id === 'd');
+
+ assert(chartSrc.getModel().getSeriesCount() === 3);
+
+ var optionGotten = chartSrc.getOption();
+ assert(optionGotten.series.length === 4);
+ assert(optionGotten.series[0].name === 'aa');
+ assert(optionGotten.series[1] == null);
+ assert(optionGotten.series[2].name === 'cc');
+ assert(optionGotten.series[3].name === 'dd');
+
+ assert(chartSrc.getModel().getSeriesByIndex(1) == null);
+ assert(chartSrc.getModel().getComponent('series', 1) == null);
+ });
+ }
+ }, {
+ text: 'replaceMerge',
+ onclick: function () {
+ chartSrc.setOption({
+ series: [{
+ id: 'm',
+ type: 'bar',
+ data: [['a11', 22], ['a33', 44]]
+ }, {
+ id: 'n',
+ type: 'bar',
+ data: [['a11', 32], ['a33', 54]]
+ }, {
+ id: 'a'
+ }, {
+ id: 'c'
+ }, {
+ id: 'd'
+ }]
+ }, {replaceMerge: 'series'});
+ }
+ }, {
+ text: 'then check',
+ onclick: function () {
+ testHelper.printAssert(chartSrc, function (assert) {
+ var seriesModels = chartSrc.getModel().getSeries();
+ assert(seriesModels.length === 5);
+ assert(seriesModels[0].componentIndex === 0);
+ assert(seriesModels[1].componentIndex === 1);
+ assert(seriesModels[2].componentIndex === 2);
+ assert(seriesModels[3].componentIndex === 3);
+ assert(seriesModels[4].componentIndex === 4);
+ assert(seriesModels[0].id === 'a');
+ assert(seriesModels[1].id === 'm');
+ assert(seriesModels[2].id === 'c');
+ assert(seriesModels[3].id === 'd');
+ assert(seriesModels[4].id === 'n');
+
+ assert(chartSrc.getModel().getSeriesCount() === 5);
+
+ var optionGotten = chartSrc.getOption();
+ assert(optionGotten.series.length === 5);
+ assert(optionGotten.series[0].id === 'a');
+ assert(optionGotten.series[1].id === 'm');
+ assert(optionGotten.series[2].id === 'c');
+ assert(optionGotten.series[3].id === 'd');
+ assert(optionGotten.series[4].id === 'n');
+
+ assert(chartSrc.getModel().getSeriesByIndex(1).id == 'm');
+ assert(chartSrc.getModel().getComponent('series', 1).id == 'm');
+ });
+ }
+ }, {
+ text: 'reset all',
+ onclick: function () {
+ chartSrc.setOption(makeInitOption(), true);
+ }
+ }],
+ height: 200
+ });
+
+ var chartTar = testHelper.create(echarts, 'main_replaceMerge_reproduce_by_getOption_tar', {
+ title: [
+ '↓↓↓ reproduce ↓↓↓ ',
+ ],
+ option: {},
+ height: 200
+ });
+
+ });
+ </script>
+
+
+
+
+
+
</body>
</html>
---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@echarts.apache.org
For additional commands, e-mail: commits-help@echarts.apache.org