number.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563
  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. /*
  41. * A third-party license is embedded for some of the code in this file:
  42. * The method "quantile" was copied from "d3.js".
  43. * (See more details in the comment of the method below.)
  44. * The use of the source code of this file is also subject to the terms
  45. * and consitions of the license of "d3.js" (BSD-3Clause, see
  46. * </licenses/LICENSE-d3>).
  47. */
  48. import * as zrUtil from 'zrender/lib/core/util.js';
  49. var RADIAN_EPSILON = 1e-4;
  50. // Although chrome already enlarge this number to 100 for `toFixed`, but
  51. // we sill follow the spec for compatibility.
  52. var ROUND_SUPPORTED_PRECISION_MAX = 20;
  53. function _trim(str) {
  54. return str.replace(/^\s+|\s+$/g, '');
  55. }
  56. /**
  57. * Linear mapping a value from domain to range
  58. * @param val
  59. * @param domain Domain extent domain[0] can be bigger than domain[1]
  60. * @param range Range extent range[0] can be bigger than range[1]
  61. * @param clamp Default to be false
  62. */
  63. export function linearMap(val, domain, range, clamp) {
  64. var d0 = domain[0];
  65. var d1 = domain[1];
  66. var r0 = range[0];
  67. var r1 = range[1];
  68. var subDomain = d1 - d0;
  69. var subRange = r1 - r0;
  70. if (subDomain === 0) {
  71. return subRange === 0 ? r0 : (r0 + r1) / 2;
  72. }
  73. // Avoid accuracy problem in edge, such as
  74. // 146.39 - 62.83 === 83.55999999999999.
  75. // See echarts/test/ut/spec/util/number.js#linearMap#accuracyError
  76. // It is a little verbose for efficiency considering this method
  77. // is a hotspot.
  78. if (clamp) {
  79. if (subDomain > 0) {
  80. if (val <= d0) {
  81. return r0;
  82. } else if (val >= d1) {
  83. return r1;
  84. }
  85. } else {
  86. if (val >= d0) {
  87. return r0;
  88. } else if (val <= d1) {
  89. return r1;
  90. }
  91. }
  92. } else {
  93. if (val === d0) {
  94. return r0;
  95. }
  96. if (val === d1) {
  97. return r1;
  98. }
  99. }
  100. return (val - d0) / subDomain * subRange + r0;
  101. }
  102. /**
  103. * Convert a percent string to absolute number.
  104. * Returns NaN if percent is not a valid string or number
  105. */
  106. export function parsePercent(percent, all) {
  107. switch (percent) {
  108. case 'center':
  109. case 'middle':
  110. percent = '50%';
  111. break;
  112. case 'left':
  113. case 'top':
  114. percent = '0%';
  115. break;
  116. case 'right':
  117. case 'bottom':
  118. percent = '100%';
  119. break;
  120. }
  121. if (zrUtil.isString(percent)) {
  122. if (_trim(percent).match(/%$/)) {
  123. return parseFloat(percent) / 100 * all;
  124. }
  125. return parseFloat(percent);
  126. }
  127. return percent == null ? NaN : +percent;
  128. }
  129. export function round(x, precision, returnStr) {
  130. if (precision == null) {
  131. precision = 10;
  132. }
  133. // Avoid range error
  134. precision = Math.min(Math.max(0, precision), ROUND_SUPPORTED_PRECISION_MAX);
  135. // PENDING: 1.005.toFixed(2) is '1.00' rather than '1.01'
  136. x = (+x).toFixed(precision);
  137. return returnStr ? x : +x;
  138. }
  139. /**
  140. * Inplacd asc sort arr.
  141. * The input arr will be modified.
  142. */
  143. export function asc(arr) {
  144. arr.sort(function (a, b) {
  145. return a - b;
  146. });
  147. return arr;
  148. }
  149. /**
  150. * Get precision.
  151. */
  152. export function getPrecision(val) {
  153. val = +val;
  154. if (isNaN(val)) {
  155. return 0;
  156. }
  157. // It is much faster than methods converting number to string as follows
  158. // let tmp = val.toString();
  159. // return tmp.length - 1 - tmp.indexOf('.');
  160. // especially when precision is low
  161. // Notice:
  162. // (1) If the loop count is over about 20, it is slower than `getPrecisionSafe`.
  163. // (see https://jsbench.me/2vkpcekkvw/1)
  164. // (2) If the val is less than for example 1e-15, the result may be incorrect.
  165. // (see test/ut/spec/util/number.test.ts `getPrecision_equal_random`)
  166. if (val > 1e-14) {
  167. var e = 1;
  168. for (var i = 0; i < 15; i++, e *= 10) {
  169. if (Math.round(val * e) / e === val) {
  170. return i;
  171. }
  172. }
  173. }
  174. return getPrecisionSafe(val);
  175. }
  176. /**
  177. * Get precision with slow but safe method
  178. */
  179. export function getPrecisionSafe(val) {
  180. // toLowerCase for: '3.4E-12'
  181. var str = val.toString().toLowerCase();
  182. // Consider scientific notation: '3.4e-12' '3.4e+12'
  183. var eIndex = str.indexOf('e');
  184. var exp = eIndex > 0 ? +str.slice(eIndex + 1) : 0;
  185. var significandPartLen = eIndex > 0 ? eIndex : str.length;
  186. var dotIndex = str.indexOf('.');
  187. var decimalPartLen = dotIndex < 0 ? 0 : significandPartLen - 1 - dotIndex;
  188. return Math.max(0, decimalPartLen - exp);
  189. }
  190. /**
  191. * Minimal dicernible data precisioin according to a single pixel.
  192. */
  193. export function getPixelPrecision(dataExtent, pixelExtent) {
  194. var log = Math.log;
  195. var LN10 = Math.LN10;
  196. var dataQuantity = Math.floor(log(dataExtent[1] - dataExtent[0]) / LN10);
  197. var sizeQuantity = Math.round(log(Math.abs(pixelExtent[1] - pixelExtent[0])) / LN10);
  198. // toFixed() digits argument must be between 0 and 20.
  199. var precision = Math.min(Math.max(-dataQuantity + sizeQuantity, 0), 20);
  200. return !isFinite(precision) ? 20 : precision;
  201. }
  202. /**
  203. * Get a data of given precision, assuring the sum of percentages
  204. * in valueList is 1.
  205. * The largest remainder method is used.
  206. * https://en.wikipedia.org/wiki/Largest_remainder_method
  207. *
  208. * @param valueList a list of all data
  209. * @param idx index of the data to be processed in valueList
  210. * @param precision integer number showing digits of precision
  211. * @return percent ranging from 0 to 100
  212. */
  213. export function getPercentWithPrecision(valueList, idx, precision) {
  214. if (!valueList[idx]) {
  215. return 0;
  216. }
  217. var seats = getPercentSeats(valueList, precision);
  218. return seats[idx] || 0;
  219. }
  220. /**
  221. * Get a data of given precision, assuring the sum of percentages
  222. * in valueList is 1.
  223. * The largest remainder method is used.
  224. * https://en.wikipedia.org/wiki/Largest_remainder_method
  225. *
  226. * @param valueList a list of all data
  227. * @param precision integer number showing digits of precision
  228. * @return {Array<number>}
  229. */
  230. export function getPercentSeats(valueList, precision) {
  231. var sum = zrUtil.reduce(valueList, function (acc, val) {
  232. return acc + (isNaN(val) ? 0 : val);
  233. }, 0);
  234. if (sum === 0) {
  235. return [];
  236. }
  237. var digits = Math.pow(10, precision);
  238. var votesPerQuota = zrUtil.map(valueList, function (val) {
  239. return (isNaN(val) ? 0 : val) / sum * digits * 100;
  240. });
  241. var targetSeats = digits * 100;
  242. var seats = zrUtil.map(votesPerQuota, function (votes) {
  243. // Assign automatic seats.
  244. return Math.floor(votes);
  245. });
  246. var currentSum = zrUtil.reduce(seats, function (acc, val) {
  247. return acc + val;
  248. }, 0);
  249. var remainder = zrUtil.map(votesPerQuota, function (votes, idx) {
  250. return votes - seats[idx];
  251. });
  252. // Has remainding votes.
  253. while (currentSum < targetSeats) {
  254. // Find next largest remainder.
  255. var max = Number.NEGATIVE_INFINITY;
  256. var maxId = null;
  257. for (var i = 0, len = remainder.length; i < len; ++i) {
  258. if (remainder[i] > max) {
  259. max = remainder[i];
  260. maxId = i;
  261. }
  262. }
  263. // Add a vote to max remainder.
  264. ++seats[maxId];
  265. remainder[maxId] = 0;
  266. ++currentSum;
  267. }
  268. return zrUtil.map(seats, function (seat) {
  269. return seat / digits;
  270. });
  271. }
  272. /**
  273. * Solve the floating point adding problem like 0.1 + 0.2 === 0.30000000000000004
  274. * See <http://0.30000000000000004.com/>
  275. */
  276. export function addSafe(val0, val1) {
  277. var maxPrecision = Math.max(getPrecision(val0), getPrecision(val1));
  278. // const multiplier = Math.pow(10, maxPrecision);
  279. // return (Math.round(val0 * multiplier) + Math.round(val1 * multiplier)) / multiplier;
  280. var sum = val0 + val1;
  281. // // PENDING: support more?
  282. return maxPrecision > ROUND_SUPPORTED_PRECISION_MAX ? sum : round(sum, maxPrecision);
  283. }
  284. // Number.MAX_SAFE_INTEGER, ie do not support.
  285. export var MAX_SAFE_INTEGER = 9007199254740991;
  286. /**
  287. * To 0 - 2 * PI, considering negative radian.
  288. */
  289. export function remRadian(radian) {
  290. var pi2 = Math.PI * 2;
  291. return (radian % pi2 + pi2) % pi2;
  292. }
  293. /**
  294. * @param {type} radian
  295. * @return {boolean}
  296. */
  297. export function isRadianAroundZero(val) {
  298. return val > -RADIAN_EPSILON && val < RADIAN_EPSILON;
  299. }
  300. // eslint-disable-next-line
  301. var TIME_REG = /^(?:(\d{4})(?:[-\/](\d{1,2})(?:[-\/](\d{1,2})(?:[T ](\d{1,2})(?::(\d{1,2})(?::(\d{1,2})(?:[.,](\d+))?)?)?(Z|[\+\-]\d\d:?\d\d)?)?)?)?)?$/; // jshint ignore:line
  302. /**
  303. * @param value valid type: number | string | Date, otherwise return `new Date(NaN)`
  304. * These values can be accepted:
  305. * + An instance of Date, represent a time in its own time zone.
  306. * + Or string in a subset of ISO 8601, only including:
  307. * + only year, month, date: '2012-03', '2012-03-01', '2012-03-01 05', '2012-03-01 05:06',
  308. * + separated with T or space: '2012-03-01T12:22:33.123', '2012-03-01 12:22:33.123',
  309. * + time zone: '2012-03-01T12:22:33Z', '2012-03-01T12:22:33+8000', '2012-03-01T12:22:33-05:00',
  310. * all of which will be treated as local time if time zone is not specified
  311. * (see <https://momentjs.com/>).
  312. * + Or other string format, including (all of which will be treated as local time):
  313. * '2012', '2012-3-1', '2012/3/1', '2012/03/01',
  314. * '2009/6/12 2:00', '2009/6/12 2:05:08', '2009/6/12 2:05:08.123'
  315. * + a timestamp, which represent a time in UTC.
  316. * @return date Never be null/undefined. If invalid, return `new Date(NaN)`.
  317. */
  318. export function parseDate(value) {
  319. if (value instanceof Date) {
  320. return value;
  321. } else if (zrUtil.isString(value)) {
  322. // Different browsers parse date in different way, so we parse it manually.
  323. // Some other issues:
  324. // new Date('1970-01-01') is UTC,
  325. // new Date('1970/01/01') and new Date('1970-1-01') is local.
  326. // See issue #3623
  327. var match = TIME_REG.exec(value);
  328. if (!match) {
  329. // return Invalid Date.
  330. return new Date(NaN);
  331. }
  332. // Use local time when no timezone offset is specified.
  333. if (!match[8]) {
  334. // match[n] can only be string or undefined.
  335. // But take care of '12' + 1 => '121'.
  336. return new Date(+match[1], +(match[2] || 1) - 1, +match[3] || 1, +match[4] || 0, +(match[5] || 0), +match[6] || 0, match[7] ? +match[7].substring(0, 3) : 0);
  337. }
  338. // Timezoneoffset of Javascript Date has considered DST (Daylight Saving Time,
  339. // https://tc39.github.io/ecma262/#sec-daylight-saving-time-adjustment).
  340. // For example, system timezone is set as "Time Zone: America/Toronto",
  341. // then these code will get different result:
  342. // `new Date(1478411999999).getTimezoneOffset(); // get 240`
  343. // `new Date(1478412000000).getTimezoneOffset(); // get 300`
  344. // So we should not use `new Date`, but use `Date.UTC`.
  345. else {
  346. var hour = +match[4] || 0;
  347. if (match[8].toUpperCase() !== 'Z') {
  348. hour -= +match[8].slice(0, 3);
  349. }
  350. return new Date(Date.UTC(+match[1], +(match[2] || 1) - 1, +match[3] || 1, hour, +(match[5] || 0), +match[6] || 0, match[7] ? +match[7].substring(0, 3) : 0));
  351. }
  352. } else if (value == null) {
  353. return new Date(NaN);
  354. }
  355. return new Date(Math.round(value));
  356. }
  357. /**
  358. * Quantity of a number. e.g. 0.1, 1, 10, 100
  359. *
  360. * @param val
  361. * @return
  362. */
  363. export function quantity(val) {
  364. return Math.pow(10, quantityExponent(val));
  365. }
  366. /**
  367. * Exponent of the quantity of a number
  368. * e.g., 1234 equals to 1.234*10^3, so quantityExponent(1234) is 3
  369. *
  370. * @param val non-negative value
  371. * @return
  372. */
  373. export function quantityExponent(val) {
  374. if (val === 0) {
  375. return 0;
  376. }
  377. var exp = Math.floor(Math.log(val) / Math.LN10);
  378. /**
  379. * exp is expected to be the rounded-down result of the base-10 log of val.
  380. * But due to the precision loss with Math.log(val), we need to restore it
  381. * using 10^exp to make sure we can get val back from exp. #11249
  382. */
  383. if (val / Math.pow(10, exp) >= 10) {
  384. exp++;
  385. }
  386. return exp;
  387. }
  388. /**
  389. * find a “nice” number approximately equal to x. Round the number if round = true,
  390. * take ceiling if round = false. The primary observation is that the “nicest”
  391. * numbers in decimal are 1, 2, and 5, and all power-of-ten multiples of these numbers.
  392. *
  393. * See "Nice Numbers for Graph Labels" of Graphic Gems.
  394. *
  395. * @param val Non-negative value.
  396. * @param round
  397. * @return Niced number
  398. */
  399. export function nice(val, round) {
  400. var exponent = quantityExponent(val);
  401. var exp10 = Math.pow(10, exponent);
  402. var f = val / exp10; // 1 <= f < 10
  403. var nf;
  404. if (round) {
  405. if (f < 1.5) {
  406. nf = 1;
  407. } else if (f < 2.5) {
  408. nf = 2;
  409. } else if (f < 4) {
  410. nf = 3;
  411. } else if (f < 7) {
  412. nf = 5;
  413. } else {
  414. nf = 10;
  415. }
  416. } else {
  417. if (f < 1) {
  418. nf = 1;
  419. } else if (f < 2) {
  420. nf = 2;
  421. } else if (f < 3) {
  422. nf = 3;
  423. } else if (f < 5) {
  424. nf = 5;
  425. } else {
  426. nf = 10;
  427. }
  428. }
  429. val = nf * exp10;
  430. // Fix 3 * 0.1 === 0.30000000000000004 issue (see IEEE 754).
  431. // 20 is the uppper bound of toFixed.
  432. return exponent >= -20 ? +val.toFixed(exponent < 0 ? -exponent : 0) : val;
  433. }
  434. /**
  435. * This code was copied from "d3.js"
  436. * <https://github.com/d3/d3/blob/9cc9a875e636a1dcf36cc1e07bdf77e1ad6e2c74/src/arrays/quantile.js>.
  437. * See the license statement at the head of this file.
  438. * @param ascArr
  439. */
  440. export function quantile(ascArr, p) {
  441. var H = (ascArr.length - 1) * p + 1;
  442. var h = Math.floor(H);
  443. var v = +ascArr[h - 1];
  444. var e = H - h;
  445. return e ? v + e * (ascArr[h] - v) : v;
  446. }
  447. /**
  448. * Order intervals asc, and split them when overlap.
  449. * expect(numberUtil.reformIntervals([
  450. * {interval: [18, 62], close: [1, 1]},
  451. * {interval: [-Infinity, -70], close: [0, 0]},
  452. * {interval: [-70, -26], close: [1, 1]},
  453. * {interval: [-26, 18], close: [1, 1]},
  454. * {interval: [62, 150], close: [1, 1]},
  455. * {interval: [106, 150], close: [1, 1]},
  456. * {interval: [150, Infinity], close: [0, 0]}
  457. * ])).toEqual([
  458. * {interval: [-Infinity, -70], close: [0, 0]},
  459. * {interval: [-70, -26], close: [1, 1]},
  460. * {interval: [-26, 18], close: [0, 1]},
  461. * {interval: [18, 62], close: [0, 1]},
  462. * {interval: [62, 150], close: [0, 1]},
  463. * {interval: [150, Infinity], close: [0, 0]}
  464. * ]);
  465. * @param list, where `close` mean open or close
  466. * of the interval, and Infinity can be used.
  467. * @return The origin list, which has been reformed.
  468. */
  469. export function reformIntervals(list) {
  470. list.sort(function (a, b) {
  471. return littleThan(a, b, 0) ? -1 : 1;
  472. });
  473. var curr = -Infinity;
  474. var currClose = 1;
  475. for (var i = 0; i < list.length;) {
  476. var interval = list[i].interval;
  477. var close_1 = list[i].close;
  478. for (var lg = 0; lg < 2; lg++) {
  479. if (interval[lg] <= curr) {
  480. interval[lg] = curr;
  481. close_1[lg] = !lg ? 1 - currClose : 1;
  482. }
  483. curr = interval[lg];
  484. currClose = close_1[lg];
  485. }
  486. if (interval[0] === interval[1] && close_1[0] * close_1[1] !== 1) {
  487. list.splice(i, 1);
  488. } else {
  489. i++;
  490. }
  491. }
  492. return list;
  493. function littleThan(a, b, lg) {
  494. return a.interval[lg] < b.interval[lg] || a.interval[lg] === b.interval[lg] && (a.close[lg] - b.close[lg] === (!lg ? 1 : -1) || !lg && littleThan(a, b, 1));
  495. }
  496. }
  497. /**
  498. * [Numeric is defined as]:
  499. * `parseFloat(val) == val`
  500. * For example:
  501. * numeric:
  502. * typeof number except NaN, '-123', '123', '2e3', '-2e3', '011', 'Infinity', Infinity,
  503. * and they rounded by white-spaces or line-terminal like ' -123 \n ' (see es spec)
  504. * not-numeric:
  505. * null, undefined, [], {}, true, false, 'NaN', NaN, '123ab',
  506. * empty string, string with only white-spaces or line-terminal (see es spec),
  507. * 0x12, '0x12', '-0x12', 012, '012', '-012',
  508. * non-string, ...
  509. *
  510. * @test See full test cases in `test/ut/spec/util/number.js`.
  511. * @return Must be a typeof number. If not numeric, return NaN.
  512. */
  513. export function numericToNumber(val) {
  514. var valFloat = parseFloat(val);
  515. return valFloat == val // eslint-disable-line eqeqeq
  516. && (valFloat !== 0 || !zrUtil.isString(val) || val.indexOf('x') <= 0) // For case ' 0x0 '.
  517. ? valFloat : NaN;
  518. }
  519. /**
  520. * Definition of "numeric": see `numericToNumber`.
  521. */
  522. export function isNumeric(val) {
  523. return !isNaN(numericToNumber(val));
  524. }
  525. /**
  526. * Use random base to prevent users hard code depending on
  527. * this auto generated marker id.
  528. * @return An positive integer.
  529. */
  530. export function getRandomIdBase() {
  531. return Math.round(Math.random() * 9);
  532. }
  533. /**
  534. * Get the greatest common divisor.
  535. *
  536. * @param {number} a one number
  537. * @param {number} b the other number
  538. */
  539. export function getGreatestCommonDividor(a, b) {
  540. if (b === 0) {
  541. return a;
  542. }
  543. return getGreatestCommonDividor(b, a % b);
  544. }
  545. /**
  546. * Get the least common multiple.
  547. *
  548. * @param {number} a one number
  549. * @param {number} b the other number
  550. */
  551. export function getLeastCommonMultiple(a, b) {
  552. if (a == null) {
  553. return b;
  554. }
  555. if (b == null) {
  556. return a;
  557. }
  558. return a * b / getGreatestCommonDividor(a, b);
  559. }