project/node_modules/echarts/lib/model/OptionManager.js

458 lines
17 KiB
JavaScript

/*
* 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.
*/
/**
* AUTO-GENERATED FILE. DO NOT MODIFY.
*/
/*
* 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 { normalizeToArray
// , MappingExistingItem, setComponentTypeToKeyInfo, mappingToExists
} from '../util/model.js';
import { each, clone, map, isTypedArray, setAsPrimitive, isArray, isObject
// , HashMap , createHashMap, extend, merge,
} from 'zrender/lib/core/util.js';
import { error } from '../util/log.js';
var QUERY_REG = /^(min|max)?(.+)$/;
// Key: mainType
// type FakeComponentsMap = HashMap<(MappingExistingItem & { subType: string })[]>;
/**
* TERM EXPLANATIONS:
* See `ECOption` and `ECUnitOption` in `src/util/types.ts`.
*/
var OptionManager = /** @class */function () {
// timeline.notMerge is not supported in ec3. Firstly there is rearly
// case that notMerge is needed. Secondly supporting 'notMerge' requires
// rawOption cloned and backuped when timeline changed, which does no
// good to performance. What's more, that both timeline and setOption
// method supply 'notMerge' brings complex and some problems.
// Consider this case:
// (step1) chart.setOption({timeline: {notMerge: false}, ...}, false);
// (step2) chart.setOption({timeline: {notMerge: true}, ...}, false);
function OptionManager(api) {
this._timelineOptions = [];
this._mediaList = [];
/**
* -1, means default.
* empty means no media.
*/
this._currentMediaIndices = [];
this._api = api;
}
OptionManager.prototype.setOption = function (rawOption, optionPreprocessorFuncs, opt) {
if (rawOption) {
// That set dat primitive is dangerous if user reuse the data when setOption again.
each(normalizeToArray(rawOption.series), function (series) {
series && series.data && isTypedArray(series.data) && setAsPrimitive(series.data);
});
each(normalizeToArray(rawOption.dataset), function (dataset) {
dataset && dataset.source && isTypedArray(dataset.source) && setAsPrimitive(dataset.source);
});
}
// Caution: some series modify option data, if do not clone,
// it should ensure that the repeat modify correctly
// (create a new object when modify itself).
rawOption = clone(rawOption);
// FIXME
// If some property is set in timeline options or media option but
// not set in baseOption, a warning should be given.
var optionBackup = this._optionBackup;
var newParsedOption = parseRawOption(rawOption, optionPreprocessorFuncs, !optionBackup);
this._newBaseOption = newParsedOption.baseOption;
// For setOption at second time (using merge mode);
if (optionBackup) {
// FIXME
// the restore merge solution is essentially incorrect.
// the mapping can not be 100% consistent with ecModel, which probably brings
// potential bug!
// The first merge is delayed, because 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 options will not be merged to the former, but just substitute them.
if (newParsedOption.timelineOptions.length) {
optionBackup.timelineOptions = newParsedOption.timelineOptions;
}
if (newParsedOption.mediaList.length) {
optionBackup.mediaList = newParsedOption.mediaList;
}
if (newParsedOption.mediaDefault) {
optionBackup.mediaDefault = newParsedOption.mediaDefault;
}
} else {
this._optionBackup = newParsedOption;
}
};
OptionManager.prototype.mountOption = function (isRecreate) {
var optionBackup = this._optionBackup;
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 `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`.
? optionBackup.baseOption : this._newBaseOption);
};
OptionManager.prototype.getTimelineOption = function (ecModel) {
var option;
var timelineOptions = this._timelineOptions;
if (timelineOptions.length) {
// getTimelineOption can only be called after ecModel inited,
// so we can get currentIndex from timelineModel.
var timelineModel = ecModel.getComponent('timeline');
if (timelineModel) {
option = clone(
// FIXME:TS as TimelineModel or quivlant interface
timelineOptions[timelineModel.getCurrentIndex()]);
}
}
return option;
};
OptionManager.prototype.getMediaOption = function (ecModel) {
var ecWidth = this._api.getWidth();
var ecHeight = this._api.getHeight();
var mediaList = this._mediaList;
var mediaDefault = this._mediaDefault;
var indices = [];
var result = [];
// No media defined.
if (!mediaList.length && !mediaDefault) {
return result;
}
// Multi media may be applied, the latter defined media has higher priority.
for (var i = 0, len = mediaList.length; i < len; i++) {
if (applyMediaQuery(mediaList[i].query, ecWidth, ecHeight)) {
indices.push(i);
}
}
// FIXME
// Whether mediaDefault should force users to provide? Otherwise
// the change by media query can not be recorvered.
if (!indices.length && mediaDefault) {
indices = [-1];
}
if (indices.length && !indicesEquals(indices, this._currentMediaIndices)) {
result = map(indices, function (index) {
return clone(index === -1 ? mediaDefault.option : mediaList[index].option);
});
}
// Otherwise return nothing.
this._currentMediaIndices = indices;
return result;
};
return OptionManager;
}();
/**
* [RAW_OPTION_PATTERNS]
* (Note: "series: []" represents all other props in `ECUnitOption`)
*
* (1) No prop "baseOption" declared:
* Root option is used as "baseOption" (except prop "options" and "media").
* ```js
* option = {
* series: [],
* timeline: {},
* options: [],
* };
* option = {
* series: [],
* media: {},
* };
* option = {
* series: [],
* timeline: {},
* options: [],
* media: {},
* }
* ```
*
* (2) Prop "baseOption" declared:
* If "baseOption" declared, `ECUnitOption` props can only be declared
* inside "baseOption" except prop "timeline" (compat ec2).
* ```js
* option = {
* baseOption: {
* timeline: {},
* series: [],
* },
* options: []
* };
* option = {
* baseOption: {
* series: [],
* },
* media: []
* };
* option = {
* baseOption: {
* timeline: {},
* series: [],
* },
* options: []
* media: []
* };
* option = {
* // ec3 compat ec2: allow (only) `timeline` declared
* // outside baseOption. Keep this setting for compat.
* timeline: {},
* baseOption: {
* series: [],
* },
* options: [],
* media: []
* };
* ```
*/
function parseRawOption(
// `rawOption` May be modified
rawOption, optionPreprocessorFuncs, isNew) {
var mediaList = [];
var mediaDefault;
var baseOption;
var declaredBaseOption = rawOption.baseOption;
// Compatible with ec2, [RAW_OPTION_PATTERNS] above.
var timelineOnRoot = rawOption.timeline;
var timelineOptionsOnRoot = rawOption.options;
var mediaOnRoot = rawOption.media;
var hasMedia = !!rawOption.media;
var hasTimeline = !!(timelineOptionsOnRoot || timelineOnRoot || declaredBaseOption && declaredBaseOption.timeline);
if (declaredBaseOption) {
baseOption = declaredBaseOption;
// For merge option.
if (!baseOption.timeline) {
baseOption.timeline = timelineOnRoot;
}
}
// For convenience, enable to use the root option as the `baseOption`:
// `{ ...normalOptionProps, media: [{ ... }, { ... }] }`
else {
if (hasTimeline || hasMedia) {
rawOption.options = rawOption.media = null;
}
baseOption = rawOption;
}
if (hasMedia) {
if (isArray(mediaOnRoot)) {
each(mediaOnRoot, function (singleMedia) {
if (process.env.NODE_ENV !== 'production') {
// Real case of wrong config.
if (singleMedia && !singleMedia.option && isObject(singleMedia.query) && isObject(singleMedia.query.option)) {
error('Illegal media option. Must be like { media: [ { query: {}, option: {} } ] }');
}
}
if (singleMedia && singleMedia.option) {
if (singleMedia.query) {
mediaList.push(singleMedia);
} else if (!mediaDefault) {
// Use the first media default.
mediaDefault = singleMedia;
}
}
});
} else {
if (process.env.NODE_ENV !== 'production') {
// Real case of wrong config.
error('Illegal media option. Must be an array. Like { media: [ {...}, {...} ] }');
}
}
}
doPreprocess(baseOption);
each(timelineOptionsOnRoot, function (option) {
return doPreprocess(option);
});
each(mediaList, function (media) {
return doPreprocess(media.option);
});
function doPreprocess(option) {
each(optionPreprocessorFuncs, function (preProcess) {
preProcess(option, isNew);
});
}
return {
baseOption: baseOption,
timelineOptions: timelineOptionsOnRoot || [],
mediaDefault: mediaDefault,
mediaList: mediaList
};
}
/**
* @see <http://www.w3.org/TR/css3-mediaqueries/#media1>
* Support: width, height, aspectRatio
* Can use max or min as prefix.
*/
function applyMediaQuery(query, ecWidth, ecHeight) {
var realMap = {
width: ecWidth,
height: ecHeight,
aspectratio: ecWidth / ecHeight // lower case for convenience.
};
var applicable = true;
each(query, function (value, attr) {
var matched = attr.match(QUERY_REG);
if (!matched || !matched[1] || !matched[2]) {
return;
}
var operator = matched[1];
var realAttr = matched[2].toLowerCase();
if (!compare(realMap[realAttr], value, operator)) {
applicable = false;
}
});
return applicable;
}
function compare(real, expect, operator) {
if (operator === 'min') {
return real >= expect;
} else if (operator === 'max') {
return real <= expect;
} else {
// Equals
return real === expect;
}
}
function indicesEquals(indices1, indices2) {
// indices is always order by asc and has only finite number.
return indices1.join(',') === indices2.join(',');
}
/**
* Consider case:
* `chart.setOption(opt1);`
* Then user do some interaction like dataZoom, dataView changing.
* `chart.setOption(opt2);`
* Then user press 'reset button' in toolbox.
*
* After doing that all of the interaction effects should be reset, the
* chart should be the same as the result of invoke
* `chart.setOption(opt1); chart.setOption(opt2);`.
*
* Although it is not able ensure that
* `chart.setOption(opt1); chart.setOption(opt2);` is equivalents to
* `chart.setOption(merge(opt1, opt2));` exactly,
* this might be the only simple way to implement that feature.
*
* MEMO: We've considered some other approaches:
* 1. Each model handles its self restoration but not uniform treatment.
* (Too complex in logic and error-prone)
* 2. Use a shadow ecModel. (Performance expensive)
*
* FIXME: A possible solution:
* Add a extra level of model for each component model. The inheritance chain would be:
* ecModel <- componentModel <- componentActionModel <- dataItemModel
* And all of the actions can only modify the `componentActionModel` rather than
* `componentModel`. `setOption` will only modify the `ecModel` and `componentModel`.
* When "resotre" action triggered, model from `componentActionModel` will be discarded
* instead of recreating the "ecModel" from the "_optionBackup".
*/
// 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 (newOptsInMainType, mainType) {
// if (newOptsInMainType == null) {
// return;
// }
// if (!ComponentModel.hasClass(mainType)) {
// if (tarOption) {
// tarOption[mainType] = merge(tarOption[mainType], newOptsInMainType, true);
// }
// }
// else {
// 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);
// }
// }
// }
// if (fakeCmpt) {
// notInit && resultTarOptsInMainType.push(fakeCmptOpt);
// resultFakeCmptsInMainType.push(fakeCmpt);
// }
// else {
// notInit && resultTarOptsInMainType.push(void 0);
// resultFakeCmptsInMainType.push(void 0);
// }
// });
// }
// });
// }
export default OptionManager;