Axis.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669
  1. /* *
  2. * (c) 2010-2019 Torstein Honsi
  3. *
  4. * Extenstion for 3d axes
  5. *
  6. * License: www.highcharts.com/license
  7. */
  8. 'use strict';
  9. import H from '../parts/Globals.js';
  10. import '../parts/Utilities.js';
  11. import '../parts/Axis.js';
  12. import '../parts/Chart.js';
  13. import '../parts/Tick.js';
  14. var ZAxis,
  15. addEvent = H.addEvent,
  16. Axis = H.Axis,
  17. Chart = H.Chart,
  18. deg2rad = H.deg2rad,
  19. extend = H.extend,
  20. merge = H.merge,
  21. perspective = H.perspective,
  22. perspective3D = H.perspective3D,
  23. pick = H.pick,
  24. shapeArea = H.shapeArea,
  25. splat = H.splat,
  26. Tick = H.Tick,
  27. wrap = H.wrap;
  28. /**
  29. * @optionparent xAxis
  30. */
  31. var extendedOptions = {
  32. labels: {
  33. /**
  34. * Defines how the labels are be repositioned according to the 3D chart
  35. * orientation.
  36. *
  37. * - `'offset'`: Maintain a fixed horizontal/vertical distance from the
  38. * tick marks, despite the chart orientation. This is the backwards
  39. * compatible behavior, and causes skewing of X and Z axes.
  40. *
  41. * - `'chart'`: Preserve 3D position relative to the chart.
  42. * This looks nice, but hard to read if the text isn't
  43. * forward-facing.
  44. *
  45. * - `'flap'`: Rotated text along the axis to compensate for the chart
  46. * orientation. This tries to maintain text as legible as possible
  47. * on all orientations.
  48. *
  49. * - `'ortho'`: Rotated text along the axis direction so that the labels
  50. * are orthogonal to the axis. This is very similar to `'flap'`,
  51. * but prevents skewing the labels (X and Y scaling are still
  52. * present).
  53. *
  54. * @sample highcharts/3d/skewed-labels/
  55. * Skewed labels
  56. *
  57. * @since 5.0.15
  58. * @validvalue ['offset', 'chart', 'flap', 'ortho']
  59. * @product highcharts
  60. */
  61. position3d: 'offset',
  62. /**
  63. * If enabled, the axis labels will skewed to follow the perspective.
  64. *
  65. * This will fix overlapping labels and titles, but texts become less
  66. * legible due to the distortion.
  67. *
  68. * The final appearance depends heavily on `labels.position3d`.
  69. *
  70. * @sample highcharts/3d/skewed-labels/
  71. * Skewed labels
  72. *
  73. * @since 5.0.15
  74. * @product highcharts
  75. */
  76. skew3d: false
  77. },
  78. title: {
  79. /**
  80. * Defines how the title is repositioned according to the 3D chart
  81. * orientation.
  82. *
  83. * - `'offset'`: Maintain a fixed horizontal/vertical distance from the
  84. * tick marks, despite the chart orientation. This is the backwards
  85. * compatible behavior, and causes skewing of X and Z axes.
  86. *
  87. * - `'chart'`: Preserve 3D position relative to the chart.
  88. * This looks nice, but hard to read if the text isn't
  89. * forward-facing.
  90. *
  91. * - `'flap'`: Rotated text along the axis to compensate for the chart
  92. * orientation. This tries to maintain text as legible as possible on
  93. * all orientations.
  94. *
  95. * - `'ortho'`: Rotated text along the axis direction so that the labels
  96. * are orthogonal to the axis. This is very similar to `'flap'`, but
  97. * prevents skewing the labels (X and Y scaling are still present).
  98. *
  99. * - `undefined`: Will use the config from `labels.position3d`
  100. *
  101. * @sample highcharts/3d/skewed-labels/
  102. * Skewed labels
  103. *
  104. * @type {"offset"|"chart"|"flap"|"ortho"|null}
  105. * @since 5.0.15
  106. * @product highcharts
  107. */
  108. position3d: null,
  109. /**
  110. * If enabled, the axis title will skewed to follow the perspective.
  111. *
  112. * This will fix overlapping labels and titles, but texts become less
  113. * legible due to the distortion.
  114. *
  115. * The final appearance depends heavily on `title.position3d`.
  116. *
  117. * A `null` value will use the config from `labels.skew3d`.
  118. *
  119. * @sample highcharts/3d/skewed-labels/
  120. * Skewed labels
  121. *
  122. * @type {boolean|null}
  123. * @since 5.0.15
  124. * @product highcharts
  125. */
  126. skew3d: null
  127. }
  128. };
  129. merge(true, Axis.prototype.defaultOptions, extendedOptions);
  130. addEvent(Axis, 'afterSetOptions', function () {
  131. var options;
  132. if (this.chart.is3d && this.chart.is3d() && this.coll !== 'colorAxis') {
  133. options = this.options;
  134. options.tickWidth = pick(options.tickWidth, 0);
  135. options.gridLineWidth = pick(options.gridLineWidth, 1);
  136. }
  137. });
  138. wrap(Axis.prototype, 'getPlotLinePath', function (proceed) {
  139. var path = proceed.apply(this, [].slice.call(arguments, 1));
  140. // Do not do this if the chart is not 3D
  141. if (!this.chart.is3d() || this.coll === 'colorAxis') {
  142. return path;
  143. }
  144. if (path === null) {
  145. return path;
  146. }
  147. var chart = this.chart,
  148. options3d = chart.options.chart.options3d,
  149. d = this.isZAxis ? chart.plotWidth : options3d.depth,
  150. frame = chart.frame3d;
  151. var pArr = [
  152. this.swapZ({ x: path[1], y: path[2], z: 0 }),
  153. this.swapZ({ x: path[1], y: path[2], z: d }),
  154. this.swapZ({ x: path[4], y: path[5], z: 0 }),
  155. this.swapZ({ x: path[4], y: path[5], z: d })
  156. ];
  157. var pathSegments = [];
  158. if (!this.horiz) { // Y-Axis
  159. if (frame.front.visible) {
  160. pathSegments.push(pArr[0], pArr[2]);
  161. }
  162. if (frame.back.visible) {
  163. pathSegments.push(pArr[1], pArr[3]);
  164. }
  165. if (frame.left.visible) {
  166. pathSegments.push(pArr[0], pArr[1]);
  167. }
  168. if (frame.right.visible) {
  169. pathSegments.push(pArr[2], pArr[3]);
  170. }
  171. } else if (this.isZAxis) { // Z-Axis
  172. if (frame.left.visible) {
  173. pathSegments.push(pArr[0], pArr[2]);
  174. }
  175. if (frame.right.visible) {
  176. pathSegments.push(pArr[1], pArr[3]);
  177. }
  178. if (frame.top.visible) {
  179. pathSegments.push(pArr[0], pArr[1]);
  180. }
  181. if (frame.bottom.visible) {
  182. pathSegments.push(pArr[2], pArr[3]);
  183. }
  184. } else { // X-Axis
  185. if (frame.front.visible) {
  186. pathSegments.push(pArr[0], pArr[2]);
  187. }
  188. if (frame.back.visible) {
  189. pathSegments.push(pArr[1], pArr[3]);
  190. }
  191. if (frame.top.visible) {
  192. pathSegments.push(pArr[0], pArr[1]);
  193. }
  194. if (frame.bottom.visible) {
  195. pathSegments.push(pArr[2], pArr[3]);
  196. }
  197. }
  198. pathSegments = perspective(pathSegments, this.chart, false);
  199. return this.chart.renderer.toLineSegments(pathSegments);
  200. });
  201. // Do not draw axislines in 3D
  202. wrap(Axis.prototype, 'getLinePath', function (proceed) {
  203. // Do not do this if the chart is not 3D
  204. if (!this.chart.is3d() || this.coll === 'colorAxis') {
  205. return proceed.apply(this, [].slice.call(arguments, 1));
  206. }
  207. return [];
  208. });
  209. wrap(Axis.prototype, 'getPlotBandPath', function (proceed) {
  210. // Do not do this if the chart is not 3D
  211. if (!this.chart.is3d() || this.coll === 'colorAxis') {
  212. return proceed.apply(this, [].slice.call(arguments, 1));
  213. }
  214. var args = arguments,
  215. from = args[1],
  216. to = args[2],
  217. path = [],
  218. fromPath = this.getPlotLinePath(from),
  219. toPath = this.getPlotLinePath(to);
  220. if (fromPath && toPath) {
  221. for (var i = 0; i < fromPath.length; i += 6) {
  222. path.push(
  223. 'M', fromPath[i + 1], fromPath[i + 2],
  224. 'L', fromPath[i + 4], fromPath[i + 5],
  225. 'L', toPath[i + 4], toPath[i + 5],
  226. 'L', toPath[i + 1], toPath[i + 2],
  227. 'Z'
  228. );
  229. }
  230. }
  231. return path;
  232. });
  233. function fix3dPosition(axis, pos, isTitle) {
  234. // Do not do this if the chart is not 3D
  235. if (!axis.chart.is3d() || axis.coll === 'colorAxis') {
  236. return pos;
  237. }
  238. var chart = axis.chart,
  239. alpha = deg2rad * chart.options.chart.options3d.alpha,
  240. beta = deg2rad * chart.options.chart.options3d.beta,
  241. positionMode = pick(
  242. isTitle && axis.options.title.position3d,
  243. axis.options.labels.position3d
  244. ),
  245. skew = pick(
  246. isTitle && axis.options.title.skew3d,
  247. axis.options.labels.skew3d
  248. ),
  249. frame = chart.frame3d,
  250. plotLeft = chart.plotLeft,
  251. plotRight = chart.plotWidth + plotLeft,
  252. plotTop = chart.plotTop,
  253. plotBottom = chart.plotHeight + plotTop,
  254. // Indicates we are labelling an X or Z axis on the "back" of the chart
  255. reverseFlap = false,
  256. offsetX = 0,
  257. offsetY = 0,
  258. vecX,
  259. vecY = { x: 0, y: 1, z: 0 };
  260. pos = axis.swapZ({ x: pos.x, y: pos.y, z: 0 });
  261. if (axis.isZAxis) { // Z Axis
  262. if (axis.opposite) {
  263. if (frame.axes.z.top === null) {
  264. return {};
  265. }
  266. offsetY = pos.y - plotTop;
  267. pos.x = frame.axes.z.top.x;
  268. pos.y = frame.axes.z.top.y;
  269. vecX = frame.axes.z.top.xDir;
  270. reverseFlap = !frame.top.frontFacing;
  271. } else {
  272. if (frame.axes.z.bottom === null) {
  273. return {};
  274. }
  275. offsetY = pos.y - plotBottom;
  276. pos.x = frame.axes.z.bottom.x;
  277. pos.y = frame.axes.z.bottom.y;
  278. vecX = frame.axes.z.bottom.xDir;
  279. reverseFlap = !frame.bottom.frontFacing;
  280. }
  281. } else if (axis.horiz) { // X Axis
  282. if (axis.opposite) {
  283. if (frame.axes.x.top === null) {
  284. return {};
  285. }
  286. offsetY = pos.y - plotTop;
  287. pos.y = frame.axes.x.top.y;
  288. pos.z = frame.axes.x.top.z;
  289. vecX = frame.axes.x.top.xDir;
  290. reverseFlap = !frame.top.frontFacing;
  291. } else {
  292. if (frame.axes.x.bottom === null) {
  293. return {};
  294. }
  295. offsetY = pos.y - plotBottom;
  296. pos.y = frame.axes.x.bottom.y;
  297. pos.z = frame.axes.x.bottom.z;
  298. vecX = frame.axes.x.bottom.xDir;
  299. reverseFlap = !frame.bottom.frontFacing;
  300. }
  301. } else { // Y Axis
  302. if (axis.opposite) {
  303. if (frame.axes.y.right === null) {
  304. return {};
  305. }
  306. offsetX = pos.x - plotRight;
  307. pos.x = frame.axes.y.right.x;
  308. pos.z = frame.axes.y.right.z;
  309. vecX = frame.axes.y.right.xDir;
  310. // Rotate 90º on opposite edge
  311. vecX = { x: vecX.z, y: vecX.y, z: -vecX.x };
  312. } else {
  313. if (frame.axes.y.left === null) {
  314. return {};
  315. }
  316. offsetX = pos.x - plotLeft;
  317. pos.x = frame.axes.y.left.x;
  318. pos.z = frame.axes.y.left.z;
  319. vecX = frame.axes.y.left.xDir;
  320. }
  321. }
  322. if (positionMode === 'chart') {
  323. // Labels preserve their direction relative to the chart
  324. // nothing to do
  325. } else if (positionMode === 'flap') {
  326. // Labels are be rotated around the axis direction to face the screen
  327. if (!axis.horiz) { // Y Axis
  328. vecX = { x: Math.cos(beta), y: 0, z: Math.sin(beta) };
  329. } else { // X and Z Axis
  330. var sin = Math.sin(alpha);
  331. var cos = Math.cos(alpha);
  332. if (axis.opposite) {
  333. sin = -sin;
  334. }
  335. if (reverseFlap) {
  336. sin = -sin;
  337. }
  338. vecY = { x: vecX.z * sin, y: cos, z: -vecX.x * sin };
  339. }
  340. } else if (positionMode === 'ortho') {
  341. // Labels will be rotated to be ortogonal to the axis
  342. if (!axis.horiz) { // Y Axis
  343. vecX = { x: Math.cos(beta), y: 0, z: Math.sin(beta) };
  344. } else { // X and Z Axis
  345. var sina = Math.sin(alpha);
  346. var cosa = Math.cos(alpha);
  347. var sinb = Math.sin(beta);
  348. var cosb = Math.cos(beta);
  349. var vecZ = { x: sinb * cosa, y: -sina, z: -cosa * cosb };
  350. vecY = {
  351. x: vecX.y * vecZ.z - vecX.z * vecZ.y,
  352. y: vecX.z * vecZ.x - vecX.x * vecZ.z,
  353. z: vecX.x * vecZ.y - vecX.y * vecZ.x
  354. };
  355. var scale = 1 / Math.sqrt(
  356. vecY.x * vecY.x + vecY.y * vecY.y + vecY.z * vecY.z
  357. );
  358. if (reverseFlap) {
  359. scale = -scale;
  360. }
  361. vecY = { x: scale * vecY.x, y: scale * vecY.y, z: scale * vecY.z };
  362. }
  363. } else { // positionMode == 'offset'
  364. // Labels will be skewd to maintain vertical / horizontal offsets from
  365. // axis
  366. if (!axis.horiz) { // Y Axis
  367. vecX = { x: Math.cos(beta), y: 0, z: Math.sin(beta) };
  368. } else { // X and Z Axis
  369. vecY = {
  370. x: Math.sin(beta) * Math.sin(alpha),
  371. y: Math.cos(alpha),
  372. z: -Math.cos(beta) * Math.sin(alpha)
  373. };
  374. }
  375. }
  376. pos.x += offsetX * vecX.x + offsetY * vecY.x;
  377. pos.y += offsetX * vecX.y + offsetY * vecY.y;
  378. pos.z += offsetX * vecX.z + offsetY * vecY.z;
  379. var projected = perspective([pos], axis.chart)[0];
  380. if (skew) {
  381. // Check if the label text would be mirrored
  382. var isMirrored = shapeArea(perspective([
  383. pos,
  384. { x: pos.x + vecX.x, y: pos.y + vecX.y, z: pos.z + vecX.z },
  385. { x: pos.x + vecY.x, y: pos.y + vecY.y, z: pos.z + vecY.z }
  386. ], axis.chart)) < 0;
  387. if (isMirrored) {
  388. vecX = { x: -vecX.x, y: -vecX.y, z: -vecX.z };
  389. }
  390. var pointsProjected = perspective([
  391. { x: pos.x, y: pos.y, z: pos.z },
  392. { x: pos.x + vecX.x, y: pos.y + vecX.y, z: pos.z + vecX.z },
  393. { x: pos.x + vecY.x, y: pos.y + vecY.y, z: pos.z + vecY.z }
  394. ], axis.chart);
  395. projected.matrix = [
  396. pointsProjected[1].x - pointsProjected[0].x,
  397. pointsProjected[1].y - pointsProjected[0].y,
  398. pointsProjected[2].x - pointsProjected[0].x,
  399. pointsProjected[2].y - pointsProjected[0].y,
  400. projected.x,
  401. projected.y
  402. ];
  403. projected.matrix[4] -= projected.x * projected.matrix[0] +
  404. projected.y * projected.matrix[2];
  405. projected.matrix[5] -= projected.x * projected.matrix[1] +
  406. projected.y * projected.matrix[3];
  407. }
  408. return projected;
  409. }
  410. /*
  411. Tick extensions
  412. */
  413. wrap(Tick.prototype, 'getMarkPath', function (proceed) {
  414. var path = proceed.apply(this, [].slice.call(arguments, 1));
  415. var pArr = [
  416. fix3dPosition(this.axis, { x: path[1], y: path[2], z: 0 }),
  417. fix3dPosition(this.axis, { x: path[4], y: path[5], z: 0 })
  418. ];
  419. return this.axis.chart.renderer.toLineSegments(pArr);
  420. });
  421. addEvent(Tick, 'afterGetLabelPosition', function (e) {
  422. extend(e.pos, fix3dPosition(this.axis, e.pos));
  423. });
  424. wrap(Axis.prototype, 'getTitlePosition', function (proceed) {
  425. var pos = proceed.apply(this, [].slice.call(arguments, 1));
  426. return fix3dPosition(this, pos, true);
  427. });
  428. addEvent(Axis, 'drawCrosshair', function (e) {
  429. if (this.chart.is3d() && this.coll !== 'colorAxis') {
  430. if (e.point) {
  431. e.point.crosshairPos = this.isXAxis ?
  432. e.point.axisXpos :
  433. this.len - (e.point.axisYpos);
  434. }
  435. }
  436. });
  437. addEvent(Axis, 'destroy', function () {
  438. ['backFrame', 'bottomFrame', 'sideFrame'].forEach(function (prop) {
  439. if (this[prop]) {
  440. this[prop] = this[prop].destroy();
  441. }
  442. }, this);
  443. });
  444. /*
  445. Z-AXIS
  446. */
  447. Axis.prototype.swapZ = function (p, insidePlotArea) {
  448. if (this.isZAxis) {
  449. var plotLeft = insidePlotArea ? 0 : this.chart.plotLeft;
  450. return {
  451. x: plotLeft + p.z,
  452. y: p.y,
  453. z: p.x - plotLeft
  454. };
  455. }
  456. return p;
  457. };
  458. ZAxis = H.ZAxis = function () {
  459. this.init.apply(this, arguments);
  460. };
  461. extend(ZAxis.prototype, Axis.prototype);
  462. extend(ZAxis.prototype, {
  463. isZAxis: true,
  464. setOptions: function (userOptions) {
  465. userOptions = merge({
  466. offset: 0,
  467. lineWidth: 0
  468. }, userOptions);
  469. Axis.prototype.setOptions.call(this, userOptions);
  470. this.coll = 'zAxis';
  471. },
  472. setAxisSize: function () {
  473. Axis.prototype.setAxisSize.call(this);
  474. this.width = this.len = this.chart.options.chart.options3d.depth;
  475. this.right = this.chart.chartWidth - this.width - this.left;
  476. },
  477. getSeriesExtremes: function () {
  478. var axis = this,
  479. chart = axis.chart;
  480. axis.hasVisibleSeries = false;
  481. // Reset properties in case we're redrawing (#3353)
  482. axis.dataMin =
  483. axis.dataMax =
  484. axis.ignoreMinPadding =
  485. axis.ignoreMaxPadding = null;
  486. if (axis.buildStacks) {
  487. axis.buildStacks();
  488. }
  489. // loop through this axis' series
  490. axis.series.forEach(function (series) {
  491. if (series.visible || !chart.options.chart.ignoreHiddenSeries) {
  492. var seriesOptions = series.options,
  493. zData,
  494. threshold = seriesOptions.threshold;
  495. axis.hasVisibleSeries = true;
  496. // Validate threshold in logarithmic axes
  497. if (axis.positiveValuesOnly && threshold <= 0) {
  498. threshold = null;
  499. }
  500. zData = series.zData;
  501. if (zData.length) {
  502. axis.dataMin = Math.min(
  503. pick(axis.dataMin, zData[0]),
  504. Math.min.apply(null, zData)
  505. );
  506. axis.dataMax = Math.max(
  507. pick(axis.dataMax, zData[0]),
  508. Math.max.apply(null, zData)
  509. );
  510. }
  511. }
  512. });
  513. }
  514. });
  515. // Get the Z axis in addition to the default X and Y.
  516. addEvent(Chart, 'afterGetAxes', function () {
  517. var chart = this,
  518. options = this.options,
  519. zAxisOptions = options.zAxis = splat(options.zAxis || {});
  520. if (!chart.is3d()) {
  521. return;
  522. }
  523. this.zAxis = [];
  524. zAxisOptions.forEach(function (axisOptions, i) {
  525. axisOptions.index = i;
  526. // Z-Axis is shown horizontally, so it's kind of a X-Axis
  527. axisOptions.isX = true;
  528. var zAxis = new ZAxis(chart, axisOptions);
  529. zAxis.setScale();
  530. });
  531. });
  532. // Wrap getSlotWidth function to calculate individual width value for each slot
  533. // (#8042).
  534. wrap(Axis.prototype, 'getSlotWidth', function (proceed, tick) {
  535. if (this.chart.is3d() &&
  536. tick &&
  537. tick.label &&
  538. this.categories &&
  539. this.chart.frameShapes
  540. ) {
  541. var chart = this.chart,
  542. ticks = this.ticks,
  543. gridGroup = this.gridGroup.element.childNodes,
  544. firstGridLine = gridGroup[0].getBBox(),
  545. frame3DLeft = chart.frameShapes.left.getBBox(),
  546. options3d = chart.options.chart.options3d,
  547. origin = {
  548. x: chart.plotWidth / 2,
  549. y: chart.plotHeight / 2,
  550. z: options3d.depth / 2,
  551. vd: pick(options3d.depth, 1) * pick(options3d.viewDistance, 0)
  552. },
  553. labelPos,
  554. prevLabelPos,
  555. nextLabelPos,
  556. slotWidth,
  557. tickId = tick.pos,
  558. prevTick = ticks[tickId - 1],
  559. nextTick = ticks[tickId + 1];
  560. // Check whether the tick is not the first one and previous tick exists,
  561. // then calculate position of previous label.
  562. if (tickId !== 0 && prevTick && prevTick.label.xy) { // #8621
  563. prevLabelPos = perspective3D({
  564. x: prevTick.label.xy.x,
  565. y: prevTick.label.xy.y,
  566. z: null
  567. }, origin, origin.vd);
  568. }
  569. // If next label position is defined, then recalculate its position
  570. // basing on the perspective.
  571. if (nextTick && nextTick.label.xy) {
  572. nextLabelPos = perspective3D({
  573. x: nextTick.label.xy.x,
  574. y: nextTick.label.xy.y,
  575. z: null
  576. }, origin, origin.vd);
  577. }
  578. labelPos = {
  579. x: tick.label.xy.x,
  580. y: tick.label.xy.y,
  581. z: null
  582. };
  583. labelPos = perspective3D(labelPos, origin, origin.vd);
  584. // If tick is first one, check whether next label position is already
  585. // calculated, then return difference between the first and the second
  586. // label. If there is no next label position calculated, return the
  587. // difference between the first grid line and left 3d frame.
  588. slotWidth = Math.abs(
  589. prevLabelPos ?
  590. labelPos.x - prevLabelPos.x : nextLabelPos ?
  591. nextLabelPos.x - labelPos.x :
  592. firstGridLine.x - frame3DLeft.x
  593. );
  594. return slotWidth;
  595. }
  596. return proceed.apply(this, [].slice.call(arguments, 1));
  597. });