model.js 35 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967
  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. /**
  20. * AUTO-GENERATED FILE. DO NOT MODIFY.
  21. */
  22. /*
  23. * Licensed to the Apache Software Foundation (ASF) under one
  24. * or more contributor license agreements. See the NOTICE file
  25. * distributed with this work for additional information
  26. * regarding copyright ownership. The ASF licenses this file
  27. * to you under the Apache License, Version 2.0 (the
  28. * "License"); you may not use this file except in compliance
  29. * with the License. You may obtain a copy of the License at
  30. *
  31. * http://www.apache.org/licenses/LICENSE-2.0
  32. *
  33. * Unless required by applicable law or agreed to in writing,
  34. * software distributed under the License is distributed on an
  35. * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
  36. * KIND, either express or implied. See the License for the
  37. * specific language governing permissions and limitations
  38. * under the License.
  39. */
  40. import { each, isObject, isArray, createHashMap, map, assert, isString, indexOf, isStringSafe, isNumber, hasOwn, retrieve2 } from 'zrender/lib/core/util.js';
  41. import env from 'zrender/lib/core/env.js';
  42. import { isNumeric, getRandomIdBase, getPrecision, round } from './number.js';
  43. import { error, warn } from './log.js';
  44. function interpolateNumber(p0, p1, percent) {
  45. return (p1 - p0) * percent + p0;
  46. }
  47. /**
  48. * Make the name displayable. But we should
  49. * make sure it is not duplicated with user
  50. * specified name, so use '\0';
  51. */
  52. var DUMMY_COMPONENT_NAME_PREFIX = 'series\0';
  53. var INTERNAL_COMPONENT_ID_PREFIX = '\0_ec_\0';
  54. /**
  55. * If value is not array, then translate it to array.
  56. * @param {*} value
  57. * @return {Array} [value] or value
  58. */
  59. export function normalizeToArray(value) {
  60. return value instanceof Array ? value : value == null ? [] : [value];
  61. }
  62. /**
  63. * Sync default option between normal and emphasis like `position` and `show`
  64. * In case some one will write code like
  65. * label: {
  66. * show: false,
  67. * position: 'outside',
  68. * fontSize: 18
  69. * },
  70. * emphasis: {
  71. * label: { show: true }
  72. * }
  73. */
  74. export function defaultEmphasis(opt, key, subOpts) {
  75. // Caution: performance sensitive.
  76. if (opt) {
  77. opt[key] = opt[key] || {};
  78. opt.emphasis = opt.emphasis || {};
  79. opt.emphasis[key] = opt.emphasis[key] || {};
  80. // Default emphasis option from normal
  81. for (var i = 0, len = subOpts.length; i < len; i++) {
  82. var subOptName = subOpts[i];
  83. if (!opt.emphasis[key].hasOwnProperty(subOptName) && opt[key].hasOwnProperty(subOptName)) {
  84. opt.emphasis[key][subOptName] = opt[key][subOptName];
  85. }
  86. }
  87. }
  88. }
  89. export var TEXT_STYLE_OPTIONS = ['fontStyle', 'fontWeight', 'fontSize', 'fontFamily', 'rich', 'tag', 'color', 'textBorderColor', 'textBorderWidth', 'width', 'height', 'lineHeight', 'align', 'verticalAlign', 'baseline', 'shadowColor', 'shadowBlur', 'shadowOffsetX', 'shadowOffsetY', 'textShadowColor', 'textShadowBlur', 'textShadowOffsetX', 'textShadowOffsetY', 'backgroundColor', 'borderColor', 'borderWidth', 'borderRadius', 'padding'];
  90. // modelUtil.LABEL_OPTIONS = modelUtil.TEXT_STYLE_OPTIONS.concat([
  91. // 'position', 'offset', 'rotate', 'origin', 'show', 'distance', 'formatter',
  92. // 'fontStyle', 'fontWeight', 'fontSize', 'fontFamily',
  93. // // FIXME: deprecated, check and remove it.
  94. // 'textStyle'
  95. // ]);
  96. /**
  97. * The method does not ensure performance.
  98. * data could be [12, 2323, {value: 223}, [1221, 23], {value: [2, 23]}]
  99. * This helper method retrieves value from data.
  100. */
  101. export function getDataItemValue(dataItem) {
  102. return isObject(dataItem) && !isArray(dataItem) && !(dataItem instanceof Date) ? dataItem.value : dataItem;
  103. }
  104. /**
  105. * data could be [12, 2323, {value: 223}, [1221, 23], {value: [2, 23]}]
  106. * This helper method determine if dataItem has extra option besides value
  107. */
  108. export function isDataItemOption(dataItem) {
  109. return isObject(dataItem) && !(dataItem instanceof Array);
  110. // // markLine data can be array
  111. // && !(dataItem[0] && isObject(dataItem[0]) && !(dataItem[0] instanceof Array));
  112. }
  113. ;
  114. /**
  115. * Mapping to existings for merge.
  116. *
  117. * Mode "normalMege":
  118. * The mapping result (merge result) will keep the order of the existing
  119. * component, rather than the order of new option. Because we should ensure
  120. * some specified index reference (like xAxisIndex) keep work.
  121. * And in most cases, "merge option" is used to update partial option but not
  122. * be expected to change the order.
  123. *
  124. * Mode "replaceMege":
  125. * (1) Only the id mapped components will be merged.
  126. * (2) Other existing components (except internal components) will be removed.
  127. * (3) Other new options will be used to create new component.
  128. * (4) The index of the existing components will not be modified.
  129. * That means their might be "hole" after the removal.
  130. * The new components are created first at those available index.
  131. *
  132. * Mode "replaceAll":
  133. * This mode try to support that reproduce an echarts instance from another
  134. * echarts instance (via `getOption`) in some simple cases.
  135. * In this scenario, the `result` index are exactly the consistent with the `newCmptOptions`,
  136. * which ensures the component index referring (like `xAxisIndex: ?`) corrent. That is,
  137. * the "hole" in `newCmptOptions` will also be kept.
  138. * On the contrary, other modes try best to eliminate holes.
  139. * PENDING: This is an experimental mode yet.
  140. *
  141. * @return See the comment of <MappingResult>.
  142. */
  143. export function mappingToExists(existings, newCmptOptions, mode) {
  144. var isNormalMergeMode = mode === 'normalMerge';
  145. var isReplaceMergeMode = mode === 'replaceMerge';
  146. var isReplaceAllMode = mode === 'replaceAll';
  147. existings = existings || [];
  148. newCmptOptions = (newCmptOptions || []).slice();
  149. var existingIdIdxMap = createHashMap();
  150. // Validate id and name on user input option.
  151. each(newCmptOptions, function (cmptOption, index) {
  152. if (!isObject(cmptOption)) {
  153. newCmptOptions[index] = null;
  154. return;
  155. }
  156. if (process.env.NODE_ENV !== 'production') {
  157. // There is some legacy case that name is set as `false`.
  158. // But should work normally rather than throw error.
  159. if (cmptOption.id != null && !isValidIdOrName(cmptOption.id)) {
  160. warnInvalidateIdOrName(cmptOption.id);
  161. }
  162. if (cmptOption.name != null && !isValidIdOrName(cmptOption.name)) {
  163. warnInvalidateIdOrName(cmptOption.name);
  164. }
  165. }
  166. });
  167. var result = prepareResult(existings, existingIdIdxMap, mode);
  168. if (isNormalMergeMode || isReplaceMergeMode) {
  169. mappingById(result, existings, existingIdIdxMap, newCmptOptions);
  170. }
  171. if (isNormalMergeMode) {
  172. mappingByName(result, newCmptOptions);
  173. }
  174. if (isNormalMergeMode || isReplaceMergeMode) {
  175. mappingByIndex(result, newCmptOptions, isReplaceMergeMode);
  176. } else if (isReplaceAllMode) {
  177. mappingInReplaceAllMode(result, newCmptOptions);
  178. }
  179. makeIdAndName(result);
  180. // The array `result` MUST NOT contain elided items, otherwise the
  181. // forEach will omit those items and result in incorrect result.
  182. return result;
  183. }
  184. function prepareResult(existings, existingIdIdxMap, mode) {
  185. var result = [];
  186. if (mode === 'replaceAll') {
  187. return result;
  188. }
  189. // Do not use native `map` to in case that the array `existings`
  190. // contains elided items, which will be omitted.
  191. for (var index = 0; index < existings.length; index++) {
  192. var existing = existings[index];
  193. // Because of replaceMerge, `existing` may be null/undefined.
  194. if (existing && existing.id != null) {
  195. existingIdIdxMap.set(existing.id, index);
  196. }
  197. // For non-internal-componnets:
  198. // Mode "normalMerge": all existings kept.
  199. // Mode "replaceMerge": all existing removed unless mapped by id.
  200. // For internal-components:
  201. // go with "replaceMerge" approach in both mode.
  202. result.push({
  203. existing: mode === 'replaceMerge' || isComponentIdInternal(existing) ? null : existing,
  204. newOption: null,
  205. keyInfo: null,
  206. brandNew: null
  207. });
  208. }
  209. return result;
  210. }
  211. function mappingById(result, existings, existingIdIdxMap, newCmptOptions) {
  212. // Mapping by id if specified.
  213. each(newCmptOptions, function (cmptOption, index) {
  214. if (!cmptOption || cmptOption.id == null) {
  215. return;
  216. }
  217. var optionId = makeComparableKey(cmptOption.id);
  218. var existingIdx = existingIdIdxMap.get(optionId);
  219. if (existingIdx != null) {
  220. var resultItem = result[existingIdx];
  221. assert(!resultItem.newOption, 'Duplicated option on id "' + optionId + '".');
  222. resultItem.newOption = cmptOption;
  223. // In both mode, if id matched, new option will be merged to
  224. // the existings rather than creating new component model.
  225. resultItem.existing = existings[existingIdx];
  226. newCmptOptions[index] = null;
  227. }
  228. });
  229. }
  230. function mappingByName(result, newCmptOptions) {
  231. // Mapping by name if specified.
  232. each(newCmptOptions, function (cmptOption, index) {
  233. if (!cmptOption || cmptOption.name == null) {
  234. return;
  235. }
  236. for (var i = 0; i < result.length; i++) {
  237. var existing = result[i].existing;
  238. if (!result[i].newOption // Consider name: two map to one.
  239. // Can not match when both ids existing but different.
  240. && existing && (existing.id == null || cmptOption.id == null) && !isComponentIdInternal(cmptOption) && !isComponentIdInternal(existing) && keyExistAndEqual('name', existing, cmptOption)) {
  241. result[i].newOption = cmptOption;
  242. newCmptOptions[index] = null;
  243. return;
  244. }
  245. }
  246. });
  247. }
  248. function mappingByIndex(result, newCmptOptions, brandNew) {
  249. each(newCmptOptions, function (cmptOption) {
  250. if (!cmptOption) {
  251. return;
  252. }
  253. // Find the first place that not mapped by id and not internal component (consider the "hole").
  254. var resultItem;
  255. var nextIdx = 0;
  256. while (
  257. // Be `!resultItem` only when `nextIdx >= result.length`.
  258. (resultItem = result[nextIdx]
  259. // (1) Existing models that already have id should be able to mapped to. Because
  260. // after mapping performed, model will always be assigned with an id if user not given.
  261. // After that all models have id.
  262. // (2) If new option has id, it can only set to a hole or append to the last. It should
  263. // not be merged to the existings with different id. Because id should not be overwritten.
  264. // (3) Name can be overwritten, because axis use name as 'show label text'.
  265. ) && (resultItem.newOption || isComponentIdInternal(resultItem.existing) ||
  266. // In mode "replaceMerge", here no not-mapped-non-internal-existing.
  267. resultItem.existing && cmptOption.id != null && !keyExistAndEqual('id', cmptOption, resultItem.existing))) {
  268. nextIdx++;
  269. }
  270. if (resultItem) {
  271. resultItem.newOption = cmptOption;
  272. resultItem.brandNew = brandNew;
  273. } else {
  274. result.push({
  275. newOption: cmptOption,
  276. brandNew: brandNew,
  277. existing: null,
  278. keyInfo: null
  279. });
  280. }
  281. nextIdx++;
  282. });
  283. }
  284. function mappingInReplaceAllMode(result, newCmptOptions) {
  285. each(newCmptOptions, function (cmptOption) {
  286. // The feature "reproduce" requires "hole" will also reproduced
  287. // in case that component index referring are broken.
  288. result.push({
  289. newOption: cmptOption,
  290. brandNew: true,
  291. existing: null,
  292. keyInfo: null
  293. });
  294. });
  295. }
  296. /**
  297. * Make id and name for mapping result (result of mappingToExists)
  298. * into `keyInfo` field.
  299. */
  300. function makeIdAndName(mapResult) {
  301. // We use this id to hash component models and view instances
  302. // in echarts. id can be specified by user, or auto generated.
  303. // The id generation rule ensures new view instance are able
  304. // to mapped to old instance when setOption are called in
  305. // no-merge mode. So we generate model id by name and plus
  306. // type in view id.
  307. // name can be duplicated among components, which is convenient
  308. // to specify multi components (like series) by one name.
  309. // Ensure that each id is distinct.
  310. var idMap = createHashMap();
  311. each(mapResult, function (item) {
  312. var existing = item.existing;
  313. existing && idMap.set(existing.id, item);
  314. });
  315. each(mapResult, function (item) {
  316. var opt = item.newOption;
  317. // Force ensure id not duplicated.
  318. assert(!opt || opt.id == null || !idMap.get(opt.id) || idMap.get(opt.id) === item, 'id duplicates: ' + (opt && opt.id));
  319. opt && opt.id != null && idMap.set(opt.id, item);
  320. !item.keyInfo && (item.keyInfo = {});
  321. });
  322. // Make name and id.
  323. each(mapResult, function (item, index) {
  324. var existing = item.existing;
  325. var opt = item.newOption;
  326. var keyInfo = item.keyInfo;
  327. if (!isObject(opt)) {
  328. return;
  329. }
  330. // Name can be overwritten. Consider case: axis.name = '20km'.
  331. // But id generated by name will not be changed, which affect
  332. // only in that case: setOption with 'not merge mode' and view
  333. // instance will be recreated, which can be accepted.
  334. keyInfo.name = opt.name != null ? makeComparableKey(opt.name) : existing ? existing.name
  335. // Avoid that different series has the same name,
  336. // because name may be used like in color pallet.
  337. : DUMMY_COMPONENT_NAME_PREFIX + index;
  338. if (existing) {
  339. keyInfo.id = makeComparableKey(existing.id);
  340. } else if (opt.id != null) {
  341. keyInfo.id = makeComparableKey(opt.id);
  342. } else {
  343. // Consider this situatoin:
  344. // optionA: [{name: 'a'}, {name: 'a'}, {..}]
  345. // optionB [{..}, {name: 'a'}, {name: 'a'}]
  346. // Series with the same name between optionA and optionB
  347. // should be mapped.
  348. var idNum = 0;
  349. do {
  350. keyInfo.id = '\0' + keyInfo.name + '\0' + idNum++;
  351. } while (idMap.get(keyInfo.id));
  352. }
  353. idMap.set(keyInfo.id, item);
  354. });
  355. }
  356. function keyExistAndEqual(attr, obj1, obj2) {
  357. var key1 = convertOptionIdName(obj1[attr], null);
  358. var key2 = convertOptionIdName(obj2[attr], null);
  359. // See `MappingExistingItem`. `id` and `name` trade string equals to number.
  360. return key1 != null && key2 != null && key1 === key2;
  361. }
  362. /**
  363. * @return return null if not exist.
  364. */
  365. function makeComparableKey(val) {
  366. if (process.env.NODE_ENV !== 'production') {
  367. if (val == null) {
  368. throw new Error();
  369. }
  370. }
  371. return convertOptionIdName(val, '');
  372. }
  373. export function convertOptionIdName(idOrName, defaultValue) {
  374. if (idOrName == null) {
  375. return defaultValue;
  376. }
  377. return isString(idOrName) ? idOrName : isNumber(idOrName) || isStringSafe(idOrName) ? idOrName + '' : defaultValue;
  378. }
  379. function warnInvalidateIdOrName(idOrName) {
  380. if (process.env.NODE_ENV !== 'production') {
  381. warn('`' + idOrName + '` is invalid id or name. Must be a string or number.');
  382. }
  383. }
  384. function isValidIdOrName(idOrName) {
  385. return isStringSafe(idOrName) || isNumeric(idOrName);
  386. }
  387. export function isNameSpecified(componentModel) {
  388. var name = componentModel.name;
  389. // Is specified when `indexOf` get -1 or > 0.
  390. return !!(name && name.indexOf(DUMMY_COMPONENT_NAME_PREFIX));
  391. }
  392. /**
  393. * @public
  394. * @param {Object} cmptOption
  395. * @return {boolean}
  396. */
  397. export function isComponentIdInternal(cmptOption) {
  398. return cmptOption && cmptOption.id != null && makeComparableKey(cmptOption.id).indexOf(INTERNAL_COMPONENT_ID_PREFIX) === 0;
  399. }
  400. export function makeInternalComponentId(idSuffix) {
  401. return INTERNAL_COMPONENT_ID_PREFIX + idSuffix;
  402. }
  403. export function setComponentTypeToKeyInfo(mappingResult, mainType, componentModelCtor) {
  404. // Set mainType and complete subType.
  405. each(mappingResult, function (item) {
  406. var newOption = item.newOption;
  407. if (isObject(newOption)) {
  408. item.keyInfo.mainType = mainType;
  409. item.keyInfo.subType = determineSubType(mainType, newOption, item.existing, componentModelCtor);
  410. }
  411. });
  412. }
  413. function determineSubType(mainType, newCmptOption, existComponent, componentModelCtor) {
  414. var subType = newCmptOption.type ? newCmptOption.type : existComponent ? existComponent.subType
  415. // Use determineSubType only when there is no existComponent.
  416. : componentModelCtor.determineSubType(mainType, newCmptOption);
  417. // tooltip, markline, markpoint may always has no subType
  418. return subType;
  419. }
  420. /**
  421. * A helper for removing duplicate items between batchA and batchB,
  422. * and in themselves, and categorize by series.
  423. *
  424. * @param batchA Like: [{seriesId: 2, dataIndex: [32, 4, 5]}, ...]
  425. * @param batchB Like: [{seriesId: 2, dataIndex: [32, 4, 5]}, ...]
  426. * @return result: [resultBatchA, resultBatchB]
  427. */
  428. export function compressBatches(batchA, batchB) {
  429. var mapA = {};
  430. var mapB = {};
  431. makeMap(batchA || [], mapA);
  432. makeMap(batchB || [], mapB, mapA);
  433. return [mapToArray(mapA), mapToArray(mapB)];
  434. function makeMap(sourceBatch, map, otherMap) {
  435. for (var i = 0, len = sourceBatch.length; i < len; i++) {
  436. var seriesId = convertOptionIdName(sourceBatch[i].seriesId, null);
  437. if (seriesId == null) {
  438. return;
  439. }
  440. var dataIndices = normalizeToArray(sourceBatch[i].dataIndex);
  441. var otherDataIndices = otherMap && otherMap[seriesId];
  442. for (var j = 0, lenj = dataIndices.length; j < lenj; j++) {
  443. var dataIndex = dataIndices[j];
  444. if (otherDataIndices && otherDataIndices[dataIndex]) {
  445. otherDataIndices[dataIndex] = null;
  446. } else {
  447. (map[seriesId] || (map[seriesId] = {}))[dataIndex] = 1;
  448. }
  449. }
  450. }
  451. }
  452. function mapToArray(map, isData) {
  453. var result = [];
  454. for (var i in map) {
  455. if (map.hasOwnProperty(i) && map[i] != null) {
  456. if (isData) {
  457. result.push(+i);
  458. } else {
  459. var dataIndices = mapToArray(map[i], true);
  460. dataIndices.length && result.push({
  461. seriesId: i,
  462. dataIndex: dataIndices
  463. });
  464. }
  465. }
  466. }
  467. return result;
  468. }
  469. }
  470. /**
  471. * @param payload Contains dataIndex (means rawIndex) / dataIndexInside / name
  472. * each of which can be Array or primary type.
  473. * @return dataIndex If not found, return undefined/null.
  474. */
  475. export function queryDataIndex(data, payload) {
  476. if (payload.dataIndexInside != null) {
  477. return payload.dataIndexInside;
  478. } else if (payload.dataIndex != null) {
  479. return isArray(payload.dataIndex) ? map(payload.dataIndex, function (value) {
  480. return data.indexOfRawIndex(value);
  481. }) : data.indexOfRawIndex(payload.dataIndex);
  482. } else if (payload.name != null) {
  483. return isArray(payload.name) ? map(payload.name, function (value) {
  484. return data.indexOfName(value);
  485. }) : data.indexOfName(payload.name);
  486. }
  487. }
  488. /**
  489. * [CAVEAT]:
  490. * DO NOT use it in performance-sensitive scenarios.
  491. * Likely a hash map lookup; not inline-cache friendly.
  492. *
  493. * Enable property storage to any host object.
  494. * Notice: Serialization is not supported.
  495. *
  496. * For example:
  497. * let inner = makeInner();
  498. *
  499. * function some1(hostObj) {
  500. * inner(hostObj).someProperty = 1212;
  501. * ...
  502. * }
  503. * function some2() {
  504. * let fields = inner(this);
  505. * fields.someProperty1 = 1212;
  506. * fields.someProperty2 = 'xx';
  507. * ...
  508. * }
  509. */
  510. export function makeInner() {
  511. var key = '__ec_inner_' + innerUniqueIndex++;
  512. return function (hostObj) {
  513. return hostObj[key] || (hostObj[key] = {});
  514. };
  515. }
  516. var innerUniqueIndex = getRandomIdBase();
  517. /**
  518. * The same behavior as `component.getReferringComponents`.
  519. */
  520. export function parseFinder(ecModel, finderInput, opt) {
  521. var _a = preParseFinder(finderInput, opt),
  522. mainTypeSpecified = _a.mainTypeSpecified,
  523. queryOptionMap = _a.queryOptionMap,
  524. others = _a.others;
  525. var result = others;
  526. var defaultMainType = opt ? opt.defaultMainType : null;
  527. if (!mainTypeSpecified && defaultMainType) {
  528. queryOptionMap.set(defaultMainType, {});
  529. }
  530. queryOptionMap.each(function (queryOption, mainType) {
  531. var queryResult = queryReferringComponents(ecModel, mainType, queryOption, {
  532. useDefault: defaultMainType === mainType,
  533. enableAll: opt && opt.enableAll != null ? opt.enableAll : true,
  534. enableNone: opt && opt.enableNone != null ? opt.enableNone : true
  535. });
  536. result[mainType + 'Models'] = queryResult.models;
  537. result[mainType + 'Model'] = queryResult.models[0];
  538. });
  539. return result;
  540. }
  541. export function preParseFinder(finderInput, opt) {
  542. var finder;
  543. if (isString(finderInput)) {
  544. var obj = {};
  545. obj[finderInput + 'Index'] = 0;
  546. finder = obj;
  547. } else {
  548. finder = finderInput;
  549. }
  550. var queryOptionMap = createHashMap();
  551. var others = {};
  552. var mainTypeSpecified = false;
  553. each(finder, function (value, key) {
  554. // Exclude 'dataIndex' and other illegal keys.
  555. if (key === 'dataIndex' || key === 'dataIndexInside') {
  556. others[key] = value;
  557. return;
  558. }
  559. var parsedKey = key.match(/^(\w+)(Index|Id|Name)$/) || [];
  560. var mainType = parsedKey[1];
  561. var queryType = (parsedKey[2] || '').toLowerCase();
  562. if (!mainType || !queryType || opt && opt.includeMainTypes && indexOf(opt.includeMainTypes, mainType) < 0) {
  563. return;
  564. }
  565. mainTypeSpecified = mainTypeSpecified || !!mainType;
  566. var queryOption = queryOptionMap.get(mainType) || queryOptionMap.set(mainType, {});
  567. queryOption[queryType] = value;
  568. });
  569. return {
  570. mainTypeSpecified: mainTypeSpecified,
  571. queryOptionMap: queryOptionMap,
  572. others: others
  573. };
  574. }
  575. export var SINGLE_REFERRING = {
  576. useDefault: true,
  577. enableAll: false,
  578. enableNone: false
  579. };
  580. export var MULTIPLE_REFERRING = {
  581. useDefault: false,
  582. enableAll: true,
  583. enableNone: true
  584. };
  585. export function queryReferringComponents(ecModel, mainType, userOption, opt) {
  586. opt = opt || SINGLE_REFERRING;
  587. var indexOption = userOption.index;
  588. var idOption = userOption.id;
  589. var nameOption = userOption.name;
  590. var result = {
  591. models: null,
  592. specified: indexOption != null || idOption != null || nameOption != null
  593. };
  594. if (!result.specified) {
  595. // Use the first as default if `useDefault`.
  596. var firstCmpt = void 0;
  597. result.models = opt.useDefault && (firstCmpt = ecModel.getComponent(mainType)) ? [firstCmpt] : [];
  598. return result;
  599. }
  600. if (indexOption === 'none' || indexOption === false) {
  601. if (opt.enableNone) {
  602. result.models = [];
  603. return result;
  604. } else {
  605. // Do not throw; consider if some component previously does not use this method,
  606. // and start to use it, need to be fault-tolerant for backward compatibility.
  607. if (process.env.NODE_ENV !== 'production') {
  608. error('`"none"` or `false` is not a valid value on index option.');
  609. }
  610. indexOption = -1; // Can not query by index but may still query by id/name if specified.
  611. }
  612. }
  613. // `queryComponents` will return all components if
  614. // both all of index/id/name are null/undefined.
  615. if (indexOption === 'all') {
  616. if (opt.enableAll) {
  617. indexOption = idOption = nameOption = null;
  618. } else {
  619. if (process.env.NODE_ENV !== 'production') {
  620. error('`"all"` is not a valid value on index option.');
  621. }
  622. indexOption = -1;
  623. }
  624. }
  625. result.models = ecModel.queryComponents({
  626. mainType: mainType,
  627. index: indexOption,
  628. id: idOption,
  629. name: nameOption
  630. });
  631. return result;
  632. }
  633. /**
  634. * `{{mainType}Id, {mainType}Index, {mainType}Name}` takes precedence if provided in `payload`;
  635. * otherwise, query by `mainType`.
  636. * `subType` performs futher filtering if provided.
  637. */
  638. export function makeQueryConditionKindA(payload,
  639. // `mainType` is mandatory to restrict the range of query,
  640. // since `payload` in an user input.
  641. mainType,
  642. // subType may be '' by `parseClassType`
  643. subType) {
  644. if (process.env.NODE_ENV !== 'production') {
  645. assert(mainType);
  646. }
  647. var query = {};
  648. query[mainType + 'Id'] = payload[mainType + 'Id'];
  649. query[mainType + 'Index'] = payload[mainType + 'Index'];
  650. query[mainType + 'Name'] = payload[mainType + 'Name'];
  651. var condition = {
  652. mainType: mainType,
  653. query: query
  654. };
  655. subType && (condition.subType = subType); // subType is determined by `hasOwnProperty`.
  656. return condition;
  657. }
  658. export function setAttribute(dom, key, value) {
  659. dom.setAttribute ? dom.setAttribute(key, value) : dom[key] = value;
  660. }
  661. export function getAttribute(dom, key) {
  662. return dom.getAttribute ? dom.getAttribute(key) : dom[key];
  663. }
  664. export function getTooltipRenderMode(renderModeOption) {
  665. if (renderModeOption === 'auto') {
  666. // Using html when `document` exists, use richText otherwise
  667. return env.domSupported ? 'html' : 'richText';
  668. } else {
  669. return renderModeOption || 'html';
  670. }
  671. }
  672. /**
  673. * Group a list by key.
  674. */
  675. export function groupData(array, getKey // return key
  676. ) {
  677. var buckets = createHashMap();
  678. var keys = [];
  679. each(array, function (item) {
  680. var key = getKey(item);
  681. (buckets.get(key) || (keys.push(key), buckets.set(key, []))).push(item);
  682. });
  683. return {
  684. keys: keys,
  685. buckets: buckets
  686. };
  687. }
  688. /**
  689. * Interpolate raw values of a series with percent
  690. *
  691. * @param data data
  692. * @param labelModel label model of the text element
  693. * @param sourceValue start value. May be null/undefined when init.
  694. * @param targetValue end value
  695. * @param percent 0~1 percentage; 0 uses start value while 1 uses end value
  696. * @return interpolated values
  697. * If `sourceValue` and `targetValue` are `number`, return `number`.
  698. * If `sourceValue` and `targetValue` are `string`, return `string`.
  699. * If `sourceValue` and `targetValue` are `(string | number)[]`, return `(string | number)[]`.
  700. * Other cases do not supported.
  701. */
  702. export function interpolateRawValues(data, precision, sourceValue, targetValue, percent) {
  703. var isAutoPrecision = precision == null || precision === 'auto';
  704. if (targetValue == null) {
  705. return targetValue;
  706. }
  707. if (isNumber(targetValue)) {
  708. var value = interpolateNumber(sourceValue || 0, targetValue, percent);
  709. return round(value, isAutoPrecision ? Math.max(getPrecision(sourceValue || 0), getPrecision(targetValue)) : precision);
  710. } else if (isString(targetValue)) {
  711. return percent < 1 ? sourceValue : targetValue;
  712. } else {
  713. var interpolated = [];
  714. var leftArr = sourceValue;
  715. var rightArr = targetValue;
  716. var length_1 = Math.max(leftArr ? leftArr.length : 0, rightArr.length);
  717. for (var i = 0; i < length_1; ++i) {
  718. var info = data.getDimensionInfo(i);
  719. // Don't interpolate ordinal dims
  720. if (info && info.type === 'ordinal') {
  721. // In init, there is no `sourceValue`, but should better not to get undefined result.
  722. interpolated[i] = (percent < 1 && leftArr ? leftArr : rightArr)[i];
  723. } else {
  724. var leftVal = leftArr && leftArr[i] ? leftArr[i] : 0;
  725. var rightVal = rightArr[i];
  726. var value = interpolateNumber(leftVal, rightVal, percent);
  727. interpolated[i] = round(value, isAutoPrecision ? Math.max(getPrecision(leftVal), getPrecision(rightVal)) : precision);
  728. }
  729. }
  730. return interpolated;
  731. }
  732. }
  733. /**
  734. * Use an iterator to avoid exposing the internal list or duplicating it
  735. * for the outside traveller, and no extra heap allocation.
  736. * @usage
  737. * for (const it = resetIterator(); it.next();) {
  738. * const item = it.item;
  739. * const key = it.key;
  740. * const itIdx = it.itIdx;
  741. * // ...
  742. * }
  743. * @usage
  744. * const it = resetIterator();
  745. * while (it.next()) { ... }
  746. * @usage
  747. * for (resetIterator(it); it.next();) { ... }
  748. */
  749. var ListIterator = /** @class */function () {
  750. function ListIterator() {}
  751. /**
  752. * The loop condition is `idx < end` if `step > 0`;
  753. * The loop condition is `idx >= end` if `step < 0`.
  754. *
  755. * @param end By default `list.length` if `step > 0`; `0` if `step < 0`.
  756. * @param step By default `1`.
  757. */
  758. ListIterator.prototype.reset = function (list, start, end, step) {
  759. this._list = list;
  760. this._step = step = step || 1;
  761. this._idx = start;
  762. this._end = end != null ? end : step > 0 ? list.length : 0;
  763. this.item = null;
  764. this.key = NaN;
  765. return this;
  766. };
  767. ListIterator.prototype.next = function () {
  768. if (this._step > 0 ? this._idx < this._end : this._idx >= this._end) {
  769. this.item = this._list[this._idx];
  770. this.key = this._idx = this._idx + this._step;
  771. return true;
  772. }
  773. return false;
  774. };
  775. return ListIterator;
  776. }();
  777. export { ListIterator };
  778. export function clearTmpModel(model) {
  779. // Clear to avoid memory leak.
  780. model.option = model.parentModel = model.ecModel = null;
  781. }
  782. export function initExtentForUnion() {
  783. return [Infinity, -Infinity];
  784. }
  785. /**
  786. * NOTICE:
  787. * - The input `val` must be a number - type checking is not performed.
  788. * - `extent` should be initialized as `initExtentForUnion()`.
  789. */
  790. export function unionExtentFromNumber(extent, val) {
  791. if (isValidNumberForExtent(val)) {
  792. val < extent[0] && (extent[0] = val);
  793. val > extent[1] && (extent[1] = val);
  794. }
  795. }
  796. /**
  797. * NOTICE:
  798. * - The input `val` must be a number - type checking is not performed.
  799. * - `extent` should be initialized as `initExtentForUnion()`.
  800. */
  801. export function unionExtentStartFromNumber(extent, val) {
  802. if (isValidNumberForExtent(val) && val < extent[0]) {
  803. extent[0] = val;
  804. }
  805. }
  806. /**
  807. * NOTICE:
  808. * - The input `val` must be a number - type checking is not performed.
  809. * - `extent` should be initialized as `initExtentForUnion()`.
  810. */
  811. export function unionExtentEndFromNumber(extent, val) {
  812. if (isValidNumberForExtent(val) && val > extent[1]) {
  813. extent[1] = val;
  814. }
  815. }
  816. /**
  817. * NOTICE:
  818. * - `extent` should be initialized as `initExtentForUnion()`.
  819. */
  820. export function unionExtentFromExtent(tarExtent, srcExtent) {
  821. // Accept both or neither.
  822. if (isValidBoundsForExtent(srcExtent[0], srcExtent[1])) {
  823. srcExtent[0] < tarExtent[0] && (tarExtent[0] = srcExtent[0]);
  824. srcExtent[1] > tarExtent[1] && (tarExtent[1] = srcExtent[1]);
  825. }
  826. }
  827. /**
  828. * PENDING: `Infinity` from user data is not necessarily meaningless, but visualizing it requires
  829. * special handling and it will not be supported until required. So we simply ignore it here.
  830. */
  831. export function isValidNumberForExtent(val) {
  832. // Considered that number could be `NaN` and `Infinity` and should not write into the extent.
  833. // Note that `Infinity` is the initialized value from `initExtentForUnion` and may be inputted.
  834. return val != null && isFinite(val);
  835. }
  836. export function isValidBoundsForExtent(start, end) {
  837. return isValidNumberForExtent(start) && isValidNumberForExtent(end) && start <= end;
  838. }
  839. /**
  840. * `extent` should be initialized by `initExtentForUnion()`, and unioned by `unionExtent()`.
  841. * `extent` may contain `Infinity` / `NaN`, but assume no `null`/`undefined`.
  842. */
  843. export function extentHasValue(extent) {
  844. // Also considered extent may have `NaN` and `Infinity`.
  845. var span = extent[1] - extent[0];
  846. return isFinite(span) && span >= 0;
  847. }
  848. /**
  849. * NOTE: considered items are null/undefined/NaN - do nothing for this case.
  850. */
  851. export function ensureExtentAscSimply(extent) {
  852. if (isValidBoundsForExtent(extent[0], extent[1]) && extent[0] > extent[1]) {
  853. extent[0] = extent[1];
  854. }
  855. }
  856. /**
  857. * A util for ensuring the callback is called only once.
  858. * @usage
  859. * const callOnlyOnce = makeCallOnlyOnce(); // Should be static (ESM top level).
  860. * function someFunc(registers: EChartsExtensionInstallRegisters): void {
  861. * callOnlyOnce(registers, function () {
  862. * // Do something immediately and only once per registers.
  863. * }
  864. * }
  865. */
  866. export function makeCallOnlyOnce() {
  867. var hiddenKey = '__ec_once_' + onceUniqueIndex++;
  868. return function (hostObj, cb) {
  869. if (process.env.NODE_ENV !== 'production') {
  870. assert(hostObj);
  871. }
  872. if (!hasOwn(hostObj, hiddenKey)) {
  873. hostObj[hiddenKey] = 1;
  874. cb();
  875. }
  876. };
  877. }
  878. var onceUniqueIndex = getRandomIdBase();
  879. /**
  880. * @usage
  881. * - The earlier item takes precedence for duplicate items.
  882. * - The input `arr` will be modified if `resolve` is null/undefined.
  883. * - Callers can use `resolve` to manually modify the `currItem`.
  884. * The input `arr` will not be modified if `resolve` is passed.
  885. * `resolve` will be called on every item.
  886. * - Callers need to handle null/undefined (if existing) in `getKey`.
  887. */
  888. export function removeDuplicates(arr, getKey,
  889. // `existingCount`: the count before this item is added.
  890. resolve) {
  891. var dupMap = createHashMap();
  892. var writeIdx = 0;
  893. each(arr, function (item) {
  894. var key = getKey(item);
  895. if (process.env.NODE_ENV !== 'production') {
  896. assert(isString(key));
  897. }
  898. var count = dupMap.get(key) || 0;
  899. if (resolve) {
  900. resolve(item, count);
  901. }
  902. if (!count && !resolve) {
  903. arr[writeIdx++] = item;
  904. }
  905. dupMap.set(key, count + 1);
  906. });
  907. if (!resolve) {
  908. arr.length = writeIdx;
  909. }
  910. }
  911. export function removeDuplicatesGetKeyFromValueProp(item) {
  912. if (process.env.NODE_ENV !== 'production') {
  913. assert(item.value != null);
  914. }
  915. return item.value + '';
  916. }
  917. export function removeDuplicatesGetKeyFromItemItself(item) {
  918. if (process.env.NODE_ENV !== 'production') {
  919. assert(item != null);
  920. }
  921. return item + '';
  922. }
  923. export function getIncrementalId(seriesModel, useIncremental) {
  924. // 0 means disable incremental.
  925. // 1 is preserved for backward compatibility.
  926. return retrieve2(useIncremental, true) ? seriesModel.seriesIndex + 2 : 0;
  927. }
  928. export function preparePipelineContext(seriesModel, view, pipeline) {
  929. var dataLen = seriesModel.getData().count();
  930. return {
  931. progressiveRender: pipeline.progressiveEnabled && view.incrementalPrepareRender && dataLen >= pipeline.threshold,
  932. large: seriesModel.get('large') && dataLen >= seriesModel.get('largeThreshold'),
  933. // TODO: modDataCount should not updated if `appendData`, otherwise cause whole repaint.
  934. // see `test/candlestick-large3.html`
  935. modDataCount: seriesModel.get('progressiveChunkMode') === 'mod' ? seriesModel.getData().count() : null
  936. };
  937. }
  938. /**
  939. * When some task "blocks" the upstream part of the pipeline, the upstream output (typically, a TypedArray
  940. * to carry layout points, e.g., `data.getLayout('points')`) range is from the start to the final end.
  941. * A downstream comsumer should either properly record cursors when reading from the upstream output,
  942. * or fail fast on that usage.
  943. * Otherwise, take the `data.getLayout('points')` as an example, repeatedly merging it with the existing
  944. * shape path can create a big path, which degrades performance but is hard to detect.
  945. *
  946. * This method provides an assertion for that.
  947. */
  948. export function validateUpstreamOutputRange(
  949. // null/undefined is not allowed, otherwise bug-prone.
  950. upstreamOutputRange, thisTaskPlannedRange) {
  951. assert(upstreamOutputRange.start === thisTaskPlannedRange.start && upstreamOutputRange.end === thisTaskPlannedRange.end);
  952. }
  953. export function createSimpleOverallStageHandler(seriesType, overallReset) {
  954. return {
  955. seriesType: seriesType,
  956. overallReset: overallReset
  957. };
  958. }
  959. export function createSimpleOverallStageHandler2(overallReset) {
  960. return {
  961. overallReset: overallReset
  962. };
  963. }