model.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561
  1. /*
  2. * Licensed to the Apache Software Foundation (ASF) under one
  3. * or more contributor license agreements. See the NOTICE file
  4. * distributed with this work for additional information
  5. * regarding copyright ownership. The ASF licenses this file
  6. * to you under the Apache License, Version 2.0 (the
  7. * "License"); you may not use this file except in compliance
  8. * with the License. You may obtain a copy of the License at
  9. *
  10. * http://www.apache.org/licenses/LICENSE-2.0
  11. *
  12. * Unless required by applicable law or agreed to in writing,
  13. * software distributed under the License is distributed on an
  14. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  15. * KIND, either express or implied. See the License for the
  16. * specific language governing permissions and limitations
  17. * under the License.
  18. */
  19. import * as zrUtil from 'zrender/src/core/util';
  20. import env from 'zrender/src/core/env';
  21. var each = zrUtil.each;
  22. var isObject = zrUtil.isObject;
  23. var isArray = zrUtil.isArray;
  24. /**
  25. * Make the name displayable. But we should
  26. * make sure it is not duplicated with user
  27. * specified name, so use '\0';
  28. */
  29. var DUMMY_COMPONENT_NAME_PREFIX = 'series\0';
  30. /**
  31. * If value is not array, then translate it to array.
  32. * @param {*} value
  33. * @return {Array} [value] or value
  34. */
  35. export function normalizeToArray(value) {
  36. return value instanceof Array
  37. ? value
  38. : value == null
  39. ? []
  40. : [value];
  41. }
  42. /**
  43. * Sync default option between normal and emphasis like `position` and `show`
  44. * In case some one will write code like
  45. * label: {
  46. * show: false,
  47. * position: 'outside',
  48. * fontSize: 18
  49. * },
  50. * emphasis: {
  51. * label: { show: true }
  52. * }
  53. * @param {Object} opt
  54. * @param {string} key
  55. * @param {Array.<string>} subOpts
  56. */
  57. export function defaultEmphasis(opt, key, subOpts) {
  58. // Caution: performance sensitive.
  59. if (opt) {
  60. opt[key] = opt[key] || {};
  61. opt.emphasis = opt.emphasis || {};
  62. opt.emphasis[key] = opt.emphasis[key] || {};
  63. // Default emphasis option from normal
  64. for (var i = 0, len = subOpts.length; i < len; i++) {
  65. var subOptName = subOpts[i];
  66. if (!opt.emphasis[key].hasOwnProperty(subOptName)
  67. && opt[key].hasOwnProperty(subOptName)
  68. ) {
  69. opt.emphasis[key][subOptName] = opt[key][subOptName];
  70. }
  71. }
  72. }
  73. }
  74. export var TEXT_STYLE_OPTIONS = [
  75. 'fontStyle', 'fontWeight', 'fontSize', 'fontFamily',
  76. 'rich', 'tag', 'color', 'textBorderColor', 'textBorderWidth',
  77. 'width', 'height', 'lineHeight', 'align', 'verticalAlign', 'baseline',
  78. 'shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY',
  79. 'textShadowColor', 'textShadowBlur', 'textShadowOffsetX', 'textShadowOffsetY',
  80. 'backgroundColor', 'borderColor', 'borderWidth', 'borderRadius', 'padding'
  81. ];
  82. // modelUtil.LABEL_OPTIONS = modelUtil.TEXT_STYLE_OPTIONS.concat([
  83. // 'position', 'offset', 'rotate', 'origin', 'show', 'distance', 'formatter',
  84. // 'fontStyle', 'fontWeight', 'fontSize', 'fontFamily',
  85. // // FIXME: deprecated, check and remove it.
  86. // 'textStyle'
  87. // ]);
  88. /**
  89. * The method do not ensure performance.
  90. * data could be [12, 2323, {value: 223}, [1221, 23], {value: [2, 23]}]
  91. * This helper method retieves value from data.
  92. * @param {string|number|Date|Array|Object} dataItem
  93. * @return {number|string|Date|Array.<number|string|Date>}
  94. */
  95. export function getDataItemValue(dataItem) {
  96. return (isObject(dataItem) && !isArray(dataItem) && !(dataItem instanceof Date))
  97. ? dataItem.value : dataItem;
  98. }
  99. /**
  100. * data could be [12, 2323, {value: 223}, [1221, 23], {value: [2, 23]}]
  101. * This helper method determine if dataItem has extra option besides value
  102. * @param {string|number|Date|Array|Object} dataItem
  103. */
  104. export function isDataItemOption(dataItem) {
  105. return isObject(dataItem)
  106. && !(dataItem instanceof Array);
  107. // // markLine data can be array
  108. // && !(dataItem[0] && isObject(dataItem[0]) && !(dataItem[0] instanceof Array));
  109. }
  110. /**
  111. * Mapping to exists for merge.
  112. *
  113. * @public
  114. * @param {Array.<Object>|Array.<module:echarts/model/Component>} exists
  115. * @param {Object|Array.<Object>} newCptOptions
  116. * @return {Array.<Object>} Result, like [{exist: ..., option: ...}, {}],
  117. * index of which is the same as exists.
  118. */
  119. export function mappingToExists(exists, newCptOptions) {
  120. // Mapping by the order by original option (but not order of
  121. // new option) in merge mode. Because we should ensure
  122. // some specified index (like xAxisIndex) is consistent with
  123. // original option, which is easy to understand, espatially in
  124. // media query. And in most case, merge option is used to
  125. // update partial option but not be expected to change order.
  126. newCptOptions = (newCptOptions || []).slice();
  127. var result = zrUtil.map(exists || [], function (obj, index) {
  128. return {exist: obj};
  129. });
  130. // Mapping by id or name if specified.
  131. each(newCptOptions, function (cptOption, index) {
  132. if (!isObject(cptOption)) {
  133. return;
  134. }
  135. // id has highest priority.
  136. for (var i = 0; i < result.length; i++) {
  137. if (!result[i].option // Consider name: two map to one.
  138. && cptOption.id != null
  139. && result[i].exist.id === cptOption.id + ''
  140. ) {
  141. result[i].option = cptOption;
  142. newCptOptions[index] = null;
  143. return;
  144. }
  145. }
  146. for (var i = 0; i < result.length; i++) {
  147. var exist = result[i].exist;
  148. if (!result[i].option // Consider name: two map to one.
  149. // Can not match when both ids exist but different.
  150. && (exist.id == null || cptOption.id == null)
  151. && cptOption.name != null
  152. && !isIdInner(cptOption)
  153. && !isIdInner(exist)
  154. && exist.name === cptOption.name + ''
  155. ) {
  156. result[i].option = cptOption;
  157. newCptOptions[index] = null;
  158. return;
  159. }
  160. }
  161. });
  162. // Otherwise mapping by index.
  163. each(newCptOptions, function (cptOption, index) {
  164. if (!isObject(cptOption)) {
  165. return;
  166. }
  167. var i = 0;
  168. for (; i < result.length; i++) {
  169. var exist = result[i].exist;
  170. if (!result[i].option
  171. // Existing model that already has id should be able to
  172. // mapped to (because after mapping performed model may
  173. // be assigned with a id, whish should not affect next
  174. // mapping), except those has inner id.
  175. && !isIdInner(exist)
  176. // Caution:
  177. // Do not overwrite id. But name can be overwritten,
  178. // because axis use name as 'show label text'.
  179. // 'exist' always has id and name and we dont
  180. // need to check it.
  181. && cptOption.id == null
  182. ) {
  183. result[i].option = cptOption;
  184. break;
  185. }
  186. }
  187. if (i >= result.length) {
  188. result.push({option: cptOption});
  189. }
  190. });
  191. return result;
  192. }
  193. /**
  194. * Make id and name for mapping result (result of mappingToExists)
  195. * into `keyInfo` field.
  196. *
  197. * @public
  198. * @param {Array.<Object>} Result, like [{exist: ..., option: ...}, {}],
  199. * which order is the same as exists.
  200. * @return {Array.<Object>} The input.
  201. */
  202. export function makeIdAndName(mapResult) {
  203. // We use this id to hash component models and view instances
  204. // in echarts. id can be specified by user, or auto generated.
  205. // The id generation rule ensures new view instance are able
  206. // to mapped to old instance when setOption are called in
  207. // no-merge mode. So we generate model id by name and plus
  208. // type in view id.
  209. // name can be duplicated among components, which is convenient
  210. // to specify multi components (like series) by one name.
  211. // Ensure that each id is distinct.
  212. var idMap = zrUtil.createHashMap();
  213. each(mapResult, function (item, index) {
  214. var existCpt = item.exist;
  215. existCpt && idMap.set(existCpt.id, item);
  216. });
  217. each(mapResult, function (item, index) {
  218. var opt = item.option;
  219. zrUtil.assert(
  220. !opt || opt.id == null || !idMap.get(opt.id) || idMap.get(opt.id) === item,
  221. 'id duplicates: ' + (opt && opt.id)
  222. );
  223. opt && opt.id != null && idMap.set(opt.id, item);
  224. !item.keyInfo && (item.keyInfo = {});
  225. });
  226. // Make name and id.
  227. each(mapResult, function (item, index) {
  228. var existCpt = item.exist;
  229. var opt = item.option;
  230. var keyInfo = item.keyInfo;
  231. if (!isObject(opt)) {
  232. return;
  233. }
  234. // name can be overwitten. Consider case: axis.name = '20km'.
  235. // But id generated by name will not be changed, which affect
  236. // only in that case: setOption with 'not merge mode' and view
  237. // instance will be recreated, which can be accepted.
  238. keyInfo.name = opt.name != null
  239. ? opt.name + ''
  240. : existCpt
  241. ? existCpt.name
  242. // Avoid diffferent series has the same name,
  243. // because name may be used like in color pallet.
  244. : DUMMY_COMPONENT_NAME_PREFIX + index;
  245. if (existCpt) {
  246. keyInfo.id = existCpt.id;
  247. }
  248. else if (opt.id != null) {
  249. keyInfo.id = opt.id + '';
  250. }
  251. else {
  252. // Consider this situatoin:
  253. // optionA: [{name: 'a'}, {name: 'a'}, {..}]
  254. // optionB [{..}, {name: 'a'}, {name: 'a'}]
  255. // Series with the same name between optionA and optionB
  256. // should be mapped.
  257. var idNum = 0;
  258. do {
  259. keyInfo.id = '\0' + keyInfo.name + '\0' + idNum++;
  260. }
  261. while (idMap.get(keyInfo.id));
  262. }
  263. idMap.set(keyInfo.id, item);
  264. });
  265. }
  266. export function isNameSpecified(componentModel) {
  267. var name = componentModel.name;
  268. // Is specified when `indexOf` get -1 or > 0.
  269. return !!(name && name.indexOf(DUMMY_COMPONENT_NAME_PREFIX));
  270. }
  271. /**
  272. * @public
  273. * @param {Object} cptOption
  274. * @return {boolean}
  275. */
  276. export function isIdInner(cptOption) {
  277. return isObject(cptOption)
  278. && cptOption.id
  279. && (cptOption.id + '').indexOf('\0_ec_\0') === 0;
  280. }
  281. /**
  282. * A helper for removing duplicate items between batchA and batchB,
  283. * and in themselves, and categorize by series.
  284. *
  285. * @param {Array.<Object>} batchA Like: [{seriesId: 2, dataIndex: [32, 4, 5]}, ...]
  286. * @param {Array.<Object>} batchB Like: [{seriesId: 2, dataIndex: [32, 4, 5]}, ...]
  287. * @return {Array.<Array.<Object>, Array.<Object>>} result: [resultBatchA, resultBatchB]
  288. */
  289. export function compressBatches(batchA, batchB) {
  290. var mapA = {};
  291. var mapB = {};
  292. makeMap(batchA || [], mapA);
  293. makeMap(batchB || [], mapB, mapA);
  294. return [mapToArray(mapA), mapToArray(mapB)];
  295. function makeMap(sourceBatch, map, otherMap) {
  296. for (var i = 0, len = sourceBatch.length; i < len; i++) {
  297. var seriesId = sourceBatch[i].seriesId;
  298. var dataIndices = normalizeToArray(sourceBatch[i].dataIndex);
  299. var otherDataIndices = otherMap && otherMap[seriesId];
  300. for (var j = 0, lenj = dataIndices.length; j < lenj; j++) {
  301. var dataIndex = dataIndices[j];
  302. if (otherDataIndices && otherDataIndices[dataIndex]) {
  303. otherDataIndices[dataIndex] = null;
  304. }
  305. else {
  306. (map[seriesId] || (map[seriesId] = {}))[dataIndex] = 1;
  307. }
  308. }
  309. }
  310. }
  311. function mapToArray(map, isData) {
  312. var result = [];
  313. for (var i in map) {
  314. if (map.hasOwnProperty(i) && map[i] != null) {
  315. if (isData) {
  316. result.push(+i);
  317. }
  318. else {
  319. var dataIndices = mapToArray(map[i], true);
  320. dataIndices.length && result.push({seriesId: i, dataIndex: dataIndices});
  321. }
  322. }
  323. }
  324. return result;
  325. }
  326. }
  327. /**
  328. * @param {module:echarts/data/List} data
  329. * @param {Object} payload Contains dataIndex (means rawIndex) / dataIndexInside / name
  330. * each of which can be Array or primary type.
  331. * @return {number|Array.<number>} dataIndex If not found, return undefined/null.
  332. */
  333. export function queryDataIndex(data, payload) {
  334. if (payload.dataIndexInside != null) {
  335. return payload.dataIndexInside;
  336. }
  337. else if (payload.dataIndex != null) {
  338. return zrUtil.isArray(payload.dataIndex)
  339. ? zrUtil.map(payload.dataIndex, function (value) {
  340. return data.indexOfRawIndex(value);
  341. })
  342. : data.indexOfRawIndex(payload.dataIndex);
  343. }
  344. else if (payload.name != null) {
  345. return zrUtil.isArray(payload.name)
  346. ? zrUtil.map(payload.name, function (value) {
  347. return data.indexOfName(value);
  348. })
  349. : data.indexOfName(payload.name);
  350. }
  351. }
  352. /**
  353. * Enable property storage to any host object.
  354. * Notice: Serialization is not supported.
  355. *
  356. * For example:
  357. * var inner = zrUitl.makeInner();
  358. *
  359. * function some1(hostObj) {
  360. * inner(hostObj).someProperty = 1212;
  361. * ...
  362. * }
  363. * function some2() {
  364. * var fields = inner(this);
  365. * fields.someProperty1 = 1212;
  366. * fields.someProperty2 = 'xx';
  367. * ...
  368. * }
  369. *
  370. * @return {Function}
  371. */
  372. export function makeInner() {
  373. // Consider different scope by es module import.
  374. var key = '__\0ec_inner_' + innerUniqueIndex++ + '_' + Math.random().toFixed(5);
  375. return function (hostObj) {
  376. return hostObj[key] || (hostObj[key] = {});
  377. };
  378. }
  379. var innerUniqueIndex = 0;
  380. /**
  381. * @param {module:echarts/model/Global} ecModel
  382. * @param {string|Object} finder
  383. * If string, e.g., 'geo', means {geoIndex: 0}.
  384. * If Object, could contain some of these properties below:
  385. * {
  386. * seriesIndex, seriesId, seriesName,
  387. * geoIndex, geoId, geoName,
  388. * bmapIndex, bmapId, bmapName,
  389. * xAxisIndex, xAxisId, xAxisName,
  390. * yAxisIndex, yAxisId, yAxisName,
  391. * gridIndex, gridId, gridName,
  392. * ... (can be extended)
  393. * }
  394. * Each properties can be number|string|Array.<number>|Array.<string>
  395. * For example, a finder could be
  396. * {
  397. * seriesIndex: 3,
  398. * geoId: ['aa', 'cc'],
  399. * gridName: ['xx', 'rr']
  400. * }
  401. * xxxIndex can be set as 'all' (means all xxx) or 'none' (means not specify)
  402. * If nothing or null/undefined specified, return nothing.
  403. * @param {Object} [opt]
  404. * @param {string} [opt.defaultMainType]
  405. * @param {Array.<string>} [opt.includeMainTypes]
  406. * @return {Object} result like:
  407. * {
  408. * seriesModels: [seriesModel1, seriesModel2],
  409. * seriesModel: seriesModel1, // The first model
  410. * geoModels: [geoModel1, geoModel2],
  411. * geoModel: geoModel1, // The first model
  412. * ...
  413. * }
  414. */
  415. export function parseFinder(ecModel, finder, opt) {
  416. if (zrUtil.isString(finder)) {
  417. var obj = {};
  418. obj[finder + 'Index'] = 0;
  419. finder = obj;
  420. }
  421. var defaultMainType = opt && opt.defaultMainType;
  422. if (defaultMainType
  423. && !has(finder, defaultMainType + 'Index')
  424. && !has(finder, defaultMainType + 'Id')
  425. && !has(finder, defaultMainType + 'Name')
  426. ) {
  427. finder[defaultMainType + 'Index'] = 0;
  428. }
  429. var result = {};
  430. each(finder, function (value, key) {
  431. var value = finder[key];
  432. // Exclude 'dataIndex' and other illgal keys.
  433. if (key === 'dataIndex' || key === 'dataIndexInside') {
  434. result[key] = value;
  435. return;
  436. }
  437. var parsedKey = key.match(/^(\w+)(Index|Id|Name)$/) || [];
  438. var mainType = parsedKey[1];
  439. var queryType = (parsedKey[2] || '').toLowerCase();
  440. if (!mainType
  441. || !queryType
  442. || value == null
  443. || (queryType === 'index' && value === 'none')
  444. || (opt && opt.includeMainTypes && zrUtil.indexOf(opt.includeMainTypes, mainType) < 0)
  445. ) {
  446. return;
  447. }
  448. var queryParam = {mainType: mainType};
  449. if (queryType !== 'index' || value !== 'all') {
  450. queryParam[queryType] = value;
  451. }
  452. var models = ecModel.queryComponents(queryParam);
  453. result[mainType + 'Models'] = models;
  454. result[mainType + 'Model'] = models[0];
  455. });
  456. return result;
  457. }
  458. function has(obj, prop) {
  459. return obj && obj.hasOwnProperty(prop);
  460. }
  461. export function setAttribute(dom, key, value) {
  462. dom.setAttribute
  463. ? dom.setAttribute(key, value)
  464. : (dom[key] = value);
  465. }
  466. export function getAttribute(dom, key) {
  467. return dom.getAttribute
  468. ? dom.getAttribute(key)
  469. : dom[key];
  470. }
  471. export function getTooltipRenderMode(renderModeOption) {
  472. if (renderModeOption === 'auto') {
  473. // Using html when `document` exists, use richText otherwise
  474. return env.domSupported ? 'html' : 'richText';
  475. }
  476. else {
  477. return renderModeOption || 'html';
  478. }
  479. }
  480. /**
  481. * Group a list by key.
  482. *
  483. * @param {Array} array
  484. * @param {Function} getKey
  485. * param {*} Array item
  486. * return {string} key
  487. * @return {Object} Result
  488. * {Array}: keys,
  489. * {module:zrender/core/util/HashMap} buckets: {key -> Array}
  490. */
  491. export function groupData(array, getKey) {
  492. var buckets = zrUtil.createHashMap();
  493. var keys = [];
  494. zrUtil.each(array, function (item) {
  495. var key = getKey(item);
  496. (buckets.get(key)
  497. || (keys.push(key), buckets.set(key, []))
  498. ).push(item);
  499. });
  500. return {keys: keys, buckets: buckets};
  501. }