grid-axis.src.js 33 KB

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