Global.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. var _config = require("../config");
  2. var __DEV__ = _config.__DEV__;
  3. var zrUtil = require("zrender/lib/core/util");
  4. var modelUtil = require("../util/model");
  5. var Model = require("./Model");
  6. var ComponentModel = require("./Component");
  7. var globalDefault = require("./globalDefault");
  8. var colorPaletteMinin = require("./mixin/colorPalette");
  9. /**
  10. * ECharts global model
  11. *
  12. * @module {echarts/model/Global}
  13. */
  14. /**
  15. * Caution: If the mechanism should be changed some day, these cases
  16. * should be considered:
  17. *
  18. * (1) In `merge option` mode, if using the same option to call `setOption`
  19. * many times, the result should be the same (try our best to ensure that).
  20. * (2) In `merge option` mode, if a component has no id/name specified, it
  21. * will be merged by index, and the result sequence of the components is
  22. * consistent to the original sequence.
  23. * (3) `reset` feature (in toolbox). Find detailed info in comments about
  24. * `mergeOption` in module:echarts/model/OptionManager.
  25. */
  26. var each = zrUtil.each;
  27. var filter = zrUtil.filter;
  28. var map = zrUtil.map;
  29. var isArray = zrUtil.isArray;
  30. var indexOf = zrUtil.indexOf;
  31. var isObject = zrUtil.isObject;
  32. var OPTION_INNER_KEY = '\0_ec_inner';
  33. /**
  34. * @alias module:echarts/model/Global
  35. *
  36. * @param {Object} option
  37. * @param {module:echarts/model/Model} parentModel
  38. * @param {Object} theme
  39. */
  40. var GlobalModel = Model.extend({
  41. constructor: GlobalModel,
  42. init: function (option, parentModel, theme, optionManager) {
  43. theme = theme || {};
  44. this.option = null; // Mark as not initialized.
  45. /**
  46. * @type {module:echarts/model/Model}
  47. * @private
  48. */
  49. this._theme = new Model(theme);
  50. /**
  51. * @type {module:echarts/model/OptionManager}
  52. */
  53. this._optionManager = optionManager;
  54. },
  55. setOption: function (option, optionPreprocessorFuncs) {
  56. zrUtil.assert(!(OPTION_INNER_KEY in option), 'please use chart.getOption()');
  57. this._optionManager.setOption(option, optionPreprocessorFuncs);
  58. this.resetOption(null);
  59. },
  60. /**
  61. * @param {string} type null/undefined: reset all.
  62. * 'recreate': force recreate all.
  63. * 'timeline': only reset timeline option
  64. * 'media': only reset media query option
  65. * @return {boolean} Whether option changed.
  66. */
  67. resetOption: function (type) {
  68. var optionChanged = false;
  69. var optionManager = this._optionManager;
  70. if (!type || type === 'recreate') {
  71. var baseOption = optionManager.mountOption(type === 'recreate');
  72. if (!this.option || type === 'recreate') {
  73. initBase.call(this, baseOption);
  74. } else {
  75. this.restoreData();
  76. this.mergeOption(baseOption);
  77. }
  78. optionChanged = true;
  79. }
  80. if (type === 'timeline' || type === 'media') {
  81. this.restoreData();
  82. }
  83. if (!type || type === 'recreate' || type === 'timeline') {
  84. var timelineOption = optionManager.getTimelineOption(this);
  85. timelineOption && (this.mergeOption(timelineOption), optionChanged = true);
  86. }
  87. if (!type || type === 'recreate' || type === 'media') {
  88. var mediaOptions = optionManager.getMediaOption(this, this._api);
  89. if (mediaOptions.length) {
  90. each(mediaOptions, function (mediaOption) {
  91. this.mergeOption(mediaOption, optionChanged = true);
  92. }, this);
  93. }
  94. }
  95. return optionChanged;
  96. },
  97. /**
  98. * @protected
  99. */
  100. mergeOption: function (newOption) {
  101. var option = this.option;
  102. var componentsMap = this._componentsMap;
  103. var newCptTypes = []; // 如果不存在对应的 component model 则直接 merge
  104. each(newOption, function (componentOption, mainType) {
  105. if (componentOption == null) {
  106. return;
  107. }
  108. if (!ComponentModel.hasClass(mainType)) {
  109. option[mainType] = option[mainType] == null ? zrUtil.clone(componentOption) : zrUtil.merge(option[mainType], componentOption, true);
  110. } else {
  111. newCptTypes.push(mainType);
  112. }
  113. }); // FIXME OPTION 同步是否要改回原来的
  114. ComponentModel.topologicalTravel(newCptTypes, ComponentModel.getAllClassMainTypes(), visitComponent, this);
  115. this._seriesIndices = this._seriesIndices || [];
  116. function visitComponent(mainType, dependencies) {
  117. var newCptOptionList = modelUtil.normalizeToArray(newOption[mainType]);
  118. var mapResult = modelUtil.mappingToExists(componentsMap.get(mainType), newCptOptionList);
  119. modelUtil.makeIdAndName(mapResult); // Set mainType and complete subType.
  120. each(mapResult, function (item, index) {
  121. var opt = item.option;
  122. if (isObject(opt)) {
  123. item.keyInfo.mainType = mainType;
  124. item.keyInfo.subType = determineSubType(mainType, opt, item.exist);
  125. }
  126. });
  127. var dependentModels = getComponentsByTypes(componentsMap, dependencies);
  128. option[mainType] = [];
  129. componentsMap.set(mainType, []);
  130. each(mapResult, function (resultItem, index) {
  131. var componentModel = resultItem.exist;
  132. var newCptOption = resultItem.option;
  133. zrUtil.assert(isObject(newCptOption) || componentModel, 'Empty component definition'); // Consider where is no new option and should be merged using {},
  134. // see removeEdgeAndAdd in topologicalTravel and
  135. // ComponentModel.getAllClassMainTypes.
  136. if (!newCptOption) {
  137. componentModel.mergeOption({}, this);
  138. componentModel.optionUpdated({}, false);
  139. } else {
  140. var ComponentModelClass = ComponentModel.getClass(mainType, resultItem.keyInfo.subType, true);
  141. if (componentModel && componentModel instanceof ComponentModelClass) {
  142. componentModel.name = resultItem.keyInfo.name;
  143. componentModel.mergeOption(newCptOption, this);
  144. componentModel.optionUpdated(newCptOption, false);
  145. } else {
  146. // PENDING Global as parent ?
  147. var extraOpt = zrUtil.extend({
  148. dependentModels: dependentModels,
  149. componentIndex: index
  150. }, resultItem.keyInfo);
  151. componentModel = new ComponentModelClass(newCptOption, this, this, extraOpt);
  152. zrUtil.extend(componentModel, extraOpt);
  153. componentModel.init(newCptOption, this, this, extraOpt); // Call optionUpdated after init.
  154. // newCptOption has been used as componentModel.option
  155. // and may be merged with theme and default, so pass null
  156. // to avoid confusion.
  157. componentModel.optionUpdated(null, true);
  158. }
  159. }
  160. componentsMap.get(mainType)[index] = componentModel;
  161. option[mainType][index] = componentModel.option;
  162. }, this); // Backup series for filtering.
  163. if (mainType === 'series') {
  164. this._seriesIndices = createSeriesIndices(componentsMap.get('series'));
  165. }
  166. }
  167. },
  168. /**
  169. * Get option for output (cloned option and inner info removed)
  170. * @public
  171. * @return {Object}
  172. */
  173. getOption: function () {
  174. var option = zrUtil.clone(this.option);
  175. each(option, function (opts, mainType) {
  176. if (ComponentModel.hasClass(mainType)) {
  177. var opts = modelUtil.normalizeToArray(opts);
  178. for (var i = opts.length - 1; i >= 0; i--) {
  179. // Remove options with inner id.
  180. if (modelUtil.isIdInner(opts[i])) {
  181. opts.splice(i, 1);
  182. }
  183. }
  184. option[mainType] = opts;
  185. }
  186. });
  187. delete option[OPTION_INNER_KEY];
  188. return option;
  189. },
  190. /**
  191. * @return {module:echarts/model/Model}
  192. */
  193. getTheme: function () {
  194. return this._theme;
  195. },
  196. /**
  197. * @param {string} mainType
  198. * @param {number} [idx=0]
  199. * @return {module:echarts/model/Component}
  200. */
  201. getComponent: function (mainType, idx) {
  202. var list = this._componentsMap.get(mainType);
  203. if (list) {
  204. return list[idx || 0];
  205. }
  206. },
  207. /**
  208. * If none of index and id and name used, return all components with mainType.
  209. * @param {Object} condition
  210. * @param {string} condition.mainType
  211. * @param {string} [condition.subType] If ignore, only query by mainType
  212. * @param {number|Array.<number>} [condition.index] Either input index or id or name.
  213. * @param {string|Array.<string>} [condition.id] Either input index or id or name.
  214. * @param {string|Array.<string>} [condition.name] Either input index or id or name.
  215. * @return {Array.<module:echarts/model/Component>}
  216. */
  217. queryComponents: function (condition) {
  218. var mainType = condition.mainType;
  219. if (!mainType) {
  220. return [];
  221. }
  222. var index = condition.index;
  223. var id = condition.id;
  224. var name = condition.name;
  225. var cpts = this._componentsMap.get(mainType);
  226. if (!cpts || !cpts.length) {
  227. return [];
  228. }
  229. var result;
  230. if (index != null) {
  231. if (!isArray(index)) {
  232. index = [index];
  233. }
  234. result = filter(map(index, function (idx) {
  235. return cpts[idx];
  236. }), function (val) {
  237. return !!val;
  238. });
  239. } else if (id != null) {
  240. var isIdArray = isArray(id);
  241. result = filter(cpts, function (cpt) {
  242. return isIdArray && indexOf(id, cpt.id) >= 0 || !isIdArray && cpt.id === id;
  243. });
  244. } else if (name != null) {
  245. var isNameArray = isArray(name);
  246. result = filter(cpts, function (cpt) {
  247. return isNameArray && indexOf(name, cpt.name) >= 0 || !isNameArray && cpt.name === name;
  248. });
  249. } else {
  250. // Return all components with mainType
  251. result = cpts.slice();
  252. }
  253. return filterBySubType(result, condition);
  254. },
  255. /**
  256. * The interface is different from queryComponents,
  257. * which is convenient for inner usage.
  258. *
  259. * @usage
  260. * var result = findComponents(
  261. * {mainType: 'dataZoom', query: {dataZoomId: 'abc'}}
  262. * );
  263. * var result = findComponents(
  264. * {mainType: 'series', subType: 'pie', query: {seriesName: 'uio'}}
  265. * );
  266. * var result = findComponents(
  267. * {mainType: 'series'},
  268. * function (model, index) {...}
  269. * );
  270. * // result like [component0, componnet1, ...]
  271. *
  272. * @param {Object} condition
  273. * @param {string} condition.mainType Mandatory.
  274. * @param {string} [condition.subType] Optional.
  275. * @param {Object} [condition.query] like {xxxIndex, xxxId, xxxName},
  276. * where xxx is mainType.
  277. * If query attribute is null/undefined or has no index/id/name,
  278. * do not filtering by query conditions, which is convenient for
  279. * no-payload situations or when target of action is global.
  280. * @param {Function} [condition.filter] parameter: component, return boolean.
  281. * @return {Array.<module:echarts/model/Component>}
  282. */
  283. findComponents: function (condition) {
  284. var query = condition.query;
  285. var mainType = condition.mainType;
  286. var queryCond = getQueryCond(query);
  287. var result = queryCond ? this.queryComponents(queryCond) : this._componentsMap.get(mainType);
  288. return doFilter(filterBySubType(result, condition));
  289. function getQueryCond(q) {
  290. var indexAttr = mainType + 'Index';
  291. var idAttr = mainType + 'Id';
  292. var nameAttr = mainType + 'Name';
  293. return q && (q[indexAttr] != null || q[idAttr] != null || q[nameAttr] != null) ? {
  294. mainType: mainType,
  295. // subType will be filtered finally.
  296. index: q[indexAttr],
  297. id: q[idAttr],
  298. name: q[nameAttr]
  299. } : null;
  300. }
  301. function doFilter(res) {
  302. return condition.filter ? filter(res, condition.filter) : res;
  303. }
  304. },
  305. /**
  306. * @usage
  307. * eachComponent('legend', function (legendModel, index) {
  308. * ...
  309. * });
  310. * eachComponent(function (componentType, model, index) {
  311. * // componentType does not include subType
  312. * // (componentType is 'xxx' but not 'xxx.aa')
  313. * });
  314. * eachComponent(
  315. * {mainType: 'dataZoom', query: {dataZoomId: 'abc'}},
  316. * function (model, index) {...}
  317. * );
  318. * eachComponent(
  319. * {mainType: 'series', subType: 'pie', query: {seriesName: 'uio'}},
  320. * function (model, index) {...}
  321. * );
  322. *
  323. * @param {string|Object=} mainType When mainType is object, the definition
  324. * is the same as the method 'findComponents'.
  325. * @param {Function} cb
  326. * @param {*} context
  327. */
  328. eachComponent: function (mainType, cb, context) {
  329. var componentsMap = this._componentsMap;
  330. if (typeof mainType === 'function') {
  331. context = cb;
  332. cb = mainType;
  333. componentsMap.each(function (components, componentType) {
  334. each(components, function (component, index) {
  335. cb.call(context, componentType, component, index);
  336. });
  337. });
  338. } else if (zrUtil.isString(mainType)) {
  339. each(componentsMap.get(mainType), cb, context);
  340. } else if (isObject(mainType)) {
  341. var queryResult = this.findComponents(mainType);
  342. each(queryResult, cb, context);
  343. }
  344. },
  345. /**
  346. * @param {string} name
  347. * @return {Array.<module:echarts/model/Series>}
  348. */
  349. getSeriesByName: function (name) {
  350. var series = this._componentsMap.get('series');
  351. return filter(series, function (oneSeries) {
  352. return oneSeries.name === name;
  353. });
  354. },
  355. /**
  356. * @param {number} seriesIndex
  357. * @return {module:echarts/model/Series}
  358. */
  359. getSeriesByIndex: function (seriesIndex) {
  360. return this._componentsMap.get('series')[seriesIndex];
  361. },
  362. /**
  363. * @param {string} subType
  364. * @return {Array.<module:echarts/model/Series>}
  365. */
  366. getSeriesByType: function (subType) {
  367. var series = this._componentsMap.get('series');
  368. return filter(series, function (oneSeries) {
  369. return oneSeries.subType === subType;
  370. });
  371. },
  372. /**
  373. * @return {Array.<module:echarts/model/Series>}
  374. */
  375. getSeries: function () {
  376. return this._componentsMap.get('series').slice();
  377. },
  378. /**
  379. * After filtering, series may be different
  380. * frome raw series.
  381. *
  382. * @param {Function} cb
  383. * @param {*} context
  384. */
  385. eachSeries: function (cb, context) {
  386. assertSeriesInitialized(this);
  387. each(this._seriesIndices, function (rawSeriesIndex) {
  388. var series = this._componentsMap.get('series')[rawSeriesIndex];
  389. cb.call(context, series, rawSeriesIndex);
  390. }, this);
  391. },
  392. /**
  393. * Iterate raw series before filtered.
  394. *
  395. * @param {Function} cb
  396. * @param {*} context
  397. */
  398. eachRawSeries: function (cb, context) {
  399. each(this._componentsMap.get('series'), cb, context);
  400. },
  401. /**
  402. * After filtering, series may be different.
  403. * frome raw series.
  404. *
  405. * @parma {string} subType
  406. * @param {Function} cb
  407. * @param {*} context
  408. */
  409. eachSeriesByType: function (subType, cb, context) {
  410. assertSeriesInitialized(this);
  411. each(this._seriesIndices, function (rawSeriesIndex) {
  412. var series = this._componentsMap.get('series')[rawSeriesIndex];
  413. if (series.subType === subType) {
  414. cb.call(context, series, rawSeriesIndex);
  415. }
  416. }, this);
  417. },
  418. /**
  419. * Iterate raw series before filtered of given type.
  420. *
  421. * @parma {string} subType
  422. * @param {Function} cb
  423. * @param {*} context
  424. */
  425. eachRawSeriesByType: function (subType, cb, context) {
  426. return each(this.getSeriesByType(subType), cb, context);
  427. },
  428. /**
  429. * @param {module:echarts/model/Series} seriesModel
  430. */
  431. isSeriesFiltered: function (seriesModel) {
  432. assertSeriesInitialized(this);
  433. return zrUtil.indexOf(this._seriesIndices, seriesModel.componentIndex) < 0;
  434. },
  435. /**
  436. * @return {Array.<number>}
  437. */
  438. getCurrentSeriesIndices: function () {
  439. return (this._seriesIndices || []).slice();
  440. },
  441. /**
  442. * @param {Function} cb
  443. * @param {*} context
  444. */
  445. filterSeries: function (cb, context) {
  446. assertSeriesInitialized(this);
  447. var filteredSeries = filter(this._componentsMap.get('series'), cb, context);
  448. this._seriesIndices = createSeriesIndices(filteredSeries);
  449. },
  450. restoreData: function () {
  451. var componentsMap = this._componentsMap;
  452. this._seriesIndices = createSeriesIndices(componentsMap.get('series'));
  453. var componentTypes = [];
  454. componentsMap.each(function (components, componentType) {
  455. componentTypes.push(componentType);
  456. });
  457. ComponentModel.topologicalTravel(componentTypes, ComponentModel.getAllClassMainTypes(), function (componentType, dependencies) {
  458. each(componentsMap.get(componentType), function (component) {
  459. component.restoreData();
  460. });
  461. });
  462. }
  463. });
  464. /**
  465. * @inner
  466. */
  467. function mergeTheme(option, theme) {
  468. zrUtil.each(theme, function (themeItem, name) {
  469. // 如果有 component model 则把具体的 merge 逻辑交给该 model 处理
  470. if (!ComponentModel.hasClass(name)) {
  471. if (typeof themeItem === 'object') {
  472. option[name] = !option[name] ? zrUtil.clone(themeItem) : zrUtil.merge(option[name], themeItem, false);
  473. } else {
  474. if (option[name] == null) {
  475. option[name] = themeItem;
  476. }
  477. }
  478. }
  479. });
  480. }
  481. function initBase(baseOption) {
  482. baseOption = baseOption; // Using OPTION_INNER_KEY to mark that this option can not be used outside,
  483. // i.e. `chart.setOption(chart.getModel().option);` is forbiden.
  484. this.option = {};
  485. this.option[OPTION_INNER_KEY] = 1;
  486. /**
  487. * Init with series: [], in case of calling findSeries method
  488. * before series initialized.
  489. * @type {Object.<string, Array.<module:echarts/model/Model>>}
  490. * @private
  491. */
  492. this._componentsMap = zrUtil.createHashMap({
  493. series: []
  494. });
  495. /**
  496. * Mapping between filtered series list and raw series list.
  497. * key: filtered series indices, value: raw series indices.
  498. * @type {Array.<nubmer>}
  499. * @private
  500. */
  501. this._seriesIndices = null;
  502. mergeTheme(baseOption, this._theme.option); // TODO Needs clone when merging to the unexisted property
  503. zrUtil.merge(baseOption, globalDefault, false);
  504. this.mergeOption(baseOption);
  505. }
  506. /**
  507. * @inner
  508. * @param {Array.<string>|string} types model types
  509. * @return {Object} key: {string} type, value: {Array.<Object>} models
  510. */
  511. function getComponentsByTypes(componentsMap, types) {
  512. if (!zrUtil.isArray(types)) {
  513. types = types ? [types] : [];
  514. }
  515. var ret = {};
  516. each(types, function (type) {
  517. ret[type] = (componentsMap.get(type) || []).slice();
  518. });
  519. return ret;
  520. }
  521. /**
  522. * @inner
  523. */
  524. function determineSubType(mainType, newCptOption, existComponent) {
  525. var subType = newCptOption.type ? newCptOption.type : existComponent ? existComponent.subType // Use determineSubType only when there is no existComponent.
  526. : ComponentModel.determineSubType(mainType, newCptOption); // tooltip, markline, markpoint may always has no subType
  527. return subType;
  528. }
  529. /**
  530. * @inner
  531. */
  532. function createSeriesIndices(seriesModels) {
  533. return map(seriesModels, function (series) {
  534. return series.componentIndex;
  535. }) || [];
  536. }
  537. /**
  538. * @inner
  539. */
  540. function filterBySubType(components, condition) {
  541. // Using hasOwnProperty for restrict. Consider
  542. // subType is undefined in user payload.
  543. return condition.hasOwnProperty('subType') ? filter(components, function (cpt) {
  544. return cpt.subType === condition.subType;
  545. }) : components;
  546. }
  547. /**
  548. * @inner
  549. */
  550. function assertSeriesInitialized(ecModel) {}
  551. zrUtil.mixin(GlobalModel, colorPaletteMinin);
  552. var _default = GlobalModel;
  553. module.exports = _default;