grid-axis.src.js 35 KB

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