GridAxis.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884
  1. /* *
  2. *
  3. * (c) 2016 Highsoft AS
  4. * Authors: Lars A. V. Cabrera
  5. *
  6. * License: www.highcharts.com/license
  7. *
  8. * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
  9. *
  10. * */
  11. 'use strict';
  12. import Axis from './Axis.js';
  13. import H from '../Globals.js';
  14. import O from '../Options.js';
  15. var dateFormat = O.dateFormat;
  16. import Tick from './Tick.js';
  17. import U from '../Utilities.js';
  18. var addEvent = U.addEvent, defined = U.defined, erase = U.erase, find = U.find, isArray = U.isArray, isNumber = U.isNumber, merge = U.merge, pick = U.pick, timeUnits = U.timeUnits, wrap = U.wrap;
  19. var argsToArray = function (args) {
  20. return Array.prototype.slice.call(args, 1);
  21. }, isObject = function (x) {
  22. // Always use strict mode
  23. return U.isObject(x, true);
  24. }, Chart = H.Chart;
  25. var applyGridOptions = function applyGridOptions(axis) {
  26. var options = axis.options;
  27. // Center-align by default
  28. if (!options.labels) {
  29. options.labels = {};
  30. }
  31. options.labels.align = pick(options.labels.align, 'center');
  32. // @todo: Check against tickLabelPlacement between/on etc
  33. /* Prevents adding the last tick label if the axis is not a category
  34. axis.
  35. Since numeric labels are normally placed at starts and ends of a
  36. range of value, and this module makes the label point at the value,
  37. an "extra" label would appear. */
  38. if (!axis.categories) {
  39. options.showLastLabel = false;
  40. }
  41. // Prevents rotation of labels when squished, as rotating them would not
  42. // help.
  43. axis.labelRotation = 0;
  44. options.labels.rotation = 0;
  45. };
  46. /**
  47. * For a datetime axis, the scale will automatically adjust to the
  48. * appropriate unit. This member gives the default string
  49. * representations used for each unit. For intermediate values,
  50. * different units may be used, for example the `day` unit can be used
  51. * on midnight and `hour` unit be used for intermediate values on the
  52. * same axis.
  53. * For grid axes (like in Gantt charts),
  54. * it is possible to declare as a list to provide different
  55. * formats depending on available space.
  56. * For an overview of the replacement codes, see
  57. * [dateFormat](/class-reference/Highcharts#dateFormat).
  58. *
  59. * Defaults to:
  60. * ```js
  61. * {
  62. hour: {
  63. list: ['%H:%M', '%H']
  64. },
  65. day: {
  66. list: ['%A, %e. %B', '%a, %e. %b', '%E']
  67. },
  68. week: {
  69. list: ['Week %W', 'W%W']
  70. },
  71. month: {
  72. list: ['%B', '%b', '%o']
  73. }
  74. },
  75. * ```
  76. *
  77. * @sample {gantt} gantt/demo/left-axis-table
  78. * Gantt Chart with custom axis date format.
  79. *
  80. * @product gantt
  81. * @apioption xAxis.dateTimeLabelFormats
  82. */
  83. /**
  84. * Set grid options for the axis labels. Requires Highcharts Gantt.
  85. *
  86. * @since 6.2.0
  87. * @product gantt
  88. * @apioption xAxis.grid
  89. */
  90. /**
  91. * Enable grid on the axis labels. Defaults to true for Gantt charts.
  92. *
  93. * @type {boolean}
  94. * @default true
  95. * @since 6.2.0
  96. * @product gantt
  97. * @apioption xAxis.grid.enabled
  98. */
  99. /**
  100. * Set specific options for each column (or row for horizontal axes) in the
  101. * grid. Each extra column/row is its own axis, and the axis options can be set
  102. * here.
  103. *
  104. * @sample gantt/demo/left-axis-table
  105. * Left axis as a table
  106. *
  107. * @type {Array<Highcharts.XAxisOptions>}
  108. * @apioption xAxis.grid.columns
  109. */
  110. /**
  111. * Set border color for the label grid lines.
  112. *
  113. * @type {Highcharts.ColorString}
  114. * @apioption xAxis.grid.borderColor
  115. */
  116. /**
  117. * Set border width of the label grid lines.
  118. *
  119. * @type {number}
  120. * @default 1
  121. * @apioption xAxis.grid.borderWidth
  122. */
  123. /**
  124. * Set cell height for grid axis labels. By default this is calculated from font
  125. * size. This option only applies to horizontal axes.
  126. *
  127. * @sample gantt/grid-axis/cellheight
  128. * Gant chart with custom cell height
  129. * @type {number}
  130. * @apioption xAxis.grid.cellHeight
  131. */
  132. ''; // detach doclets above
  133. /**
  134. * Get the largest label width and height.
  135. *
  136. * @private
  137. * @function Highcharts.Axis#getMaxLabelDimensions
  138. *
  139. * @param {Highcharts.Dictionary<Highcharts.Tick>} ticks
  140. * All the ticks on one axis.
  141. *
  142. * @param {Array<number|string>} tickPositions
  143. * All the tick positions on one axis.
  144. *
  145. * @return {Highcharts.SizeObject}
  146. * Object containing the properties height and width.
  147. *
  148. * @todo Move this to the generic axis implementation, as it is used there.
  149. */
  150. Axis.prototype.getMaxLabelDimensions = function (ticks, tickPositions) {
  151. var dimensions = {
  152. width: 0,
  153. height: 0
  154. };
  155. tickPositions.forEach(function (pos) {
  156. var tick = ticks[pos], tickHeight = 0, tickWidth = 0, label;
  157. if (isObject(tick)) {
  158. label = isObject(tick.label) ? tick.label : {};
  159. // Find width and height of tick
  160. tickHeight = label.getBBox ? label.getBBox().height : 0;
  161. if (label.textStr) {
  162. // Set the tickWidth same as the label width after ellipsis
  163. // applied #10281
  164. tickWidth = Math.round(label.getBBox().width);
  165. }
  166. // Update the result if width and/or height are larger
  167. dimensions.height = Math.max(tickHeight, dimensions.height);
  168. dimensions.width = Math.max(tickWidth, dimensions.width);
  169. }
  170. });
  171. return dimensions;
  172. };
  173. // Adds week date format
  174. H.dateFormats.W = function (timestamp) {
  175. var d = new this.Date(timestamp);
  176. var firstDay = (this.get('Day', d) + 6) % 7;
  177. var thursday = new this.Date(d.valueOf());
  178. this.set('Date', thursday, this.get('Date', d) - firstDay + 3);
  179. var firstThursday = new this.Date(this.get('FullYear', thursday), 0, 1);
  180. if (this.get('Day', firstThursday) !== 4) {
  181. this.set('Month', d, 0);
  182. this.set('Date', d, 1 + (11 - this.get('Day', firstThursday)) % 7);
  183. }
  184. return (1 +
  185. Math.floor((thursday.valueOf() - firstThursday.valueOf()) / 604800000)).toString();
  186. };
  187. // First letter of the day of the week, e.g. 'M' for 'Monday'.
  188. H.dateFormats.E = function (timestamp) {
  189. return dateFormat('%a', timestamp, true).charAt(0);
  190. };
  191. /* eslint-disable no-invalid-this */
  192. addEvent(Chart, 'afterSetChartSize', function () {
  193. this.axes.forEach(function (axis) {
  194. (axis.grid && axis.grid.columns || []).forEach(function (column) {
  195. column.setAxisSize();
  196. column.setAxisTranslation();
  197. });
  198. });
  199. });
  200. // Center tick labels in cells.
  201. addEvent(Tick, 'afterGetLabelPosition', function (e) {
  202. var tick = this, label = tick.label, axis = tick.axis, reversed = axis.reversed, chart = axis.chart, options = axis.options, gridOptions = options.grid || {}, labelOpts = axis.options.labels, align = labelOpts.align,
  203. // verticalAlign is currently not supported for axis.labels.
  204. verticalAlign = 'middle', // labelOpts.verticalAlign,
  205. side = GridAxis.Side[axis.side], tickmarkOffset = e.tickmarkOffset, tickPositions = axis.tickPositions, tickPos = tick.pos - tickmarkOffset, nextTickPos = (isNumber(tickPositions[e.index + 1]) ?
  206. tickPositions[e.index + 1] - tickmarkOffset :
  207. axis.max + tickmarkOffset), tickSize = axis.tickSize('tick'), tickWidth = tickSize ? tickSize[0] : 0, crispCorr = tickSize ? tickSize[1] / 2 : 0, labelHeight, lblMetrics, lines, bottom, top, left, right;
  208. // Only center tick labels in grid axes
  209. if (gridOptions.enabled === true) {
  210. // Calculate top and bottom positions of the cell.
  211. if (side === 'top') {
  212. bottom = axis.top + axis.offset;
  213. top = bottom - tickWidth;
  214. }
  215. else if (side === 'bottom') {
  216. top = chart.chartHeight - axis.bottom + axis.offset;
  217. bottom = top + tickWidth;
  218. }
  219. else {
  220. bottom = axis.top + axis.len - axis.translate(reversed ? nextTickPos : tickPos);
  221. top = axis.top + axis.len - axis.translate(reversed ? tickPos : nextTickPos);
  222. }
  223. // Calculate left and right positions of the cell.
  224. if (side === 'right') {
  225. left = chart.chartWidth - axis.right + axis.offset;
  226. right = left + tickWidth;
  227. }
  228. else if (side === 'left') {
  229. right = axis.left + axis.offset;
  230. left = right - tickWidth;
  231. }
  232. else {
  233. left = Math.round(axis.left + axis.translate(reversed ? nextTickPos : tickPos)) - crispCorr;
  234. right = Math.round(axis.left + axis.translate(reversed ? tickPos : nextTickPos)) - crispCorr;
  235. }
  236. tick.slotWidth = right - left;
  237. // Calculate the positioning of the label based on
  238. // alignment.
  239. e.pos.x = (align === 'left' ?
  240. left :
  241. align === 'right' ?
  242. right :
  243. left + ((right - left) / 2) // default to center
  244. );
  245. e.pos.y = (verticalAlign === 'top' ?
  246. top :
  247. verticalAlign === 'bottom' ?
  248. bottom :
  249. top + ((bottom - top) / 2) // default to middle
  250. );
  251. lblMetrics = chart.renderer.fontMetrics(labelOpts.style.fontSize, label.element);
  252. labelHeight = label.getBBox().height;
  253. // Adjustment to y position to align the label correctly.
  254. // Would be better to have a setter or similar for this.
  255. if (!labelOpts.useHTML) {
  256. lines = Math.round(labelHeight / lblMetrics.h);
  257. e.pos.y += (
  258. // Center the label
  259. // TODO: why does this actually center the label?
  260. ((lblMetrics.b - (lblMetrics.h - lblMetrics.f)) / 2) +
  261. // Adjust for height of additional lines.
  262. -(((lines - 1) * lblMetrics.h) / 2));
  263. }
  264. else {
  265. e.pos.y += (
  266. // Readjust yCorr in htmlUpdateTransform
  267. lblMetrics.b +
  268. // Adjust for height of html label
  269. -(labelHeight / 2));
  270. }
  271. e.pos.x += (axis.horiz && labelOpts.x || 0);
  272. }
  273. });
  274. /* eslint-enable no-invalid-this */
  275. /**
  276. * Additions for grid axes.
  277. * @private
  278. * @class
  279. */
  280. var GridAxisAdditions = /** @class */ (function () {
  281. /* *
  282. *
  283. * Constructors
  284. *
  285. * */
  286. function GridAxisAdditions(axis) {
  287. this.axis = axis;
  288. }
  289. /* *
  290. *
  291. * Functions
  292. *
  293. * */
  294. /**
  295. * Checks if an axis is the outer axis in its dimension. Since
  296. * axes are placed outwards in order, the axis with the highest
  297. * index is the outermost axis.
  298. *
  299. * Example: If there are multiple x-axes at the top of the chart,
  300. * this function returns true if the axis supplied is the last
  301. * of the x-axes.
  302. *
  303. * @private
  304. *
  305. * @return {boolean}
  306. * True if the axis is the outermost axis in its dimension; false if
  307. * not.
  308. */
  309. GridAxisAdditions.prototype.isOuterAxis = function () {
  310. var axis = this.axis;
  311. var chart = axis.chart;
  312. var columnIndex = axis.grid.columnIndex;
  313. var columns = (axis.linkedParent && axis.linkedParent.grid.columns ||
  314. axis.grid.columns);
  315. var parentAxis = columnIndex ? axis.linkedParent : axis;
  316. var thisIndex = -1, lastIndex = 0;
  317. chart[axis.coll].forEach(function (otherAxis, index) {
  318. if (otherAxis.side === axis.side && !otherAxis.options.isInternal) {
  319. lastIndex = index;
  320. if (otherAxis === parentAxis) {
  321. // Get the index of the axis in question
  322. thisIndex = index;
  323. }
  324. }
  325. });
  326. return (lastIndex === thisIndex &&
  327. (isNumber(columnIndex) ? columns.length === columnIndex : true));
  328. };
  329. return GridAxisAdditions;
  330. }());
  331. /**
  332. * Axis with grid support.
  333. * @private
  334. * @class
  335. */
  336. var GridAxis = /** @class */ (function () {
  337. function GridAxis() {
  338. }
  339. /* *
  340. *
  341. * Static Functions
  342. *
  343. * */
  344. /* eslint-disable valid-jsdoc */
  345. /**
  346. * Extends axis class with grid support.
  347. * @private
  348. */
  349. GridAxis.compose = function (AxisClass) {
  350. Axis.keepProps.push('grid');
  351. wrap(AxisClass.prototype, 'unsquish', GridAxis.wrapUnsquish);
  352. // Add event handlers
  353. addEvent(AxisClass, 'init', GridAxis.onInit);
  354. addEvent(AxisClass, 'afterGetOffset', GridAxis.onAfterGetOffset);
  355. addEvent(AxisClass, 'afterGetTitlePosition', GridAxis.onAfterGetTitlePosition);
  356. addEvent(AxisClass, 'afterInit', GridAxis.onAfterInit);
  357. addEvent(AxisClass, 'afterRender', GridAxis.onAfterRender);
  358. addEvent(AxisClass, 'afterSetAxisTranslation', GridAxis.onAfterSetAxisTranslation);
  359. addEvent(AxisClass, 'afterSetOptions', GridAxis.onAfterSetOptions);
  360. addEvent(AxisClass, 'afterSetOptions', GridAxis.onAfterSetOptions2);
  361. addEvent(AxisClass, 'afterSetScale', GridAxis.onAfterSetScale);
  362. addEvent(AxisClass, 'afterTickSize', GridAxis.onAfterTickSize);
  363. addEvent(AxisClass, 'trimTicks', GridAxis.onTrimTicks);
  364. addEvent(AxisClass, 'destroy', GridAxis.onDestroy);
  365. };
  366. /**
  367. * Handle columns and getOffset.
  368. * @private
  369. */
  370. GridAxis.onAfterGetOffset = function () {
  371. var grid = this.grid;
  372. (grid && grid.columns || []).forEach(function (column) {
  373. column.getOffset();
  374. });
  375. };
  376. /**
  377. * @private
  378. */
  379. GridAxis.onAfterGetTitlePosition = function (e) {
  380. var axis = this;
  381. var options = axis.options;
  382. var gridOptions = options.grid || {};
  383. if (gridOptions.enabled === true) {
  384. // compute anchor points for each of the title align options
  385. var title = axis.axisTitle, axisHeight = axis.height, horiz = axis.horiz, axisLeft = axis.left, offset = axis.offset, opposite = axis.opposite, _a = axis.options.title, axisTitleOptions = _a === void 0 ? {} : _a, axisTop = axis.top, axisWidth = axis.width;
  386. var tickSize = axis.tickSize();
  387. var titleWidth = title && title.getBBox().width;
  388. var xOption = axisTitleOptions.x || 0;
  389. var yOption = axisTitleOptions.y || 0;
  390. var titleMargin = pick(axisTitleOptions.margin, horiz ? 5 : 10);
  391. var titleFontSize = axis.chart.renderer.fontMetrics(axisTitleOptions.style &&
  392. axisTitleOptions.style.fontSize, title).f;
  393. var crispCorr = tickSize ? tickSize[0] / 2 : 0;
  394. // TODO account for alignment
  395. // the position in the perpendicular direction of the axis
  396. var offAxis = ((horiz ? axisTop + axisHeight : axisLeft) +
  397. (horiz ? 1 : -1) * // horizontal axis reverses the margin
  398. (opposite ? -1 : 1) * // so does opposite axes
  399. crispCorr +
  400. (axis.side === GridAxis.Side.bottom ? titleFontSize : 0));
  401. e.titlePosition.x = horiz ?
  402. axisLeft - titleWidth / 2 - titleMargin + xOption :
  403. offAxis + (opposite ? axisWidth : 0) + offset + xOption;
  404. e.titlePosition.y = horiz ?
  405. (offAxis -
  406. (opposite ? axisHeight : 0) +
  407. (opposite ? titleFontSize : -titleFontSize) / 2 +
  408. offset +
  409. yOption) :
  410. axisTop - titleMargin + yOption;
  411. }
  412. };
  413. /**
  414. * @private
  415. */
  416. GridAxis.onAfterInit = function () {
  417. var axis = this;
  418. var chart = axis.chart, _a = axis.options.grid, gridOptions = _a === void 0 ? {} : _a, userOptions = axis.userOptions;
  419. if (gridOptions.enabled) {
  420. applyGridOptions(axis);
  421. /* eslint-disable no-invalid-this */
  422. // TODO: wrap the axis instead
  423. wrap(axis, 'labelFormatter', function (proceed) {
  424. var _a = this, axis = _a.axis, value = _a.value;
  425. var tickPos = axis.tickPositions;
  426. var series = (axis.isLinked ?
  427. axis.linkedParent :
  428. axis).series[0];
  429. var isFirst = value === tickPos[0];
  430. var isLast = value === tickPos[tickPos.length - 1];
  431. var point = series && find(series.options.data, function (p) {
  432. return p[axis.isXAxis ? 'x' : 'y'] === value;
  433. });
  434. // Make additional properties available for the
  435. // formatter
  436. this.isFirst = isFirst;
  437. this.isLast = isLast;
  438. this.point = point;
  439. // Call original labelFormatter
  440. return proceed.call(this);
  441. });
  442. /* eslint-enable no-invalid-this */
  443. }
  444. if (gridOptions.columns) {
  445. var columns = axis.grid.columns = [], columnIndex = axis.grid.columnIndex = 0;
  446. // Handle columns, each column is a grid axis
  447. while (++columnIndex < gridOptions.columns.length) {
  448. var columnOptions = merge(userOptions, gridOptions.columns[gridOptions.columns.length - columnIndex - 1], {
  449. linkedTo: 0,
  450. // Force to behave like category axis
  451. type: 'category',
  452. // Disable by default the scrollbar on the grid axis
  453. scrollbar: {
  454. enabled: false
  455. }
  456. });
  457. delete columnOptions.grid.columns; // Prevent recursion
  458. var column = new Axis(axis.chart, columnOptions);
  459. column.grid.isColumn = true;
  460. column.grid.columnIndex = columnIndex;
  461. // Remove column axis from chart axes array, and place it
  462. // in the columns array.
  463. erase(chart.axes, column);
  464. erase(chart[axis.coll], column);
  465. columns.push(column);
  466. }
  467. }
  468. };
  469. /**
  470. * Draw an extra line on the far side of the outermost axis,
  471. * creating floor/roof/wall of a grid. And some padding.
  472. * ```
  473. * Make this:
  474. * (axis.min) __________________________ (axis.max)
  475. * | | | | |
  476. * Into this:
  477. * (axis.min) __________________________ (axis.max)
  478. * ___|____|____|____|____|__
  479. * ```
  480. * @private
  481. */
  482. GridAxis.onAfterRender = function () {
  483. var axis = this;
  484. var grid = axis.grid;
  485. var options = axis.options;
  486. var renderer = axis.chart.renderer;
  487. var gridOptions = options.grid || {};
  488. var yStartIndex, yEndIndex, xStartIndex, xEndIndex;
  489. if (gridOptions.enabled === true) {
  490. // @todo acutual label padding (top, bottom, left, right)
  491. axis.maxLabelDimensions = axis.getMaxLabelDimensions(axis.ticks, axis.tickPositions);
  492. // Remove right wall before rendering if updating
  493. if (axis.rightWall) {
  494. axis.rightWall.destroy();
  495. }
  496. /*
  497. Draw an extra axis line on outer axes
  498. >
  499. Make this: |______|______|______|___
  500. > _________________________
  501. Into this: |______|______|______|__|
  502. */
  503. if (axis.grid && axis.grid.isOuterAxis() && axis.axisLine) {
  504. var lineWidth = options.lineWidth;
  505. if (lineWidth) {
  506. var linePath = axis.getLinePath(lineWidth);
  507. var startPoint = linePath[0];
  508. var endPoint = linePath[1];
  509. // Negate distance if top or left axis
  510. // Subtract 1px to draw the line at the end of the tick
  511. var tickLength = (axis.tickSize('tick') || [1])[0];
  512. var distance = (tickLength - 1) * ((axis.side === GridAxis.Side.top ||
  513. axis.side === GridAxis.Side.left) ? -1 : 1);
  514. // If axis is horizontal, reposition line path vertically
  515. if (startPoint[0] === 'M' && endPoint[0] === 'L') {
  516. if (axis.horiz) {
  517. startPoint[2] += distance;
  518. endPoint[2] += distance;
  519. }
  520. else {
  521. // If axis is vertical, reposition line path
  522. // horizontally
  523. startPoint[1] += distance;
  524. endPoint[1] += distance;
  525. }
  526. }
  527. if (!axis.grid.axisLineExtra) {
  528. axis.grid.axisLineExtra = renderer
  529. .path(linePath)
  530. .attr({
  531. zIndex: 7
  532. })
  533. .addClass('highcharts-axis-line')
  534. .add(axis.axisGroup);
  535. if (!renderer.styledMode) {
  536. axis.grid.axisLineExtra.attr({
  537. stroke: options.lineColor,
  538. 'stroke-width': lineWidth
  539. });
  540. }
  541. }
  542. else {
  543. axis.grid.axisLineExtra.animate({
  544. d: linePath
  545. });
  546. }
  547. // show or hide the line depending on
  548. // options.showEmpty
  549. axis.axisLine[axis.showAxis ? 'show' : 'hide'](true);
  550. }
  551. }
  552. (grid && grid.columns || []).forEach(function (column) {
  553. column.render();
  554. });
  555. }
  556. };
  557. /**
  558. * @private
  559. */
  560. GridAxis.onAfterSetAxisTranslation = function () {
  561. var axis = this;
  562. var tickInfo = axis.tickPositions && axis.tickPositions.info;
  563. var options = axis.options;
  564. var gridOptions = options.grid || {};
  565. var userLabels = axis.userOptions.labels || {};
  566. if (axis.horiz) {
  567. if (gridOptions.enabled === true) {
  568. axis.series.forEach(function (series) {
  569. series.options.pointRange = 0;
  570. });
  571. }
  572. // Lower level time ticks, like hours or minutes, represent
  573. // points in time and not ranges. These should be aligned
  574. // left in the grid cell by default. The same applies to
  575. // years of higher order.
  576. if (tickInfo &&
  577. options.dateTimeLabelFormats &&
  578. options.labels &&
  579. !defined(userLabels.align) &&
  580. (options.dateTimeLabelFormats[tickInfo.unitName].range === false ||
  581. tickInfo.count > 1 // years
  582. )) {
  583. options.labels.align = 'left';
  584. if (!defined(userLabels.x)) {
  585. options.labels.x = 3;
  586. }
  587. }
  588. }
  589. };
  590. /**
  591. * Creates a left and right wall on horizontal axes:
  592. * - Places leftmost tick at the start of the axis, to create a left
  593. * wall
  594. * - Ensures that the rightmost tick is at the end of the axis, to
  595. * create a right wall.
  596. * @private
  597. */
  598. GridAxis.onAfterSetOptions = function (e) {
  599. var options = this.options, userOptions = e.userOptions, gridAxisOptions, gridOptions = ((options && isObject(options.grid)) ? options.grid : {});
  600. if (gridOptions.enabled === true) {
  601. // Merge the user options into default grid axis options so
  602. // that when a user option is set, it takes presedence.
  603. gridAxisOptions = merge(true, {
  604. className: ('highcharts-grid-axis ' + (userOptions.className || '')),
  605. dateTimeLabelFormats: {
  606. hour: {
  607. list: ['%H:%M', '%H']
  608. },
  609. day: {
  610. list: ['%A, %e. %B', '%a, %e. %b', '%E']
  611. },
  612. week: {
  613. list: ['Week %W', 'W%W']
  614. },
  615. month: {
  616. list: ['%B', '%b', '%o']
  617. }
  618. },
  619. grid: {
  620. borderWidth: 1
  621. },
  622. labels: {
  623. padding: 2,
  624. style: {
  625. fontSize: '13px'
  626. }
  627. },
  628. margin: 0,
  629. title: {
  630. text: null,
  631. reserveSpace: false,
  632. rotation: 0
  633. },
  634. // In a grid axis, only allow one unit of certain types,
  635. // for example we shouln't have one grid cell spanning
  636. // two days.
  637. units: [[
  638. 'millisecond',
  639. [1, 10, 100]
  640. ], [
  641. 'second',
  642. [1, 10]
  643. ], [
  644. 'minute',
  645. [1, 5, 15]
  646. ], [
  647. 'hour',
  648. [1, 6]
  649. ], [
  650. 'day',
  651. [1]
  652. ], [
  653. 'week',
  654. [1]
  655. ], [
  656. 'month',
  657. [1]
  658. ], [
  659. 'year',
  660. null
  661. ]]
  662. }, userOptions);
  663. // X-axis specific options
  664. if (this.coll === 'xAxis') {
  665. // For linked axes, tickPixelInterval is used only if
  666. // the tickPositioner below doesn't run or returns
  667. // undefined (like multiple years)
  668. if (defined(userOptions.linkedTo) &&
  669. !defined(userOptions.tickPixelInterval)) {
  670. gridAxisOptions.tickPixelInterval = 350;
  671. }
  672. // For the secondary grid axis, use the primary axis'
  673. // tick intervals and return ticks one level higher.
  674. if (
  675. // Check for tick pixel interval in options
  676. !defined(userOptions.tickPixelInterval) &&
  677. // Only for linked axes
  678. defined(userOptions.linkedTo) &&
  679. !defined(userOptions.tickPositioner) &&
  680. !defined(userOptions.tickInterval)) {
  681. gridAxisOptions.tickPositioner = function (min, max) {
  682. var parentInfo = (this.linkedParent &&
  683. this.linkedParent.tickPositions &&
  684. this.linkedParent.tickPositions.info);
  685. if (parentInfo) {
  686. var unitIdx, count, unitName, i, units = gridAxisOptions.units, unitRange;
  687. for (i = 0; i < units.length; i++) {
  688. if (units[i][0] ===
  689. parentInfo.unitName) {
  690. unitIdx = i;
  691. break;
  692. }
  693. }
  694. // Get the first allowed count on the next
  695. // unit.
  696. if (units[unitIdx + 1]) {
  697. unitName = units[unitIdx + 1][0];
  698. count =
  699. (units[unitIdx + 1][1] || [1])[0];
  700. // In case the base X axis shows years, make
  701. // the secondary axis show ten times the
  702. // years (#11427)
  703. }
  704. else if (parentInfo.unitName === 'year') {
  705. unitName = 'year';
  706. count = parentInfo.count * 10;
  707. }
  708. unitRange = timeUnits[unitName];
  709. this.tickInterval = unitRange * count;
  710. return this.getTimeTicks({
  711. unitRange: unitRange,
  712. count: count,
  713. unitName: unitName
  714. }, min, max, this.options.startOfWeek);
  715. }
  716. };
  717. }
  718. }
  719. // Now merge the combined options into the axis options
  720. merge(true, this.options, gridAxisOptions);
  721. if (this.horiz) {
  722. /* _________________________
  723. Make this: ___|_____|_____|_____|__|
  724. ^ ^
  725. _________________________
  726. Into this: |_____|_____|_____|_____|
  727. ^ ^ */
  728. options.minPadding = pick(userOptions.minPadding, 0);
  729. options.maxPadding = pick(userOptions.maxPadding, 0);
  730. }
  731. // If borderWidth is set, then use its value for tick and
  732. // line width.
  733. if (isNumber(options.grid.borderWidth)) {
  734. options.tickWidth = options.lineWidth = gridOptions.borderWidth;
  735. }
  736. }
  737. };
  738. /**
  739. * @private
  740. */
  741. GridAxis.onAfterSetOptions2 = function (e) {
  742. var axis = this;
  743. var userOptions = e.userOptions;
  744. var gridOptions = userOptions && userOptions.grid || {};
  745. var columns = gridOptions.columns;
  746. // Add column options to the parent axis. Children has their column
  747. // options set on init in onGridAxisAfterInit.
  748. if (gridOptions.enabled && columns) {
  749. merge(true, axis.options, columns[columns.length - 1]);
  750. }
  751. };
  752. /**
  753. * Handle columns and setScale.
  754. * @private
  755. */
  756. GridAxis.onAfterSetScale = function () {
  757. var axis = this;
  758. (axis.grid.columns || []).forEach(function (column) {
  759. column.setScale();
  760. });
  761. };
  762. /**
  763. * Draw vertical axis ticks extra long to create cell floors and roofs.
  764. * Overrides the tickLength for vertical axes.
  765. * @private
  766. */
  767. GridAxis.onAfterTickSize = function (e) {
  768. var defaultLeftAxisOptions = Axis.defaultLeftAxisOptions;
  769. var _a = this, horiz = _a.horiz, maxLabelDimensions = _a.maxLabelDimensions, _b = _a.options.grid, gridOptions = _b === void 0 ? {} : _b;
  770. if (gridOptions.enabled && maxLabelDimensions) {
  771. var labelPadding = (Math.abs(defaultLeftAxisOptions.labels.x) * 2);
  772. var distance = horiz ?
  773. gridOptions.cellHeight || labelPadding + maxLabelDimensions.height :
  774. labelPadding + maxLabelDimensions.width;
  775. if (isArray(e.tickSize)) {
  776. e.tickSize[0] = distance;
  777. }
  778. else {
  779. e.tickSize = [distance, 0];
  780. }
  781. }
  782. };
  783. /**
  784. * @private
  785. */
  786. GridAxis.onDestroy = function (e) {
  787. var grid = this.grid;
  788. (grid.columns || []).forEach(function (column) {
  789. column.destroy(e.keepEvents);
  790. });
  791. grid.columns = void 0;
  792. };
  793. /**
  794. * Wraps axis init to draw cell walls on vertical axes.
  795. * @private
  796. */
  797. GridAxis.onInit = function (e) {
  798. var axis = this;
  799. var userOptions = e.userOptions || {};
  800. var gridOptions = userOptions.grid || {};
  801. if (gridOptions.enabled && defined(gridOptions.borderColor)) {
  802. userOptions.tickColor = userOptions.lineColor = gridOptions.borderColor;
  803. }
  804. if (!axis.grid) {
  805. axis.grid = new GridAxisAdditions(axis);
  806. }
  807. };
  808. /**
  809. * Makes tick labels which are usually ignored in a linked axis
  810. * displayed if they are within range of linkedParent.min.
  811. * ```
  812. * _____________________________
  813. * | | | | |
  814. * Make this: | | 2 | 3 | 4 |
  815. * |___|_______|_______|_______|
  816. * ^
  817. * _____________________________
  818. * | | | | |
  819. * Into this: | 1 | 2 | 3 | 4 |
  820. * |___|_______|_______|_______|
  821. * ^
  822. * ```
  823. * @private
  824. * @todo Does this function do what the drawing says? Seems to affect
  825. * ticks and not the labels directly?
  826. */
  827. GridAxis.onTrimTicks = function () {
  828. var axis = this;
  829. var options = axis.options;
  830. var gridOptions = options.grid || {};
  831. var categoryAxis = axis.categories;
  832. var tickPositions = axis.tickPositions;
  833. var firstPos = tickPositions[0];
  834. var lastPos = tickPositions[tickPositions.length - 1];
  835. var linkedMin = axis.linkedParent && axis.linkedParent.min;
  836. var linkedMax = axis.linkedParent && axis.linkedParent.max;
  837. var min = linkedMin || axis.min;
  838. var max = linkedMax || axis.max;
  839. var tickInterval = axis.tickInterval;
  840. var endMoreThanMin = (firstPos < min &&
  841. firstPos + tickInterval > min);
  842. var startLessThanMax = (lastPos > max &&
  843. lastPos - tickInterval < max);
  844. if (gridOptions.enabled === true &&
  845. !categoryAxis &&
  846. (axis.horiz || axis.isLinked)) {
  847. if (endMoreThanMin && !options.startOnTick) {
  848. tickPositions[0] = min;
  849. }
  850. if (startLessThanMax && !options.endOnTick) {
  851. tickPositions[tickPositions.length - 1] = max;
  852. }
  853. }
  854. };
  855. /**
  856. * Avoid altering tickInterval when reserving space.
  857. * @private
  858. */
  859. GridAxis.wrapUnsquish = function (proceed) {
  860. var axis = this;
  861. var _a = axis.options.grid, gridOptions = _a === void 0 ? {} : _a;
  862. if (gridOptions.enabled === true && axis.categories) {
  863. return axis.tickInterval;
  864. }
  865. return proceed.apply(axis, argsToArray(arguments));
  866. };
  867. return GridAxis;
  868. }());
  869. (function (GridAxis) {
  870. /**
  871. * Enum for which side the axis is on. Maps to axis.side.
  872. * @private
  873. */
  874. var Side;
  875. (function (Side) {
  876. Side[Side["top"] = 0] = "top";
  877. Side[Side["right"] = 1] = "right";
  878. Side[Side["bottom"] = 2] = "bottom";
  879. Side[Side["left"] = 3] = "left";
  880. })(Side = GridAxis.Side || (GridAxis.Side = {}));
  881. })(GridAxis || (GridAxis = {}));
  882. GridAxis.compose(Axis);
  883. export default GridAxis;