UNPKG

highcharts

Version:
1,268 lines (1,243 loc) 120 kB
/** * @license Highcharts Gantt JS v12.3.0 (2025-06-21) * @module highcharts/modules/treegrid * @requires highcharts * * Tree Grid * * (c) 2016-2025 Jon Arild Nygard * * License: www.highcharts.com/license */ (function webpackUniversalModuleDefinition(root, factory) { if(typeof exports === 'object' && typeof module === 'object') module.exports = factory(root["_Highcharts"], root["_Highcharts"]["StackItem"], root["_Highcharts"]["Axis"], root["_Highcharts"]["Color"]); else if(typeof define === 'function' && define.amd) define("highcharts/modules/treegrid", ["highcharts/highcharts"], function (amd1) {return factory(amd1,amd1["StackItem"],amd1["Axis"],amd1["Color"]);}); else if(typeof exports === 'object') exports["highcharts/modules/treegrid"] = factory(root["_Highcharts"], root["_Highcharts"]["StackItem"], root["_Highcharts"]["Axis"], root["_Highcharts"]["Color"]); else root["Highcharts"] = factory(root["Highcharts"], root["Highcharts"]["StackItem"], root["Highcharts"]["Axis"], root["Highcharts"]["Color"]); })(typeof window === 'undefined' ? this : window, (__WEBPACK_EXTERNAL_MODULE__944__, __WEBPACK_EXTERNAL_MODULE__184__, __WEBPACK_EXTERNAL_MODULE__532__, __WEBPACK_EXTERNAL_MODULE__620__) => { return /******/ (() => { // webpackBootstrap /******/ "use strict"; /******/ var __webpack_modules__ = ({ /***/ 184: /***/ ((module) => { module.exports = __WEBPACK_EXTERNAL_MODULE__184__; /***/ }), /***/ 532: /***/ ((module) => { module.exports = __WEBPACK_EXTERNAL_MODULE__532__; /***/ }), /***/ 620: /***/ ((module) => { module.exports = __WEBPACK_EXTERNAL_MODULE__620__; /***/ }), /***/ 944: /***/ ((module) => { module.exports = __WEBPACK_EXTERNAL_MODULE__944__; /***/ }) /******/ }); /************************************************************************/ /******/ // The module cache /******/ var __webpack_module_cache__ = {}; /******/ /******/ // The require function /******/ function __webpack_require__(moduleId) { /******/ // Check if module is in cache /******/ var cachedModule = __webpack_module_cache__[moduleId]; /******/ if (cachedModule !== undefined) { /******/ return cachedModule.exports; /******/ } /******/ // Create a new module (and put it into the cache) /******/ var module = __webpack_module_cache__[moduleId] = { /******/ // no module.id needed /******/ // no module.loaded needed /******/ exports: {} /******/ }; /******/ /******/ // Execute the module function /******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__); /******/ /******/ // Return the exports of the module /******/ return module.exports; /******/ } /******/ /************************************************************************/ /******/ /* webpack/runtime/compat get default export */ /******/ (() => { /******/ // getDefaultExport function for compatibility with non-harmony modules /******/ __webpack_require__.n = (module) => { /******/ var getter = module && module.__esModule ? /******/ () => (module['default']) : /******/ () => (module); /******/ __webpack_require__.d(getter, { a: getter }); /******/ return getter; /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/define property getters */ /******/ (() => { /******/ // define getter functions for harmony exports /******/ __webpack_require__.d = (exports, definition) => { /******/ for(var key in definition) { /******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) { /******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] }); /******/ } /******/ } /******/ }; /******/ })(); /******/ /******/ /* webpack/runtime/hasOwnProperty shorthand */ /******/ (() => { /******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop)) /******/ })(); /******/ /************************************************************************/ var __webpack_exports__ = {}; // EXPORTS __webpack_require__.d(__webpack_exports__, { "default": () => (/* binding */ treegrid_src) }); // EXTERNAL MODULE: external {"amd":["highcharts/highcharts"],"commonjs":["highcharts"],"commonjs2":["highcharts"],"root":["Highcharts"]} var highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_ = __webpack_require__(944); var highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_default = /*#__PURE__*/__webpack_require__.n(highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_); // EXTERNAL MODULE: external {"amd":["highcharts/highcharts","StackItem"],"commonjs":["highcharts","StackItem"],"commonjs2":["highcharts","StackItem"],"root":["Highcharts","StackItem"]} var highcharts_StackItem_commonjs_highcharts_StackItem_commonjs2_highcharts_StackItem_root_Highcharts_StackItem_ = __webpack_require__(184); var highcharts_StackItem_commonjs_highcharts_StackItem_commonjs2_highcharts_StackItem_root_Highcharts_StackItem_default = /*#__PURE__*/__webpack_require__.n(highcharts_StackItem_commonjs_highcharts_StackItem_commonjs2_highcharts_StackItem_root_Highcharts_StackItem_); ;// ./code/es-modules/Core/Axis/BrokenAxis.js /* * * * (c) 2009-2025 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ const { addEvent, find, fireEvent, isArray, isNumber, pick } = (highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_default()); /* * * * Composition * * */ /** * Axis with support of broken data rows. * @private */ var BrokenAxis; (function (BrokenAxis) { /* * * * Declarations * * */ /* * * * Functions * * */ /** * Adds support for broken axes. * @private */ function compose(AxisClass, SeriesClass) { if (!AxisClass.keepProps.includes('brokenAxis')) { AxisClass.keepProps.push('brokenAxis'); addEvent(AxisClass, 'init', onAxisInit); addEvent(AxisClass, 'afterInit', onAxisAfterInit); addEvent(AxisClass, 'afterSetTickPositions', onAxisAfterSetTickPositions); addEvent(AxisClass, 'afterSetOptions', onAxisAfterSetOptions); const seriesProto = SeriesClass.prototype; seriesProto.drawBreaks = seriesDrawBreaks; seriesProto.gappedPath = seriesGappedPath; addEvent(SeriesClass, 'afterGeneratePoints', onSeriesAfterGeneratePoints); addEvent(SeriesClass, 'afterRender', onSeriesAfterRender); } return AxisClass; } BrokenAxis.compose = compose; /** * @private */ function onAxisAfterInit() { if (typeof this.brokenAxis !== 'undefined') { this.brokenAxis.setBreaks(this.options.breaks, false); } } /** * Force Axis to be not-ordinal when breaks are defined. * @private */ function onAxisAfterSetOptions() { const axis = this; if (axis.brokenAxis?.hasBreaks) { axis.options.ordinal = false; } } /** * @private */ function onAxisAfterSetTickPositions() { const axis = this, brokenAxis = axis.brokenAxis; if (brokenAxis?.hasBreaks) { const tickPositions = axis.tickPositions, info = axis.tickPositions.info, newPositions = []; for (let i = 0; i < tickPositions.length; i++) { if (!brokenAxis.isInAnyBreak(tickPositions[i])) { newPositions.push(tickPositions[i]); } } axis.tickPositions = newPositions; axis.tickPositions.info = info; } } /** * @private */ function onAxisInit() { const axis = this; if (!axis.brokenAxis) { axis.brokenAxis = new Additions(axis); } } /** * @private */ function onSeriesAfterGeneratePoints() { const { isDirty, options: { connectNulls }, points, xAxis, yAxis } = this; // Set, or reset visibility of the points. Axis.setBreaks marks // the series as isDirty if (isDirty) { let i = points.length; while (i--) { const point = points[i]; // Respect nulls inside the break (#4275) const nullGap = point.y === null && connectNulls === false; const isPointInBreak = (!nullGap && (xAxis?.brokenAxis?.isInAnyBreak(point.x, true) || yAxis?.brokenAxis?.isInAnyBreak(point.y, true))); // Set point.visible if in any break. // If not in break, reset visible to original value. point.visible = isPointInBreak ? false : point.options.visible !== false; } } } /** * @private */ function onSeriesAfterRender() { this.drawBreaks(this.xAxis, ['x']); this.drawBreaks(this.yAxis, pick(this.pointArrayMap, ['y'])); } /** * @private */ function seriesDrawBreaks(axis, keys) { const series = this, points = series.points; let breaks, threshold, y; if (axis?.brokenAxis?.hasBreaks) { const brokenAxis = axis.brokenAxis; keys.forEach(function (key) { breaks = brokenAxis?.breakArray || []; threshold = axis.isXAxis ? axis.min : pick(series.options.threshold, axis.min); // Array of breaks that have been "zoomed-out" which means that // they were shown previously, but now after zoom, they are not // (#19885). const breaksOutOfRange = axis?.options?.breaks?.filter(function (brk) { let isOut = true; // Iterate to see if "brk" is in axis range for (let i = 0; i < breaks.length; i++) { const otherBreak = breaks[i]; if (otherBreak.from === brk.from && otherBreak.to === brk.to) { isOut = false; break; } } return isOut; }); points.forEach(function (point) { y = pick(point['stack' + key.toUpperCase()], point[key]); breaks.forEach(function (brk) { if (isNumber(threshold) && isNumber(y)) { let eventName = ''; if ((threshold < brk.from && y > brk.to) || (threshold > brk.from && y < brk.from)) { eventName = 'pointBreak'; } else if ((threshold < brk.from && y > brk.from && y < brk.to) || (threshold > brk.from && y > brk.to && y < brk.from)) { eventName = 'pointInBreak'; } if (eventName) { fireEvent(axis, eventName, { point, brk }); } } }); breaksOutOfRange?.forEach(function (brk) { fireEvent(axis, 'pointOutsideOfBreak', { point, brk }); }); }); }); } } /** * Extend getGraphPath by identifying gaps in the data so that we * can draw a gap in the line or area. This was moved from ordinal * axis module to broken axis module as of #5045. * * @private * @function Highcharts.Series#gappedPath * * @return {Highcharts.SVGPathArray} * Gapped path */ function seriesGappedPath() { const currentDataGrouping = this.currentDataGrouping, groupingSize = currentDataGrouping?.gapSize, points = this.points.slice(), yAxis = this.yAxis; let gapSize = this.options.gapSize, i = points.length - 1, stack; /** * Defines when to display a gap in the graph, together with the * [gapUnit](plotOptions.series.gapUnit) option. * * In case when `dataGrouping` is enabled, points can be grouped * into a larger time span. This can make the grouped points to * have a greater distance than the absolute value of `gapSize` * property, which will result in disappearing graph completely. * To prevent this situation the mentioned distance between * grouped points is used instead of previously defined * `gapSize`. * * In practice, this option is most often used to visualize gaps * in time series. In a stock chart, intraday data is available * for daytime hours, while gaps will appear in nights and * weekends. * * @see [gapUnit](plotOptions.series.gapUnit) * @see [xAxis.breaks](#xAxis.breaks) * * @sample {highstock} stock/plotoptions/series-gapsize/ * Setting the gap size to 2 introduces gaps for weekends in * daily datasets. * * @type {number} * @default 0 * @product highstock * @requires modules/broken-axis * @apioption plotOptions.series.gapSize */ /** * Together with [gapSize](plotOptions.series.gapSize), this * option defines where to draw gaps in the graph. * * When the `gapUnit` is `"relative"` (default), a gap size of 5 * means that if the distance between two points is greater than * 5 times that of the two closest points, the graph will be * broken. * * When the `gapUnit` is `"value"`, the gap is based on absolute * axis values, which on a datetime axis is milliseconds. This * also applies to the navigator series that inherits gap * options from the base series. * * @see [gapSize](plotOptions.series.gapSize) * * @type {string} * @default relative * @since 5.0.13 * @product highstock * @validvalue ["relative", "value"] * @requires modules/broken-axis * @apioption plotOptions.series.gapUnit */ if (gapSize && i > 0) { // #5008 // Gap unit is relative if (this.options.gapUnit !== 'value') { gapSize *= this.basePointRange; } // Setting a new gapSize in case dataGrouping is enabled // (#7686) if (groupingSize && groupingSize > gapSize && // Except when DG is forced (e.g. from other series) // and has lower granularity than actual points (#11351) groupingSize >= this.basePointRange) { gapSize = groupingSize; } // Extension for ordinal breaks let current, next; while (i--) { // Reassign next if it is not visible if (!(next && next.visible !== false)) { next = points[i + 1]; } current = points[i]; // Skip iteration if one of the points is not visible if (next.visible === false || current.visible === false) { continue; } if (next.x - current.x > gapSize) { const xRange = (current.x + next.x) / 2; points.splice(// Insert after this one i + 1, 0, { isNull: true, x: xRange }); // For stacked chart generate empty stack items, #6546 if (yAxis.stacking && this.options.stacking) { stack = yAxis.stacking.stacks[this.stackKey][xRange] = new (highcharts_StackItem_commonjs_highcharts_StackItem_commonjs2_highcharts_StackItem_root_Highcharts_StackItem_default())(yAxis, yAxis.options.stackLabels, false, xRange, this.stack); stack.total = 0; } } // Assign current to next for the upcoming iteration next = current; } } // Call base method return this.getGraphPath(points); } /* * * * Class * * */ /** * Provides support for broken axes. * @private * @class */ class Additions { /* * * * Static Functions * * */ /** * @private */ static isInBreak(brk, val) { const repeat = brk.repeat || Infinity, from = brk.from, length = brk.to - brk.from, test = (val >= from ? (val - from) % repeat : repeat - ((from - val) % repeat)); let ret; if (!brk.inclusive) { ret = test < length && test !== 0; } else { ret = test <= length; } return ret; } /** * @private */ static lin2Val(val) { const axis = this; const brokenAxis = axis.brokenAxis; const breakArray = brokenAxis?.breakArray; if (!breakArray || !isNumber(val)) { return val; } let nval = val, brk, i; for (i = 0; i < breakArray.length; i++) { brk = breakArray[i]; if (brk.from >= nval) { break; } else if (brk.to < nval) { nval += brk.len; } else if (Additions.isInBreak(brk, nval)) { nval += brk.len; } } return nval; } /** * @private */ static val2Lin(val) { const axis = this; const brokenAxis = axis.brokenAxis; const breakArray = brokenAxis?.breakArray; if (!breakArray || !isNumber(val)) { return val; } let nval = val, brk, i; for (i = 0; i < breakArray.length; i++) { brk = breakArray[i]; if (brk.to <= val) { nval -= brk.len; } else if (brk.from >= val) { break; } else if (Additions.isInBreak(brk, val)) { nval -= (val - brk.from); break; } } return nval; } /* * * * Constructors * * */ constructor(axis) { this.hasBreaks = false; this.axis = axis; } /* * * * Functions * * */ /** * Returns the first break found where the x is larger then break.from * and smaller then break.to. * * @param {number} x * The number which should be within a break. * * @param {Array<Highcharts.XAxisBreaksOptions>} breaks * The array of breaks to search within. * * @return {Highcharts.XAxisBreaksOptions|undefined} * Returns the first break found that matches, returns false if no break * is found. */ findBreakAt(x, breaks) { return find(breaks, function (b) { return b.from < x && x < b.to; }); } /** * @private */ isInAnyBreak(val, testKeep) { const brokenAxis = this, axis = brokenAxis.axis, breaks = axis.options.breaks || []; let i = breaks.length, inbrk, keep, ret; if (i && isNumber(val)) { while (i--) { if (Additions.isInBreak(breaks[i], val)) { inbrk = true; if (!keep) { keep = pick(breaks[i].showPoints, !axis.isXAxis); } } } if (inbrk && testKeep) { ret = inbrk && !keep; } else { ret = inbrk; } } return ret; } /** * Dynamically set or unset breaks in an axis. This function in lighter * than using Axis.update, and it also preserves animation. * * @private * @function Highcharts.Axis#setBreaks * * @param {Array<Highcharts.XAxisBreaksOptions>} [breaks] * The breaks to add. When `undefined` it removes existing breaks. * * @param {boolean} [redraw=true] * Whether to redraw the chart immediately. */ setBreaks(breaks, redraw) { const brokenAxis = this, axis = brokenAxis.axis, time = axis.chart.time, hasBreaks = isArray(breaks) && !!breaks.length && !!Object.keys(breaks[0]).length; // Check for [{}], #16368. axis.isDirty = brokenAxis.hasBreaks !== hasBreaks; brokenAxis.hasBreaks = hasBreaks; // Compile string dates breaks?.forEach((brk) => { brk.from = time.parse(brk.from) || 0; brk.to = time.parse(brk.to) || 0; }); if (breaks !== axis.options.breaks) { axis.options.breaks = axis.userOptions.breaks = breaks; } axis.forceRedraw = true; // Force recalculation in setScale // Recalculate series related to the axis. axis.series.forEach(function (series) { series.isDirty = true; }); if (!hasBreaks && axis.val2lin === Additions.val2Lin) { // Revert to prototype functions delete axis.val2lin; delete axis.lin2val; } if (hasBreaks) { axis.userOptions.ordinal = false; axis.lin2val = Additions.lin2Val; axis.val2lin = Additions.val2Lin; axis.setExtremes = function (newMin, newMax, redraw, animation, eventArguments) { // If trying to set extremes inside a break, extend min to // after, and max to before the break ( #3857 ) if (brokenAxis.hasBreaks) { const breaks = (this.options.breaks || []); let axisBreak; while ((axisBreak = brokenAxis.findBreakAt(newMin, breaks))) { newMin = axisBreak.to; } while ((axisBreak = brokenAxis.findBreakAt(newMax, breaks))) { newMax = axisBreak.from; } // If both min and max is within the same break. if (newMax < newMin) { newMax = newMin; } } axis.constructor.prototype.setExtremes.call(this, newMin, newMax, redraw, animation, eventArguments); }; axis.setAxisTranslation = function () { axis.constructor.prototype.setAxisTranslation.call(this); brokenAxis.unitLength = void 0; if (brokenAxis.hasBreaks) { const breaks = axis.options.breaks || [], // Temporary one: breakArrayT = [], breakArray = [], pointRangePadding = pick(axis.pointRangePadding, 0); let length = 0, inBrk, repeat, min = axis.userMin || axis.min, max = axis.userMax || axis.max, start, i; // Min & max check (#4247) breaks.forEach(function (brk) { repeat = brk.repeat || Infinity; if (isNumber(min) && isNumber(max)) { if (Additions.isInBreak(brk, min)) { min += ((brk.to % repeat) - (min % repeat)); } if (Additions.isInBreak(brk, max)) { max -= ((max % repeat) - (brk.from % repeat)); } } }); // Construct an array holding all breaks in the axis breaks.forEach(function (brk) { start = brk.from; repeat = brk.repeat || Infinity; if (isNumber(min) && isNumber(max)) { while (start - repeat > min) { start -= repeat; } while (start < min) { start += repeat; } for (i = start; i < max; i += repeat) { breakArrayT.push({ value: i, move: 'in' }); breakArrayT.push({ value: i + brk.to - brk.from, move: 'out', size: brk.breakSize }); } } }); breakArrayT.sort(function (a, b) { return ((a.value === b.value) ? ((a.move === 'in' ? 0 : 1) - (b.move === 'in' ? 0 : 1)) : a.value - b.value); }); // Simplify the breaks inBrk = 0; start = min; breakArrayT.forEach(function (brk) { inBrk += (brk.move === 'in' ? 1 : -1); if (inBrk === 1 && brk.move === 'in') { start = brk.value; } if (inBrk === 0 && isNumber(start)) { breakArray.push({ from: start, to: brk.value, len: brk.value - start - (brk.size || 0) }); length += (brk.value - start - (brk.size || 0)); } }); brokenAxis.breakArray = breakArray; // Used with staticScale, and below the actual axis // length, when breaks are subtracted. if (isNumber(min) && isNumber(max) && isNumber(axis.min)) { brokenAxis.unitLength = max - min - length + pointRangePadding; fireEvent(axis, 'afterBreaks'); if (axis.staticScale) { axis.transA = axis.staticScale; } else if (brokenAxis.unitLength) { axis.transA *= (max - axis.min + pointRangePadding) / brokenAxis.unitLength; } if (pointRangePadding) { axis.minPixelPadding = axis.transA * (axis.minPointOffset || 0); } axis.min = min; axis.max = max; } } }; } if (pick(redraw, true)) { axis.chart.redraw(); } } } BrokenAxis.Additions = Additions; })(BrokenAxis || (BrokenAxis = {})); /* * * * Default Export * * */ /* harmony default export */ const Axis_BrokenAxis = (BrokenAxis); // EXTERNAL MODULE: external {"amd":["highcharts/highcharts","Axis"],"commonjs":["highcharts","Axis"],"commonjs2":["highcharts","Axis"],"root":["Highcharts","Axis"]} var highcharts_Axis_commonjs_highcharts_Axis_commonjs2_highcharts_Axis_root_Highcharts_Axis_ = __webpack_require__(532); var highcharts_Axis_commonjs_highcharts_Axis_commonjs2_highcharts_Axis_root_Highcharts_Axis_default = /*#__PURE__*/__webpack_require__.n(highcharts_Axis_commonjs_highcharts_Axis_commonjs2_highcharts_Axis_root_Highcharts_Axis_); ;// ./code/es-modules/Core/Axis/GridAxis.js /* * * * (c) 2016 Highsoft AS * Authors: Lars A. V. Cabrera * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ const { dateFormats } = (highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_default()); const { addEvent: GridAxis_addEvent, defined, erase, find: GridAxis_find, isArray: GridAxis_isArray, isNumber: GridAxis_isNumber, merge, pick: GridAxis_pick, timeUnits, wrap } = (highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_default()); /* * * * Enums * * */ /** * Enum for which side the axis is on. Maps to axis.side. * @private */ var GridAxisSide; (function (GridAxisSide) { GridAxisSide[GridAxisSide["top"] = 0] = "top"; GridAxisSide[GridAxisSide["right"] = 1] = "right"; GridAxisSide[GridAxisSide["bottom"] = 2] = "bottom"; GridAxisSide[GridAxisSide["left"] = 3] = "left"; })(GridAxisSide || (GridAxisSide = {})); /* * * * Functions * * */ /** * @private */ function argsToArray(args) { return Array.prototype.slice.call(args, 1); } /** * @private */ function isObject(x) { // Always use strict mode return highcharts_commonjs_highcharts_commonjs2_highcharts_root_Highcharts_default().isObject(x, true); } /** * @private */ function applyGridOptions(axis) { const options = axis.options; // Center-align by default /* if (!options.labels) { options.labels = {}; } */ options.labels.align = GridAxis_pick(options.labels.align, 'center'); // @todo: Check against tickLabelPlacement between/on etc /* Prevents adding the last tick label if the axis is not a category axis. Since numeric labels are normally placed at starts and ends of a range of value, and this module makes the label point at the value, an "extra" label would appear. */ if (!axis.categories) { options.showLastLabel = false; } // Prevents rotation of labels when squished, as rotating them would not // help. axis.labelRotation = 0; options.labels.rotation = 0; // Allow putting ticks closer than their data points. options.minTickInterval = 1; } /** * Extends axis class with grid support. * @private */ function compose(AxisClass, ChartClass, TickClass) { if (!AxisClass.keepProps.includes('grid')) { AxisClass.keepProps.push('grid'); AxisClass.prototype.getMaxLabelDimensions = getMaxLabelDimensions; wrap(AxisClass.prototype, 'unsquish', wrapUnsquish); wrap(AxisClass.prototype, 'getOffset', wrapGetOffset); // Add event handlers GridAxis_addEvent(AxisClass, 'init', onInit); GridAxis_addEvent(AxisClass, 'afterGetTitlePosition', onAfterGetTitlePosition); GridAxis_addEvent(AxisClass, 'afterInit', onAfterInit); GridAxis_addEvent(AxisClass, 'afterRender', onAfterRender); GridAxis_addEvent(AxisClass, 'afterSetAxisTranslation', onAfterSetAxisTranslation); GridAxis_addEvent(AxisClass, 'afterSetOptions', onAfterSetOptions); GridAxis_addEvent(AxisClass, 'afterSetOptions', onAfterSetOptions2); GridAxis_addEvent(AxisClass, 'afterSetScale', onAfterSetScale); GridAxis_addEvent(AxisClass, 'afterTickSize', onAfterTickSize); GridAxis_addEvent(AxisClass, 'trimTicks', onTrimTicks); GridAxis_addEvent(AxisClass, 'destroy', onDestroy); GridAxis_addEvent(ChartClass, 'afterSetChartSize', onChartAfterSetChartSize); GridAxis_addEvent(TickClass, 'afterGetLabelPosition', onTickAfterGetLabelPosition); GridAxis_addEvent(TickClass, 'labelFormat', onTickLabelFormat); } return AxisClass; } /** * Get the largest label width and height. * * @private * @function Highcharts.Axis#getMaxLabelDimensions * * @param {Highcharts.Dictionary<Highcharts.Tick>} ticks * All the ticks on one axis. * * @param {Array<number|string>} tickPositions * All the tick positions on one axis. * * @return {Highcharts.SizeObject} * Object containing the properties height and width. * * @todo Move this to the generic axis implementation, as it is used there. */ function getMaxLabelDimensions(ticks, tickPositions) { const dimensions = { width: 0, height: 0 }; tickPositions.forEach(function (pos) { const tick = ticks[pos]; let labelHeight = 0, labelWidth = 0, label; if (isObject(tick)) { label = isObject(tick.label) ? tick.label : {}; // Find width and height of label labelHeight = label.getBBox ? label.getBBox().height : 0; if (label.textStr && !GridAxis_isNumber(label.textPxLength)) { label.textPxLength = label.getBBox().width; } labelWidth = GridAxis_isNumber(label.textPxLength) ? // Math.round ensures crisp lines Math.round(label.textPxLength) : 0; if (label.textStr) { // Set the tickWidth same as the label width after ellipsis // applied #10281 labelWidth = Math.round(label.getBBox().width); } // Update the result if width and/or height are larger dimensions.height = Math.max(labelHeight, dimensions.height); dimensions.width = Math.max(labelWidth, dimensions.width); } }); // For tree grid, add indentation if (this.type === 'treegrid' && this.treeGrid && this.treeGrid.mapOfPosToGridNode) { const treeDepth = this.treeGrid.mapOfPosToGridNode[-1].height || 0; dimensions.width += (this.options.labels.indentation * (treeDepth - 1)); } return dimensions; } /** * Handle columns and getOffset. * @private */ function wrapGetOffset(proceed) { const { grid } = this, // On the left side we handle the columns first because the offset is // calculated from the plot area and out columnsFirst = this.side === 3; if (!columnsFirst) { proceed.apply(this); } if (!grid?.isColumn) { let columns = grid?.columns || []; if (columnsFirst) { columns = columns.slice().reverse(); } columns .forEach((column) => { column.getOffset(); }); } if (columnsFirst) { proceed.apply(this); } } /** * @private */ function onAfterGetTitlePosition(e) { const axis = this; const options = axis.options; const gridOptions = options.grid || {}; if (gridOptions.enabled === true) { // Compute anchor points for each of the title align options const { axisTitle, height: axisHeight, horiz, left: axisLeft, offset, opposite, options, top: axisTop, width: axisWidth } = axis; const tickSize = axis.tickSize(); const titleWidth = axisTitle?.getBBox().width; const xOption = options.title.x; const yOption = options.title.y; const titleMargin = GridAxis_pick(options.title.margin, horiz ? 5 : 10); const titleFontSize = axisTitle ? axis.chart.renderer.fontMetrics(axisTitle).f : 0; const crispCorr = tickSize ? tickSize[0] / 2 : 0; // TODO account for alignment // the position in the perpendicular direction of the axis const offAxis = ((horiz ? axisTop + axisHeight : axisLeft) + (horiz ? 1 : -1) * // Horizontal axis reverses the margin (opposite ? -1 : 1) * // So does opposite axes crispCorr + (axis.side === GridAxisSide.bottom ? titleFontSize : 0)); e.titlePosition.x = horiz ? axisLeft - (titleWidth || 0) / 2 - titleMargin + xOption : offAxis + (opposite ? axisWidth : 0) + offset + xOption; e.titlePosition.y = horiz ? (offAxis - (opposite ? axisHeight : 0) + (opposite ? titleFontSize : -titleFontSize) / 2 + offset + yOption) : axisTop - titleMargin + yOption; } } /** * @private */ function onAfterInit() { const axis = this; const { chart, options: { grid: gridOptions = {} }, userOptions } = axis; if (gridOptions.enabled) { applyGridOptions(axis); } if (gridOptions.columns) { const columns = axis.grid.columns = []; let columnIndex = axis.grid.columnIndex = 0; // Handle columns, each column is a grid axis while (++columnIndex < gridOptions.columns.length) { const columnOptions = merge(userOptions, gridOptions.columns[columnIndex], { isInternal: true, linkedTo: 0, // Disable by default the scrollbar on the grid axis scrollbar: { enabled: false } }, // Avoid recursion { grid: { columns: void 0 } }); const column = new (highcharts_Axis_commonjs_highcharts_Axis_commonjs2_highcharts_Axis_root_Highcharts_Axis_default())(axis.chart, columnOptions, 'yAxis'); column.grid.isColumn = true; column.grid.columnIndex = columnIndex; // Remove column axis from chart axes array, and place it // in the columns array. erase(chart.axes, column); erase(chart[axis.coll] || [], column); columns.push(column); } } } /** * Draw an extra line on the far side of the outermost axis, * creating floor/roof/wall of a grid. And some padding. * ``` * Make this: * (axis.min) __________________________ (axis.max) * | | | | | * Into this: * (axis.min) __________________________ (axis.max) * ___|____|____|____|____|__ * ``` * @private */ function onAfterRender() { const axis = this, { axisTitle, grid, options } = axis, gridOptions = options.grid || {}; if (gridOptions.enabled === true) { const min = axis.min || 0, max = axis.max || 0, firstTick = axis.ticks[axis.tickPositions[0]]; // Adjust the title max width to the column width (#19657) if (axisTitle && !axis.chart.styledMode && firstTick?.slotWidth && !axis.options.title.style.width) { axisTitle.css({ width: `${firstTick.slotWidth}px` }); } // @todo actual label padding (top, bottom, left, right) axis.maxLabelDimensions = axis.getMaxLabelDimensions(axis.ticks, axis.tickPositions); // Remove right wall before rendering if updating if (axis.rightWall) { axis.rightWall.destroy(); } /* Draw an extra axis line on outer axes > Make this: |______|______|______|___ > _________________________ Into this: |______|______|______|__| */ if (axis.grid?.isOuterAxis() && axis.axisLine) { const lineWidth = options.lineWidth; if (lineWidth) { const linePath = axis.getLinePath(lineWidth), startPoint = linePath[0], endPoint = linePath[1], // Negate distance if top or left axis // Subtract 1px to draw the line at the end of the tick tickLength = (axis.tickSize('tick') || [1])[0], distance = tickLength * ((axis.side === GridAxisSide.top || axis.side === GridAxisSide.left) ? -1 : 1); // If axis is horizontal, reposition line path vertically if (startPoint[0] === 'M' && endPoint[0] === 'L') { if (axis.horiz) { startPoint[2] += distance; endPoint[2] += distance; } else { startPoint[1] += distance; endPoint[1] += distance; } } // If it doesn't exist, add an upper and lower border // for the vertical grid axis. if (!axis.horiz && axis.chart.marginRight) { const upperBorderStartPoint = startPoint, upperBorderEndPoint = [ 'L', axis.left, startPoint[2] || 0 ], upperBorderPath = [ upperBorderStartPoint, upperBorderEndPoint ], lowerBorderEndPoint = [ 'L', axis.chart.chartWidth - axis.chart.marginRight, axis.toPixels(max + axis.tickmarkOffset) ], lowerBorderStartPoint = [ 'M', endPoint[1] || 0, axis.toPixels(max + axis.tickmarkOffset) ], lowerBorderPath = [ lowerBorderStartPoint, lowerBorderEndPoint ]; if (!axis.grid.upperBorder && min % 1 !== 0) { axis.grid.upperBorder = axis.grid.renderBorder(upperBorderPath); } if (axis.grid.upperBorder) { axis.grid.upperBorder.attr({ stroke: options.lineColor, 'stroke-width': options.lineWidth }); axis.grid.upperBorder.animate({ d: upperBorderPath }); } if (!axis.grid.lowerBorder && max % 1 !== 0) { axis.grid.lowerBorder = axis.grid.renderBorder(lowerBorderPath); } if (axis.grid.lowerBorder) { axis.grid.lowerBorder.attr({ stroke: options.lineColor, 'stroke-width': options.lineWidth }); axis.grid.lowerBorder.animate({ d: lowerBorderPath }); } } // Render an extra line parallel to the existing axes, to // close the grid. if (!axis.grid.axisLineExtra) { axis.grid.axisLineExtra = axis.grid.renderBorder(linePath); } else { axis.grid.axisLineExtra.attr({ stroke: options.lineColor, 'stroke-width': options.lineWidth }); axis.grid.axisLineExtra.animate({ d: linePath }); } // Show or hide the line depending on options.showEmpty axis.axisLine[axis.showAxis ? 'show' : 'hide'](); } } (grid?.columns || []).forEach((column) => column.render()); // Manipulate the tick mark visibility // based on the axis.max- allows smooth scrolling. if (!axis.horiz && axis.chart.hasRendered && (axis.scrollbar || axis.linkedParent?.scrollbar) && axis.tickPositions.length) { const tickmarkOffset = axis.tickmarkOffset, lastTick = axis.tickPositions[axis.tickPositions.length - 1], firstTick = axis.tickPositions[0]; let label, tickMark; while ((label = axis.hiddenLabels.pop()) && label.element) { label.show(); // #15453 } while ((tickMark = axis.hiddenMarks.pop()) && tickMark.element) { tickMark.show(); // #16439 } // Hide/show first tick label. label = axis.ticks[firstTick].label; if (label) { if (min - firstTick > tickmarkOffset) { axis.hiddenLabels.push(label.hide()); } else { label.show(); } } // Hide/show last tick mark/label. label = axis.ticks[lastTick].label; if (label) { if (lastTick - max > tickmarkOffset) { axis.hiddenLabels.push(label.hide()); } else { label.show(); } } const mark = axis.ticks[lastTick].mark; if (mark && lastTick - max < tickmarkOffset && lastTick - max > 0 && axis.ticks[lastTick].isLast) { axis.hiddenMarks.push(mark.hide()); } } } } /** * @private */ function onAfterSetAxisTranslation() { const axis = this; const tickInfo = axis.tickPositions?.info; const options = axis.options; const gridOptions = options.grid || {}; const userLabels = axis.userOptions.labels || {}; // Fire this only for the Gantt type chart, #14868. if (gridOptions.enabled) { if (axis.horiz) { axis.series.forEach((series) => { series.options.pointRange = 0; }); // Lower level time ticks, like hours or minutes, represent // points in time and not ranges. These should be aligned // left in the grid cell by default. The same applies to // years of higher order. if (tickInfo && options.dateTimeLabelFormats && options.labels && !defined(userLabels.align) && (options.dateTimeLabelFormats[tickInfo.unitName] .range === false || tickInfo.count > 1 // Years )) { options.labels.align = 'left'; if (!defined(userLabels.x)) { options.labels.x = 3; } } } else { // Don't trim ticks which not in min/max range but // they are still in the min/max plus tickInterval. if (this.type !== 'treegrid' && axis.grid && axis.grid.columns) { this.minPointOffset = this.tickInterval; } } } } /** * Creates a left and right wall on horizontal axes: * - Places leftmost tick at the start of the axis, to create a left * wall * - Ensures that the rightmost tick is at the end of the axis, to * create a right wall. * @private */ function onAfterSetOptions(e) { const options = this.options, userOptions = e.userOptions, gridOptions = ((options && isObject(options.grid)) ? options.grid : {}); let gridAxisOptions; if (gridOptions.enabled === true) { // Merge the user options into default grid axis options so // that when a user option is set, it takes precedence. gridAxisOptions = merge(true, { className: ('highcharts-grid-axis ' + (userOptions.className || '')), dateTimeLabelFormats: { hour: { list: ['%[HM]', '%[H]'] }, day: { list: ['%[AeB]', '%[aeb]', '%[E]'] }, week: { list: ['Week %W', 'W%W'] }, month: { list: ['%[B]', '%[b]', '%o'] } }, grid: { borderWidth: 1 }, labels: { padding: 2, style: { fontSize: '0.9em' } }, margin: 0, title: { text: null, reserveSpace: false, rotation: 0, style: { textOverflow: 'ellipsis' } }, // In a grid axis, only allow one unit of certain types, // for example we shouldn't have one grid cell spanning // two days. units: [[ 'millisecond', // Unit name [1, 10, 100] ], [