123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725 |
- /*
- * 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.
- */
- var _config = require("../config");
- var __DEV__ = _config.__DEV__;
- var _util = require("zrender/lib/core/util");
- var each = _util.each;
- var filter = _util.filter;
- var map = _util.map;
- var isArray = _util.isArray;
- var indexOf = _util.indexOf;
- var isObject = _util.isObject;
- var isString = _util.isString;
- var createHashMap = _util.createHashMap;
- var assert = _util.assert;
- var clone = _util.clone;
- var merge = _util.merge;
- var extend = _util.extend;
- var mixin = _util.mixin;
- var modelUtil = require("../util/model");
- var Model = require("./Model");
- var ComponentModel = require("./Component");
- var globalDefault = require("./globalDefault");
- var colorPaletteMixin = require("./mixin/colorPalette");
- var _sourceHelper = require("../data/helper/sourceHelper");
- var resetSourceDefaulter = _sourceHelper.resetSourceDefaulter;
- /*
- * 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.
- */
- /**
- * ECharts global model
- *
- * @module {echarts/model/Global}
- */
- /**
- * Caution: If the mechanism should be changed some day, these cases
- * should be considered:
- *
- * (1) In `merge option` mode, if using the same option to call `setOption`
- * many times, the result should be the same (try our best to ensure that).
- * (2) In `merge option` mode, if a component has no id/name specified, it
- * will be merged by index, and the result sequence of the components is
- * consistent to the original sequence.
- * (3) `reset` feature (in toolbox). Find detailed info in comments about
- * `mergeOption` in module:echarts/model/OptionManager.
- */
- var OPTION_INNER_KEY = '\0_ec_inner';
- /**
- * @alias module:echarts/model/Global
- *
- * @param {Object} option
- * @param {module:echarts/model/Model} parentModel
- * @param {Object} theme
- */
- var GlobalModel = Model.extend({
- init: function (option, parentModel, theme, optionManager) {
- theme = theme || {};
- this.option = null; // Mark as not initialized.
- /**
- * @type {module:echarts/model/Model}
- * @private
- */
- this._theme = new Model(theme);
- /**
- * @type {module:echarts/model/OptionManager}
- */
- this._optionManager = optionManager;
- },
- setOption: function (option, optionPreprocessorFuncs) {
- assert(!(OPTION_INNER_KEY in option), 'please use chart.getOption()');
- this._optionManager.setOption(option, optionPreprocessorFuncs);
- this.resetOption(null);
- },
- /**
- * @param {string} type null/undefined: reset all.
- * 'recreate': force recreate all.
- * 'timeline': only reset timeline option
- * 'media': only reset media query option
- * @return {boolean} Whether option changed.
- */
- resetOption: function (type) {
- var optionChanged = false;
- var optionManager = this._optionManager;
- if (!type || type === 'recreate') {
- var baseOption = optionManager.mountOption(type === 'recreate');
- if (!this.option || type === 'recreate') {
- initBase.call(this, baseOption);
- } else {
- this.restoreData();
- this.mergeOption(baseOption);
- }
- optionChanged = true;
- }
- if (type === 'timeline' || type === 'media') {
- this.restoreData();
- }
- if (!type || type === 'recreate' || type === 'timeline') {
- var timelineOption = optionManager.getTimelineOption(this);
- timelineOption && (this.mergeOption(timelineOption), optionChanged = true);
- }
- if (!type || type === 'recreate' || type === 'media') {
- var mediaOptions = optionManager.getMediaOption(this, this._api);
- if (mediaOptions.length) {
- each(mediaOptions, function (mediaOption) {
- this.mergeOption(mediaOption, optionChanged = true);
- }, this);
- }
- }
- return optionChanged;
- },
- /**
- * @protected
- */
- mergeOption: function (newOption) {
- var option = this.option;
- var componentsMap = this._componentsMap;
- var newCptTypes = [];
- resetSourceDefaulter(this); // If no component class, merge directly.
- // For example: color, animaiton options, etc.
- each(newOption, function (componentOption, mainType) {
- if (componentOption == null) {
- return;
- }
- if (!ComponentModel.hasClass(mainType)) {
- // globalSettingTask.dirty();
- option[mainType] = option[mainType] == null ? clone(componentOption) : merge(option[mainType], componentOption, true);
- } else if (mainType) {
- newCptTypes.push(mainType);
- }
- });
- ComponentModel.topologicalTravel(newCptTypes, ComponentModel.getAllClassMainTypes(), visitComponent, this);
- function visitComponent(mainType, dependencies) {
- var newCptOptionList = modelUtil.normalizeToArray(newOption[mainType]);
- var mapResult = modelUtil.mappingToExists(componentsMap.get(mainType), newCptOptionList);
- modelUtil.makeIdAndName(mapResult); // Set mainType and complete subType.
- each(mapResult, function (item, index) {
- var opt = item.option;
- if (isObject(opt)) {
- item.keyInfo.mainType = mainType;
- item.keyInfo.subType = determineSubType(mainType, opt, item.exist);
- }
- });
- var dependentModels = getComponentsByTypes(componentsMap, dependencies);
- option[mainType] = [];
- componentsMap.set(mainType, []);
- each(mapResult, function (resultItem, index) {
- var componentModel = resultItem.exist;
- var newCptOption = resultItem.option;
- assert(isObject(newCptOption) || componentModel, 'Empty component definition'); // Consider where is no new option and should be merged using {},
- // see removeEdgeAndAdd in topologicalTravel and
- // ComponentModel.getAllClassMainTypes.
- if (!newCptOption) {
- componentModel.mergeOption({}, this);
- componentModel.optionUpdated({}, false);
- } else {
- var ComponentModelClass = ComponentModel.getClass(mainType, resultItem.keyInfo.subType, true);
- if (componentModel && componentModel.constructor === ComponentModelClass) {
- componentModel.name = resultItem.keyInfo.name; // componentModel.settingTask && componentModel.settingTask.dirty();
- componentModel.mergeOption(newCptOption, this);
- componentModel.optionUpdated(newCptOption, false);
- } else {
- // PENDING Global as parent ?
- var extraOpt = extend({
- dependentModels: dependentModels,
- componentIndex: index
- }, resultItem.keyInfo);
- componentModel = new ComponentModelClass(newCptOption, this, this, extraOpt);
- extend(componentModel, extraOpt);
- componentModel.init(newCptOption, this, this, extraOpt); // Call optionUpdated after init.
- // newCptOption has been used as componentModel.option
- // and may be merged with theme and default, so pass null
- // to avoid confusion.
- componentModel.optionUpdated(null, true);
- }
- }
- componentsMap.get(mainType)[index] = componentModel;
- option[mainType][index] = componentModel.option;
- }, this); // Backup series for filtering.
- if (mainType === 'series') {
- createSeriesIndices(this, componentsMap.get('series'));
- }
- }
- this._seriesIndicesMap = createHashMap(this._seriesIndices = this._seriesIndices || []);
- },
- /**
- * Get option for output (cloned option and inner info removed)
- * @public
- * @return {Object}
- */
- getOption: function () {
- var option = clone(this.option);
- each(option, function (opts, mainType) {
- if (ComponentModel.hasClass(mainType)) {
- var opts = modelUtil.normalizeToArray(opts);
- for (var i = opts.length - 1; i >= 0; i--) {
- // Remove options with inner id.
- if (modelUtil.isIdInner(opts[i])) {
- opts.splice(i, 1);
- }
- }
- option[mainType] = opts;
- }
- });
- delete option[OPTION_INNER_KEY];
- return option;
- },
- /**
- * @return {module:echarts/model/Model}
- */
- getTheme: function () {
- return this._theme;
- },
- /**
- * @param {string} mainType
- * @param {number} [idx=0]
- * @return {module:echarts/model/Component}
- */
- getComponent: function (mainType, idx) {
- var list = this._componentsMap.get(mainType);
- if (list) {
- return list[idx || 0];
- }
- },
- /**
- * If none of index and id and name used, return all components with mainType.
- * @param {Object} condition
- * @param {string} condition.mainType
- * @param {string} [condition.subType] If ignore, only query by mainType
- * @param {number|Array.<number>} [condition.index] Either input index or id or name.
- * @param {string|Array.<string>} [condition.id] Either input index or id or name.
- * @param {string|Array.<string>} [condition.name] Either input index or id or name.
- * @return {Array.<module:echarts/model/Component>}
- */
- queryComponents: function (condition) {
- var mainType = condition.mainType;
- if (!mainType) {
- return [];
- }
- var index = condition.index;
- var id = condition.id;
- var name = condition.name;
- var cpts = this._componentsMap.get(mainType);
- if (!cpts || !cpts.length) {
- return [];
- }
- var result;
- if (index != null) {
- if (!isArray(index)) {
- index = [index];
- }
- result = filter(map(index, function (idx) {
- return cpts[idx];
- }), function (val) {
- return !!val;
- });
- } else if (id != null) {
- var isIdArray = isArray(id);
- result = filter(cpts, function (cpt) {
- return isIdArray && indexOf(id, cpt.id) >= 0 || !isIdArray && cpt.id === id;
- });
- } else if (name != null) {
- var isNameArray = isArray(name);
- result = filter(cpts, function (cpt) {
- return isNameArray && indexOf(name, cpt.name) >= 0 || !isNameArray && cpt.name === name;
- });
- } else {
- // Return all components with mainType
- result = cpts.slice();
- }
- return filterBySubType(result, condition);
- },
- /**
- * The interface is different from queryComponents,
- * which is convenient for inner usage.
- *
- * @usage
- * var result = findComponents(
- * {mainType: 'dataZoom', query: {dataZoomId: 'abc'}}
- * );
- * var result = findComponents(
- * {mainType: 'series', subType: 'pie', query: {seriesName: 'uio'}}
- * );
- * var result = findComponents(
- * {mainType: 'series',
- * filter: function (model, index) {...}}
- * );
- * // result like [component0, componnet1, ...]
- *
- * @param {Object} condition
- * @param {string} condition.mainType Mandatory.
- * @param {string} [condition.subType] Optional.
- * @param {Object} [condition.query] like {xxxIndex, xxxId, xxxName},
- * where xxx is mainType.
- * If query attribute is null/undefined or has no index/id/name,
- * do not filtering by query conditions, which is convenient for
- * no-payload situations or when target of action is global.
- * @param {Function} [condition.filter] parameter: component, return boolean.
- * @return {Array.<module:echarts/model/Component>}
- */
- findComponents: function (condition) {
- var query = condition.query;
- var mainType = condition.mainType;
- var queryCond = getQueryCond(query);
- var result = queryCond ? this.queryComponents(queryCond) : this._componentsMap.get(mainType);
- return doFilter(filterBySubType(result, condition));
- function getQueryCond(q) {
- var indexAttr = mainType + 'Index';
- var idAttr = mainType + 'Id';
- var nameAttr = mainType + 'Name';
- return q && (q[indexAttr] != null || q[idAttr] != null || q[nameAttr] != null) ? {
- mainType: mainType,
- // subType will be filtered finally.
- index: q[indexAttr],
- id: q[idAttr],
- name: q[nameAttr]
- } : null;
- }
- function doFilter(res) {
- return condition.filter ? filter(res, condition.filter) : res;
- }
- },
- /**
- * @usage
- * eachComponent('legend', function (legendModel, index) {
- * ...
- * });
- * eachComponent(function (componentType, model, index) {
- * // componentType does not include subType
- * // (componentType is 'xxx' but not 'xxx.aa')
- * });
- * eachComponent(
- * {mainType: 'dataZoom', query: {dataZoomId: 'abc'}},
- * function (model, index) {...}
- * );
- * eachComponent(
- * {mainType: 'series', subType: 'pie', query: {seriesName: 'uio'}},
- * function (model, index) {...}
- * );
- *
- * @param {string|Object=} mainType When mainType is object, the definition
- * is the same as the method 'findComponents'.
- * @param {Function} cb
- * @param {*} context
- */
- eachComponent: function (mainType, cb, context) {
- var componentsMap = this._componentsMap;
- if (typeof mainType === 'function') {
- context = cb;
- cb = mainType;
- componentsMap.each(function (components, componentType) {
- each(components, function (component, index) {
- cb.call(context, componentType, component, index);
- });
- });
- } else if (isString(mainType)) {
- each(componentsMap.get(mainType), cb, context);
- } else if (isObject(mainType)) {
- var queryResult = this.findComponents(mainType);
- each(queryResult, cb, context);
- }
- },
- /**
- * @param {string} name
- * @return {Array.<module:echarts/model/Series>}
- */
- getSeriesByName: function (name) {
- var series = this._componentsMap.get('series');
- return filter(series, function (oneSeries) {
- return oneSeries.name === name;
- });
- },
- /**
- * @param {number} seriesIndex
- * @return {module:echarts/model/Series}
- */
- getSeriesByIndex: function (seriesIndex) {
- return this._componentsMap.get('series')[seriesIndex];
- },
- /**
- * Get series list before filtered by type.
- * FIXME: rename to getRawSeriesByType?
- *
- * @param {string} subType
- * @return {Array.<module:echarts/model/Series>}
- */
- getSeriesByType: function (subType) {
- var series = this._componentsMap.get('series');
- return filter(series, function (oneSeries) {
- return oneSeries.subType === subType;
- });
- },
- /**
- * @return {Array.<module:echarts/model/Series>}
- */
- getSeries: function () {
- return this._componentsMap.get('series').slice();
- },
- /**
- * @return {number}
- */
- getSeriesCount: function () {
- return this._componentsMap.get('series').length;
- },
- /**
- * After filtering, series may be different
- * frome raw series.
- *
- * @param {Function} cb
- * @param {*} context
- */
- eachSeries: function (cb, context) {
- assertSeriesInitialized(this);
- each(this._seriesIndices, function (rawSeriesIndex) {
- var series = this._componentsMap.get('series')[rawSeriesIndex];
- cb.call(context, series, rawSeriesIndex);
- }, this);
- },
- /**
- * Iterate raw series before filtered.
- *
- * @param {Function} cb
- * @param {*} context
- */
- eachRawSeries: function (cb, context) {
- each(this._componentsMap.get('series'), cb, context);
- },
- /**
- * After filtering, series may be different.
- * frome raw series.
- *
- * @param {string} subType.
- * @param {Function} cb
- * @param {*} context
- */
- eachSeriesByType: function (subType, cb, context) {
- assertSeriesInitialized(this);
- each(this._seriesIndices, function (rawSeriesIndex) {
- var series = this._componentsMap.get('series')[rawSeriesIndex];
- if (series.subType === subType) {
- cb.call(context, series, rawSeriesIndex);
- }
- }, this);
- },
- /**
- * Iterate raw series before filtered of given type.
- *
- * @parma {string} subType
- * @param {Function} cb
- * @param {*} context
- */
- eachRawSeriesByType: function (subType, cb, context) {
- return each(this.getSeriesByType(subType), cb, context);
- },
- /**
- * @param {module:echarts/model/Series} seriesModel
- */
- isSeriesFiltered: function (seriesModel) {
- assertSeriesInitialized(this);
- return this._seriesIndicesMap.get(seriesModel.componentIndex) == null;
- },
- /**
- * @return {Array.<number>}
- */
- getCurrentSeriesIndices: function () {
- return (this._seriesIndices || []).slice();
- },
- /**
- * @param {Function} cb
- * @param {*} context
- */
- filterSeries: function (cb, context) {
- assertSeriesInitialized(this);
- var filteredSeries = filter(this._componentsMap.get('series'), cb, context);
- createSeriesIndices(this, filteredSeries);
- },
- restoreData: function (payload) {
- var componentsMap = this._componentsMap;
- createSeriesIndices(this, componentsMap.get('series'));
- var componentTypes = [];
- componentsMap.each(function (components, componentType) {
- componentTypes.push(componentType);
- });
- ComponentModel.topologicalTravel(componentTypes, ComponentModel.getAllClassMainTypes(), function (componentType, dependencies) {
- each(componentsMap.get(componentType), function (component) {
- (componentType !== 'series' || !isNotTargetSeries(component, payload)) && component.restoreData();
- });
- });
- }
- });
- function isNotTargetSeries(seriesModel, payload) {
- if (payload) {
- var index = payload.seiresIndex;
- var id = payload.seriesId;
- var name = payload.seriesName;
- return index != null && seriesModel.componentIndex !== index || id != null && seriesModel.id !== id || name != null && seriesModel.name !== name;
- }
- }
- /**
- * @inner
- */
- function mergeTheme(option, theme) {
- // PENDING
- // NOT use `colorLayer` in theme if option has `color`
- var notMergeColorLayer = option.color && !option.colorLayer;
- each(theme, function (themeItem, name) {
- if (name === 'colorLayer' && notMergeColorLayer) {
- return;
- } // 如果有 component model 则把具体的 merge 逻辑交给该 model 处理
- if (!ComponentModel.hasClass(name)) {
- if (typeof themeItem === 'object') {
- option[name] = !option[name] ? clone(themeItem) : merge(option[name], themeItem, false);
- } else {
- if (option[name] == null) {
- option[name] = themeItem;
- }
- }
- }
- });
- }
- function initBase(baseOption) {
- 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.
- this.option = {};
- this.option[OPTION_INNER_KEY] = 1;
- /**
- * Init with series: [], in case of calling findSeries method
- * before series initialized.
- * @type {Object.<string, Array.<module:echarts/model/Model>>}
- * @private
- */
- this._componentsMap = createHashMap({
- series: []
- });
- /**
- * Mapping between filtered series list and raw series list.
- * key: filtered series indices, value: raw series indices.
- * @type {Array.<nubmer>}
- * @private
- */
- this._seriesIndices;
- this._seriesIndicesMap;
- mergeTheme(baseOption, this._theme.option); // TODO Needs clone when merging to the unexisted property
- merge(baseOption, globalDefault, false);
- this.mergeOption(baseOption);
- }
- /**
- * @inner
- * @param {Array.<string>|string} types model types
- * @return {Object} key: {string} type, value: {Array.<Object>} models
- */
- function getComponentsByTypes(componentsMap, types) {
- if (!isArray(types)) {
- types = types ? [types] : [];
- }
- var ret = {};
- each(types, function (type) {
- ret[type] = (componentsMap.get(type) || []).slice();
- });
- return ret;
- }
- /**
- * @inner
- */
- function determineSubType(mainType, newCptOption, existComponent) {
- var subType = newCptOption.type ? newCptOption.type : existComponent ? existComponent.subType // Use determineSubType only when there is no existComponent.
- : ComponentModel.determineSubType(mainType, newCptOption); // tooltip, markline, markpoint may always has no subType
- return subType;
- }
- /**
- * @inner
- */
- function createSeriesIndices(ecModel, seriesModels) {
- ecModel._seriesIndicesMap = createHashMap(ecModel._seriesIndices = map(seriesModels, function (series) {
- return series.componentIndex;
- }) || []);
- }
- /**
- * @inner
- */
- function filterBySubType(components, condition) {
- // Using hasOwnProperty for restrict. Consider
- // subType is undefined in user payload.
- return condition.hasOwnProperty('subType') ? filter(components, function (cpt) {
- return cpt.subType === condition.subType;
- }) : components;
- }
- /**
- * @inner
- */
- function assertSeriesInitialized(ecModel) {}
- mixin(GlobalModel, colorPaletteMixin);
- var _default = GlobalModel;
- module.exports = _default;
|