breakImpl.js 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639
  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 { assert, clone, each, find, isString, map, trim } from 'zrender/lib/core/util.js';
  41. import { error } from '../util/log.js';
  42. import { registerScaleBreakHelperImpl } from './break.js';
  43. import { round as fixRound } from '../util/number.js';
  44. /**
  45. * @caution
  46. * Must not export anything except `installScaleBreakHelper`
  47. */
  48. var ScaleBreakContextImpl = /** @class */function () {
  49. function ScaleBreakContextImpl() {
  50. // [CAVEAT]: Should set only by `ScaleBreakContext#setBreaks`!
  51. this.breaks = [];
  52. // [CAVEAT]: Should update only by `ScaleBreakContext#update`!
  53. // They are the values that scaleExtent[0] and scaleExtent[1] are mapped to a numeric axis
  54. // that breaks are applied, primarily for optimization of `Scale#normalize`.
  55. this._elapsedExtent = [Infinity, -Infinity];
  56. }
  57. ScaleBreakContextImpl.prototype.setBreaks = function (parsed) {
  58. // @ts-ignore
  59. this.breaks = parsed.breaks;
  60. };
  61. /**
  62. * [CAVEAT]: Must be called immediately each time scale extent and breaks are updated!
  63. */
  64. ScaleBreakContextImpl.prototype.update = function (scaleExtent) {
  65. updateAxisBreakGapReal(this, scaleExtent);
  66. var elapsedExtent = this._elapsedExtent;
  67. elapsedExtent[0] = this.elapse(scaleExtent[0]);
  68. elapsedExtent[1] = this.elapse(scaleExtent[1]);
  69. };
  70. ScaleBreakContextImpl.prototype.hasBreaks = function () {
  71. return !!this.breaks.length;
  72. };
  73. /**
  74. * When iteratively generating ticks by nice interval, currently the `interval`, which is
  75. * calculated by break-elapsed extent span, is probably very small comparing to the original
  76. * extent, leading to a large number of iteration and tick generation, even over `safeLimit`.
  77. * Thus stepping over breaks is necessary in that loop.
  78. *
  79. * "Nice" should be ensured on ticks when step over the breaks. Thus this method returns
  80. * a integer multiple of the "nice tick interval".
  81. *
  82. * This method does little work; it is just for unifying and restricting the behavior.
  83. */
  84. ScaleBreakContextImpl.prototype.calcNiceTickMultiple = function (tickVal, estimateNiceMultiple) {
  85. for (var idx = 0; idx < this.breaks.length; idx++) {
  86. var brk = this.breaks[idx];
  87. if (brk.vmin < tickVal && tickVal < brk.vmax) {
  88. var multiple = estimateNiceMultiple(tickVal, brk.vmax);
  89. if (process.env.NODE_ENV !== 'production') {
  90. // If not, it may cause dead loop or not nice tick.
  91. assert(multiple >= 0 && Math.round(multiple) === multiple);
  92. }
  93. return multiple;
  94. }
  95. }
  96. return 0;
  97. };
  98. ScaleBreakContextImpl.prototype.getExtentSpan = function () {
  99. return this._elapsedExtent[1] - this._elapsedExtent[0];
  100. };
  101. ScaleBreakContextImpl.prototype.normalize = function (val) {
  102. var elapsedSpan = this._elapsedExtent[1] - this._elapsedExtent[0];
  103. // The same logic as `Scale#normalize`.
  104. if (elapsedSpan === 0) {
  105. return 0.5;
  106. }
  107. return (this.elapse(val) - this._elapsedExtent[0]) / elapsedSpan;
  108. };
  109. ScaleBreakContextImpl.prototype.scale = function (val) {
  110. return this.unelapse(val * (this._elapsedExtent[1] - this._elapsedExtent[0]) + this._elapsedExtent[0]);
  111. };
  112. /**
  113. * Suppose:
  114. * AXIS_BREAK_LAST_BREAK_END_BASE: 0
  115. * AXIS_BREAK_ELAPSED_BASE: 0
  116. * breaks: [
  117. * {start: -400, end: -300, gap: 27},
  118. * {start: -100, end: 100, gap: 10},
  119. * {start: 200, end: 400, gap: 300},
  120. * ]
  121. * The mapping will be:
  122. * | |
  123. * 400 + -> + 237
  124. * | | | | (gap: 300)
  125. * 200 + -> + -63
  126. * | |
  127. * 100 + -> + -163
  128. * | | | | (gap: 10)
  129. * -100 + -> + -173
  130. * | |
  131. * -300 + -> + -373
  132. * | | | | (gap: 27)
  133. * -400 + -> + -400
  134. * | |
  135. * origianl elapsed
  136. *
  137. * Note:
  138. * The mapping has nothing to do with "scale extent".
  139. */
  140. ScaleBreakContextImpl.prototype.elapse = function (val) {
  141. // If the value is in the break, return the normalized value in the break
  142. var elapsedVal = AXIS_BREAK_ELAPSED_BASE;
  143. var lastBreakEnd = AXIS_BREAK_LAST_BREAK_END_BASE;
  144. var stillOver = true;
  145. for (var i = 0; i < this.breaks.length; i++) {
  146. var brk = this.breaks[i];
  147. if (val <= brk.vmax) {
  148. if (val > brk.vmin) {
  149. elapsedVal += brk.vmin - lastBreakEnd + (val - brk.vmin) / (brk.vmax - brk.vmin) * brk.gapReal;
  150. } else {
  151. elapsedVal += val - lastBreakEnd;
  152. }
  153. lastBreakEnd = brk.vmax;
  154. stillOver = false;
  155. break;
  156. }
  157. elapsedVal += brk.vmin - lastBreakEnd + brk.gapReal;
  158. lastBreakEnd = brk.vmax;
  159. }
  160. if (stillOver) {
  161. elapsedVal += val - lastBreakEnd;
  162. }
  163. return elapsedVal;
  164. };
  165. ScaleBreakContextImpl.prototype.unelapse = function (elapsedVal) {
  166. var lastElapsedEnd = AXIS_BREAK_ELAPSED_BASE;
  167. var lastBreakEnd = AXIS_BREAK_LAST_BREAK_END_BASE;
  168. var stillOver = true;
  169. var unelapsedVal = 0;
  170. for (var i = 0; i < this.breaks.length; i++) {
  171. var brk = this.breaks[i];
  172. var elapsedStart = lastElapsedEnd + brk.vmin - lastBreakEnd;
  173. var elapsedEnd = elapsedStart + brk.gapReal;
  174. if (elapsedVal <= elapsedEnd) {
  175. if (elapsedVal > elapsedStart) {
  176. unelapsedVal = brk.vmin + (elapsedVal - elapsedStart) / (elapsedEnd - elapsedStart) * (brk.vmax - brk.vmin);
  177. } else {
  178. unelapsedVal = lastBreakEnd + elapsedVal - lastElapsedEnd;
  179. }
  180. lastBreakEnd = brk.vmax;
  181. stillOver = false;
  182. break;
  183. }
  184. lastElapsedEnd = elapsedEnd;
  185. lastBreakEnd = brk.vmax;
  186. }
  187. if (stillOver) {
  188. unelapsedVal = lastBreakEnd + elapsedVal - lastElapsedEnd;
  189. }
  190. return unelapsedVal;
  191. };
  192. return ScaleBreakContextImpl;
  193. }();
  194. ;
  195. function createScaleBreakContext() {
  196. return new ScaleBreakContextImpl();
  197. }
  198. // Both can start with any finite value, and are not necessaryily equal. But they need to
  199. // be the same in `axisBreakElapse` and `axisBreakUnelapse` respectively.
  200. var AXIS_BREAK_ELAPSED_BASE = 0;
  201. var AXIS_BREAK_LAST_BREAK_END_BASE = 0;
  202. /**
  203. * `gapReal` in brkCtx.breaks will be calculated.
  204. */
  205. function updateAxisBreakGapReal(brkCtx, scaleExtent) {
  206. // Considered the effect:
  207. // - Use dataZoom to move some of the breaks outside the extent.
  208. // - Some scenarios that `series.clip: false`.
  209. //
  210. // How to calculate `prctBrksGapRealSum`:
  211. // Based on the formula:
  212. // xxx.span = brk.vmax - brk.vmin
  213. // xxx.tpPrct.val / xxx.tpAbs.val means ParsedAxisBreak['gapParsed']['val']
  214. // .S/.E means a break that is semi in scaleExtent[0] or scaleExtent[1]
  215. // valP = (
  216. // + (fullyInExtBrksSum.tpAbs.gapReal - fullyInExtBrksSum.tpAbs.span)
  217. // + (semiInExtBrk.S.tpAbs.gapReal - semiInExtBrk.S.tpAbs.span) * semiInExtBrk.S.tpAbs.inExtFrac
  218. // + (semiInExtBrk.E.tpAbs.gapReal - semiInExtBrk.E.tpAbs.span) * semiInExtBrk.E.tpAbs.inExtFrac
  219. // )
  220. // valQ = (
  221. // - fullyInExtBrksSum.tpPrct.span
  222. // - semiInExtBrk.S.tpPrct.span * semiInExtBrk.S.tpPrct.inExtFrac
  223. // - semiInExtBrk.E.tpPrct.span * semiInExtBrk.E.tpPrct.inExtFrac
  224. // )
  225. // gapPrctSum = sum(xxx.tpPrct.val)
  226. // gapPrctSum = prctBrksGapRealSum / (
  227. // + (scaleExtent[1] - scaleExtent[0]) + valP + valQ
  228. // + fullyInExtBrksSum.tpPrct.gapReal
  229. // + semiInExtBrk.S.tpPrct.gapReal * semiInExtBrk.S.tpPrct.inExtFrac
  230. // + semiInExtBrk.E.tpPrct.gapReal * semiInExtBrk.E.tpPrct.inExtFrac
  231. // )
  232. // Assume:
  233. // xxx.tpPrct.gapReal = xxx.tpPrct.val / gapPrctSum * prctBrksGapRealSum
  234. // (NOTE: This is not accurate when semi-in-extent break exist because its
  235. // proportion is not linear, but this assumption approximately works.)
  236. // Derived as follows:
  237. // prctBrksGapRealSum = gapPrctSum * ( (scaleExtent[1] - scaleExtent[0]) + valP + valQ )
  238. // / (1
  239. // - fullyInExtBrksSum.tpPrct.val
  240. // - semiInExtBrk.S.tpPrct.val * semiInExtBrk.S.tpPrct.inExtFrac
  241. // - semiInExtBrk.E.tpPrct.val * semiInExtBrk.E.tpPrct.inExtFrac
  242. // )
  243. var gapPrctSum = 0;
  244. var fullyInExtBrksSum = {
  245. tpAbs: {
  246. span: 0,
  247. val: 0
  248. },
  249. tpPrct: {
  250. span: 0,
  251. val: 0
  252. }
  253. };
  254. var init = function () {
  255. return {
  256. has: false,
  257. span: NaN,
  258. inExtFrac: NaN,
  259. val: NaN
  260. };
  261. };
  262. var semiInExtBrk = {
  263. S: {
  264. tpAbs: init(),
  265. tpPrct: init()
  266. },
  267. E: {
  268. tpAbs: init(),
  269. tpPrct: init()
  270. }
  271. };
  272. each(brkCtx.breaks, function (brk) {
  273. var gapParsed = brk.gapParsed;
  274. if (gapParsed.type === 'tpPrct') {
  275. gapPrctSum += gapParsed.val;
  276. }
  277. var clampedBrk = clampBreakByExtent(brk, scaleExtent);
  278. if (clampedBrk) {
  279. var vminClamped = clampedBrk.vmin !== brk.vmin;
  280. var vmaxClamped = clampedBrk.vmax !== brk.vmax;
  281. var clampedSpan = clampedBrk.vmax - clampedBrk.vmin;
  282. if (vminClamped && vmaxClamped) {
  283. // Do nothing, which simply makes the result `gapReal` cover the entire scaleExtent.
  284. // This transform is not consistent with the other cases but practically works.
  285. } else if (vminClamped || vmaxClamped) {
  286. var sOrE = vminClamped ? 'S' : 'E';
  287. semiInExtBrk[sOrE][gapParsed.type].has = true;
  288. semiInExtBrk[sOrE][gapParsed.type].span = clampedSpan;
  289. semiInExtBrk[sOrE][gapParsed.type].inExtFrac = clampedSpan / (brk.vmax - brk.vmin);
  290. semiInExtBrk[sOrE][gapParsed.type].val = gapParsed.val;
  291. } else {
  292. fullyInExtBrksSum[gapParsed.type].span += clampedSpan;
  293. fullyInExtBrksSum[gapParsed.type].val += gapParsed.val;
  294. }
  295. }
  296. });
  297. var prctBrksGapRealSum = gapPrctSum * (0 + (scaleExtent[1] - scaleExtent[0]) + (fullyInExtBrksSum.tpAbs.val - fullyInExtBrksSum.tpAbs.span) + (semiInExtBrk.S.tpAbs.has ? (semiInExtBrk.S.tpAbs.val - semiInExtBrk.S.tpAbs.span) * semiInExtBrk.S.tpAbs.inExtFrac : 0) + (semiInExtBrk.E.tpAbs.has ? (semiInExtBrk.E.tpAbs.val - semiInExtBrk.E.tpAbs.span) * semiInExtBrk.E.tpAbs.inExtFrac : 0) - fullyInExtBrksSum.tpPrct.span - (semiInExtBrk.S.tpPrct.has ? semiInExtBrk.S.tpPrct.span * semiInExtBrk.S.tpPrct.inExtFrac : 0) - (semiInExtBrk.E.tpPrct.has ? semiInExtBrk.E.tpPrct.span * semiInExtBrk.E.tpPrct.inExtFrac : 0)) / (1 - fullyInExtBrksSum.tpPrct.val - (semiInExtBrk.S.tpPrct.has ? semiInExtBrk.S.tpPrct.val * semiInExtBrk.S.tpPrct.inExtFrac : 0) - (semiInExtBrk.E.tpPrct.has ? semiInExtBrk.E.tpPrct.val * semiInExtBrk.E.tpPrct.inExtFrac : 0));
  298. each(brkCtx.breaks, function (brk) {
  299. var gapParsed = brk.gapParsed;
  300. if (gapParsed.type === 'tpPrct') {
  301. brk.gapReal = gapPrctSum !== 0
  302. // prctBrksGapRealSum is supposed to be non-negative but add a safe guard
  303. ? Math.max(prctBrksGapRealSum, 0) * gapParsed.val / gapPrctSum : 0;
  304. }
  305. if (gapParsed.type === 'tpAbs') {
  306. brk.gapReal = gapParsed.val;
  307. }
  308. if (brk.gapReal == null) {
  309. brk.gapReal = 0;
  310. }
  311. });
  312. }
  313. function pruneTicksByBreak(pruneByBreak, ticks, breaks, getValue, interval, scaleExtent) {
  314. if (pruneByBreak === 'no') {
  315. return;
  316. }
  317. each(breaks, function (brk) {
  318. // break.vmin/vmax that out of extent must not impact the visible of
  319. // normal ticks and labels.
  320. var clampedBrk = clampBreakByExtent(brk, scaleExtent);
  321. if (!clampedBrk) {
  322. return;
  323. }
  324. // Remove some normal ticks to avoid zigzag shapes overlapping with split lines
  325. // and to avoid break labels overlapping with normal tick labels (thouth it can
  326. // also be avoided by `axisLabel.hideOverlap`).
  327. // It's OK to O(n^2) since the number of `ticks` are small.
  328. for (var j = ticks.length - 1; j >= 0; j--) {
  329. var tick = ticks[j];
  330. var val = getValue(tick);
  331. // 1. Ensure there is no ticks inside `break.vmin` and `break.vmax`.
  332. // 2. Use an empirically gap value here. Theoritically `zigzagAmplitude` is
  333. // supposed to be involved to provide better precision but it will brings
  334. // more complexity. The empirically gap value is conservative because break
  335. // labels and normal tick lables are prone to overlapping.
  336. var gap = interval * 3 / 4;
  337. if (val > clampedBrk.vmin - gap && val < clampedBrk.vmax + gap && (pruneByBreak !== 'preserve_extent_bound' || val !== scaleExtent[0] && val !== scaleExtent[1])) {
  338. ticks.splice(j, 1);
  339. }
  340. }
  341. });
  342. }
  343. function addBreaksToTicks(
  344. // The input ticks should be in accending order.
  345. ticks, breaks, scaleExtent,
  346. // Keep the break ends at the same level to avoid an awkward appearance.
  347. getTimeProps) {
  348. each(breaks, function (brk) {
  349. var clampedBrk = clampBreakByExtent(brk, scaleExtent);
  350. if (!clampedBrk) {
  351. return;
  352. }
  353. // - When neight `break.vmin` nor `break.vmax` is in scale extent,
  354. // break label should not be displayed and we do not add them to the result.
  355. // - When only one of `break.vmin` and `break.vmax` is inside the extent and the
  356. // other is outsite, we comply with the extent and display only part of the breaks area,
  357. // because the extent might be determined by user settings (such as `axis.min/max`)
  358. ticks.push({
  359. value: clampedBrk.vmin,
  360. "break": {
  361. type: 'vmin',
  362. parsedBreak: clampedBrk
  363. },
  364. time: getTimeProps ? getTimeProps(clampedBrk) : undefined
  365. });
  366. // When gap is 0, start tick overlap with end tick, but we still count both of them. Break
  367. // area shape can address that overlapping. `axisLabel` need draw both start and end separately,
  368. // otherwise it brings complexity to the logic of label overlapping resolving (e.g., when label
  369. // rotated), and introduces inconsistency to users in `axisLabel.formatter` between gap is 0 or not.
  370. ticks.push({
  371. value: clampedBrk.vmax,
  372. "break": {
  373. type: 'vmax',
  374. parsedBreak: clampedBrk
  375. },
  376. time: getTimeProps ? getTimeProps(clampedBrk) : undefined
  377. });
  378. });
  379. if (breaks.length) {
  380. ticks.sort(function (a, b) {
  381. return a.value - b.value;
  382. });
  383. }
  384. }
  385. /**
  386. * If break and extent does not intersect, return null/undefined.
  387. * If the intersection is only a point at scaleExtent[0] or scaleExtent[1], return null/undefined.
  388. */
  389. function clampBreakByExtent(brk, scaleExtent) {
  390. var vmin = Math.max(brk.vmin, scaleExtent[0]);
  391. var vmax = Math.min(brk.vmax, scaleExtent[1]);
  392. return vmin < vmax || vmin === vmax && vmin > scaleExtent[0] && vmin < scaleExtent[1] ? {
  393. vmin: vmin,
  394. vmax: vmax,
  395. breakOption: brk.breakOption,
  396. gapParsed: brk.gapParsed,
  397. gapReal: brk.gapReal
  398. } : null;
  399. }
  400. function parseAxisBreakOption(
  401. // raw user input breaks, retrieved from axis model.
  402. breakOptionList, parse, opt) {
  403. var parsedBreaks = [];
  404. if (!breakOptionList) {
  405. return {
  406. breaks: parsedBreaks
  407. };
  408. }
  409. function validatePercent(normalizedPercent, msg) {
  410. if (normalizedPercent >= 0 && normalizedPercent < 1 - 1e-5) {
  411. // Avoid division error.
  412. return true;
  413. }
  414. if (process.env.NODE_ENV !== 'production') {
  415. error(msg + " must be >= 0 and < 1, rather than " + normalizedPercent + " .");
  416. }
  417. return false;
  418. }
  419. each(breakOptionList, function (brkOption) {
  420. if (!brkOption || brkOption.start == null || brkOption.end == null) {
  421. if (process.env.NODE_ENV !== 'production') {
  422. error('The input axis breaks start/end should not be empty.');
  423. }
  424. return;
  425. }
  426. if (brkOption.isExpanded) {
  427. return;
  428. }
  429. var parsedBrk = {
  430. breakOption: clone(brkOption),
  431. vmin: parse(brkOption.start),
  432. vmax: parse(brkOption.end),
  433. gapParsed: {
  434. type: 'tpAbs',
  435. val: 0
  436. },
  437. gapReal: null
  438. };
  439. if (brkOption.gap != null) {
  440. var isPrct = false;
  441. if (isString(brkOption.gap)) {
  442. var trimmedGap = trim(brkOption.gap);
  443. if (trimmedGap.match(/%$/)) {
  444. var normalizedPercent = parseFloat(trimmedGap) / 100;
  445. if (!validatePercent(normalizedPercent, 'Percent gap')) {
  446. normalizedPercent = 0;
  447. }
  448. parsedBrk.gapParsed.type = 'tpPrct';
  449. parsedBrk.gapParsed.val = normalizedPercent;
  450. isPrct = true;
  451. }
  452. }
  453. if (!isPrct) {
  454. var absolute = parse(brkOption.gap);
  455. if (!isFinite(absolute) || absolute < 0) {
  456. if (process.env.NODE_ENV !== 'production') {
  457. error("Axis breaks gap must positive finite rather than (" + brkOption.gap + ").");
  458. }
  459. absolute = 0;
  460. }
  461. parsedBrk.gapParsed.type = 'tpAbs';
  462. parsedBrk.gapParsed.val = absolute;
  463. }
  464. }
  465. if (parsedBrk.vmin === parsedBrk.vmax) {
  466. parsedBrk.gapParsed.type = 'tpAbs';
  467. parsedBrk.gapParsed.val = 0;
  468. }
  469. if (opt && opt.noNegative) {
  470. each(['vmin', 'vmax'], function (se) {
  471. if (parsedBrk[se] < 0) {
  472. if (process.env.NODE_ENV !== 'production') {
  473. error("Axis break." + se + " must not be negative.");
  474. }
  475. parsedBrk[se] = 0;
  476. }
  477. });
  478. }
  479. // Ascending numerical order is the prerequisite of the calculation in Scale#normalize.
  480. // User are allowed to input desending vmin/vmax for simplifying the usage.
  481. if (parsedBrk.vmin > parsedBrk.vmax) {
  482. var tmp = parsedBrk.vmax;
  483. parsedBrk.vmax = parsedBrk.vmin;
  484. parsedBrk.vmin = tmp;
  485. }
  486. parsedBreaks.push(parsedBrk);
  487. });
  488. // Ascending numerical order is the prerequisite of the calculation in Scale#normalize.
  489. parsedBreaks.sort(function (item1, item2) {
  490. return item1.vmin - item2.vmin;
  491. });
  492. // Make sure that the intervals in breaks are not overlap.
  493. var lastEnd = -Infinity;
  494. each(parsedBreaks, function (brk, idx) {
  495. if (lastEnd > brk.vmin) {
  496. if (process.env.NODE_ENV !== 'production') {
  497. error('Axis breaks must not overlap.');
  498. }
  499. parsedBreaks[idx] = null;
  500. }
  501. lastEnd = brk.vmax;
  502. });
  503. return {
  504. breaks: parsedBreaks.filter(function (brk) {
  505. return !!brk;
  506. })
  507. };
  508. }
  509. function identifyAxisBreak(brk, identifier) {
  510. return serializeAxisBreakIdentifier(identifier) === serializeAxisBreakIdentifier(brk);
  511. }
  512. function serializeAxisBreakIdentifier(identifier) {
  513. // We use user input start/end to identify break. Considered cases like `start: new Date(xxx)`,
  514. // Theoretically `Scale#parse` should be used here, but not used currently to reduce dependencies,
  515. // since simply converting to string happens to be correct.
  516. return identifier.start + '_\0_' + identifier.end;
  517. }
  518. /**
  519. * - A break pair represents `[vmin, vmax]`,
  520. * - Only both vmin and vmax item exist, they are counted as a pair.
  521. */
  522. function retrieveAxisBreakPairs(itemList, getVisualAxisBreak, returnIdx) {
  523. var idxPairList = [];
  524. each(itemList, function (el, idx) {
  525. var vBreak = getVisualAxisBreak(el);
  526. if (vBreak && vBreak.type === 'vmin') {
  527. idxPairList.push([idx]);
  528. }
  529. });
  530. each(itemList, function (el, idx) {
  531. var vBreak = getVisualAxisBreak(el);
  532. if (vBreak && vBreak.type === 'vmax') {
  533. var idxPair = find(idxPairList,
  534. // parsedBreak may be changed, can only use breakOption to match them.
  535. function (pr) {
  536. return identifyAxisBreak(getVisualAxisBreak(itemList[pr[0]]).parsedBreak.breakOption, vBreak.parsedBreak.breakOption);
  537. });
  538. idxPair && idxPair.push(idx);
  539. }
  540. });
  541. var result = [];
  542. each(idxPairList, function (idxPair) {
  543. if (idxPair.length === 2) {
  544. result.push(returnIdx ? idxPair : [itemList[idxPair[0]], itemList[idxPair[1]]]);
  545. }
  546. });
  547. return result;
  548. }
  549. function getTicksLogTransformBreak(tick, logBase, logOriginalBreaks, fixRoundingError) {
  550. var vBreak;
  551. var brkRoundingCriterion;
  552. if (tick["break"]) {
  553. var brk = tick["break"].parsedBreak;
  554. var originalBreak = find(logOriginalBreaks, function (brk) {
  555. return identifyAxisBreak(brk.breakOption, tick["break"].parsedBreak.breakOption);
  556. });
  557. var vmin = fixRoundingError(Math.pow(logBase, brk.vmin), originalBreak.vmin);
  558. var vmax = fixRoundingError(Math.pow(logBase, brk.vmax), originalBreak.vmax);
  559. var gapParsed = {
  560. type: brk.gapParsed.type,
  561. val: brk.gapParsed.type === 'tpAbs' ? fixRound(Math.pow(logBase, brk.vmin + brk.gapParsed.val)) - vmin : brk.gapParsed.val
  562. };
  563. vBreak = {
  564. type: tick["break"].type,
  565. parsedBreak: {
  566. breakOption: brk.breakOption,
  567. vmin: vmin,
  568. vmax: vmax,
  569. gapParsed: gapParsed,
  570. gapReal: brk.gapReal
  571. }
  572. };
  573. brkRoundingCriterion = originalBreak[tick["break"].type];
  574. }
  575. return {
  576. brkRoundingCriterion: brkRoundingCriterion,
  577. vBreak: vBreak
  578. };
  579. }
  580. function logarithmicParseBreaksFromOption(breakOptionList, logBase, parse) {
  581. var opt = {
  582. noNegative: true
  583. };
  584. var parsedOriginal = parseAxisBreakOption(breakOptionList, parse, opt);
  585. var parsedLogged = parseAxisBreakOption(breakOptionList, parse, opt);
  586. var loggedBase = Math.log(logBase);
  587. parsedLogged.breaks = map(parsedLogged.breaks, function (brk) {
  588. var vmin = Math.log(brk.vmin) / loggedBase;
  589. var vmax = Math.log(brk.vmax) / loggedBase;
  590. var gapParsed = {
  591. type: brk.gapParsed.type,
  592. val: brk.gapParsed.type === 'tpAbs' ? Math.log(brk.vmin + brk.gapParsed.val) / loggedBase - vmin : brk.gapParsed.val
  593. };
  594. return {
  595. vmin: vmin,
  596. vmax: vmax,
  597. gapParsed: gapParsed,
  598. gapReal: brk.gapReal,
  599. breakOption: brk.breakOption
  600. };
  601. });
  602. return {
  603. parsedOriginal: parsedOriginal,
  604. parsedLogged: parsedLogged
  605. };
  606. }
  607. var BREAK_MIN_MAX_TO_PARAM = {
  608. vmin: 'start',
  609. vmax: 'end'
  610. };
  611. function makeAxisLabelFormatterParamBreak(extraParam, vBreak) {
  612. if (vBreak) {
  613. extraParam = extraParam || {};
  614. extraParam["break"] = {
  615. type: BREAK_MIN_MAX_TO_PARAM[vBreak.type],
  616. start: vBreak.parsedBreak.vmin,
  617. end: vBreak.parsedBreak.vmax
  618. };
  619. }
  620. return extraParam;
  621. }
  622. export function installScaleBreakHelper() {
  623. registerScaleBreakHelperImpl({
  624. createScaleBreakContext: createScaleBreakContext,
  625. pruneTicksByBreak: pruneTicksByBreak,
  626. addBreaksToTicks: addBreaksToTicks,
  627. parseAxisBreakOption: parseAxisBreakOption,
  628. identifyAxisBreak: identifyAxisBreak,
  629. serializeAxisBreakIdentifier: serializeAxisBreakIdentifier,
  630. retrieveAxisBreakPairs: retrieveAxisBreakPairs,
  631. getTicksLogTransformBreak: getTicksLogTransformBreak,
  632. logarithmicParseBreaksFromOption: logarithmicParseBreaksFromOption,
  633. makeAxisLabelFormatterParamBreak: makeAxisLabelFormatterParamBreak
  634. });
  635. }