breakImpl.js 24 KB

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