// SPDX-License-Identifier: LicenseRef-Highcharts
/**
 * @license Highcharts JS v13.0.0 (2026-06-11)
 * @module highcharts/standalone-navigator
 *
 * Standalone Navigator for Highcharts.
 *
 * (c) 2009-2026 Highsoft AS
 *
 * A commercial license may be required depending on use,
 * see www.highcharts.com/license
 */
/******/ // The require scope
/******/ var __webpack_require__ = {};
/******/ 
/************************************************************************/
/******/ /* 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__ = {};

;// ./code/es-modules/Core/Globals.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */

/* *
 *
 *  Namespace
 *
 * */
/**
 * Shared Highcharts properties.
 * @internal
 */
var Globals;
(function (Globals) {
    /* *
     *
     *  Constants
     *
     * */
    Globals.SVG_NS = 'http://www.w3.org/2000/svg', Globals.product = 'Highcharts', Globals.version = '13.0.0', Globals.win = (typeof window !== 'undefined' ?
        window :
        {}), // eslint-disable-line node/no-unsupported-features/es-builtins
    Globals.doc = Globals.win.document, Globals.svg = !!Globals.doc?.createElementNS?.(Globals.SVG_NS, 'svg')?.createSVGRect, Globals.pageLang = Globals.doc?.documentElement?.closest('[lang]')?.lang, Globals.userAgent = Globals.win.navigator?.userAgent || '', Globals.isChrome = Globals.win.chrome, Globals.isFirefox = Globals.userAgent.indexOf('Firefox') !== -1, Globals.isMS = /(edge|msie|trident)/i.test(Globals.userAgent) && !Globals.win.opera, Globals.isSafari = !Globals.isChrome && Globals.userAgent.indexOf('Safari') !== -1, Globals.isTouchDevice = /(Mobile|Android|Windows Phone)/.test(Globals.userAgent), Globals.isWebKit = Globals.userAgent.indexOf('AppleWebKit') !== -1, Globals.deg2rad = Math.PI * 2 / 360, Globals.marginNames = [
        'plotTop',
        'marginRight',
        'marginBottom',
        'plotLeft'
    ], Globals.noop = function () { }, Globals.supportsPassiveEvents = (function () {
        // Checks whether the browser supports passive events, (#11353).
        let supportsPassive = false;
        // Object.defineProperty doesn't work on IE as well as passive
        // events - instead of using polyfill, we can exclude IE totally.
        if (!Globals.isMS) {
            const opts = Object.defineProperty({}, 'passive', {
                get: function () {
                    supportsPassive = true;
                }
            });
            if (Globals.win.addEventListener && Globals.win.removeEventListener) {
                Globals.win.addEventListener('testPassive', Globals.noop, opts);
                Globals.win.removeEventListener('testPassive', Globals.noop, opts);
            }
        }
        return supportsPassive;
    }());
    /**
     * An array containing the current chart objects in the page. A chart's
     * position in the array is preserved throughout the page's lifetime. When
     * a chart is destroyed, the array item becomes `undefined`.
     *
     * @name Highcharts.charts
     * @type {Array<Highcharts.Chart|undefined>}
     */
    Globals.charts = [];
    /**
     * A shared registry between all bundles to keep track of applied
     * compositions.
     * @internal
     */
    Globals.composed = [];
    /**
     * A hook for defining additional date format specifiers. New
     * specifiers are defined as key-value pairs by using the
     * specifier as key, and a function which takes the timestamp as
     * value. This function returns the formatted portion of the
     * date.
     *
     * Using `dateFormats` is also a convenient way to define new keys for
     * complex locale-aware date formats compatible with the
     * [Intl.DateTimeFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat)
     * browser API, whenever the built-in formats are not sufficient.
     *
     * @sample highcharts/global/dateformats/
     *         Adding support for week number
     * @sample highcharts/global/dateformats-object/
     *         A locale-aware date format using `Intl.DateTimeFormat`
     *
     * @name Highcharts.dateFormats
     * @type {Record<string, Highcharts.TimeFormatCallbackFunction>}
     */
    Globals.dateFormats = {};
    /**
     * @internal
     * @deprecated
     * @todo Use only `Core/Series/SeriesRegistry.seriesTypes`
     */
    Globals.seriesTypes = {};
    /** @internal */
    Globals.symbolSizes = {};
    /* *
     *
     *  Properties
     *
     * */
    // eslint-disable-next-line prefer-const
    Globals.chartCount = 0;
})(Globals || (Globals = {}));
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Core_Globals = (Globals);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * Theme options that should get applied to the chart. In module mode it
 * might not be possible to change this property because of read-only
 * restrictions, instead use {@link Highcharts.setOptions}.
 *
 * @deprecated 9.1.2
 * @name Highcharts.theme
 * @type {Highcharts.Options}
 */
(''); // Keeps doclets above in JS file

;// ./code/es-modules/Core/Chart/ChartDefaults.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */

/* *
 *
 *  API Options
 *
 * */
/**
 * General options for the chart.
 *
 * @optionparent chart
 */
const ChartDefaults = {
    /**
     * Default `mapData` for all series, in terms of a GeoJSON or TopoJSON
     * object. If set to a string, it functions as an index into the
     * `Highcharts.maps` array.
     *
     * For picking out individual shapes and geometries to use for each series
     * of the map, see [series.mapData](#series.map.mapData).
     *
     * @sample    maps/demo/geojson
     *            Loading GeoJSON data
     * @sample    maps/chart/topojson
     *            Loading TopoJSON data
     *
     * @type      {string|Array<*>|Highcharts.GeoJSON|Highcharts.TopoJSON}
     * @since     5.0.0
     * @product   highmaps
     * @apioption chart.map
     */
    /**
     * Set lat/lon transformation definitions for the chart. If not defined,
     * these are extracted from the map data.
     *
     * @type      {*}
     * @since     5.0.0
     * @product   highmaps
     * @apioption chart.mapTransforms
     */
    /**
     * When using multiple axes, the ticks of two or more opposite axes
     * will automatically be aligned by adding ticks to the axis or axes
     * with the least ticks, as if `tickAmount` were specified.
     *
     * This can be prevented by setting `alignTicks` to false. If the grid
     * lines look messy, it's a good idea to hide them for the secondary
     * axis by setting `gridLineWidth` to 0.
     *
     * If `startOnTick` or `endOnTick` in the axis options are set to false,
     * then the `alignTicks ` will be disabled for the axis.
     *
     * Disabled for logarithmic axes.
     *
     * @sample {highcharts} highcharts/chart/alignticks-true/
     *         True by default
     * @sample {highcharts} highcharts/chart/alignticks-false/
     *         False
     * @sample {highstock} stock/chart/alignticks-true/
     *         True by default
     * @sample {highstock} stock/chart/alignticks-false/
     *         False
     *
     * @type      {boolean}
     * @default   true
     * @product   highcharts highstock gantt
     * @apioption chart.alignTicks
     */
    /**
     * When using multiple axes, align the thresholds. When this is true, other
     * ticks will also be aligned.
     *
     * Note that for line series and some other series types, the `threshold`
     * option is set to `null` by default. This will in turn cause their y-axis
     * to not have a threshold. In order to avoid that, set the series
     * `threshold` to 0 or another number.
     *
     * If `startOnTick` or `endOnTick` in the axis options are set to false, or
     * if the axis is logarithmic, the threshold will not be aligned.
     *
     * @sample {highcharts} highcharts/chart/alignthresholds/ Set to true
     *
     * @since 10.0.0
     * @product   highcharts highstock gantt
     * @apioption chart.alignThresholds
     */
    alignThresholds: false,
    /**
     * Set the overall animation for all chart updating. Animation can be
     * disabled throughout the chart by setting it to false here. It can
     * be overridden for each individual API method as a function parameter.
     * The only animation not affected by this option is the initial series
     * animation, see [plotOptions.series.animation](
     * #plotOptions.series.animation).
     *
     * The animation can either be set as a boolean or a configuration
     * object. If `true`, it will use the 'swing' jQuery easing and a
     * duration of 500 ms. If used as a configuration object, the following
     * properties are supported:
     *
     * - `defer`: The animation delay time in milliseconds.
     *
     * - `duration`: The duration of the animation in milliseconds.
     *
     * - `easing`: A string reference to an easing function set on the
     *   `Math` object. See
     *   [the easing demo](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-animation-easing/).
     *
     * When zooming on a series with less than 100 points, the chart redraw
     * will be done with animation, but in case of more data points, it is
     * necessary to set this option to ensure animation on zoom.
     *
     * @sample {highcharts} highcharts/chart/animation-none/
     *         Updating with no animation
     * @sample {highcharts} highcharts/chart/animation-duration/
     *         With a longer duration
     * @sample {highcharts} highcharts/chart/animation-easing/
     *         With a jQuery UI easing
     * @sample {highmaps} maps/chart/animation-none/
     *         Updating with no animation
     * @sample {highmaps} maps/chart/animation-duration/
     *         With a longer duration
     *
     * @type      {boolean|Partial<Highcharts.AnimationOptionsObject>}
     * @default   true
     * @apioption chart.animation
     */
    /**
     * A CSS class name to apply to the charts container `div`, allowing
     * unique CSS styling for each chart.
     *
     * @type      {string}
     * @apioption chart.className
     */
    /**
     * Event listeners for the chart.
     *
     * @apioption chart.events
     */
    /**
     * Fires when a series is added to the chart after load time, using the
     * `addSeries` method. One parameter, `event`, is passed to the
     * function, containing common event information. Through
     * `event.options` you can access the series options that were passed to
     * the `addSeries` method. Returning false prevents the series from
     * being added.
     *
     * @sample {highcharts} highcharts/chart/events-addseries/
     *         Alert on add series
     * @sample {highstock} stock/chart/events-addseries/
     *         Alert on add series
     *
     * @type      {Highcharts.ChartAddSeriesCallbackFunction}
     * @since     1.2.0
     * @context   Highcharts.Chart
     * @apioption chart.events.addSeries
     */
    /**
     * Fires when clicking on the plot background. One parameter, `event`,
     * is passed to the function, containing common event information.
     *
     * Information on the clicked spot can be found through `event.xAxis`
     * and `event.yAxis`, which are arrays containing the axes of each
     * dimension and each axis' value at the clicked spot. The primary axes
     * are `event.xAxis[0]` and `event.yAxis[0]`. Remember the unit of a
     * datetime axis is milliseconds since 1970-01-01 00:00:00.
     *
     * ```js
     * click: function(e) {
     *     console.log(
     *         Highcharts.dateFormat('%Y-%m-%d %H:%M:%S', e.xAxis[0].value),
     *         e.yAxis[0].value
     *     )
     * }
     * ```
     *
     * @sample {highcharts} highcharts/chart/events-click/
     *         Alert coordinates on click
     * @sample {highcharts} highcharts/chart/events-container/
     *         Alternatively, attach event to container
     * @sample {highstock} stock/chart/events-click/
     *         Alert coordinates on click
     * @sample {highstock} highcharts/chart/events-container/
     *         Alternatively, attach event to container
     * @sample {highmaps} maps/chart/events-click/
     *         Record coordinates on click
     * @sample {highmaps} highcharts/chart/events-container/
     *         Alternatively, attach event to container
     *
     * @type      {Highcharts.ChartClickCallbackFunction}
     * @since     1.2.0
     * @context   Highcharts.Chart
     * @apioption chart.events.click
     */
    /**
     * Fires when the chart is finished loading. Since v4.2.2, it also waits
     * for images to be loaded, for example from point markers. One
     * parameter, `event`, is passed to the function, containing common
     * event information.
     *
     * There is also a second parameter to the chart constructor where a
     * callback function can be passed to be executed on chart.load.
     *
     * @sample {highcharts} highcharts/chart/events-load/
     *         Alert on chart load
     * @sample {highcharts} highcharts/chart/events-render/
     *         Load vs Redraw vs Render
     * @sample {highstock} stock/chart/events-load/
     *         Alert on chart load
     * @sample {highmaps} maps/chart/events-load/
     *         Add series on chart load
     *
     * @type      {Highcharts.ChartLoadCallbackFunction}
     * @context   Highcharts.Chart
     * @apioption chart.events.load
     */
    /**
     * Fires when the chart is redrawn, either after a call to
     * `chart.redraw()` or after an axis, series or point is modified with
     * the `redraw` option set to `true`. One parameter, `event`, is passed
     * to the function, containing common event information.
     *
     * @sample {highcharts} highcharts/chart/events-redraw/
     *         Alert on chart redraw
     * @sample {highcharts} highcharts/chart/events-render/
     *         Load vs Redraw vs Render
     * @sample {highstock} stock/chart/events-redraw/
     *         Alert on chart redraw when adding a series or moving the
     *         zoomed range
     * @sample {highmaps} maps/chart/events-redraw/
     *         Set subtitle on chart redraw
     *
     * @type      {Highcharts.ChartRedrawCallbackFunction}
     * @since     1.2.0
     * @context   Highcharts.Chart
     * @apioption chart.events.redraw
     */
    /**
     * Fires after initial load of the chart (directly after the `load`
     * event), and after each redraw (directly after the `redraw` event).
     *
     * @sample {highcharts} highcharts/chart/events-render/
     *         Load vs Redraw vs Render
     *
     * @type      {Highcharts.ChartRenderCallbackFunction}
     * @since     5.0.7
     * @context   Highcharts.Chart
     * @apioption chart.events.render
     */
    /**
     * Fires when an area of the chart has been selected. Selection is
     * enabled by setting the chart's zoomType. One parameter, `event`, is
     * passed to the function, containing common event information. The
     * default action for the selection event is to zoom the chart to the
     * selected area. It can be prevented by calling
     * `event.preventDefault()` or return false.
     *
     * Information on the selected area can be found through `event.xAxis`
     * and `event.yAxis`, which are arrays containing the axes of each
     * dimension and each axis' min and max values. The primary axes are
     * `event.xAxis[0]` and `event.yAxis[0]`. Remember the unit of a
     * datetime axis is milliseconds since 1970-01-01 00:00:00.
     *
     * ```js
     * selection: function(event) {
     *     // log the min and max of the primary, datetime x-axis
     *     console.log(
     *         Highcharts.dateFormat(
     *             '%Y-%m-%d %H:%M:%S',
     *             event.xAxis[0].min
     *         ),
     *         Highcharts.dateFormat(
     *             '%Y-%m-%d %H:%M:%S',
     *             event.xAxis[0].max
     *         )
     *     );
     *     // log the min and max of the y axis
     *     console.log(event.yAxis[0].min, event.yAxis[0].max);
     * }
     * ```
     *
     * @sample {highcharts} highcharts/chart/events-selection/
     *         Report on selection and reset
     * @sample {highcharts} highcharts/chart/events-selection-points/
     *         Select a range of points through a drag selection
     * @sample {highstock} stock/chart/events-selection/
     *         Report on selection and reset
     * @sample {highstock} highcharts/chart/events-selection-points/
     *         Select a range of points through a drag selection
     *         (Highcharts)
     *
     * @type      {Highcharts.ChartSelectionCallbackFunction}
     * @apioption chart.events.selection
     */
    /**
     * The margin between the outer edge of the chart and the plot area.
     * The numbers in the array designate top, right, bottom and left
     * respectively. Use the options `marginTop`, `marginRight`,
     * `marginBottom` and `marginLeft` for shorthand setting of one option.
     *
     * By default there is no margin. The actual space is dynamically
     * calculated from the offset of axis labels, axis title, title,
     * subtitle and legend in addition to the `spacingTop`, `spacingRight`,
     * `spacingBottom` and `spacingLeft` options.
     *
     * @sample {highcharts} highcharts/chart/margins-zero/
     *         Zero margins
     * @sample {highstock} stock/chart/margin-zero/
     *         Zero margins
     *
     * @type      {number|Array<number>}
     * @apioption chart.margin
     */
    /**
     * The margin between the bottom outer edge of the chart and the plot
     * area. Use this to set a fixed pixel value for the margin as opposed
     * to the default dynamic margin. See also `spacingBottom`.
     *
     * @sample {highcharts} highcharts/chart/marginbottom/
     *         100px bottom margin
     * @sample {highstock} stock/chart/marginbottom/
     *         100px bottom margin
     * @sample {highmaps} maps/chart/margin/
     *         100px margins
     *
     * @type      {number}
     * @since     2.0
     * @apioption chart.marginBottom
     */
    /**
     * The margin between the left outer edge of the chart and the plot
     * area. Use this to set a fixed pixel value for the margin as opposed
     * to the default dynamic margin. See also `spacingLeft`.
     *
     * @sample {highcharts} highcharts/chart/marginleft/
     *         150px left margin
     * @sample {highstock} stock/chart/marginleft/
     *         150px left margin
     * @sample {highmaps} maps/chart/margin/
     *         100px margins
     *
     * @type      {number}
     * @since     2.0
     * @apioption chart.marginLeft
     */
    /**
     * The margin between the right outer edge of the chart and the plot
     * area. Use this to set a fixed pixel value for the margin as opposed
     * to the default dynamic margin. See also `spacingRight`.
     *
     * @sample {highcharts} highcharts/chart/marginright/
     *         100px right margin
     * @sample {highstock} stock/chart/marginright/
     *         100px right margin
     * @sample {highmaps} maps/chart/margin/
     *         100px margins
     *
     * @type      {number}
     * @since     2.0
     * @apioption chart.marginRight
     */
    /**
     * The margin between the top outer edge of the chart and the plot area.
     * Use this to set a fixed pixel value for the margin as opposed to
     * the default dynamic margin. See also `spacingTop`.
     *
     * @sample {highcharts} highcharts/chart/margintop/ 100px top margin
     * @sample {highstock} stock/chart/margintop/
     *         100px top margin
     * @sample {highmaps} maps/chart/margin/
     *         100px margins
     *
     * @type      {number}
     * @since     2.0
     * @apioption chart.marginTop
     */
    /**
     * Callback function to override the default function that formats all
     * the numbers in the chart. Returns a string with the formatted number.
     * Since v12.6.0, the callback also receives `ctx` as the last argument,
     * so that arrow functions can access the same context as regular
     * functions using `this`.
     *
     * @sample highcharts/members/highcharts-numberformat
     *      Arabic digits in Highcharts
     * @type {Highcharts.NumberFormatterCallbackFunction}
     * @since 8.0.0
     * @apioption chart.numberFormatter
     */
    /**
     * When a chart with an x and a y-axis is rendered, we first pre-render the
     * labels of both in order to measure them. Then, if either of the axis
     * labels take up so much space that it significantly affects the length of
     * the other axis, we repeat the process.
     *
     * By default we stop at two axis layout runs, but it may be that the second
     * run also alter the space required by either axis, for example if it
     * causes the labels to rotate. In this situation, a subsequent redraw of
     * the chart may cause the tick and label placement to change for apparently
     * no reason.
     *
     * Use the `axisLayoutRuns` option to set the maximum allowed number of
     * repetitions. But keep in mind that the default value of 2 is set because
     * every run costs performance time.
     *
     * **Note:** Changing that option to higher than the default might decrease
     * performance significantly, especially with bigger sets of data.
     *
     * @type      {number}
     * @default   2
     * @since     11.3.0
     * @apioption chart.axisLayoutRuns
     */
    /**
     * Allows setting a key to switch between zooming and panning. Can be
     * one of `alt`, `ctrl`, `meta` (the command key on Mac and Windows
     * key on Windows) or `shift`. The keys are mapped directly to the key
     * properties of the click event argument (`event.altKey`,
     * `event.ctrlKey`, `event.metaKey` and `event.shiftKey`).
     *
     * @type       {"alt" | "ctrl" | "meta" | "shift"}
     * @since      4.0.3
     * @product    highcharts gantt
     * @apioption  chart.panKey
     */
    /**
     * Allow panning in a chart. Best used with [panKey](#chart.panKey)
     * to combine zooming and panning.
     *
     * On touch devices, when the [tooltip.followTouchMove](
     * #tooltip.followTouchMove) option is `true` (default), panning
     * requires two fingers. To allow panning with one finger, set
     * `followTouchMove` to `false`.
     *
     * **Note:** If both zooming and panning are enabled without keys, zooming
     * will take precedence by default. To prioritize panning, either set
     * [chart.zooming.key](#chart.zooming.key) or panKey.
     *
     * @sample  {highcharts} highcharts/chart/pankey/ Zooming and panning
     * @sample  {highstock} stock/chart/panning/ Zooming and xy panning
     */
    panning: {
        /**
         * Enable or disable chart panning.
         *
         * @type    {boolean}
         * @default {highcharts} false
         * @default {highstock|highmaps} true
         */
        enabled: false,
        /**
         * Decides in what dimensions the user can pan the chart. Can be
         * one of `x`, `y`, or `xy`.
         *
         * During panning, all axes will behave as if
         * [`startOnTick`](#yAxis.startOnTick) and
         * [`endOnTick`](#yAxis.endOnTick) were set to `false`. After the
         * panning action is finished, the axes will adjust to their actual
         * settings.
         *
         * **Note:** For non-cartesian series, the only supported panning type
         * is `xy`, as zooming in a single direction is not applicable due to
         * the radial nature of the coordinate system.
         *
         * @sample {highcharts} highcharts/chart/panning-type
         *         Zooming and xy panning
         *
         * @declare    Highcharts.OptionsChartPanningTypeValue
         * @type       {string}
         * @validvalue ["x", "y", "xy"]
         * @product    highcharts highstock gantt
         */
        type: 'x'
    },
    /**
     * Deprecated. Use
     * [chart.zooming.pinchType](#chart.zooming.pinchType)
     * instead.
     *
     * Equivalent to [zoomType](#chart.zoomType), but for multitouch
     * gestures only. By default, the `pinchType` is the same as the
     * `zoomType` setting. However, pinching can be enabled separately in
     * some cases, for example in stock charts where a mouse drag pans the
     * chart, while pinching is enabled. When [tooltip.followTouchMove](
     * #tooltip.followTouchMove) is true, pinchType only applies to
     * two-finger touches.
     *
     * @type       {string}
     * @default    {highcharts} undefined
     * @default    {highstock} undefined
     * @since      3.0
     * @product    highcharts highstock gantt
     * @deprecated 10.2.1
     * @validvalue ["x", "y", "xy"]
     * @apioption  chart.pinchType
     */
    /**
     * Whether to apply styled mode. When in styled mode, no presentational
     * attributes or CSS are applied to the chart SVG. Instead, CSS rules
     * are required to style the chart. The default style sheet is
     * available from `https://code.highcharts.com/css/highcharts.css`.
     *
     * [Read more in the docs](https://www.highcharts.com/docs/chart-design-and-style/style-by-css)
     * on what classes and variables are available.
     *
     * @sample highcharts/css/colors
     *         Color theming with CSS
     * @sample highcharts/css/prefers-color-scheme
     *         Dynamic theme based on system settings
     *
     * @type       {boolean}
     * @default    false
     * @since      7.0
     * @apioption  chart.styledMode
     */
    styledMode: false,
    /**
     * The corner radius of the outer chart border.
     *
     * @sample {highcharts} highcharts/chart/borderradius/
     *         20px radius
     * @sample {highstock} stock/chart/border/
     *         10px radius
     * @sample {highmaps} maps/chart/border/
     *         Border options
     *
     */
    borderRadius: 0,
    /**
     * In styled mode, this sets how many colors the class names
     * should rotate between. With ten colors, series (or points) are
     * given class names like `highcharts-color-0`, `highcharts-color-1`
     * [...] `highcharts-color-9`. The equivalent in non-styled mode
     * is to set colors using the [colors](#colors) setting.
     *
     * @since 5.0.0
     */
    colorCount: 10,
    /**
     * By default, (because of memory and performance reasons) the chart does
     * not copy the data but keeps it as a reference. In some cases, this might
     * result in mutating the original data source. In order to prevent that,
     * set that property to false. Please note that changing that might decrease
     * performance, especially with bigger sets of data.
     *
     * @type       {boolean}
     * @since 10.1.0
     */
    allowMutatingData: true,
    /**
     * If true, the axes will scale to the remaining visible series once
     * one series is hidden. If false, hiding and showing a series will
     * not affect the axes or the other series. For stacks, once one series
     * within the stack is hidden, the rest of the stack will close in
     * around it even if the axis is not affected.
     *
     * @sample {highcharts} highcharts/chart/ignorehiddenseries-true/
     *         True by default
     * @sample {highcharts} highcharts/chart/ignorehiddenseries-false/
     *         False
     * @sample {highcharts} highcharts/chart/ignorehiddenseries-true-stacked/
     *         True with stack
     * @sample {highstock} stock/chart/ignorehiddenseries-true/
     *         True by default
     * @sample {highstock} stock/chart/ignorehiddenseries-false/
     *         False
     *
     * @since   1.2.0
     * @product highcharts highstock gantt
     */
    ignoreHiddenSeries: true,
    /**
     * Whether to invert the axes so that the x axis is vertical and y axis
     * is horizontal. When `true`, the x axis is [reversed](#xAxis.reversed)
     * by default.
     *
     * @productdesc {highcharts}
     * If a bar series is present in the chart, it will be inverted
     * automatically. Inverting the chart doesn't have an effect if there
     * are no cartesian series in the chart.
     *
     * @sample {highcharts} highcharts/chart/inverted/
     *         Inverted line
     * @sample {highstock} stock/navigator/inverted/
     *         Inverted stock chart
     *
     * @type      {boolean}
     * @default   false
     * @product   highcharts highstock gantt
     * @apioption chart.inverted
     */
    /**
     * The distance between the outer edge of the chart and the content,
     * like title or legend, or axis title and labels if present. The
     * numbers in the array designate top, right, bottom and left
     * respectively. Use the options spacingTop, spacingRight, spacingBottom
     * and spacingLeft options for shorthand setting of one option.
     *
     * @see     [chart.margin](#chart.margin)
     * @type    {Array<number>}
     * @default [10, 10, 15, 10]
     * @since   3.0.6
     */
    spacing: [10, 10, 15, 10],
    /**
     * Deprecated. Use
     * [chart.zooming.resetButton](#chart.zooming.resetButton)
     * instead.
     *
     * The button that appears after a selection zoom, allowing the user
     * to reset zoom. This option is deprecated in favor of
     * [zooming](#chart.zooming).
     *
     * @since      2.2
     * @deprecated 10.2.1
     */
    resetZoomButton: {
        /**
         * What frame the button placement should be related to. Can be
         * either `plotBox` or `spacingBox`.
         *
         * @sample {highcharts} highcharts/chart/resetzoombutton-relativeto/
         *         Relative to the chart
         * @sample {highstock} highcharts/chart/resetzoombutton-relativeto/
         *         Relative to the chart
         *
         * @type      {Highcharts.ButtonRelativeToValue}
         * @apioption chart.resetZoomButton.relativeTo
         */
        /**
         * A collection of attributes for the button. The object takes SVG
         * attributes like `fill`, `stroke`, `stroke-width` or `r`, the
         * border radius. The theme also supports `style`, a collection of
         * CSS properties for the text. Equivalent attributes for the hover
         * state are given in `theme.states.hover`.
         *
         * @sample {highcharts} highcharts/chart/resetzoombutton-theme/
         *         Theming the button
         * @sample {highstock} highcharts/chart/resetzoombutton-theme/
         *         Theming the button
         *
         * @type {Highcharts.SVGAttributes}
         */
        theme: {
        /**
         * The z-index of the button.
         *
         * @type {number}
         * @apioption chart.resetZoomButton.theme.zIndex
         */
        },
        /**
         * The position of the button.
         *
         * @sample {highcharts} highcharts/chart/resetzoombutton-position/
         *         Above the plot area
         * @sample {highstock} highcharts/chart/resetzoombutton-position/
         *         Above the plot area
         * @sample {highmaps} highcharts/chart/resetzoombutton-position/
         *         Above the plot area
         *
         * @type {Highcharts.AlignObject}
         */
        position: {
        /**
         * The horizontal alignment of the button.
         *
         * @type {number}
         * @apioption chart.resetZoomButton.position.align
         */
        /**
         * The horizontal offset of the button.
         *
         * @type {number}
         * @apioption chart.resetZoomButton.position.x
         */
        /**
         * The vertical alignment of the button.
         *
         * @type      {Highcharts.VerticalAlignValue}
         * @apioption chart.resetZoomButton.position.verticalAlign
         */
        /**
         * The vertical offset of the button.
         *
         * @type {number}
         * @apioption chart.resetZoomButton.position.y
         */
        }
    },
    /**
     * The corner radius of the plot area border in pixels. Also applies clip
     * to the plot area background and data inside, like columns in a column
     * series or fill in an area series.
     *
     * @sample highcharts/chart/plotborderradius/
     *         Plot border radius
     * @sample {highmaps} maps/chart/plotborder/
     *         Map with plot border options
     *
     * @type      {number}
     * @default   0
     * @since     13.0.0
     * @apioption chart.plotBorderRadius
     */
    /**
     * The pixel width of the plot area border.
     *
     * @sample {highcharts} highcharts/chart/plotborderwidth/
     *         1px border
     * @sample {highstock} stock/chart/plotborder/
     *         2px border
     * @sample {highmaps} maps/chart/plotborder/
     *         Plot border options
     *
     * @type      {number}
     * @default   0
     * @apioption chart.plotBorderWidth
     */
    /**
     * Whether to apply a drop shadow to the plot area. Requires that
     * plotBackgroundColor be set. The shadow can be an object configuration
     * containing `color`, `offsetX`, `offsetY`, `opacity` and `width`.
     *
     * @sample {highcharts} highcharts/chart/plotshadow/
     *         Plot shadow
     * @sample {highstock} stock/chart/plotshadow/
     *         Plot shadow
     * @sample {highmaps} maps/chart/plotborder/
     *         Plot border options
     *
     * @type      {boolean|Highcharts.ShadowOptionsObject}
     * @default   false
     * @apioption chart.plotShadow
     */
    /**
     * When true, cartesian charts like line, spline, area and column are
     * transformed into the polar coordinate system. This produces _polar
     * charts_, also known as _radar charts_.
     *
     * @sample {highcharts} highcharts/demo/polar/
     *         Polar chart
     * @sample {highcharts} highcharts/demo/polar-wind-rose/
     *         Wind rose, stacked polar column chart
     * @sample {highcharts} highcharts/demo/polar-spider/
     *         Spider web chart
     * @sample {highcharts} highcharts/parallel-coordinates/polar/
     *         Star plot, multivariate data in a polar chart
     *
     * @type      {boolean}
     * @default   false
     * @since     2.3.0
     * @product   highcharts
     * @requires  highcharts-more
     * @apioption chart.polar
     */
    /**
     * Whether to reflow the chart to fit the width of the container div
     * on resizing the window.
     *
     * @sample {highcharts} highcharts/chart/reflow-true/
     *         True by default
     * @sample {highcharts} highcharts/chart/reflow-false/
     *         False
     * @sample {highstock} stock/chart/reflow-true/
     *         True by default
     * @sample {highstock} stock/chart/reflow-false/
     *         False
     * @sample {highmaps} maps/chart/reflow-true/
     *         True by default
     * @sample {highmaps} maps/chart/reflow-false/
     *         False
     *
     * @since 2.1
     */
    reflow: true,
    /**
     * The HTML element where the chart will be rendered. If it is a string,
     * the element by that id is used. The HTML element can also be passed
     * by direct reference, or as the first argument of the chart
     * constructor, in which case the option is not needed.
     *
     * @sample {highcharts} highcharts/chart/reflow-true/
     *         String
     * @sample {highcharts} highcharts/chart/renderto-object/
     *         Object reference
     * @sample {highstock} stock/chart/renderto-string/
     *         String
     * @sample {highstock} stock/chart/renderto-object/
     *         Object reference
     *
     * @type      {string|Highcharts.HTMLDOMElement}
     * @apioption chart.renderTo
     */
    /**
     * The background color of the marker square when selecting (zooming
     * in on) an area of the chart.
     *
     * @see In styled mode, the selection marker fill is set with the
     *      `.highcharts-selection-marker` class.
     *
     * @type      {Highcharts.ColorType}
     * @since     2.1.7
     */
    selectionMarkerFill: 'color-mix(var(--highcharts-highlight-color-80) 25%, transparent)', // eslint-disable-line max-len
    /**
     * Whether to apply a drop shadow to the global series group. This causes
     * all the series to have the same shadow. Contrary to the `series.shadow`
     * option, this prevents items from casting shadows on each other, like for
     * others series in a stack. The shadow can be an object configuration
     * containing `color`, `offsetX`, `offsetY`, `opacity` and `width`.
     *
     * @sample highcharts/chart/seriesgroupshadow/
     *         Shadow
     *
     * @type      {boolean|Highcharts.ShadowOptionsObject}
     * @default   false
     * @apioption chart.seriesGroupShadow
     */
    /**
     * Whether to apply a drop shadow to the outer chart area. Requires
     * that backgroundColor be set. The shadow can be an object
     * configuration containing `color`, `offsetX`, `offsetY`, `opacity` and
     * `width`.
     *
     * @sample {highcharts} highcharts/chart/shadow/
     *         Shadow
     * @sample {highstock} stock/chart/shadow/
     *         Shadow
     * @sample {highmaps} maps/chart/border/
     *         Chart border and shadow
     *
     * @type      {boolean|Highcharts.ShadowOptionsObject}
     * @default   false
     * @apioption chart.shadow
     */
    /**
     * Whether to show the axes initially. This only applies to empty charts
     * where series are added dynamically, as axes are automatically added
     * to cartesian series.
     *
     * @sample {highcharts} highcharts/chart/showaxes-false/
     *         False by default
     * @sample {highcharts} highcharts/chart/showaxes-true/
     *         True
     *
     * @type      {boolean}
     * @since     1.2.5
     * @product   highcharts gantt
     * @apioption chart.showAxes
     */
    /**
     * The space between the bottom edge of the chart and the content (plot
     * area, axis title and labels, title, subtitle or legend in top
     * position).
     *
     * @sample {highcharts} highcharts/chart/spacingbottom/
     *         Spacing bottom set to 100
     * @sample {highstock} stock/chart/spacingbottom/
     *         Spacing bottom set to 100
     * @sample {highmaps} maps/chart/spacing/
     *         Spacing 100 all around
     *
     * @type      {number}
     * @default   15
     * @since     2.1
     * @apioption chart.spacingBottom
     */
    /**
     * The space between the left edge of the chart and the content (plot
     * area, axis title and labels, title, subtitle or legend in top
     * position).
     *
     * @sample {highcharts} highcharts/chart/spacingleft/
     *         Spacing left set to 100
     * @sample {highstock} stock/chart/spacingleft/
     *         Spacing left set to 100
     * @sample {highmaps} maps/chart/spacing/
     *         Spacing 100 all around
     *
     * @type      {number}
     * @default   10
     * @since     2.1
     * @apioption chart.spacingLeft
     */
    /**
     * The space between the right edge of the chart and the content (plot
     * area, axis title and labels, title, subtitle or legend in top
     * position).
     *
     * @sample {highcharts} highcharts/chart/spacingright-100/
     *         Spacing set to 100
     * @sample {highcharts} highcharts/chart/spacingright-legend/
     *         Legend in right position with default spacing
     * @sample {highstock} stock/chart/spacingright/
     *         Spacing set to 100
     * @sample {highmaps} maps/chart/spacing/
     *         Spacing 100 all around
     *
     * @type      {number}
     * @default   10
     * @since     2.1
     * @apioption chart.spacingRight
     */
    /**
     * The space between the top edge of the chart and the content (plot
     * area, axis title and labels, title, subtitle or legend in top
     * position).
     *
     * @sample {highcharts} highcharts/chart/spacingtop-100/
     *         A top spacing of 100
     * @sample {highcharts} highcharts/chart/spacingtop-10/
     *         Floating chart title makes the plot area align to the default
     *         spacingTop of 10.
     * @sample {highstock} stock/chart/spacingtop/
     *         A top spacing of 100
     * @sample {highmaps} maps/chart/spacing/
     *         Spacing 100 all around
     *
     * @type      {number}
     * @default   10
     * @since     2.1
     * @apioption chart.spacingTop
     */
    /**
     * Additional CSS styles to apply inline to the container `div` and the root
     * SVG.
     *
     * According to the CSS syntax documentation, it is recommended to quote
     * font family names that contain white space, digits, or punctuation
     * characters other than hyphens. In such cases, wrap the fontFamily
     * name as follows: `fontFamily: '"Font name"'`.
     *
     * Since v11, the root font size is 1rem by default, and all child element
     * are given a relative `em` font size by default. This allows implementers
     * to control all the chart's font sizes by only setting the root level.
     *
     * @see    In styled mode, general chart styles can be set with the
     *         `.highcharts-root` class.
     * @sample {highcharts} highcharts/chart/style-serif-font/
     *         Using a serif type font
     * @sample {highcharts} highcharts/chart/style-special-font/
     *         Using a font with special character in name
     * @sample {highcharts} highcharts/members/relative-font-size/
     *         Relative font sizes
     * @sample {highcharts} highcharts/css/em/
     *         Styled mode with relative font sizes
     * @sample {highstock} stock/chart/style/
     *         Using a serif type font
     * @sample {highmaps} maps/chart/style-serif-font/
     *         Using a serif type font
     *
     * @type      {Highcharts.CSSObject}
     * @default   {"fontFamily": "-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', sans-serif", "fontSize":"1rem"}
     * @apioption chart.style
     */
    /**
     * The default series type for the chart. Can be any of the chart types
     * listed under [plotOptions](#plotOptions) and [series](#series) or can
     * be a series provided by an additional module.
     *
     * In TypeScript this option has no effect in sense of typing and
     * instead the `type` option must always be set in the series.
     *
     * @sample {highcharts} highcharts/chart/type-bar/
     *         Bar
     * @sample {highstock} stock/chart/type/
     *         Areaspline
     * @sample {highmaps} maps/chart/type-mapline/
     *         Mapline
     *
     * @type       {string}
     * @default    {highcharts} line
     * @default    {highstock} line
     * @default    {highmaps} map
     * @since      2.1.0
     * @apioption  chart.type
     */
    type: 'line',
    /**
     * Deprecated. Use
     * [chart.zooming.type](#chart.zooming.type)
     * instead.
     *
     * Decides in what dimensions the user can zoom by dragging the mouse.
     * Can be one of `x`, `y` or `xy`.
     *
     * @see [panKey](#chart.panKey)
     *
     * @sample {highcharts} highcharts/chart/zoomtype-none/
     *         None by default
     * @sample {highcharts} highcharts/chart/zoomtype-x/
     *         X
     * @sample {highcharts} highcharts/chart/zoomtype-y/
     *         Y
     * @sample {highcharts} highcharts/chart/zoomtype-xy/
     *         Xy
     * @sample {highcharts} highcharts/chart/zoomtype-polar/
     *         Zoom on polar chart
     * @sample {highstock} stock/demo/basic-line/
     *         None by default
     * @sample {highstock} stock/chart/zoomtype-x/
     *         X
     * @sample {highstock} stock/chart/zoomtype-y/
     *         Y
     * @sample {highstock} stock/chart/zoomtype-xy/
     *         Xy
     * @sample {highmaps} maps/chart/zoomtype-xy/
     *         Map with selection zoom
     *
     * @type       {string}
     * @validvalue ["x", "y", "xy"]
     * @deprecated 10.2.1
     * @apioption  chart.zoomType
     */
    /**
     * Deprecated. Use
     * [chart.zooming.singleTouch](#chart.zooming.singleTouch)
     * instead.
     *
     * Enables zooming by a single touch, in combination with
     * [chart.zoomType](#chart.zoomType). When enabled, two-finger pinch
     * will still work as set up by [chart.pinchType](#chart.pinchType).
     * However, `zoomBySingleTouch` will interfere with touch-dragging the
     * chart to read the tooltip. And especially when vertical zooming is
     * enabled, it will make it hard to scroll vertically on the page.
     *
     * @sample     highcharts/chart/zoombysingletouch
     *             Zoom by single touch enabled, with buttons to toggle
     *
     * @product    highcharts highstock gantt
     * @since      9.0.0
     * @type       {boolean}
     * @default    false
     * @deprecated 10.2.1
     * @apioption  chart.zoomBySingleTouch
     */
    /**
     * Chart zooming options.
     *
     * @sample     highcharts/plotoptions/sankey-node-color
     *             Zooming in sankey series
     * @sample     highcharts/series-treegraph/link-types
     *             Zooming in treegraph series
     *
     * @since 10.2.1
     */
    zooming: {
        /**
         * Equivalent to [type](#chart.zooming.type), but for multitouch
         * gestures only. By default, the `pinchType` is the same as the
         * `type` setting. However, pinching can be enabled separately in
         * some cases, for example in stock charts where a mouse drag pans the
         * chart, while pinching is enabled. When [tooltip.followTouchMove](
         * #tooltip.followTouchMove) is true, pinchType only applies to
         * two-finger touches.
         *
         * @type       {string}
         * @default    {highcharts} undefined
         * @default    {highstock} x
         * @product    highcharts highstock gantt
         * @validvalue ["x", "y", "xy"]
         * @apioption  chart.zooming.pinchType
         */
        /**
         * Decides in what dimensions the user can zoom by dragging the mouse.
         * Can be one of `x`, `y` or `xy`.
         *
         * **Note:** For non-cartesian series, the only supported zooming type
         * is `xy`, as zooming in a single direction is not applicable due to
         * the radial nature of the coordinate system.
         *
         * @declare    Highcharts.OptionsChartZoomingTypeValue
         * @type       {string}
         * @default    {highcharts} undefined
         * @product    highcharts highstock gantt
         * @validvalue ["x", "y", "xy"]
         * @apioption  chart.zooming.type
         */
        /**
         * Enables zooming by a single touch, in combination with
         * [chart.zooming.type](#chart.zooming.type). When enabled, two-finger
         * pinch will still work as set up by [chart.zooming.pinchType]
         * (#chart.zooming.pinchType). However, `singleTouch` will interfere
         * with touch-dragging the chart to read the tooltip. And especially
         * when vertical zooming is enabled, it will make it hard to scroll
         * vertically on the page.
         *
         * @sample  highcharts/chart/zoombysingletouch
         *          Zoom by single touch enabled, with buttons to toggle
         *
         * @product highcharts highstock gantt
         */
        singleTouch: false,
        /**
         * The button that appears after a selection zoom, allowing the user
         * to reset zoom.
         */
        resetButton: {
            /**
             * What frame the button placement should be related to. Can be
             * either `plotBox` or `spacingBox`.
             *
             * @sample {highcharts} highcharts/chart/resetzoombutton-relativeto/
             *         Relative to the chart
             * @sample {highstock} highcharts/chart/resetzoombutton-relativeto/
             *         Relative to the chart
             *
             * @type      {Highcharts.ButtonRelativeToValue}
             * @default   plotBox
             * @apioption chart.zooming.resetButton.relativeTo
             */
            /**
             * A collection of attributes for the button. The object takes SVG
             * attributes like `fill`, `stroke`, `stroke-width` or `r`, the
             * border radius. The theme also supports `style`, a collection of
             * CSS properties for the text. Equivalent attributes for the hover
             * state are given in `theme.states.hover`.
             *
             * @sample {highcharts} highcharts/chart/resetzoombutton-theme/
             *         Theming the button
             * @sample {highstock} highcharts/chart/resetzoombutton-theme/
             *         Theming the button
             *
             * @default { zIndex: 6 }
             * @type  {Highcharts.SVGAttributes}
             * @since 10.2.1
             */
            theme: {
                /** @internal */
                zIndex: 6
            },
            /**
             * The position of the button.
             *
             * Note: Adjusting position values might cause overlap with chart
             * elements. Ensure coordinates do not obstruct other components or
             * data visibility.
             *
             * @sample {highcharts} highcharts/chart/resetzoombutton-position/
             *         Above the plot area
             * @sample {highstock} highcharts/chart/resetzoombutton-position/
             *         Above the plot area
             * @sample {highmaps} highcharts/chart/resetzoombutton-position/
             *         Above the plot area
             *
             * @type  {Highcharts.AlignObject}
             * @since 10.2.1
             */
            position: {
                align: 'right',
                /**
                 * The vertical alignment of the button.
                 *
                 * @type      {Highcharts.VerticalAlignValue}
                 * @default   top
                 * @apioption chart.zooming.resetButton.position.verticalAlign
                 */
                x: -10,
                y: 10
            }
        }
    },
    /**
     * An explicit width for the chart. By default (when `null`) the width
     * is calculated from the offset width of the containing element.
     *
     * @sample {highcharts} highcharts/chart/width/
     *         800px wide
     * @sample {highstock} stock/chart/width/
     *         800px wide
     * @sample {highmaps} maps/chart/size/
     *         Chart with explicit size
     *
     * @type {null|number|string}
     */
    width: null,
    /**
     * An explicit height for the chart. If a _number_, the height is
     * given in pixels. If given a _percentage string_ (for example
     * `'56%'`), the height is given as the percentage of the actual chart
     * width. This allows for preserving the aspect ratio across responsive
     * sizes.
     *
     * By default (when `null`) the height is calculated from the offset
     * height of the containing element, or 400 pixels if the containing
     * element's height is 0.
     *
     * @sample {highcharts} highcharts/chart/height/
     *         Forced 200px height
     * @sample {highstock} stock/chart/height/
     *         300px height
     * @sample {highmaps} maps/chart/size/
     *         Chart with explicit size
     * @sample highcharts/chart/height-percent/
     *         Highcharts with percentage height
     * @sample highcharts/chart/height-inherited/
     *         Chart with inherited height
     *
     * @type {null|number|string}
     */
    height: null,
    /**
     * The color of the outer chart border.
     *
     * @see In styled mode, the stroke is set with the
     *      `.highcharts-background` class.
     *
     * @sample {highcharts} highcharts/chart/bordercolor/
     *         Brown border
     * @sample {highstock} stock/chart/border/
     *         Brown border
     * @sample {highmaps} maps/chart/border/
     *         Border options
     *
     * @type {Highcharts.ColorType}
     */
    borderColor: 'var(--highcharts-highlight-color-80)',
    /**
     * The pixel width of the outer chart border.
     *
     * @see In styled mode, the stroke is set with the
     *      `.highcharts-background` class.
     *
     * @sample {highcharts} highcharts/chart/borderwidth/
     *         5px border
     * @sample {highstock} stock/chart/border/
     *         2px border
     * @sample {highmaps} maps/chart/border/
     *         Border options
     *
     * @type      {number}
     * @default   0
     * @apioption chart.borderWidth
     */
    /**
     * The background color of the outer chart area.
     *
     * @see In styled mode, the background is set with the
     *      `.highcharts-background` class.
     *
     * @sample {highcharts} highcharts/chart/backgroundcolor-color/
     *         Color
     * @sample {highcharts} highcharts/chart/backgroundcolor-gradient/
     *         Gradient
     * @sample {highstock} stock/chart/backgroundcolor-color/
     *         Color
     * @sample {highstock} stock/chart/backgroundcolor-gradient/
     *         Gradient
     * @sample {highmaps} maps/chart/backgroundcolor-color/
     *         Color
     * @sample {highmaps} maps/chart/backgroundcolor-gradient/
     *         Gradient
     *
     * @type {Highcharts.ColorType}
     */
    backgroundColor: 'var(--highcharts-background-color)',
    /**
     * The background color or gradient for the plot area. If not set, the
     * plot area will have the background color set to `'none'`.
     *
     * @see In styled mode, the plot background is set with the
     *      `.highcharts-plot-background` class.
     *
     * @sample {highcharts} highcharts/chart/plotbackgroundcolor-color/
     *         Color
     * @sample {highcharts} highcharts/chart/plotbackgroundcolor-gradient/
     *         Gradient
     * @sample {highstock} stock/chart/plotbackgroundcolor-color/
     *         Color
     * @sample {highstock} stock/chart/plotbackgroundcolor-gradient/
     *         Gradient
     * @sample {highmaps} maps/chart/plotbackgroundcolor-color/
     *         Color
     * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/
     *         Gradient
     *
     * @type      {Highcharts.ColorType}
     * @apioption chart.plotBackgroundColor
     */
    /**
     * The URL for an image to use as the plot background. To set an image
     * as the background for the entire chart, set a CSS background image
     * to the container element. Note that for the image to be applied to
     * exported charts, its URL needs to be accessible by the export server.
     *
     * @see In styled mode, a plot background image can be set with the
     *      `.highcharts-plot-background` class and a [custom pattern](
     *      https://www.highcharts.com/docs/chart-design-and-style/gradients-shadows-and-patterns).
     *
     * @sample {highcharts} highcharts/chart/plotbackgroundimage/
     *         Skies
     * @sample {highstock} stock/chart/plotbackgroundimage/
     *         Skies
     *
     * @type      {string}
     * @apioption chart.plotBackgroundImage
     */
    /**
     * The color of the inner chart or plot area border.
     *
     * @see In styled mode, a plot border stroke can be set with the
     *      `.highcharts-plot-border` class.
     *
     * @sample {highcharts} highcharts/chart/plotbordercolor/
     *         Blue border
     * @sample {highstock} stock/chart/plotborder/
     *         Blue border
     * @sample {highmaps} maps/chart/plotborder/
     *         Plot border options
     *
     * @type {Highcharts.ColorType}
     */
    plotBorderColor: 'var(--highcharts-neutral-color-20)'
};
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Chart_ChartDefaults = (ChartDefaults);

;// ./code/es-modules/Core/Color/PaletteDefaults.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Honsi
 *
 *  A commercial license may be required depending on use.
 *  See www.highcharts.com/license
 *
 *
 * */

/**
 * The palette object specifies colors for the charts and how to apply them.
 *
 * Each color scheme (`light` and `dark`) has three primary colors:
 * `backgroundColor`, `neutralColor`, and `highlightColor`. For the neutral and
 * highlight colors, a series of interpolated color variants are generated, and
 * accessed as CSS variables in the chart settings in their prefixed and
 * hyphenated form. For example,
 *
 * * `var(--highcharts-neutral-color-80)` is a blend of the `neutralColor` and
 *   the `backgroundColor`, with a weight of 80% `neutralColor` and 20%
 *   `backgroundColor`. It is the default for the main title
 *   (`title.style.color`), axis labels and more.
 * * `var(--highcharts-highlight-color-80)` is a blend of the `highlightColor`
 *   and the `backgroundColor`, with a weight of 80% `highlightColor` and 20%
 *   `backgroundColor`. It is used for the zoom selection marker (with opacity),
 *   Stock Range Selector date input boxes and more.
 *
 * The `colors` array is used for data series and points, and is not blended
 * with the `backgroundColor`. It is also accessible as CSS variables, and this
 * is the recommended way to hard-code colors to match series and data.
 * For example, `var(--highcharts-color-0)` is the first color in the `colors`
 * array, `var(--highcharts-color-1)` is the second color, and so on.
 *
 * The palette only accepts plain color strings in the options. Gradients and
 * patterns are only supported in other color settings, such as `series.color`
 * or `point.color`, but they can use CSS variables from the palette.
 *
 * @sample       highcharts/palette/general
 *               General palette options
 * @sample       highcharts/palette/branding
 *               Branded chart
 * @sample       highcharts/demo/pie-gradient
 *               Using palette colors in gradients
 * @type         {*}
 * @since        13.0.0
 * @optionparent palette
 */
const palette = {
    /**
     * Colors for data series and points. This setting is generic for both
     * light and dark mode, and can be overridden for each mode in
     * `light.colors` and `dark.colors`.
     *
     * @sample highcharts/palette/data-colors-points
     *         Colors for points
     * @sample highcharts/palette/data-colors-series
     *         Colors for series
     * @type   {Array<Highcharts.ColorString>}
     * @default [
     *       '#2caffe',
     *       '#544fc5',
     *       '#00e272',
     *       '#fe6a35',
     *       '#6b8abc',
     *       '#d568fb',
     *       '#2ee0ca',
     *       '#fa4b42',
     *       '#feb56a',
     *       '#91e8e1'
     *      ]
     * @since 13.0.0
     * @apioption palette.colors
     */
    /**
     * Color scheme to use. When `light dark` is set, the chart will
     * automatically switch between light and dark mode based on the user's
     * system preferences, or whether the `.highcharts-light` or
     * `.highcharts-dark` class is applied to a parent of the container.
     *
     * When `inherit` is set, the chart will inherit the color scheme from the
     * closest parent element with a defined color scheme, or default to `light`
     * if none is found.
     *
     * @sample     highcharts/palette/colorscheme
     *             Dark color scheme
     * @sample     highcharts/palette/exporting-light
     *             Dark chart with light export
     *
     * @type       {'light dark'|'light'|'dark'|'inherit'}
     * @default    light dark
     * @since      13.0.0
     * @apioption  palette.colorScheme
     */
    /**
     * Whether to inject the CSS for the dark and light mode. If not injected,
     * you need to include the CSS manually in your page or application, with
     * the appropriate CSS variables. A file with the default CSS variables is
     * available at
     * [css/highcharts-palette.css](https://code.highcharts.com/css/highcharts-palette.css).
     *
     * @sample     highcharts/palette/injectcss-false
     *             Not injecting CSS variables, import file instead
     *
     * @type       {boolean}
     * @default    true
     * @since      13.0.0
     * @apioption  palette.injectCSS
     */
    /**
     * Palette options for dark mode.
     *
     * @extends palette.light
     */
    dark: {
        /**
         * Colors for data series and points. This array extends the colors
         * defined for the general `palette.colors` array, item by item. If an
         * item is `null` or `undefined`, the general color will be used.
         *
         * @type   {Array<Highcharts.ColorString|null|undefined>}
         * @apioption palette.dark.colors
         */
        /**
         * @type {Highcharts.ColorString}
         */
        backgroundColor: '#141414',
        /**
         * @type {Highcharts.ColorString}
         */
        neutralColor: '#ffffff',
        /**
         * @type {Highcharts.ColorString}
         */
        highlightColor: '#2caffe'
    },
    /**
     * Palette options for light mode.
     */
    light: {
        /**
         * Colors for data series and points. This array extends the colors
         * defined for the general `palette.colors` array, item by item. If an
         * item is `null` or `undefined`, the general color will be used.
         *
         * @type   {Array<Highcharts.ColorString|null|undefined>}
         * @apioption palette.light.colors
         */
        /**
         * Chart background, point stroke for markers and columns etc. The
         * background is also used as base for interpolated neutral and
         * highlight colors.
         * @type {Highcharts.ColorString}
         */
        backgroundColor: '#ffffff',
        /**
         * Neutral color to be used as base for interpolated colors. For
         * example, `var(--highcharts-neutral-color-60)` is a blend of the
         * `neutralColor` and the `backgroundColor`, with a weight of 60%
         * `neutralColor` and 40% `backgroundColor`.
         *
         * Neutral colors are by default used for basic text, lines, borders,
         * basically most elements except the data itself, and highlighted
         * elements.
         * @type {Highcharts.ColorString}
         */
        neutralColor: '#000000',
        /**
         * Highlight color to be used as base for interpolated colors. For
         * example, `var(--highcharts-highlight-color-60)` is a blend of the
         * `highlightColor` and the `backgroundColor`, with a weight of 60%
         * `highlightColor` and 40% `backgroundColor`.
         *
         * Highlight colors are by default used for highlighted or active
         * elements, like the zoom selection marker, clickable Stock Range
         * Selector date input boxes, clickable drilldown labels and more.
         * @type {Highcharts.ColorString}
         */
        highlightColor: '#0022ff',
        /**
         * Positive color used in stock charts, such as for technical
         * indicators, annotations or hollow candlestick series.
         * @type {Highcharts.ColorString}
         */
        positiveColor: '#06b535',
        /**
         * Negative color used in stock charts, such as for technical
         * indicators, annotations or hollow candlestick series.
         * @type {Highcharts.ColorString}
         */
        negativeColor: '#f21313'
    }
};
/* harmony default export */ const PaletteDefaults = (palette);

;// ./code/es-modules/Shared/Utilities.js
/* *
 *
 *  (c) 2009-2026 Highsoft AS
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */

const { doc, win } = Core_Globals;
/**
 * Add an event listener.
 *
 * @function Highcharts.addEvent<T>
 *
 * @param  {Highcharts.Class<T>|T} el
 *         The element or object to add a listener to. It can be a
 *         {@link HTMLDOMElement}, an {@link SVGElement} or any other object.
 *
 * @param  {string} type
 *         The event type.
 *
 * @param  {Highcharts.EventCallbackFunction<T>|Function} fn
 *         The function callback to execute when the event is fired.
 *
 * @param  {Highcharts.EventOptionsObject} [options]
 *         Options for adding the event.
 *
 * @sample highcharts/members/addevent
 *         Use a general `render` event to draw shapes on a chart
 *
 * @return {Function}
 *         A callback function to remove the added event.
 */
function addEvent(el, type, fn, options = {}) {
    // Add hcEvents to either the prototype (in case we're running addEvent on a
    // class) or the instance. If hasOwnProperty('hcEvents') is false, it is
    // inherited down the prototype chain, in which case we need to set the
    // property on this instance (which may itself be a prototype).
    const owner = typeof el === 'function' && el.prototype || el;
    if (!Object.hasOwnProperty.call(owner, 'hcEvents')) {
        owner.hcEvents = {};
    }
    const events = owner.hcEvents;
    // Allow click events added to points, otherwise they will be prevented by
    // the TouchPointer.pinch function after a pinch zoom operation (#7091).
    if (Core_Globals.Point && // Without H a dependency loop occurs
        el instanceof Core_Globals.Point &&
        el.series &&
        el.series.chart) {
        el.series.chart.runTrackerClick = true;
    }
    // Handle DOM events
    // If the browser supports passive events, add it to improve performance
    // on touch events (#11353).
    const addEventListener = el.addEventListener;
    if (addEventListener) {
        addEventListener.call(el, type, fn, Core_Globals.supportsPassiveEvents ? {
            passive: options.passive === void 0 ?
                type.indexOf('touch') !== -1 : options.passive,
            capture: false
        } : false);
    }
    if (!events[type]) {
        events[type] = [];
    }
    const eventObject = {
        fn,
        order: typeof options.order === 'number' ? options.order : Infinity
    };
    events[type].push(eventObject);
    // Order the calls
    events[type].sort((a, b) => a.order - b.order);
    // Return a function that can be called to remove this event.
    return function () {
        removeEvent(el, type, fn);
    };
}
/**
 * Non-recursive method to find the lowest member of an array. `Math.min` raises
 * a maximum call stack size exceeded error in Chrome when trying to apply more
 * than 150.000 points. This method is slightly slower, but safe.
 *
 * @function Highcharts.arrayMin
 *
 * @param {Array<*>} data
 *        An array of numbers.
 *
 * @return {number}
 *         The lowest number.
 */
function arrayMin(data) {
    let i = data.length, min = data[0];
    while (i--) {
        if (data[i] < min) {
            min = data[i];
        }
    }
    return min;
}
/**
 * Non-recursive method to find the lowest member of an array. `Math.max` raises
 * a maximum call stack size exceeded error in Chrome when trying to apply more
 * than 150.000 points. This method is slightly slower, but safe.
 *
 * @function Highcharts.arrayMax
 *
 * @param {Array<*>} data
 *        An array of numbers.
 *
 * @return {number}
 *         The highest number.
 */
function arrayMax(data) {
    let i = data.length, max = data[0];
    while (i--) {
        if (data[i] > max) {
            max = data[i];
        }
    }
    return max;
}
/**
 * Set or get an attribute or an object of attributes.
 *
 * To use as a setter, pass a key and a value, or let the second argument be a
 * collection of keys and values. When using a collection, passing a value of
 * `null` or `undefined` will remove the attribute.
 *
 * To use as a getter, pass only a string as the second argument.
 *
 * @function Highcharts.attr
 *
 * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} elem
 *        The DOM element to receive the attribute(s).
 *
 * @param {string|Highcharts.HTMLAttributes|Highcharts.SVGAttributes} [keyOrAttribs]
 *        The property or an object of key-value pairs.
 *
 * @param {number|string} [value]
 *        The value if a single property is set.
 *
 * @return {string|null|undefined}
 *         When used as a getter, return the value.
 */
function attr(elem, keyOrAttribs, value) {
    const isGetter = isString(keyOrAttribs) && !defined(value);
    let ret;
    const attrSingle = (value, key) => {
        // Set the value
        if (defined(value)) {
            elem.setAttribute(key, value);
            // Get the value
        }
        else if (isGetter) {
            ret = elem.getAttribute(key);
            // IE7 and below cannot get class through getAttribute (#7850)
            if (!ret && key === 'class') {
                ret = elem.getAttribute(key + 'Name');
            }
            // Remove the value
        }
        else {
            elem.removeAttribute(key);
        }
    };
    // If keyOrAttribs is a string
    if (isString(keyOrAttribs)) {
        attrSingle(value, keyOrAttribs);
        // Else if keyOrAttribs is defined, it is a hash of key/value pairs
    }
    else {
        objectEach(keyOrAttribs, attrSingle);
    }
    return ret;
}
/**
 * Constrain a value to within a lower and upper threshold.
 *
 * @internal
 * @param {number} value The initial value
 * @param {number} min The lower threshold
 * @param {number} max The upper threshold
 * @return {number} Returns a number value within min and max.
 */
function clamp(value, min, max) {
    return value > min ? value < max ? value : max : min;
}
/**
 * Fix JS round off float errors.
 *
 * @function Highcharts.correctFloat
 *
 * @param {number} num
 *        A float number to fix.
 *
 * @param {number} [prec=14]
 *        The precision.
 *
 * @return {number}
 *         The corrected float number.
 */
function correctFloat(num, prec) {
    // When the number is higher than 1e14 use the number (#16275)
    return num > 1e14 ? num : parseFloat(num.toPrecision(prec || 14));
}
/**
 * Utility function to create an HTML element with attributes and styles.
 *
 * @function Highcharts.createElement
 *
 * @param {string} tag
 *        The HTML tag.
 *
 * @param {Highcharts.HTMLAttributes} [attribs]
 *        Attributes as an object of key-value pairs.
 *
 * @param {Highcharts.CSSObject} [styles]
 *        Styles as an object of key-value pairs.
 *
 * @param {Highcharts.HTMLDOMElement} [parent]
 *        The parent HTML object.
 *
 * @param {boolean} [nopad=false]
 *        If true, remove all padding, border and margin.
 *
 * @return {Highcharts.HTMLDOMElement}
 *         The created DOM element.
 */
function createElement(tag, attribs, styles, parent, nopad) {
    const el = doc.createElement(tag);
    if (attribs) {
        extend(el, attribs);
    }
    if (nopad) {
        css(el, { padding: '0', border: 'none', margin: '0' });
    }
    if (styles) {
        css(el, styles);
    }
    if (parent) {
        parent.appendChild(el);
    }
    return el;
}
/**
 * Utility for crisping a line position to the nearest full pixel depending on
 * the line width.
 *
 * @internal
 * @param {number} value       The raw pixel position
 * @param {number} lineWidth   The line width
 * @param {boolean} [inverted] Whether the containing group is inverted.
 *                             Crisping round numbers on the y-scale need to go
 *                             to the other side because the coordinate system
 *                             is flipped (scaleY is -1)
 * @return {number}            The pixel position to use for a crisp display
 */
function crisp(value, lineWidth = 0, inverted) {
    const mod = lineWidth % 2 / 2, inverter = inverted ? -1 : 1;
    return (Math.round(value * inverter - mod) + mod) * inverter;
}
/**
 * Set CSS on a given element.
 *
 * @function Highcharts.css
 *
 * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} el
 *        An HTML DOM element.
 *
 * @param {Highcharts.CSSObject} styles
 *        Style object with camel case property names.
 *
 * @return {void}
 */
function css(el, styles) {
    extend(el.style, styles);
}
/**
 * Check if an object is null or undefined.
 *
 * @function Highcharts.defined
 *
 * @param {*} obj
 *        The object to check.
 *
 * @return {boolean}
 *         False if the object is null or undefined, otherwise true.
 */
function defined(obj) {
    return typeof obj !== 'undefined' && obj !== null;
}
/**
 * Utility method that destroys any SVGElement instances that are properties on
 * the given object. It loops all properties and invokes destroy if there is a
 * destroy method. The property is then delete.
 *
 * @function Highcharts.destroyObjectProperties
 *
 * @param {*} obj
 *        The object to destroy properties on.
 *
 * @param {*} [except]
 *        Exception, do not destroy this property, only delete it.
 */
function destroyObjectProperties(obj, except, destructablesOnly) {
    objectEach(obj, function (val, n) {
        // If the object is non-null and destroy is defined
        if (val !== except && val?.destroy) {
            // Invoke the destroy
            val.destroy();
        }
        // Delete the property from the object
        if (val?.destroy || !destructablesOnly) {
            delete obj[n];
        }
    });
}
/**
 * Discard a HTML element
 *
 * @function Highcharts.discardElement
 *
 * @param {Highcharts.HTMLDOMElement} element
 *        The HTML node to discard.
 */
function discardElement(element) {
    element?.parentElement?.removeChild(element);
}
// eslint-disable-next-line valid-jsdoc
/**
 * Return the deep difference between two objects. It can either return the new
 * properties, or optionally return the old values of new properties.
 * @internal
 */
function diffObjects(newer, older, keepOlder, collectionsWithUpdate) {
    const ret = {};
    /**
     * Recurse over a set of options and its current values, and store the
     * current values in the ret object.
     */
    function diff(newer, older, ret, depth) {
        const keeper = keepOlder ? older : newer;
        objectEach(newer, function (newerVal, key) {
            if (!depth &&
                collectionsWithUpdate &&
                collectionsWithUpdate.indexOf(key) > -1 &&
                older[key]) {
                newerVal = splat(newerVal);
                ret[key] = [];
                // Iterate over collections like series, xAxis or yAxis and map
                // the items by index.
                for (let i = 0; i < Math.max(newerVal.length, older[key].length); i++) {
                    // Item exists in current data (#6347)
                    if (older[key][i]) {
                        // If the item is missing from the new data, we need to
                        // save the whole config structure. Like when
                        // responsively updating from a dual axis layout to a
                        // single axis and back (#13544).
                        if (newerVal[i] === void 0) {
                            ret[key][i] = older[key][i];
                            // Otherwise, proceed
                        }
                        else {
                            ret[key][i] = {};
                            diff(newerVal[i], older[key][i], ret[key][i], depth + 1);
                        }
                    }
                }
            }
            else if (isObject(newerVal, true) &&
                !newerVal.nodeType // #10044
            ) {
                ret[key] = isArray(newerVal) ? [] : {};
                diff(newerVal, older[key] || {}, ret[key], depth + 1);
                // Delete empty nested objects
                if (Object.keys(ret[key]).length === 0 &&
                    // Except colorAxis which is a special case where the empty
                    // object means it is enabled. Which is unfortunate and we
                    // should try to find a better way.
                    !(key === 'colorAxis' && depth === 0)) {
                    delete ret[key];
                }
            }
            else if (newer[key] !== older[key] ||
                // If the newer key is explicitly undefined, keep it (#10525)
                (key in newer && !(key in older))) {
                if (key !== '__proto__' && key !== 'constructor') {
                    ret[key] = keeper[key];
                }
            }
        });
    }
    diff(newer, older, ret, 0);
    return ret;
}
/**
 * Remove the last occurrence of an item from an array.
 *
 * @function Highcharts.erase
 *
 * @param {Array<*>} arr
 *        The array.
 *
 * @param {*} item
 *        The item to remove.
 *
 * @return {void}
 */
function erase(arr, item) {
    let i = arr.length;
    while (i--) {
        if (arr[i] === item) {
            arr.splice(i, 1);
            break;
        }
    }
}
/**
 * Utility function to extend an object with the members of another.
 *
 * @function Highcharts.extend<T>
 *
 * @param {T|undefined} a
 *        The object to be extended.
 *
 * @param {Partial<T>} b
 *        The object to add to the first one.
 *
 * @return {T}
 *         Object a, the original object.
 */
function extend(a, b) {
    let n;
    if (!a) {
        a = {};
    }
    for (n in b) { // eslint-disable-line guard-for-in
        a[n] = b[n];
    }
    return a;
}
// eslint-disable-next-line valid-jsdoc
/**
 * Extend a prototyped class by new members.
 *
 * @deprecated 11.0.0
 * @function Highcharts.extendClass<T>
 *
 * @param {Highcharts.Class<T>} parent
 *        The parent prototype to inherit.
 *
 * @param {Highcharts.Dictionary<*>} members
 *        A collection of prototype members to add or override compared to the
 *        parent prototype.
 *
 * @return {Highcharts.Class<T>}
 *         A new prototype.
 */
function extendClass(parent, members) {
    const obj = (function () { });
    obj.prototype = new parent(); // eslint-disable-line new-cap
    extend(obj.prototype, members);
    return obj;
}
/**
 * Fire an event that was registered with {@link Highcharts#addEvent}.
 *
 * @function Highcharts.fireEvent<T>
 *
 * @param {T} el
 *        The object to fire the event on. It can be a {@link HTMLDOMElement},
 *        an {@link SVGElement} or any other object.
 *
 * @param {string} type
 *        The type of event.
 *
 * @param {Highcharts.Dictionary<*>|Event} [eventArguments]
 *        Custom event arguments that are passed on as an argument to the event
 *        handler.
 *
 * @param {Highcharts.EventCallbackFunction<T>|Function} [defaultFunction]
 *        The default function to execute if the other listeners haven't
 *        returned false.
 *
 * @return {void}
 */
function fireEvent(el, type, eventArguments, defaultFunction) {
    eventArguments = eventArguments || {};
    if (doc?.createEvent &&
        (el.dispatchEvent ||
            (el.fireEvent &&
                // Enable firing events on Highcharts instance.
                el !== Core_Globals))) {
        const e = doc.createEvent('Events');
        e.initEvent(type, true, true);
        eventArguments = extend(e, eventArguments);
        if (el.dispatchEvent) {
            el.dispatchEvent(eventArguments);
        }
        else {
            el.fireEvent(type, eventArguments);
        }
    }
    else if (el.hcEvents) {
        if (!eventArguments.target) {
            // We're running a custom event
            extend(eventArguments, {
                // Attach a simple preventDefault function to skip
                // default handler if called. The built-in
                // defaultPrevented property is not overwritable (#5112)
                preventDefault: function () {
                    eventArguments.defaultPrevented = true;
                },
                // Setting target to native events fails with clicking
                // the zoom-out button in Chrome.
                target: el,
                // If the type is not set, we're running a custom event
                // (#2297). If it is set, we're running a browser event.
                type: type
            });
        }
        const events = [];
        let object = el;
        let multilevel = false;
        // Recurse up the inheritance chain and collect hcEvents set as own
        // objects on the prototypes.
        while (object.hcEvents) {
            if (Object.hasOwnProperty.call(object, 'hcEvents') &&
                object.hcEvents[type]) {
                if (events.length) {
                    multilevel = true;
                }
                events.unshift.apply(events, object.hcEvents[type]);
            }
            object = Object.getPrototypeOf(object);
        }
        // For performance reasons, only sort the event handlers in case we are
        // dealing with multiple levels in the prototype chain. Otherwise, the
        // events are already sorted in the addEvent function.
        if (multilevel) {
            // Order the calls
            events.sort((a, b) => a.order - b.order);
        }
        // Call the collected event handlers
        events.forEach((obj) => {
            // If the event handler returns false, prevent the default handler
            // from executing
            if (obj.fn.call(el, eventArguments, el) === false) {
                eventArguments.preventDefault();
            }
        });
    }
    // Run the default if not prevented
    if (defaultFunction && !eventArguments.defaultPrevented) {
        defaultFunction.call(el, eventArguments);
    }
}
/**
 * Convenience function to get the align factor, used several places for
 * computing positions
 * @internal
 */
const getAlignFactor = (align = '') => ({
    center: 0.5,
    right: 1,
    middle: 0.5,
    bottom: 1
}[align] || 0);
/**
 * Find the closest distance between two values of a two-dimensional array
 * @internal
 * @function Highcharts.getClosestDistance
 *
 * @param {Array<Array<number>>} arrays
 *          An array of arrays of numbers
 *
 * @return {number | undefined}
 *          The closest distance between values
 */
function getClosestDistance(arrays, onError) {
    const allowNegative = !onError;
    let closest, loopLength, distance, i;
    arrays.forEach((xData) => {
        if (xData.length > 1) {
            loopLength = xData.length - 1;
            for (i = loopLength; i > 0; i--) {
                distance = xData[i] - xData[i - 1];
                if (distance < 0 && !allowNegative) {
                    onError?.();
                    // Only one call
                    onError = void 0;
                }
                else if (distance && (typeof closest === 'undefined' || distance < closest)) {
                    closest = distance;
                }
            }
        }
    });
    return closest;
}
/**
 * Get the magnitude of a number.
 *
 * @function Highcharts.getMagnitude
 *
 * @param {number} num
 *        The number.
 *
 * @return {number}
 *         The magnitude, where 1-9 are magnitude 1, 10-99 magnitude 2 etc.
 */
function getMagnitude(num) {
    return Math.pow(10, Math.floor(Math.log(num) / Math.LN10));
}
/**
 * Returns the value of a property path on a given object.
 *
 * @internal
 * @function getNestedProperty
 *
 * @param {string} path
 * Path to the property, for example `custom.myValue`.
 *
 * @param {unknown} parent
 * Instance containing the property on the specific path.
 *
 * @return {unknown}
 * The unknown property value.
 */
function getNestedProperty(path, parent) {
    const pathElements = path.split('.');
    while (pathElements.length && defined(parent)) {
        const pathElement = pathElements.shift();
        // Filter on the key
        if (typeof pathElement === 'undefined' ||
            pathElement === '__proto__') {
            return; // Undefined
        }
        if (pathElement === 'this') {
            let thisProp;
            if (isObject(parent)) {
                thisProp = parent['@this'];
            }
            return thisProp ?? parent;
        }
        const child = parent[pathElement.replace(/[\\'"]/g, '')];
        // Filter on the child
        if (!defined(child) ||
            typeof child === 'function' ||
            typeof child.nodeType === 'number' ||
            child === win) {
            return; // Undefined
        }
        // Else, proceed
        parent = child;
    }
    return parent;
}
/**
 * Get the computed CSS value for given element and property, only for numerical
 * properties. For width and height, the dimension of the inner box (excluding
 * padding) is returned. Used for fitting the chart within the container.
 *
 * @function Highcharts.getStyle
 *
 * @param {Highcharts.HTMLDOMElement} el
 * An HTML element.
 *
 * @param {string} prop
 * The property name.
 *
 * @param {boolean} [toInt=true]
 * Parse to integer.
 *
 * @return {number|string|undefined}
 * The style value.
 */
function getStyle(el, prop, toInt) {
    let style;
    // For width and height, return the actual inner pixel size (#4913)
    if (prop === 'width') {
        let offsetWidth = Math.min(el.offsetWidth, el.scrollWidth);
        // In flex boxes, we need to use getBoundingClientRect and floor it,
        // because scrollWidth doesn't support subpixel precision (#6427) ...
        const boundingClientRectWidth = el.getBoundingClientRect?.().width;
        // ...unless if the containing div or its parents are transform-scaled
        // down, in which case the boundingClientRect can't be used as it is
        // also scaled down (#9871, #10498).
        if (boundingClientRectWidth < offsetWidth &&
            boundingClientRectWidth >= offsetWidth - 1) {
            offsetWidth = Math.floor(boundingClientRectWidth);
        }
        return Math.max(0, // #8377
        (offsetWidth -
            (getStyle(el, 'padding-left', true) || 0) -
            (getStyle(el, 'padding-right', true) || 0)));
    }
    if (prop === 'height') {
        return Math.max(0, // #8377
        (Math.min(el.offsetHeight, el.scrollHeight) -
            (getStyle(el, 'padding-top', true) || 0) -
            (getStyle(el, 'padding-bottom', true) || 0)));
    }
    // Otherwise, get the computed style
    const css = win.getComputedStyle(el, void 0); // eslint-disable-line no-undefined
    if (css) {
        style = css.getPropertyValue(prop);
        if (pick(toInt, prop !== 'opacity')) {
            style = pInt(style);
        }
    }
    return style;
}
/**
 * Return the value of the first element in the array that satisfies the
 * provided testing function.
 *
 * @function Highcharts.find<T>
 *
 * @param {Array<T>} arr
 *        The array to test.
 *
 * @param {Function} callback
 *        The callback function. The function receives the item as the first
 *        argument. Return `true` if this item satisfies the condition.
 *
 * @return {T|undefined}
 *         The value of the element.
 */
const find = Array.prototype.find ?
    function (arr, callback) {
        return arr.find(callback);
    } :
    // Legacy implementation. PhantomJS, IE <= 11 etc. #7223.
    function (arr, callback) {
        let i;
        const length = arr.length;
        for (i = 0; i < length; i++) {
            if (callback(arr[i], i)) { // eslint-disable-line node/callback-return
                return arr[i];
            }
        }
    };
/**
 * Internal clear timeout. The function checks that the `id` was not removed
 * (e.g. by `chart.destroy()`). For the details see
 * [issue #7901](https://github.com/highcharts/highcharts/issues/7901).
 *
 * @internal
 *
 * @function Highcharts.clearTimeout
 *
 * @param {number|undefined} id
 * Id of a timeout.
 */
function internalClearTimeout(id) {
    if (defined(id)) {
        clearTimeout(id);
    }
}
/**
 * Utility function to check if an Object is a HTML Element.
 *
 * @function Highcharts.isDOMElement
 *
 * @param {*} obj
 *        The item to check.
 *
 * @return {boolean}
 *         True if the argument is a HTML Element.
 */
function isDOMElement(obj) {
    return isObject(obj) && typeof obj.nodeType === 'number';
}
/**
 * Utility function to check if an Object is a class.
 *
 * @function Highcharts.isClass
 *
 * @param {object|undefined} obj
 *        The item to check.
 *
 * @return {boolean}
 *         True if the argument is a class.
 */
function isClass(obj) {
    const c = obj?.constructor;
    return !!(isObject(obj, true) &&
        !isDOMElement(obj) &&
        (c?.name && c.name !== 'Object'));
}
/**
 * Utility function to check if an item is a number and it is finite (not NaN,
 * Infinity or -Infinity).
 *
 * @function Highcharts.isNumber
 *
 * @param {*} n
 *        The item to check.
 *
 * @return {boolean}
 *         True if the item is a finite number
 */
function isNumber(n) {
    return typeof n === 'number' && !isNaN(n) && n < Infinity && n > -Infinity;
}
/**
 * Utility function to check for string type.
 *
 * @function Highcharts.isString
 *
 * @param {*} s
 *        The item to check.
 *
 * @return {boolean}
 *         True if the argument is a string.
 */
function isString(s) {
    return typeof s === 'string';
}
/**
 * Utility function to check if an item is an array.
 *
 * @function Highcharts.isArray
 *
 * @param {*} obj
 *        The item to check.
 *
 * @return {boolean}
 *         True if the argument is an array.
 */
function isArray(obj) {
    const str = Object.prototype.toString.call(obj);
    return str === '[object Array]' || str === '[object Array Iterator]';
}
/**
 * Utility function to check if object is a function.
 *
 * @function Highcharts.isFunction
 *
 * @param {*} obj
 *        The item to check.
 *
 * @return {boolean}
 *         True if the argument is a function.
 */
function isFunction(obj) {
    return typeof obj === 'function';
}
/**
 * Utility function to check if an item is of type object.
 *
 * @function Highcharts.isObject
 *
 * @param {*} obj
 *        The item to check.
 *
 * @param {boolean} [strict=false]
 *        Also checks that the object is not an array.
 *
 * @return {boolean}
 *         True if the argument is an object.
 */
function isObject(obj, strict) {
    return (!!obj &&
        typeof obj === 'object' &&
        (!strict || !isArray(obj))); // eslint-disable-line @typescript-eslint/no-explicit-any
}
/**
 * Utility function to deep merge two or more objects and return a third object.
 * If the first argument is true, the contents of the second object is copied
 * into the first object. The merge function can also be used with a single
 * object argument to create a deep copy of an object.
 *
 * @function Highcharts.merge<T>
 *
 * @param {true | T} extendOrSource
 *        Whether to extend the left-side object,
 *        or the first object to merge as a deep copy.
 *
 * @param {...Array<object|undefined>} [sources]
 *        Object(s) to merge into the previous one.
 *
 * @return {T}
 *         The merged object. If the first argument is true, the return is the
 *         same as the second argument.
 */
function merge(extendOrSource, ...sources) {
    let i, args = [extendOrSource, ...sources], ret = {};
    const doCopy = function (copy, original) {
        // An object is replacing a primitive
        if (typeof copy !== 'object') {
            copy = {};
        }
        objectEach(original, function (value, key) {
            // Prototype pollution (#14883)
            if (key === '__proto__' || key === 'constructor') {
                return;
            }
            // Copy the contents of objects, but not arrays or DOM nodes
            if (isObject(value, true) &&
                !isClass(value) &&
                !isDOMElement(value)) {
                copy[key] = doCopy(copy[key] || {}, value);
                // Primitives and arrays are copied over directly
            }
            else {
                copy[key] = original[key];
            }
        });
        return copy;
    };
    // If first argument is true, copy into the existing object. Used in
    // setOptions.
    if (extendOrSource === true) {
        ret = args[1];
        args = Array.prototype.slice.call(args, 2);
    }
    // For each argument, extend the return
    const len = args.length;
    for (i = 0; i < len; i++) {
        ret = doCopy(ret, args[i]);
    }
    return ret;
}
/**
 * Take an interval and normalize it to multiples of round numbers.
 *
 * @deprecated 5.0.3
 * @function Highcharts.normalizeTickInterval
 *
 * @param {number} interval
 *        The raw, un-rounded interval.
 *
 * @param {Array<*>} [multiples]
 *        Allowed multiples.
 *
 * @param {number} [magnitude]
 *        The magnitude of the number.
 *
 * @param {boolean} [allowDecimals]
 *        Whether to allow decimals.
 *
 * @param {boolean} [hasTickAmount]
 *        If it has tickAmount, avoid landing on tick intervals lower than
 *        original.
 *
 * @return {number}
 *         The normalized interval.
 *
 * @todo
 * Move this function to the Axis prototype. It is here only for historical
 * reasons.
 */
function normalizeTickInterval(interval, multiples, magnitude, allowDecimals, hasTickAmount) {
    let i, retInterval = interval;
    // Round to a tenfold of 1, 2, 2.5 or 5
    magnitude = pick(magnitude, getMagnitude(interval));
    const normalized = interval / magnitude;
    // Multiples for a linear scale
    if (!multiples) {
        multiples = hasTickAmount ?
            // Finer grained ticks when the tick amount is hard set, including
            // when alignTicks is true on multiple axes (#4580).
            [1, 1.2, 1.5, 2, 2.5, 3, 4, 5, 6, 8, 10] :
            // Else, let ticks fall on rounder numbers
            [1, 2, 2.5, 5, 10];
        // The allowDecimals option
        if (allowDecimals === false) {
            if (magnitude === 1) {
                multiples = multiples.filter(function (num) {
                    return num % 1 === 0;
                });
            }
            else if (magnitude <= 0.1) {
                multiples = [1 / magnitude];
            }
        }
    }
    // Normalize the interval to the nearest multiple
    for (i = 0; i < multiples.length; i++) {
        retInterval = multiples[i];
        // Only allow tick amounts smaller than natural
        if ((hasTickAmount &&
            retInterval * magnitude >= interval) ||
            (!hasTickAmount &&
                (normalized <=
                    (multiples[i] +
                        (multiples[i + 1] || multiples[i])) / 2))) {
            break;
        }
    }
    // Multiply back to the correct magnitude. Correct floats to appropriate
    // precision (#6085).
    retInterval = correctFloat(retInterval * magnitude, -Math.round(Math.log(0.001) / Math.LN10));
    return retInterval;
}
/**
 * Iterate over object key pairs in an object.
 *
 * @function Highcharts.objectEach<T>
 *
 * @param {*} obj
 *        The object to iterate over.
 *
 * @param {Highcharts.ObjectEachCallbackFunction<T>} fn
 *        The iterator callback. It passes three arguments:
 *        * value - The property value.
 *        * key - The property key.
 *        * obj - The object that objectEach is being applied to.
 *
 * @param {T} [ctx]
 *        The context.
 */
function objectEach(obj, fn, ctx) {
    for (const key in obj) {
        if (Object.hasOwnProperty.call(obj, key)) {
            fn.call(ctx || obj[key], obj[key], key, obj);
        }
    }
}
/**
 * Get the element's offset position, corrected for `overflow: auto`.
 *
 * @function Highcharts.offset
 *
 * @param {global.Element} el
 *        The DOM element.
 *
 * @return {Highcharts.OffsetObject}
 *         An object containing `left` and `top` properties for the position in
 *         the page.
 */
function offset(el) {
    const docElem = doc.documentElement, box = (el.parentElement || el.parentNode) ?
        el.getBoundingClientRect() :
        { top: 0, left: 0, width: 0, height: 0 };
    return {
        top: box.top + (win.pageYOffset || docElem.scrollTop) -
            (docElem.clientTop || 0),
        left: box.left + (win.pageXOffset || docElem.scrollLeft) -
            (docElem.clientLeft || 0),
        width: box.width,
        height: box.height
    };
}
/**
 * Left-pad a string to a given length by adding a character repetitively.
 *
 * @function Highcharts.pad
 *
 * @param {number} number
 *        The input string or number.
 *
 * @param {number} [length]
 *        The desired string length.
 *
 * @param {string} [padder=0]
 *        The character to pad with.
 *
 * @return {string}
 *         The padded string.
 */
function pad(number, length, padder) {
    return new Array((length || 2) +
        1 -
        String(number)
            .replace('-', '')
            .length).join(padder || '0') + number;
}
/* eslint-disable jsdoc/check-param-names */
/**
 * Return the first value that is not null or undefined.
 *
 * @function Highcharts.pick<T>
 *
 * @param {...Array<T|null|undefined>} items
 *        Variable number of arguments to inspect.
 *
 * @return {T}
 *         The value of the first argument that is not null or undefined.
 */
function pick() {
    const args = arguments;
    const length = args.length;
    for (let i = 0; i < length; i++) {
        const arg = args[i];
        if (typeof arg !== 'undefined' && arg !== null) {
            return arg;
        }
    }
}
/* eslint-enable jsdoc/check-param-names */
/**
 * Shortcut for parseInt
 *
 * @internal
 * @function Highcharts.pInt
 *
 * @param {*} s
 *        any
 *
 * @param {number} [mag]
 *        Magnitude
 *
 * @return {number}
 *         number
 */
function pInt(s, mag) {
    return parseInt(s, mag || 10);
}
/**
 * Adds an item to an array, if it is not present in the array.
 *
 * @internal
 *
 * @function Highcharts.pushUnique
 *
 * @param {Array<unknown>} array
 * The array to add the item to.
 *
 * @param {unknown} item
 * The item to add.
 *
 * @return {boolean}
 * Returns true, if the item was not present and has been added.
 */
function pushUnique(array, item) {
    return array.indexOf(item) < 0 && !!array.push(item);
}
/**
 * Return a length based on either the integer value, or a percentage of a base.
 *
 * @function Highcharts.relativeLength
 *
 * @param {Highcharts.RelativeSize} value
 *        A percentage string or a number.
 *
 * @param {number} base
 *        The full length that represents 100%.
 *
 * @param {number} [offset=0]
 *        A pixel offset to apply for percentage values. Used internally in
 *        axis positioning.
 *
 * @return {number}
 *         The computed length.
 */
function relativeLength(value, base, offset) {
    return (/%$/).test(value) ?
        (base * parseFloat(value) / 100) + (offset || 0) :
        parseFloat(value);
}
/**
 * Replaces text in a string with a given replacement in a loop to catch nested
 * matches after previous replacements.
 *
 * @internal
 *
 * @function Highcharts.replaceNested
 *
 * @param {string} text
 * Text to search and modify.
 *
 * @param {...Array<(RegExp|string)>} replacements
 * One or multiple tuples with search pattern (`[0]: (string|RegExp)`) and
 * replacement (`[1]: string`) for matching text.
 *
 * @return {string}
 * Text with replacements.
 */
function replaceNested(text, ...replacements) {
    let previous, replacement;
    do {
        previous = text;
        for (replacement of replacements) {
            text = text.replace(replacement[0], replacement[1]);
        }
    } while (text !== previous);
    return text;
}
/**
 * Remove an event that was added with {@link Highcharts#addEvent}.
 *
 * @function Highcharts.removeEvent<T>
 *
 * @param {Highcharts.Class<T>|T} el
 *        The element to remove events on.
 *
 * @param {string} [type]
 *        The type of events to remove. If undefined, all events are removed
 *        from the element.
 *
 * @param {Highcharts.EventCallbackFunction<T>} [fn]
 *        The specific callback to remove. If undefined, all events that match
 *        the element and optionally the type are removed.
 *
 * @return {void}
 */
function removeEvent(el, type, fn) {
    /** @internal */
    function removeOneEvent(type, fn) {
        const removeEventListener = el.removeEventListener;
        if (removeEventListener) {
            removeEventListener.call(el, type, fn, false);
        }
    }
    /** @internal */
    function removeAllEvents(eventCollection) {
        let types, len;
        if (!el.nodeName) {
            return; // Break on non-DOM events
        }
        if (type) {
            types = {};
            types[type] = true;
        }
        else {
            types = eventCollection;
        }
        objectEach(types, function (_val, n) {
            if (eventCollection[n]) {
                len = eventCollection[n].length;
                while (len--) {
                    removeOneEvent(n, eventCollection[n][len].fn);
                }
            }
        });
    }
    const owner = typeof el === 'function' && el.prototype || el;
    if (Object.hasOwnProperty.call(owner, 'hcEvents')) {
        const events = owner.hcEvents;
        if (type) {
            const typeEvents = (events[type] || []);
            if (fn) {
                events[type] = typeEvents.filter(function (obj) {
                    return fn !== obj.fn;
                });
                removeOneEvent(type, fn);
            }
            else {
                removeAllEvents(events);
                events[type] = [];
            }
        }
        else {
            removeAllEvents(events);
            delete owner.hcEvents;
        }
    }
}
/**
 * Check if an element is an array, and if not, make it into an array.
 *
 * @function Highcharts.splat
 *
 * @param {*} obj
 *        The object to splat.
 *
 * @return {Array}
 *         The produced or original array.
 */
function splat(obj) {
    return isArray(obj) ? obj : [obj];
}
/**
 * Sort an object array and keep the order of equal items. The ECMAScript
 * standard does not specify the behavior when items are equal.
 *
 * @function Highcharts.stableSort
 *
 * @param {Array<*>} arr
 *        The array to sort.
 *
 * @param {Function} sortFunction
 *        The function to sort it with, like with regular Array.prototype.sort.
 */
function stableSort(arr, sortFunction) {
    // @todo It seems like Chrome since v70 sorts in a stable way internally,
    // plus all other browsers do it, so over time we may be able to remove this
    // function
    const length = arr.length;
    let sortValue, i;
    // Add index to each item
    for (i = 0; i < length; i++) {
        arr[i].safeI = i; // Stable sort index
    }
    arr.sort(function (a, b) {
        sortValue = sortFunction(a, b);
        return sortValue === 0 ? a.safeI - b.safeI : sortValue;
    });
    // Remove index from items
    for (i = 0; i < length; i++) {
        delete arr[i].safeI; // Stable sort index
    }
}
/**
 * Set a timeout if the delay is given, otherwise perform the function
 * synchronously.
 *
 * @function Highcharts.syncTimeout
 *
 * @param {Function} fn
 *        The function callback.
 *
 * @param {number} delay
 *        Delay in milliseconds.
 *
 * @param {*} [context]
 *        An optional context to send to the function callback.
 *
 * @return {number}
 *         An identifier for the timeout that can later be cleared with
 *         Highcharts.clearTimeout. Returns -1 if there is no timeout.
 */
function syncTimeout(fn, delay, context) {
    if (delay > 0) {
        return setTimeout(fn, delay, context);
    }
    fn.call(0, context);
    return -1;
}
/**
 * @internal
 */
function ucfirst(s) {
    return ((isString(s) ?
        s.substring(0, 1).toUpperCase() + s.substring(1) :
        String(s)));
}
/**
 * Wrap a method with extended functionality, preserving the original function.
 *
 * @function Highcharts.wrap
 *
 * @param {*} obj
 *        The context object that the method belongs to. In real cases, this is
 *        often a prototype.
 *
 * @param {string} method
 *        The name of the method to extend.
 *
 * @param {Highcharts.WrapProceedFunction} func
 *        A wrapper function callback. This function is called with the same
 *        arguments as the original function, except that the original function
 *        is unshifted and passed as the first argument.
 */
function wrap(obj, method, func) {
    const proceed = obj[method];
    obj[method] = function () {
        const outerArgs = arguments, scope = this;
        return func.apply(this, [
            function () {
                return proceed.apply(scope, arguments.length ? arguments : outerArgs);
            }
        ].concat([].slice.call(arguments)));
    };
}

;// ./code/es-modules/Core/Utilities.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */



const { charts, win: Utilities_win } = Core_Globals;
/* *
 *
 *  Functions
 *
 * */
/**
 * Provide error messages for debugging, with links to online explanation. This
 * function can be overridden to provide custom error handling.
 *
 * @sample highcharts/chart/highcharts-error/
 *         Custom error handler
 *
 * @function Highcharts.error
 *
 * @param {number|string} code
 *        The error code. See
 *        [errors.xml](https://github.com/highcharts/highcharts/blob/master/errors/errors.xml)
 *        for available codes. If it is a string, the error message is printed
 *        directly in the console.
 *
 * @param {boolean} [stop=false]
 *        Whether to throw an error or just log a warning in the console.
 *
 * @param {Highcharts.Chart} [chart]
 *        Reference to the chart that causes the error. Used in 'debugger'
 *        module to display errors directly on the chart.
 *        Important note: This argument is undefined for errors that lack
 *        access to the Chart instance. In such case, the error will be
 *        displayed on the last created chart.
 *
 * @param {Highcharts.Dictionary<string>} [params]
 *        Additional parameters for the generated message.
 *
 * @return {void}
 */
function error(code, stop, chart, params) {
    const severity = stop ? 'Highcharts error' : 'Highcharts warning';
    if (code === 32) {
        code = `${severity}: Deprecated member`;
    }
    const isCode = isNumber(code);
    let message = isCode ?
        `${severity} #${code}: www.highcharts.com/errors/${code}/` :
        code.toString();
    const defaultHandler = function () {
        if (stop) {
            throw new Error(message);
        }
        // Else ...
        if (Utilities_win.console &&
            error.messages.indexOf(message) === -1 // Prevent console floating
        ) {
            console.warn(message); // eslint-disable-line no-console
        }
    };
    if (typeof params !== 'undefined') {
        let additionalMessages = '';
        if (isCode) {
            message += '?';
        }
        objectEach(params, function (value, key) {
            additionalMessages += `\n - ${key}: ${value}`;
            if (isCode) {
                message += encodeURI(key) + '=' + encodeURI(value);
            }
        });
        message += additionalMessages;
    }
    fireEvent(Core_Globals, 'displayError', { chart, code, message, params }, defaultHandler);
    error.messages.push(message);
}
(function (error) {
    error.messages = [];
})(error || (error = {}));
/**
 * Insert a series or an axis in a collection with other items, either the
 * chart series or yAxis series or axis collections, in the correct order
 * according to the index option and whether it is internal. Used internally
 * when adding series and axes.
 *
 * @internal
 * @function Highcharts.Chart#insertItem
 * @param  {Highcharts.Series|Highcharts.Axis} item
 *         The item to insert
 * @param  {Array<Highcharts.Series>|Array<Highcharts.Axis>} collection
 *         A collection of items, like `chart.series` or `xAxis.series`.
 * @return {number} The index of the series in the collection.
 */
function insertItem(item, collection) {
    const indexOption = item.options.index, length = collection.length;
    let i;
    for (
    // Internal item (navigator) should always be pushed to the end
    i = item.options.isInternal ? length : 0; i < length + 1; i++) {
        if (
        // No index option, reached the end of the collection,
        // equivalent to pushing
        !collection[i] ||
            // Handle index option, the element to insert has lower index
            (isNumber(indexOption) &&
                indexOption < pick(collection[i].options.index, collection[i]._i)) ||
            // Insert the new item before other internal items
            // (navigator)
            collection[i].options.isInternal) {
            collection.splice(i, 0, item);
            break;
        }
    }
    return i;
}
/**
 * The time unit lookup
 *
 * @ignore
 */
const timeUnits = {
    millisecond: 1,
    second: 1000,
    minute: 60000,
    hour: 3600000,
    day: 24 * 3600000,
    week: 7 * 24 * 3600000,
    month: 28 * 24 * 3600000,
    year: 364 * 24 * 3600000
};
/**
 * Easing definition
 *
 * @internal
 * @function Math.easeInOutSine
 *
 * @param {number} pos
 * Current position, ranging from 0 to 1.
 *
 * @return {number}
 * Ease result
 */
Math.easeInOutSine = function (pos) {
    return -0.5 * (Math.cos(Math.PI * pos) - 1);
};
let serialMode;
/**
 * Get a unique key for using in internal element id's and pointers. The key is
 * composed of a random hash specific to this Highcharts instance, and a
 * counter.
 *
 * @example
 * let id = uniqueKey(); // => 'highcharts-x45f6hp-0'
 *
 * @function Highcharts.uniqueKey
 *
 * @return {string}
 * A unique key.
 */
const uniqueKey = (function () {
    const hash = Math.random().toString(36).substring(2, 9) + '-';
    let id = 0;
    return function () {
        return 'highcharts-' + (serialMode ? '' : hash) + id++;
    };
}());
/**
 * Activates a serial mode for element IDs provided by
 * {@link Highcharts.uniqueKey}. This mode can be used in automated tests, where
 * a simple comparison of two rendered SVG graphics is needed.
 *
 * **Note:** This is only for testing purposes and will break functionality in
 * webpages with multiple charts.
 *
 * @example
 * if (
 *   process &&
 *   process.env.NODE_ENV === 'development'
 * ) {
 *   Highcharts.useSerialIds(true);
 * }
 *
 * @function Highcharts.useSerialIds
 *
 * @param {boolean} [mode]
 * Changes the state of serial mode.
 *
 * @return {boolean|undefined}
 * State of the serial mode.
 */
function useSerialIds(mode) {
    return (serialMode = pick(mode, serialMode));
}
/* *
 *
 *  External
 *
 * */
// Register Highcharts as a plugin in jQuery
if (Utilities_win.jQuery) {
    /**
     * Highcharts-extended JQuery.
     *
     * @external JQuery
     */
    /* eslint-disable jsdoc/check-param-names */
    /**
     * Helper function to return the chart of the current JQuery selector
     * element.
     *
     * @function external:JQuery#highcharts
     *
     * @return {Highcharts.Chart}
     *         The chart that is linked to the JQuery selector element.
     */ /**
    * Factory function to create a chart in the current JQuery selector
    * element.
    *
    * @function external:JQuery#highcharts
    *
    * @param {'Chart'|'Map'|'StockChart'|string} [className]
    *        Name of the factory class in the Highcharts namespace.
    *
    * @param {Highcharts.Options} [options]
    *        The chart options structure.
    *
    * @param {Highcharts.ChartCallbackFunction|true} [callback]
    *        Function to run when the chart has loaded and all external
    *        images are loaded. Defining a
    *        [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load)
    *        handler is equivalent. Set to `true` to return a promise that
    *        resolves when the chart is ready.
    *
    * @return {JQuery}
    *         The current JQuery selector.
    */
    Utilities_win.jQuery.fn.highcharts = function () {
        const args = [].slice.call(arguments);
        if (this[0]) { // `this[0]` is the renderTo div
            // Create the chart
            if (args[0]) {
                new Core_Globals[ // eslint-disable-line computed-property-spacing, no-new
                // Constructor defaults to Chart
                isString(args[0]) ? args.shift() : 'Chart'](this[0], args[0], args[1]);
                return this;
            }
            // When called without parameters or with the return argument,
            // return an existing chart
            return charts[attr(this[0], 'data-highcharts-chart')];
        }
    };
    /* eslint-enable jsdoc/check-param-names */
}
/* *
 *
 *  API Declarations
 *
 * */
/**
 * Creates a frame for the animated SVG element.
 *
 * @callback Highcharts.AnimationStepCallbackFunction
 *
 * @param {Highcharts.SVGElement} this
 *        The SVG element to animate.
 *
 * @return {void}
 */
/**
 * Interface description for a class.
 *
 * @interface Highcharts.Class<T>
 * @extends Function
 */ /**
* Class constructor.
* @function Highcharts.Class<T>#new
* @param {...Array<*>} args
*        Constructor arguments.
* @return {T}
*         Class instance.
*/
/**
 * A style object with camel case property names to define visual appearance of
 * a SVG element or HTML element. The properties can be whatever styles are
 * supported on the given SVG or HTML element.
 *
 * @example
 * {
 *    fontFamily: 'monospace',
 *    fontSize: '1.2em'
 * }
 *
 * @interface Highcharts.CSSObject
 */ /**
* @name Highcharts.CSSObject#[key:string]
* @type {boolean|number|string|undefined}
*/ /**
* Background style for the element.
* @name Highcharts.CSSObject#background
* @type {string|undefined}
*/ /**
* Background color of the element.
* @name Highcharts.CSSObject#backgroundColor
* @type {Highcharts.ColorString|undefined}
*/ /**
* Border style for the element.
* @name Highcharts.CSSObject#border
* @type {string|undefined}
*/ /**
* Radius of the element border.
* @name Highcharts.CSSObject#borderRadius
* @type {string|undefined}
*/ /**
* Color used in the element. The 'contrast' option is a Highcharts custom
* property that results in black or white, depending on the background of the
* element.
* @name Highcharts.CSSObject#color
* @type {'contrast'|Highcharts.ColorString|undefined}
*/ /**
* Style of the mouse cursor when resting over the element.
* @name Highcharts.CSSObject#cursor
* @type {Highcharts.CursorValue|undefined}
*/ /**
* Font family of the element text. Multiple values have to be in decreasing
* preference order and separated by comma.
* @name Highcharts.CSSObject#fontFamily
* @type {string|undefined}
*/ /**
* Font size of the element text.
* @name Highcharts.CSSObject#fontSize
* @type {number|string|undefined}
*/ /**
* Font weight of the element text.
* @name Highcharts.CSSObject#fontWeight
* @type {string|undefined}
*/ /**
* Height of the element.
* @name Highcharts.CSSObject#height
* @type {number|undefined}
*/ /**
* The maximum number of lines. If lines are cropped away, an ellipsis will be
* added.
* @name Highcharts.CSSObject#lineClamp
* @type {number|undefined}
*/ /**
* Width of the element border.
* @name Highcharts.CSSObject#lineWidth
* @type {number|undefined}
*/ /**
* Opacity of the element.
* @name Highcharts.CSSObject#opacity
* @type {number|undefined}
*/ /**
* Space around the element content.
* @name Highcharts.CSSObject#padding
* @type {string|undefined}
*/ /**
* Behavior of the element when the mouse cursor rests over it.
* @name Highcharts.CSSObject#pointerEvents
* @type {string|undefined}
*/ /**
* Positioning of the element.
* @name Highcharts.CSSObject#position
* @type {string|undefined}
*/ /**
* Alignment of the element text.
* @name Highcharts.CSSObject#textAlign
* @type {string|undefined}
*/ /**
* Additional decoration of the element text.
* @name Highcharts.CSSObject#textDecoration
* @type {string|undefined}
*/ /**
* Outline style of the element text.
* @name Highcharts.CSSObject#textOutline
* @type {string|undefined}
*/ /**
* Line break style of the element text. Highcharts SVG elements support
* `ellipsis` when a `width` is set.
* @name Highcharts.CSSObject#textOverflow
* @type {string|undefined}
*/ /**
* Top spacing of the element relative to the parent element.
* @name Highcharts.CSSObject#top
* @type {string|undefined}
*/ /**
* Animated transition of selected element properties.
* @name Highcharts.CSSObject#transition
* @type {string|undefined}
*/ /**
* Line break style of the element text.
* @name Highcharts.CSSObject#whiteSpace
* @type {string|undefined}
*/ /**
* Width of the element.
* @name Highcharts.CSSObject#width
* @type {number|undefined}
*/
/**
 * All possible cursor styles.
 *
 * @typedef {'alias'|'all-scroll'|'auto'|'cell'|'col-resize'|'context-menu'|'copy'|'crosshair'|'default'|'e-resize'|'ew-resize'|'grab'|'grabbing'|'help'|'move'|'n-resize'|'ne-resize'|'nesw-resize'|'no-drop'|'none'|'not-allowed'|'ns-resize'|'nw-resize'|'nwse-resize'|'pointer'|'progress'|'row-resize'|'s-resize'|'se-resize'|'sw-resize'|'text'|'vertical-text'|'w-resize'|'wait'|'zoom-in'|'zoom-out'} Highcharts.CursorValue
 */
/**
 * All possible dash styles.
 *
 * @typedef {'Dash'|'DashDot'|'Dot'|'LongDash'|'LongDashDot'|'LongDashDotDot'|'ShortDash'|'ShortDashDot'|'ShortDashDotDot'|'ShortDot'|'Solid'} Highcharts.DashStyleValue
 */
/**
 * Generic dictionary in TypeScript notation.
 * Use the native `AnyRecord` instead.
 *
 * @deprecated 8.1.2
 * @interface Highcharts.Dictionary<T>
 */ /**
* @name Highcharts.Dictionary<T>#[key:string]
* @type {T}
*/
/**
 * @typedef {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} Highcharts.DOMElementType
 */
/**
 * The function callback to execute when the event is fired. The `this` context
 * contains the instance, that fired the event.
 *
 * @callback Highcharts.EventCallbackFunction<T>
 *
 * @param {T} this
 *
 * @param {Highcharts.Dictionary<*>|Event} [eventArguments]
 *        Event arguments.
 *
 * @param {T} [ctx]
 *        Since v12.6.0, the callback context is passed as the last argument,
 *        so arrow functions can access the same context as regular functions
 *        using `this`.
 *
 * @return {boolean|void}
 */
/**
 * The event options for adding function callback.
 *
 * @interface Highcharts.EventOptionsObject
 */ /**
* The order the event handler should be called. This opens for having one
* handler be called before another, independent of in which order they were
* added.
* @name Highcharts.EventOptionsObject#order
* @type {number}
*/ /**
* Whether an event should be passive or not.
* When set to `true`, the function specified by listener will never call
* `preventDefault()`.
* @name Highcharts.EventOptionsObject#passive
* @type boolean
*/
/**
 * Formats data as a string. Usually the data is accessible through the `this`
 * keyword.
 *
 * @callback Highcharts.FormatterCallbackFunction<T>
 *
 * @param {T} this
 *        Context to format
 *
 * @param {T} [ctx]
 *        Since v12.6.0, the callback context is passed as an extra argument,
 *        so arrow functions can access the same context as regular functions
 *        using `this`.
 *
 * @return {string}
 *         Formatted text
 */
/**
 * An object of key-value pairs for HTML attributes.
 *
 * @typedef {Highcharts.Dictionary<boolean|number|string|Function>} Highcharts.HTMLAttributes
 */
/**
 * An HTML DOM element. The type is a reference to the regular HTMLElement in
 * the global scope.
 *
 * @typedef {global.HTMLElement} Highcharts.HTMLDOMElement
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement
 */
/**
 * The iterator callback.
 *
 * @callback Highcharts.ObjectEachCallbackFunction<T>
 *
 * @param {T} this
 *        The context.
 *
 * @param {*} value
 *        The property value.
 *
 * @param {string} key
 *        The property key.
 *
 * @param {*} obj
 *        The object that objectEach is being applied to.
 */
/**
 * An object containing `left` and `top` properties for the position in the
 * page.
 *
 * @interface Highcharts.OffsetObject
 */ /**
* Left distance to the page border.
* @name Highcharts.OffsetObject#left
* @type {number}
*/ /**
* Top distance to the page border.
* @name Highcharts.OffsetObject#top
* @type {number}
*/
/**
 * Describes a range.
 *
 * @interface Highcharts.RangeObject
 */ /**
* Maximum number of the range.
* @name Highcharts.RangeObject#max
* @type {number}
*/ /**
* Minimum number of the range.
* @name Highcharts.RangeObject#min
* @type {number}
*/
/**
 * If a number is given, it defines the pixel length. If a percentage string is
 * given, like for example `'50%'`, the setting defines a length relative to a
 * base size, for example the size of a container.
 *
 * @typedef {number|string} Highcharts.RelativeSize
 */
/**
 * Proceed function to call original (wrapped) function.
 *
 * @callback Highcharts.WrapProceedFunction
 *
 * @param {*} [arg1]
 *        Optional argument. Without any arguments defaults to first argument of
 *        the wrapping function.
 *
 * @param {*} [arg2]
 *        Optional argument. Without any arguments defaults to second argument
 *        of the wrapping function.
 *
 * @param {*} [arg3]
 *        Optional argument. Without any arguments defaults to third argument of
 *        the wrapping function.
 *
 * @return {*}
 *         Return value of the original function.
 */
/**
 * The Highcharts object is the placeholder for all other members, and various
 * utility functions. The most important member of the namespace would be the
 * chart constructor.
 *
 * @example
 * let chart = Highcharts.chart('container', { ... });
 *
 * @namespace Highcharts
 */
''; // Detach doclets above

;// ./code/es-modules/Shared/TimeBase.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { pageLang, win: TimeBase_win } = Core_Globals;


/* *
 *
 *  Constants
 *
 * */
// To do: Remove this when we no longer need support for Safari < v14.1
const hasOldSafariBug = Core_Globals.isSafari &&
    TimeBase_win.Intl &&
    !TimeBase_win.Intl.DateTimeFormat.prototype.formatRange;
const isDateTimeFormatOptions = (obj) => obj.main === void 0;
/* *
 *
 *  Class
 *
 * */
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
 * The Time class. Time settings are applied in general for each page using
 * `Highcharts.setOptions`, or individually for each Chart item through the
 * [time](https://api.highcharts.com/highcharts/time) options set.
 *
 * The Time object is available from {@link Highcharts.Chart#time}, which refers
 * to  `Highcharts.time` unless individual time settings are applied for each
 * chart.
 *
 * When configuring time settings for individual chart instances, be aware that
 * using `Highcharts.dateFormat` or `Highcharts.time.dateFormat` within
 * formatter callbacks relies on the global time object, which applies the
 * global language and time zone settings. To ensure charts with local time
 * settings function correctly, use `chart.time.dateFormat? instead. However,
 * the recommended best practice is to use `setOptions` to define global time
 * settings unless specific configurations are needed for each chart.
 *
 * @example
 * // Apply time settings globally
 * Highcharts.setOptions({
 *     time: {
 *         timezone: 'Europe/London'
 *     }
 * });
 *
 * // Apply time settings by instance
 * const chart = Highcharts.chart('container', {
 *     time: {
 *         timezone: 'America/New_York'
 *     },
 *     series: [{
 *         data: [1, 4, 3, 5]
 *     }]
 * });
 *
 * // Use the Time object of a chart instance
 * console.log(
 *        'Current time in New York',
 *        chart.time.dateFormat('%Y-%m-%d %H:%M:%S', Date.now())
 * );
 *
 * // Standalone use
 * const time = new Highcharts.Time({
 *    timezone: 'America/New_York'
 * });
 * const s = time.dateFormat('%Y-%m-%d %H:%M:%S', Date.UTC(2020, 0, 1));
 * console.log(s); // => 2019-12-31 19:00:00
 *
 * @since 6.0.5
 *
 * @class
 * @name Highcharts.Time
 *
 * @param {Highcharts.TimeOptions} [options] Time options as defined in
 * [chart.options.time](/highcharts/time).
 */
class TimeBase {
    /* *
     *
     *  Constructors
     *
     * */
    constructor(options, lang) {
        /* *
         *
         *  Properties
         *
         * */
        this.options = {
            timezone: 'UTC'
        };
        this.variableTimezone = false;
        this.Date = TimeBase_win.Date;
        this.update(options);
        this.lang = lang;
    }
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Update the Time object with current options. It is called internally on
     * initializing Highcharts, after running `Highcharts.setOptions` and on
     * `Chart.update`.
     *
     * @internal
     * @function Highcharts.Time#update
     *
     * @param {Highcharts.TimeOptions} [options]
     *
     */
    update(options = {}) {
        this.dTLCache = {};
        this.options = options = merge(true, this.options, options);
        const { timezoneOffset, useUTC, locale } = options;
        // Allow using a different Date class
        this.Date = options.Date || TimeBase_win.Date || Date;
        // Assign the time zone. Handle the legacy, deprecated `useUTC` option.
        let timezone = options.timezone;
        if (defined(useUTC)) {
            timezone = useUTC ? 'UTC' : void 0;
        }
        // The Etc/GMT time zones do not support offsets with half-hour
        // resolutions
        if (timezoneOffset && timezoneOffset % 60 === 0) {
            timezone = 'Etc/GMT' + ((timezoneOffset > 0 ? '+' : '')) + timezoneOffset / 60;
        }
        /*
         * The time object has options allowing for variable time zones, meaning
         * the axis ticks or series data needs to consider this.
         */
        this.variableTimezone = timezone !== 'UTC' &&
            timezone?.indexOf('Etc/GMT') !== 0;
        this.timezone = timezone;
        // Update locale.
        if (this.lang && locale) {
            this.lang.locale = locale;
        }
        // Assign default time formats from locale strings
        ['months', 'shortMonths', 'weekdays', 'shortWeekdays'].forEach((name) => {
            const isMonth = /months/i.test(name), isShort = /short/.test(name), options = {
                timeZone: 'UTC'
            };
            options[isMonth ? 'month' : 'weekday'] = isShort ? 'short' : 'long';
            this[name] = (isMonth ?
                [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] :
                [3, 4, 5, 6, 7, 8, 9]).map((position) => this.dateFormat(options, (isMonth ? 31 : 1) * 24 * 36e5 * position));
        });
    }
    /**
     * Get a date in terms of numbers (year, month, day etc) for further
     * processing. Takes the current `timezone` setting into account. Inverse of
     * `makeTime` and the native `Date` constructor and `Date.UTC`.
     *
     * The date is returned in array format with the following indices:
     *
     * 0: year,
     * 1: month (zero based),
     * 2: day,
     * 3: hours,
     * 4: minutes,
     * 5: seconds,
     * 6: milliseconds,
     * 7: weekday (Sunday as 0)
     *
     * @function Highcharts.Time#toParts
     *
     * @param {number|Date} [timestamp]
     *                 The timestamp in milliseconds since January 1st 1970.
     *                 A Date object is also accepted.
     *
     * @return {Array<number>} The date parts in array format.
     */
    toParts(timestamp) {
        const [weekday, dayOfMonth, month, year, hours, minutes, seconds] = this.dateTimeFormat({
            weekday: 'narrow',
            day: 'numeric',
            month: 'numeric',
            year: 'numeric',
            hour: 'numeric',
            minute: 'numeric',
            second: 'numeric'
        }, timestamp, 'es')
            // The ', ' splitter is for all modern browsers:
            //      L, 6/3/2023, 14:30:00
            // The ' ' splitter is for legacy Safari with no comma between date
            // and time (#22445):
            //      L, 6/3/2023 14:30:00
            .split(/(?:, | |\/|:)/g);
        return [
            +year,
            +month - 1,
            +dayOfMonth,
            +hours,
            +minutes,
            +seconds,
            // Milliseconds
            Math.floor(Number(timestamp) || 0) % 1000,
            // Spanish weekday index
            'DLMXJVS'.indexOf(weekday)
        ];
    }
    /**
     * Shorthand to get a cached `Intl.DateTimeFormat` instance.
     */
    dateTimeFormat(options, timestamp, locale = this.options.locale || pageLang) {
        const cacheKey = JSON.stringify(options) + locale;
        if (isString(options)) {
            options = this.str2dtf(options);
        }
        let dTL = this.dTLCache[cacheKey];
        if (!dTL) {
            options.timeZone ?? (options.timeZone = this.timezone);
            try {
                dTL = new Intl.DateTimeFormat(locale, options);
            }
            catch (e) {
                if (/Invalid time zone/i.test(e.message)) {
                    error(34);
                    options.timeZone = 'UTC';
                    dTL = new Intl.DateTimeFormat(locale, options);
                }
                else {
                    error(e.message, false);
                }
            }
        }
        this.dTLCache[cacheKey] = dTL;
        return dTL?.format(timestamp) || '';
    }
    /**
     * Take a locale-aware string format and return a full DateTimeFormat in
     * object form.
     */
    str2dtf(s, dtf = {}) {
        const mapping = {
            L: { fractionalSecondDigits: 3 },
            S: { second: '2-digit' },
            M: { minute: 'numeric' },
            H: { hour: '2-digit' },
            k: { hour: 'numeric' },
            E: { weekday: 'narrow' },
            a: { weekday: 'short' },
            A: { weekday: 'long' },
            d: { day: '2-digit' },
            e: { day: 'numeric' },
            b: { month: 'short' },
            B: { month: 'long' },
            m: { month: '2-digit' },
            o: { month: 'numeric' },
            y: { year: '2-digit' },
            Y: { year: 'numeric' }
        };
        Object.keys(mapping).forEach((key) => {
            if (s.indexOf(key) !== -1) {
                extend(dtf, mapping[key]);
            }
        });
        return dtf;
    }
    /**
     * Make a time and returns milliseconds. Similar to `Date.UTC`, but takes
     * the current `timezone` setting into account.
     *
     * @function Highcharts.Time#makeTime
     *
     * @param {number} year
     *        The year
     *
     * @param {number} month
     *        The month. Zero-based, so January is 0.
     *
     * @param {number} [date=1]
     *        The day of the month
     *
     * @param {number} [hours=0]
     *        The hour of the day, 0-23.
     *
     * @param {number} [minutes=0]
     *        The minutes
     *
     * @param {number} [seconds=0]
     *        The seconds
     *
     * @return {number}
     *         The time in milliseconds since January 1st 1970.
     */
    makeTime(year, month, date = 1, hours = 0, minutes, seconds, milliseconds) {
        // eslint-disable-next-line new-cap
        let d = this.Date.UTC(year, month, date, hours, minutes || 0, seconds || 0, milliseconds || 0);
        if (this.timezone !== 'UTC') {
            const offset = this.getTimezoneOffset(d);
            d += offset;
            // Adjustments close to DST transitions
            if (
            // Optimize for speed by limiting the number of calls to
            // `getTimezoneOffset`. According to
            // https://en.wikipedia.org/wiki/Daylight_saving_time_by_country,
            // DST change may only occur in these months.
            [2, 3, 8, 9, 10, 11].indexOf(month) !== -1 &&
                // DST transitions occur only in the night-time
                (hours < 5 || hours > 20)) {
                const newOffset = this.getTimezoneOffset(d);
                if (offset !== newOffset) {
                    d += newOffset - offset;
                    // A special case for transitioning from summer time to winter
                    // time. When the clock is set back, the same time is repeated
                    // twice, i.e. 02:30 am is repeated since the clock is set back
                    // from 3 am to 2 am. We need to make the same time as local
                    // Date does.
                }
                else if (offset - 36e5 === this.getTimezoneOffset(d - 36e5) &&
                    !hasOldSafariBug) {
                    d -= 36e5;
                }
            }
        }
        return d;
    }
    /**
     * Parse a datetime string. Unless the string contains time zone
     * information, apply the current `timezone` from options. If the argument
     * is a number, return it.
     *
     * @function Highcharts.Time#parse
     * @param    {string|number|undefined} s The datetime string to parse
     * @return   {number|undefined}          Parsed JavaScript timestamp
     */
    parse(s) {
        if (!isString(s)) {
            return s ?? void 0;
        }
        s = s
            // Firefox fails on YYYY/MM/DD
            .replace(/\//g, '-')
            // Replace some non-standard notations
            .replace(/(GMT|UTC)/, '');
        // Extend shorthand hour timezone offset like +02
        // .replace(/([+-][0-9]{2})$/, '$1:00');
        // Check if the string has time zone information
        const hasTimezone = s.indexOf('Z') > -1 ||
            /([+-][0-9]{2}):?[0-9]{2}$/.test(s), 
        // YYYY-MM-DD and YYYY-MM are always UTC
        isYYYYMMDD = /^[0-9]{4}-[0-9]{2}(-[0-9]{2}|)$/.test(s);
        if (!hasTimezone && !isYYYYMMDD) {
            s += 'Z';
        }
        const ts = Date.parse(s);
        if (isNumber(ts)) {
            // Unless the string contains time zone information, convert from
            // the local time result of `Date.parse` via UTC into the current
            // timezone of the time object.
            return ts + ((!hasTimezone || isYYYYMMDD) ?
                this.getTimezoneOffset(ts) :
                0);
        }
    }
    /**
     * Get the time zone offset based on the current timezone information as
     * set in the global options.
     *
     * @function Highcharts.Time#getTimezoneOffset
     *
     * @param {number} timestamp
     *        The JavaScript timestamp to inspect.
     *
     * @return {number}
     *         The timezone offset in minutes compared to UTC.
     */
    getTimezoneOffset(timestamp) {
        if (this.timezone !== 'UTC') {
            // eslint-disable-next-line @typescript-eslint/no-unused-vars
            const [date, gmt, hours, colon, minutes = 0] = this.dateTimeFormat({ timeZoneName: 'shortOffset' }, timestamp, 'en')
                .split(/(GMT|:)/)
                .map(Number), offset = -(hours + minutes / 60) * 60 * 60000;
            // Possible future NaNs stop here
            if (isNumber(offset)) {
                return offset;
            }
        }
        return 0;
    }
    /**
     * Formats a JavaScript date timestamp (milliseconds since January 1 1970)
     * into a human readable date string.
     *
     * The `format` parameter accepts two types of values:
     * - An object containing settings that are passed directly on to
     *   [Intl.DateTimeFormat.prototype.format](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/format).
     * - A format string containing either individual or locale-aware format
     *   keys. **Individual keys**, for example `%Y-%m-%d`, are listed below.
     *   **Locale-aware keys** are grouped by square brackets, for example
     *   `%[Ymd]`. The order of keys within the square bracket doesn't affect
     *   the output, which is determined by the locale. See example below.
     *   Internally, the locale-aware format keys are just a shorthand for the
     *   full object formats, but are particularly practical in
     *   [templating](https://www.highcharts.com/docs/chart-concepts/templating)
     *   where full object definitions are not an option.
     *
     * The available string format keys are listed below. Additional formats can
     * be given in the {@link Highcharts.dateFormats} hook.
     *
     * Supported format keys:
     * | Key  | Description                     | Notes on locale-aware format |
     * -------|----------------------------------------------|-------|
     * | `%A` | Long weekday, like 'Monday'                  |       |
     * | `%a` | Short weekday, like 'Mon'                    |       |
     * | `%E` | Narrow weekday, single character             |       |
     * | `%d` | Two digit day of the month, 01 to 31         |       |
     * | `%e` | Day of the month, 1 through 31               |       |
     * | `%w` | Day of the week, 0 through 6                 | N/A   |
     * | `%v` | The prefix "week from", read from `lang.weekFrom` | N/A |
     * | `%b` | Short month, like 'Jan'                      |       |
     * | `%B` | Long month, like 'January'                   |       |
     * | `%m` | Two digit month number, 01 through 12        |       |
     * | `%o` | Month number, 1 through 12                   |       |
     * | `%y` | Two digits year, like 24 for 2024            |       |
     * | `%Y` | Four digits year, like 2024                  |       |
     * | `%H` | Two digits hours in 24h format, 00 through 23 | Depending on the locale, 12h format may be inserted. |
     * | `%k` | Hours in 24h format, 0 through 23            | Depending on the locale, 12h format may be inserted. |
     * | `%I` | Two digits hours in 12h format, 00 through 11 | N/A. The locale determines the hour format. |
     * | `%l` | Hours in 12h format, 1 through 12            | N/A. The locale determines the hour format. |
     * | `%M` | Two digits minutes, 00 through 59            |       |
     * | `%p` | Upper case AM or PM                          | N/A. The locale determines whether to add AM and PM. |
     * | `%P` | Lower case AM or PM                          | N/A. The locale determines whether to add AM and PM. |
     * | `%S` | Two digits seconds, 00 through 59            |       |
     * | `%L` | Milliseconds (naming from Ruby)              |       |
     *
     * @example
     * // Object format, US English
     * const time1 = new Highcharts.Time({ locale: 'en-US' });
     * console.log(
     *     time1.dateFormat({
     *         day: 'numeric',
     *         month: 'short',
     *         year: 'numeric',
     *         hour: 'numeric',
     *         minute: 'numeric'
     *     }, Date.UTC(2024, 11, 31))
     * ); // => Dec 31, 2024, 12:00 AM
     *
     * // Object format, British English
     * const time2 = new Highcharts.Time({ locale: 'en-GB' });
     * console.log(
     *     time2.dateFormat({
     *         day: 'numeric',
     *         month: 'short',
     *         year: 'numeric',
     *         hour: 'numeric',
     *         minute: 'numeric'
     *     }, Date.UTC(2024, 11, 31))
     * ); // => 31 Dec 2024, 00:00
     *
     * // Individual key string replacement
     * const time3 = new Highcharts.Time();
     * console.log(
     *     time3.dateFormat('%Y-%m-%d %H:%M:%S', Date.UTC(2024, 11, 31))
     * ); // => 2024-12-31 00:00:00
     *
     * // Locale-aware keys, US English
     * const time4 = new Highcharts.Time({ locale: 'en-US' });
     * console.log(
     *     time4.dateFormat('%[YebHM]', Date.UTC(2024, 11, 31))
     * ); // => Dec 31, 2024, 12:00 AM
     *
     * // Locale-aware keys, British English
     * const time5 = new Highcharts.Time({ locale: 'en-GB' });
     * console.log(
     *     time5.dateFormat('%[YebHM]', Date.UTC(2024, 11, 31))
     * ); // => 31 Dec 2024, 00:00
     *
     * // Mixed locale-aware and individual keys
     * console.log(
     *     time5.dateFormat('%[Yeb], %H:%M', Date.UTC(2024, 11, 31))
     * ); // => 31 Dec 2024, 00:00
     *
     * @function Highcharts.Time#dateFormat
     *
     * @param {string|Highcharts.DateTimeFormatOptions} format
     *        The desired string format where various time representations are
     *        prefixed with %, or an object representing the locale-aware format
     *        options.
     *
     * @param {number} [timestamp]
     *        The JavaScript timestamp.
     *
     * @param {boolean} [upperCaseFirst=false]
     *        Upper case first letter in the return.
     *
     * @return {string}
     *         The formatted date.
     */
    dateFormat(format, timestamp, upperCaseFirst) {
        const lang = this.lang;
        if (!defined(timestamp) || isNaN(timestamp)) {
            return lang?.invalidDate || '';
        }
        format = format ?? '%Y-%m-%d %H:%M:%S';
        // First, identify and replace locale-aware formats like %[Ymd]
        if (isString(format)) {
            const localeAwareRegex = /%\[([a-zA-Z]+)\]/g;
            let match;
            while ((match = localeAwareRegex.exec(format))) {
                format = format.replace(match[0], this.dateTimeFormat(match[1], timestamp, lang?.locale));
            }
        }
        // Then, replace static formats like %Y, %m, %d etc.
        if (isString(format) && format.indexOf('%') !== -1) {
            const time = this, [fullYear, month, dayOfMonth, hours, minutes, seconds, milliseconds, weekday] = this.toParts(timestamp), langWeekdays = lang?.weekdays || this.weekdays, shortWeekdays = lang?.shortWeekdays || this.shortWeekdays, months = lang?.months || this.months, shortMonths = lang?.shortMonths || this.shortMonths, 
            // List all format keys. Custom formats can be added from the
            // outside.
            replacements = extend({
                // Day
                // Short weekday, like 'Mon'
                a: shortWeekdays ?
                    shortWeekdays[weekday] :
                    langWeekdays[weekday].substr(0, 3),
                // Long weekday, like 'Monday'
                A: langWeekdays[weekday],
                // Two digit day of the month, 01 to 31
                d: pad(dayOfMonth),
                // Day of the month, 1 through 31
                e: pad(dayOfMonth, 2, ' '),
                // Day of the week, 0 through 6
                w: weekday,
                // Week (none implemented)
                // 'W': weekNumber(),
                v: lang?.weekFrom ?? '',
                // Month
                // Short month, like 'Jan'
                b: shortMonths[month],
                // Long month, like 'January'
                B: months[month],
                // Two digit month number, 01 through 12
                m: pad(month + 1),
                // Month number, 1 through 12 (#8150)
                o: month + 1,
                // Year
                // Two digits year, like 09 for 2009
                y: fullYear.toString().substr(2, 2),
                // Four digits year, like 2009
                Y: fullYear,
                // Time
                // Two digits hours in 24h format, 00 through 23
                H: pad(hours),
                // Hours in 24h format, 0 through 23
                k: hours,
                // Two digits hours in 12h format, 00 through 11
                I: pad((hours % 12) || 12),
                // Hours in 12h format, 1 through 12
                l: (hours % 12) || 12,
                // Two digits minutes, 00 through 59
                M: pad(minutes),
                // Upper case AM or PM
                p: hours < 12 ? 'AM' : 'PM',
                // Lower case AM or PM
                P: hours < 12 ? 'am' : 'pm',
                // Two digits seconds, 00 through 59
                S: pad(seconds),
                // Milliseconds (naming from Ruby)
                L: pad(milliseconds, 3)
            }, Core_Globals.dateFormats);
            // Do the replaces
            objectEach(replacements, function (val, key) {
                if (isString(format)) {
                    // Regex would do it in one line, but this is faster
                    while (format.indexOf('%' + key) !== -1) {
                        format = format.replace('%' + key, typeof val === 'function' ?
                            val.call(time, timestamp, time) :
                            val);
                    }
                }
            });
        }
        else if (isObject(format)) {
            const tzHours = (this.getTimezoneOffset(timestamp) || 0) /
                (60000 * 60), timeZone = this.timezone || ('Etc/GMT' + (tzHours >= 0 ? '+' : '') + tzHours), { prefix = '', suffix = '' } = format;
            format = prefix + this.dateTimeFormat(extend({ timeZone }, format), timestamp) + suffix;
        }
        // Optionally sentence-case the string and return
        return upperCaseFirst ? ucfirst(format) : format;
    }
    /**
     * Resolve legacy formats of dateTimeLabelFormats (strings and arrays) into
     * an object.
     * @internal
     * @param {string|Array<T>|Highcharts.Dictionary<T>} f
     * General format description
     * @return {Highcharts.Dictionary<T>}
     * The object definition
     */
    resolveDTLFormat(f) {
        if (!isObject(f, true)) { // Check for string or array
            f = splat(f);
            return {
                main: f[0],
                from: f[1],
                to: f[2]
            };
        }
        // Type-check DateTimeFormatOptions against DateTimeLabelFormatObject
        if (isObject(f, true) && isDateTimeFormatOptions(f)) {
            return { main: f };
        }
        return f;
    }
    /**
     * Get the optimal date format for a point, based on a range.
     *
     * @internal
     * @function Highcharts.Time#getDateFormat
     *
     * @param {number} range
     *        The time range
     *
     * @param {number} timestamp
     *        The timestamp of the date
     *
     * @param {number} startOfWeek
     *        An integer representing the first day of the week, where 0 is
     *        Sunday.
     *
     * @param {Highcharts.Dictionary<string>} dateTimeLabelFormats
     *        A map of time units to formats.
     *
     * @return {string}
     *         The optimal date format for a point.
     */
    getDateFormat(range, timestamp, startOfWeek, dateTimeLabelFormats) {
        const dateStr = this.dateFormat('%m-%d %H:%M:%S.%L', timestamp), blank = '01-01 00:00:00.000', strpos = {
            millisecond: 15,
            second: 12,
            minute: 9,
            hour: 6,
            day: 3
        };
        let n = 'millisecond', 
        // For sub-millisecond data, #4223
        lastN = n;
        for (n in timeUnits) { // eslint-disable-line guard-for-in
            // If the range is exactly one week and we're looking at a
            // Sunday/Monday, go for the week format
            if (range &&
                range === timeUnits.week &&
                +this.dateFormat('%w', timestamp) === startOfWeek &&
                dateStr.substr(6) === blank.substr(6)) {
                n = 'week';
                break;
            }
            // The first format that is too great for the range
            if (range && timeUnits[n] > range) {
                n = lastN;
                break;
            }
            // If the point is placed every day at 23:59, we need to show
            // the minutes as well. #2637.
            if (strpos[n] &&
                dateStr.substr(strpos[n]) !== blank.substr(strpos[n])) {
                break;
            }
            // Weeks are outside the hierarchy, only apply them on
            // Mondays/Sundays like in the first condition
            if (n !== 'week') {
                lastN = n;
            }
        }
        return this.resolveDTLFormat(dateTimeLabelFormats[n]).main;
    }
}
/* *
 *
 * Default export
 *
 * */
/* harmony default export */ const Shared_TimeBase = (TimeBase);
/* *
 *
 * API Declarations
 *
 * */
/**
 * Normalized interval.
 *
 * @interface Highcharts.TimeNormalizedObject
 */ /**
* The count.
*
* @name Highcharts.TimeNormalizedObject#count
* @type {number|undefined}
*/ /**
* The interval in axis values (ms).
*
* @name Highcharts.TimeNormalizedObject#unitRange
* @type {number}
*/
/**
 * Function of an additional date format specifier.
 *
 * @callback Highcharts.TimeFormatCallbackFunction
 *
 * @param {number} timestamp
 *        The time to format.
 *
 * @return {string}
 *         The formatted portion of the date.
 */
/**
 * Time ticks.
 *
 * @interface Highcharts.AxisTickPositionsArray
 * @extends global.Array<number>
 */ /**
* @name Highcharts.AxisTickPositionsArray#info
* @type {Highcharts.TimeTicksInfoObject|undefined}
*/
/**
 * A callback to return the time zone offset for a given datetime. It
 * takes the timestamp in terms of milliseconds since January 1 1970,
 * and returns the timezone offset in minutes. This provides a hook
 * for drawing time based charts in specific time zones using their
 * local DST crossover dates, with the help of external libraries.
 *
 * @callback Highcharts.TimezoneOffsetCallbackFunction
 *
 * @param {number} timestamp
 * Timestamp in terms of milliseconds since January 1 1970.
 *
 * @return {number}
 * Timezone offset in minutes.
 */
/**
 * Options for formatting dates and times using the [Intl.DateTimeFormat](
 * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat
 * ) API, and extended with some custom options for Highcharts.
 *
 * @interface Highcharts.DateTimeFormatOptions
 */ /**
* The locale matching algorithm to use.
*
* @name Highcharts.DateTimeFormatOptions#localeMatcher
* @type {string|undefined}
*/ /**
* The time zone to use. The default is the browser's default time zone.
*
* @name Highcharts.DateTimeFormatOptions#timeZone
* @type {string|undefined}
*/ /**
* Whether to use 12-hour time (as opposed to 24-hour time).
*
* @name Highcharts.DateTimeFormatOptions#hour12
* @type {'auto'|'always'|'never'|undefined}
*/ /**
* The format matching algorithm to use.
*
* @name Highcharts.DateTimeFormatOptions#formatMatcher
* @type {string|undefined}
*/ /**
* The representation of the weekday.
*
* @name Highcharts.DateTimeFormatOptions#weekday
* @type {'narrow'|'short'|'long'|undefined}
*/ /**
* The representation of the era.
*
* @name Highcharts.DateTimeFormatOptions#era
* @type {'narrow'|'short'|'long'|undefined}
*/ /**
* The representation of the year.
*
* @name Highcharts.DateTimeFormatOptions#year
* @type {'numeric'|'2-digit'|undefined}
*/ /**
* The representation of the month.
* "narrow", "short", "long".
*
* @name Highcharts.DateTimeFormatOptions#month
* @type {'numeric'|'2-digit'|'narrow'|'short'|'long'|undefined}
*/ /**
* The representation of the day.
*
* @name Highcharts.DateTimeFormatOptions#day
* @type {'numeric'|'2-digit'|undefined}
*/ /**
* The representation of the hour.
*
* @name Highcharts.DateTimeFormatOptions#hour
* @type {'numeric'|'2-digit'|undefined}
*/ /**
* The representation of the minute.
*
* @name Highcharts.DateTimeFormatOptions#minute
* @type {'numeric'|'2-digit'|undefined}
*/ /**
* The representation of the second.
*
* @name Highcharts.DateTimeFormatOptions#second
* @type {'numeric'|'2-digit'|undefined}
*/ /**
* The number of fractional digits to use. 3 means milliseconds.
*
* @name Highcharts.DateTimeFormatOptions#fractionalSecondDigits
* @type {1|2|3|undefined}
*/ /**
* The representation of the time zone name.
*
* @name Highcharts.DateTimeFormatOptions#timeZoneName
* @type {'short'|'long'|undefined}
*/ /**
* A prefix for the time string. Custom Highcharts option.
*
* @name Highcharts.DateTimeFormatOptions#prefix
* @type {'string'|undefined}
*/ /**
* A suffix for the time string. Custom Highcharts option.
*
* @name Highcharts.DateTimeFormatOptions#suffix
* @type {'string'|undefined}
*/
''; // Keeps doclets above in JS file

;// ./code/es-modules/Core/Time.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */




/* *
 *
 *  Constants
 *
 * */
class Time extends Shared_TimeBase {
    getBoundaryTicks(tickPositions, unitRange, visibleMin, visibleMax) {
        const boundaryTicks = {};
        // No boundary ticks for year range.
        if (unitRange === timeUnits.year) {
            return boundaryTicks;
        }
        const hasVisibleRange = defined(visibleMin) && defined(visibleMax), needsHourBoundary = unitRange < timeUnits.hour, needsDayBoundary = unitRange < timeUnits.day, needsMonthBoundary = unitRange < timeUnits.month, tickAlignment = (unitRange >= timeUnits.hour ?
            900000 : unitRange >= timeUnits.minute ?
            60000 : unitRange >= timeUnits.second ?
            1000 : false);
        // Handle boundary ticks. Use a reasonable dropout threshold
        // to prevent looping over dense data grouping (#6156).
        if (tickPositions.length < 10000) {
            let isFirstVisibleTick = true;
            for (let i = 0; i < tickPositions.length; i++) {
                const t = tickPositions[i];
                // Loop only over visible ticks.
                if (hasVisibleRange) {
                    if (t < visibleMin) {
                        continue;
                    }
                    if (t > visibleMax) {
                        break;
                    }
                }
                // Mark first visible tick as boundary, if timeUnit is month or
                // hour.
                if (isFirstVisibleTick) {
                    if (unitRange === timeUnits.month) {
                        boundaryTicks[t] = 'year';
                        isFirstVisibleTick = false;
                        continue;
                    }
                    if (unitRange === timeUnits.hour) {
                        boundaryTicks[t] = 'day';
                        isFirstVisibleTick = false;
                        continue;
                    }
                }
                // Skip misaligned ticks to save performance.
                if (tickAlignment && t % tickAlignment !== 0) {
                    isFirstVisibleTick = false;
                    continue;
                }
                const [, // Unused 'year' var
                month, day, hours, minutes, seconds, milliseconds] = this.toParts(t);
                const isMidnight = !hours && !minutes && !seconds && !milliseconds;
                if (needsHourBoundary && minutes === 0) {
                    boundaryTicks[t] = 'hour';
                }
                if (needsDayBoundary && isMidnight) {
                    boundaryTicks[t] = 'day';
                }
                if (needsMonthBoundary && day === 1 && isMidnight) {
                    boundaryTicks[t] = 'month';
                }
                if (month === 0 && day === 1 && isMidnight) {
                    boundaryTicks[t] = 'year';
                }
                isFirstVisibleTick = false;
            }
        }
        return boundaryTicks;
    }
    /**
     * Return an array with time positions distributed on round time values
     * right and right after min and max. Used in datetime axes as well as for
     * grouping data on a datetime axis.
     *
     * @function Highcharts.Time#getTimeTicks
     *
     * @param {Highcharts.TimeNormalizedObject} normalizedInterval
     *        The interval in axis values (ms) and the count
     *
     * @param {number} [min]
     *        The minimum in axis values
     *
     * @param {number} [max]
     *        The maximum in axis values
     *
     * @param {number} [startOfWeek=1]
     *
     * @return {Highcharts.AxisTickPositionsArray}
     * Time positions
     */
    getTimeTicks(normalizedInterval, min, max, startOfWeek) {
        const time = this, tickPositions = [], { count = 1, unitRange } = normalizedInterval, visibleMin = min, visibleMax = max;
        let [year, month, dayOfMonth, hours, minutes, seconds] = time.toParts(min), milliseconds = (min || 0) % 1000, variableDayLength;
        startOfWeek ?? (startOfWeek = 1);
        if (defined(min)) { // #1300
            milliseconds = unitRange >= timeUnits.second ?
                0 : // #3935
                count * Math.floor(milliseconds / count);
            if (unitRange >= timeUnits.second) { // Second
                seconds = unitRange >= timeUnits.minute ?
                    0 : // #3935
                    count * Math.floor(seconds / count);
            }
            if (unitRange >= timeUnits.minute) { // Minute
                minutes = unitRange >= timeUnits.hour ?
                    0 :
                    count * Math.floor(minutes / count);
            }
            if (unitRange >= timeUnits.hour) { // Hour
                hours = unitRange >= timeUnits.day ?
                    0 :
                    count * Math.floor(hours / count);
            }
            if (unitRange >= timeUnits.day) { // Day
                dayOfMonth = unitRange >= timeUnits.month ?
                    1 :
                    Math.max(1, count * Math.floor(dayOfMonth / count));
            }
            if (unitRange >= timeUnits.month) { // Month
                month = unitRange >= timeUnits.year ? 0 :
                    count * Math.floor(month / count);
            }
            if (unitRange >= timeUnits.year) { // Year
                year -= year % count;
            }
            // Week is a special case that runs outside the hierarchy
            if (unitRange === timeUnits.week) {
                if (count) {
                    min = time.makeTime(year, month, dayOfMonth, hours, minutes, seconds, milliseconds);
                }
                // Get start of current week, independent of count
                const weekday = this.dateTimeFormat({
                    timeZone: this.timezone,
                    weekday: 'narrow'
                }, min, 'es'), 
                // Spanish weekday index
                weekdayNo = 'DLMXJVS'.indexOf(weekday);
                dayOfMonth += -weekdayNo + startOfWeek +
                    // We don't want to skip days that are before
                    // startOfWeek (#7051)
                    (weekdayNo < startOfWeek ? -7 : 0);
            }
            min = time.makeTime(year, month, dayOfMonth, hours, minutes, seconds, milliseconds);
            // Handle local timezone offset
            if (time.variableTimezone && defined(max)) {
                // Detect whether we need to take the DST crossover into
                // consideration. If we're crossing over DST, the day length may
                // be 23h or 25h and we need to compute the exact clock time for
                // each tick instead of just adding hours. This comes at a cost,
                // so first we find out if it is needed (#4951).
                variableDayLength = (
                // Long range, assume we're crossing over.
                max - min > 4 * timeUnits.month ||
                    // Short range, check if min and max are in different time
                    // zones.
                    time.getTimezoneOffset(min) !==
                        time.getTimezoneOffset(max));
            }
            // Iterate and add tick positions at appropriate values
            let t = min, i = 1;
            while (t < max) {
                tickPositions.push(t);
                // Increase the years
                if (unitRange === timeUnits.year) {
                    t = time.makeTime(year + i * count, 0);
                    // Increase the months
                }
                else if (unitRange === timeUnits.month) {
                    t = time.makeTime(year, month + i * count);
                    // If we're using local time, the interval is not fixed as it
                    // jumps one hour at the DST crossover
                }
                else if (variableDayLength && (unitRange === timeUnits.day ||
                    unitRange === timeUnits.week)) {
                    t = time.makeTime(year, month, dayOfMonth +
                        i * count * (unitRange === timeUnits.day ? 1 : 7));
                }
                else if (variableDayLength &&
                    unitRange === timeUnits.hour &&
                    count > 1) {
                    // Make sure boundary ticks are preserved across DST (#6797,
                    // #7621)
                    t = time.makeTime(year, month, dayOfMonth, hours + i * count);
                    // Else, the interval is fixed and we use simple addition
                }
                else {
                    t += unitRange * count;
                }
                i++;
            }
            // Push the last time
            tickPositions.push(t);
        }
        tickPositions.info = extend(normalizedInterval, {
            boundaryTicks: this.getBoundaryTicks(tickPositions, unitRange, visibleMin, visibleMax),
            totalRange: unitRange * count
        });
        return tickPositions;
    }
}
/* *
 *
 * Default export
 *
 * */
/* harmony default export */ const Core_Time = (Time);

;// ./code/es-modules/Core/Defaults.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */



const { isTouchDevice } = Core_Globals;



/* *
 *
 *  API Options
 *
 * */
/**
 * Global default settings.
 *
 * @name Highcharts.defaultOptions
 * @type {Highcharts.Options}
 */ /**
* @optionparent
* @internal
*/
const defaultOptions = {
    palette: PaletteDefaults,
    /**
     * An array containing the default colors for the chart's series. When
     * all colors are used, new colors are pulled from the start again.
     *
     * Default colors can also be set on a series or series.type basis,
     * see [column.colors](#plotOptions.column.colors),
     * [pie.colors](#plotOptions.pie.colors).
     *
     * In styled mode, the colors option doesn't exist. Instead, colors
     * are defined in CSS and applied either through series or point class
     * names, or through the [chart.colorCount](#chart.colorCount) option.
     *
     * The defaults from v13 invoke CSS variables that are set by the
     * `palette` option's light and dark themes.
     *
     * @sample {highcharts} highcharts/chart/colors/
     *         Assign a global color theme
     * @sample highcharts/members/theme-v10/
     *         Latest release styled like version 10
     *
     * @type    {Array<Highcharts.ColorType>}
     * @default [
     *     'var(--highcharts-color-0)',
     *     'var(--highcharts-color-1)',
     *     'var(--highcharts-color-2)',
     *     'var(--highcharts-color-3)',
     *     'var(--highcharts-color-4)',
     *     'var(--highcharts-color-5)',
     *     'var(--highcharts-color-6)',
     *     'var(--highcharts-color-7)',
     *     'var(--highcharts-color-8)',
     *     'var(--highcharts-color-9)'
     * ]
     */
    colors: new Array(10).fill(1).map((_, i) => `var(--highcharts-color-${i})`),
    /**
     * Options for one or many chart-level data tables. The `dataTable` option,
     * or its array members, can be either configuration objects or instances of
     * the `DataTable` class. If a `DataTable` instance is passed, it
     * will be used directly. If a configuration object is passed, a new
     * `DataTable` instance will be created based on the provided
     * configuration.
     *
     * The data table is mapped to the series data points based on the
     * [series.dataMapping](#plotOptions.series.dataMapping) option, unless the
     * column keys match the point property names (`x`, `y` etc.), in which case
     * the mapping is automatic.
     *
     * @sample {highstock} stock/datatable/candlestick
     *         Candlestick chart with data table
     * @sample {highstock} stock/datatable/live-candlestick
     *         Live candlestick
     * @sample {highmaps} maps/datatable/chart-datatable
     *         Map with data table and data mapping
     * @sample {highmaps} maps/demo/basic-map
     *         World map
     * @sample {gantt} gantt/datatable/chart-datatable
     *         Gantt chart with data table
     *
     * @sample highcharts/datatable/chart-datatable-single/
     *         Chart with one data table as option
     * @sample highcharts/datatable/chart-datatable-single/
     *         Chart with one data table as instance
     * @sample highcharts/datatable/chart-datatable-multiple/
     *         Chart with two data tables
     * @sample highcharts/data/getdatatable
     *         Data table from CSV
     *
     * @type {Highcharts.DataTable|Highcharts.DataTableOptionsObject|Array<Highcharts.DataTable|Highcharts.DataTableOptionsObject>}
     * @since     13.0.0
     * @apioption dataTable
     */
    /**
     * Styled mode only. Configuration object for adding SVG definitions for
     * reusable elements. See [gradients, shadows and
     * patterns](https://www.highcharts.com/docs/chart-design-and-style/gradients-shadows-and-patterns)
     * for more information and code examples.
     *
     * @type      {*}
     * @since     5.0.0
     * @apioption defs
     */
    /**
     * @ignore-option
     */
    symbols: ['circle', 'diamond', 'square', 'triangle', 'triangle-down'],
    /**
     * An object containing language-related strings and settings. A typical
     * setup uses `Highcharts.setOptions` to make the options apply to all
     * charts in the same page.
     *
     * Some language options, like `months` and `weekdays`, are only used
     * with non-locale-aware date formats.
     *
     * ```js
     * Highcharts.setOptions({
     *     lang: {
     *         locale: 'fr'
     *     }
     * });
     * ```
     *
     * @optionparent lang
     */
    lang: {
        weekFrom: 'week from',
        /**
         * The default chart title.
         *
         * @since 12.2.0
         */
        chartTitle: 'Chart title',
        /**
         * The browser locale to use for date and number formatting. The actual
         * locale used for each chart is determined in three steps:
         * 1. If this `lang.locale` option is specified, it is used.
         * 2. Else, look for the closest ancestor HTML element with a `lang`
         *    attribute, typically the `<html>` element.
         * 3. If no 'lang' attribute is found, use the default browser locale.
         *
         * Use `en-GB`, British English, for approximate consistency with
         * Highcharts v < 12.
         *
         * @sample highcharts/lang/locale/
         *         Set the locale using the `lang.locale` option
         * @sample highcharts/lang/locale-attribute/
         *         Pick up the locale from the HTML `lang` attribute
         * @sample highcharts/members/highcharts-numberformat
         *         Arabic locale with digits and dates         *
         *
         * @since 12.0.0
         * @type {string|Array<string>}
         */
        locale: void 0,
        /**
         * The loading text that appears when the chart is set into the loading
         * state following a call to `chart.showLoading`.
         */
        loading: 'Loading...',
        /**
         * An array containing the months names. Corresponds to the `%B` format
         * in `Highcharts.dateFormat()`. Defaults to 'undefined',
         * meaning the default month names are used according to the
         * `lang.locale` or browser settings.
         *
         * @type    {Array<string>}
         */
        months: void 0,
        /**
         * [Format string](https://www.highcharts.com/docs/chart-concepts/templating) for the default series name.
         *
         * @since 12.2.0
         */
        seriesName: 'Series {add index 1}',
        /**
         * An array containing the months names in abbreviated form. Corresponds
         * to the `%b` format in `Highcharts.dateFormat()`. Defaults to
         * 'undefined', meaning the default short month names are used according
         * to the `lang.locale` or browser settings.
         *
         * @type    {Array<string>}
         */
        shortMonths: void 0,
        /**
         * An array containing the weekday names. Corresponds
         * to the `%A` format in `Highcharts.dateFormat()`. Defaults to
         * 'undefined', meaning the default weekday names are used according to
         * the `lang.locale` or browser settings.
         *
         * @type    {Array<string>}
         */
        weekdays: void 0,
        /**
         * Short week days, starting Sunday. Corresponds to the `%a` format in
         * `Highcharts.dateFormat()`. Defaults to 'undefined', meaning
         * the default short weekday names are used according to the
         * `lang.locale` or browser settings.
         *
         * @sample highcharts/lang/shortweekdays/
         *         Finnish two-letter abbreviations
         *
         * @type      {Array<string>}
         * @since     4.2.4
         * @apioption lang.shortWeekdays
         */
        /**
         * What to show in a date field for invalid dates. Defaults to an empty
         * string.
         *
         * @type      {string}
         * @since     4.1.8
         * @product   highcharts highstock
         * @apioption lang.invalidDate
         */
        /**
         * The title appearing on hovering the zoom in button. The text itself
         * defaults to "+" and can be changed in the button options.
         *
         * @type      {string}
         * @default   Zoom in
         * @product   highmaps
         * @apioption lang.zoomIn
         */
        /**
         * The title appearing on hovering the zoom out button. The text itself
         * defaults to "-" and can be changed in the button options.
         *
         * @type      {string}
         * @default   Zoom out
         * @product   highmaps
         * @apioption lang.zoomOut
         */
        /**
         * The default decimal point used in the `Highcharts.numberFormat`
         * method unless otherwise specified in the function arguments. Defaults
         * to the locale decimal point as determined by `lang.locale`.
         *
         * @type      {string}
         * @default   undefined
         * @since     1.2.2
         * @apioption lang.decimalPoint
         */
        /**
         * [Metric prefixes](https://en.wikipedia.org/wiki/Metric_prefix) used
         * to shorten high numbers in axis labels. Replacing any of the
         * positions with `null` causes the full number to be written. Setting
         * `numericSymbols` to `undefined` disables shortening altogether.
         *
         * @sample {highcharts} highcharts/lang/numericsymbols/
         *         Replacing the symbols with text
         * @sample {highstock} highcharts/lang/numericsymbols/
         *         Replacing the symbols with text
         *
         * @type    {Array<string>}
         * @default ["k", "M", "G", "T", "P", "E"]
         * @since   2.3.0
         */
        numericSymbols: ['k', 'M', 'G', 'T', 'P', 'E'],
        /**
         * The default name for a pie slice (point).
         * @since 12.2.0
         */
        pieSliceName: 'Slice',
        /**
         * The magnitude of [numericSymbols](#lang.numericSymbol) replacements.
         * Use 10000 for Japanese, Korean and various Chinese locales, which
         * use symbols for 10^4, 10^8 and 10^12.
         *
         * @sample highcharts/lang/numericsymbolmagnitude/
         *         10000 magnitude for Japanese
         *
         * @type      {number}
         * @default   1000
         * @since     5.0.3
         * @apioption lang.numericSymbolMagnitude
         */
        /**
         * The default thousands separator used in the `Highcharts.numberFormat`
         * method unless otherwise specified in the function arguments. Defaults
         * to the locale thousands separator as determined by `lang.locale`.
         *
         * @type      {string}
         * @default   undefined
         * @since     1.2.2
         * @apioption lang.thousandsSep
         */
        /**
         * The text for the label appearing when a chart is zoomed.
         *
         * @since 1.2.4
         */
        resetZoom: 'Reset zoom',
        /**
         * The default title of the Y axis
         *
         * @since 12.2.0
         */
        yAxisTitle: 'Values',
        /**
         * The tooltip title for the label appearing when a chart is zoomed.
         *
         * @since 1.2.4
         */
        resetZoomTitle: 'Reset zoom level 1:1'
    },
    /**
     * Global options that don't apply to each chart. These options must be set
     * using the `Highcharts.setOptions` method.
     *
     * ```js
     * Highcharts.setOptions({
     *     global: {
     *         buttonTheme: {
     *             fill: '#d0d0d0'
     *         }
     *     }
     * });
     * ```
     */
    global: {
        /**
         * General theme for buttons. This applies to the zoom button, exporting
         * context menu, map navigation, range selector buttons and custom
         * buttons generated using the `SVGRenderer.button` function. However,
         * each of these may be overridden with more specific options.
         *
         * @sample highcharts/global/buttontheme
         *         General button theme
         * @since 11.4.2
         */
        buttonTheme: {
            /**
             * The fill color for buttons
             */
            fill: 'var(--highcharts-neutral-color-3)',
            /**
             * The padding of buttons
             */
            padding: 8,
            /**
             * The border radius for buttons
             */
            r: 2,
            /**
             * The stroke color for buttons
             */
            stroke: 'var(--highcharts-neutral-color-20)',
            /**
             * The stroke width for buttons
             */
            'stroke-width': 1,
            /**
             * CSS styling for the buttons' text
             */
            style: {
                /**
                 * @type {Highcharts.ColorType}
                 */
                color: 'var(--highcharts-neutral-color-80)',
                cursor: 'pointer',
                fontSize: '0.8em',
                fontWeight: 'normal'
            },
            /**
             * State overrides for the buttons
             */
            states: {
                /**
                 * Hover state overrides for the buttons are applied in addition
                 * to the normal state options
                 */
                hover: {
                    fill: 'var(--highcharts-neutral-color-10)'
                },
                /**
                 * Select state overrides for the buttons are applied in
                 * addition to the normal state options
                 */
                select: {
                    /**
                     * @type {Highcharts.ColorType}
                     */
                    fill: 'var(--highcharts-highlight-color-10)',
                    style: {
                        /**
                         * @type {Highcharts.ColorType}
                         */
                        color: 'var(--highcharts-neutral-color-100)',
                        fontWeight: 'bold'
                    }
                },
                /**
                 * Disabled state overrides for the buttons are applied in
                 * addition to the normal state options
                 */
                disabled: {
                    /**
                     * Disabled state CSS style overrides for the buttons' text
                     */
                    style: {
                        color: 'var(--highcharts-neutral-color-20)'
                    }
                }
            }
        }
    },
    /**
     * Time options that can apply globally or to individual charts. These
     * settings affect how `datetime` axes are laid out, how tooltips are
     * formatted, how series
     * [pointIntervalUnit](#plotOptions.series.pointIntervalUnit) works and how
     * the Highcharts Stock range selector handles time.
     *
     * The common use case is that all charts in the same Highcharts object
     * share the same time settings, in which case the global settings are set
     * using `setOptions`.
     *
     * ```js
     * // Apply time settings globally
     * Highcharts.setOptions({
     *     time: {
     *         timezone: 'Europe/London'
     *     }
     * });
     * // Apply time settings by instance
     * const chart = Highcharts.chart('container', {
     *     time: {
     *         timezone: 'America/New_York'
     *     },
     *     series: [{
     *         data: [1, 4, 3, 5]
     *     }]
     * });
     *
     * // Use the Time object
     * console.log(
     *        'Current time in New York',
     *        chart.time.dateFormat('%Y-%m-%d %H:%M:%S', Date.now())
     * );
     * ```
     *
     * Since v6.0.5, the time options were moved from the `global` object to the
     * `time` object, and time options can be set on each individual chart.
     *
     * @sample {highcharts|highstock}
     *         highcharts/time/timezone/
     *         Set the timezone globally
     * @sample {highcharts}
     *         highcharts/time/individual/
     *         Set the timezone per chart instance
     * @sample {highstock}
     *         stock/time/individual/
     *         Set the timezone per chart instance
     *
     * @since     6.0.5
     * @optionparent time
     */
    time: {
        /**
         * A custom `Date` class for advanced date handling. For example,
         * [JDate](https://github.com/tahajahangir/jdate) can be hooked in to
         * handle Jalali dates.
         *
         * @type      {*}
         * @since     4.0.4
         * @product   highcharts highstock gantt
         */
        Date: void 0,
        /**
         * A named time zone. Supported time zone names rely on the browser
         * implementations, as described in the [mdn
         * docs](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/DateTimeFormat/DateTimeFormat#timezone).
         * If the given time zone is not recognized by the browser, Highcharts
         * provides a warning and falls back to returning a 0 offset,
         * corresponding to the UTC time zone.
         *
         * The time zone affects axis scaling, tickmark placement and
         * time display in `Highcharts.dateFormat`.
         *
         * Setting `timezone` to `undefined` falls back to the default browser
         * timezone setting.
         *
         * Until v11.2.0, this option depended on moment.js.
         *
         * @sample {highcharts|highstock} highcharts/time/timezone/ Europe/Oslo
         *
         * @type      {string}
         * @since     5.0.7
         * @product   highcharts highstock gantt
         */
        timezone: 'UTC',
        /**
         * The timezone offset in minutes. Positive values are west, negative
         * values are east of UTC, as in the ECMAScript
         * [getTimezoneOffset](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Date/getTimezoneOffset)
         * method. Use this to display UTC based data in a predefined time zone.
         *
         * This option is deprecated as of v11.4.1 and will be removed in a
         * future release. Use the [time.timezone](#time.timezone) option
         * instead.
         *
         * @see [time.getTimezoneOffset](#time.getTimezoneOffset)
         *
         * @sample {highcharts|highstock} highcharts/time/timezoneoffset/
         *         Timezone offset
         *
         * @since     3.0.8
         * @deprecated 11.4.2
         * @product   highcharts highstock gantt
         */
        timezoneOffset: 0,
        /**
         * Whether to use UTC time for axis scaling, tickmark placement and
         * time display in `Highcharts.dateFormat`. Advantages of using UTC
         * is that the time displays equally regardless of the user agent's
         * time zone settings. Local time can be used when the data is loaded
         * in real time or when correct Daylight Saving Time transitions are
         * required.
         *
         * Setting `useUTC` to true is equivalent to setting `time.timezone` to
         * `"UTC"`. Setting `useUTC` to false is equivalent to setting
         * `time.timezone` to `undefined`.
         *
         * @see [timezone](#time.timezone)
         *
         * @sample {highcharts} highcharts/time/useutc-true/
         *         True by default
         * @sample {highcharts} highcharts/time/useutc-false/
         *         False
         *
         * @deprecated 12.0.0
         */
        useUTC: void 0
    },
    chart: Chart_ChartDefaults,
    /**
     * The chart's main title.
     *
     * @sample {highmaps} maps/title/title/
     *         Title options demonstrated
     * @sample {highcharts} highcharts/title/align-auto/
     *         Default title alignment
     */
    title: {
        /**
         * When the title is floating, the plot area will not move to make space
         * for it.
         *
         * @sample {highcharts} highcharts/chart/zoomtype-none/
         *         False by default
         * @sample {highcharts} highcharts/title/floating/
         *         True - title on top of the plot area
         * @sample {highstock} stock/chart/title-floating/
         *         True - title on top of the plot area
         *
         * @type      {boolean}
         * @default   false
         * @since     2.1
         * @apioption title.floating
         */
        /**
         * Whether to
         * [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
         * to render the text.
         *
         * @type      {boolean}
         * @default   false
         * @apioption title.useHTML
         */
        /**
         * The vertical alignment of the title. Can be one of `"top"`,
         * `"middle"` and `"bottom"`. When a value is given, the title behaves
         * as if [floating](#title.floating) were `true`.
         *
         * @sample {highcharts} highcharts/title/verticalalign/
         *         Chart title in bottom right corner
         * @sample {highstock} stock/chart/title-verticalalign/
         *         Chart title in bottom right corner
         *
         * @type      {Highcharts.VerticalAlignValue}
         * @since     2.1
         * @apioption title.verticalAlign
         */
        /**
         * The x position of the title relative to the alignment within
         * `chart.spacingLeft` and `chart.spacingRight`.
         *
         * @sample {highcharts} highcharts/title/align/
         *         Aligned to the plot area (x = 70px = margin left - spacing
         *         left)
         * @sample {highstock} stock/chart/title-align/
         *         Aligned to the plot area (x = 50px = margin left - spacing
         *         left)
         *
         * @type      {number}
         * @default   0
         * @since     2.0
         * @apioption title.x
         */
        /**
         * The y position of the title relative to the alignment within
         * [chart.spacingTop](#chart.spacingTop) and [chart.spacingBottom](
         * #chart.spacingBottom). By default it depends on the font size.
         *
         * @sample {highcharts} highcharts/title/y/
         *         Title inside the plot area
         * @sample {highstock} stock/chart/title-verticalalign/
         *         Chart title in bottom right corner
         *
         * @type      {number}
         * @since     2.0
         * @apioption title.y
         */
        /**
         * CSS styles for the title. Use this for font styling, but use `align`,
         * `x` and `y` for text alignment.
         *
         * Note that the default [title.minScale](#title.minScale) option also
         * affects the rendered font size. In order to keep the font size fixed
         * regardless of title length, set `minScale` to 1.
         *
         * In styled mode, the title style is given in the `.highcharts-title`
         * class.
         *
         * @sample {highcharts} highcharts/title/style/
         *         Custom color and weight
         * @sample {highstock} stock/chart/title-style/
         *         Custom color and weight
         * @sample highcharts/css/titles/
         *         Styled mode
         *
         * @type      {Highcharts.CSSObject}
         * @default   {highcharts|highmaps} { "color": "#333333", "fontSize": "18px" }
         * @default   {highstock} { "color": "#333333", "fontSize": "16px" }
         */
        style: {
            /**
             * @type {Highcharts.ColorType}
             */
            color: 'var(--highcharts-neutral-color-80)',
            fontWeight: 'bold'
        },
        /**
         * The title of the chart. To disable the title, set the `text` to
         * `undefined`.
         *
         * @sample {highcharts} highcharts/title/text/
         *         Custom title
         * @sample {highstock} stock/chart/title-text/
         *         Custom title
         *
         * @default {highcharts|highmaps} Chart title
         * @default {highstock} undefined
         */
        text: 'Chart title',
        /**
         * The horizontal alignment of the title. Can be one of "left", "center"
         * and "right".
         *
         * Since v12 it defaults to `undefined`, meaning the alignment is
         * computed for best fit. If the text fits in one line, it aligned to
         * the center, but if it is wrapped into multiple lines, it is aligned
         * to the left.
         *
         * @sample {highcharts} highcharts/title/align-auto/
         *         Default alignment, dynamic
         * @sample {highcharts} highcharts/title/align/
         *         Aligned to the plot area (x = 70px = margin left - spacing
         *         left)
         * @sample {highstock} stock/chart/title-align/
         *         Aligned to the plot area (x = 50px = margin left - spacing
         *         left)
         *
         * @type      {Highcharts.AlignValue}
         * @default   undefined
         * @since     2.0
         * @apioption title.align
         */
        /**
         * The margin between the title and the plot area, or if a subtitle
         * is present, the margin between the subtitle and the plot area.
         *
         * @sample {highcharts} highcharts/title/margin-50/
         *         A chart title margin of 50
         * @sample {highcharts} highcharts/title/margin-subtitle/
         *         The same margin applied with a subtitle
         * @sample {highstock} stock/chart/title-margin/
         *         A chart title margin of 50
         *
         * @since 2.1
         */
        margin: 15,
        /**
         * When the title is too wide to fit in the chart, the default behavior
         * is to scale it down to fit, or apply word wrap if it is scaled down
         * to `minScale` and still doesn't fit.
         *
         * The default value reflects the scale, when using default font sizes,
         * when the title font size matches that of the subtitle. The title
         * still stands out as it is bold by default.
         *
         * Set `minScale` to 1 to avoid downscaling.
         *
         * @sample {highcharts} highcharts/title/align-auto/
         *         Downscaling demonstrated
         *
         * @since 12.0.0
         */
        minScale: 0.67
    },
    /**
     * The chart's subtitle. This can be used both to display a subtitle below
     * the main title, and to display random text anywhere in the chart. The
     * subtitle can be updated after chart initialization through the
     * `Chart.setTitle` method.
     *
     * @sample {highcharts} highcharts/title/align-auto/
     *         Default title alignment
     * @sample {highmaps} maps/title/subtitle/
     *         Subtitle options demonstrated
     */
    subtitle: {
        /**
         * The horizontal alignment of the subtitle. Can be one of "left",
         * "center" and "right". Since v12, it defaults to `undefined`, meaning
         * the actual alignment is inherited from the alignment of the main
         * title.
         *
         * @sample {highcharts} highcharts/title/align-auto/
         *         Default title and subtitle alignment, dynamic
         * @sample {highcharts} highcharts/subtitle/align/
         *         Footnote at right of plot area
         * @sample {highstock} stock/chart/subtitle-footnote
         *         Footnote at bottom right of plot area
         *
         * @type  {Highcharts.AlignValue}
         * @default undefined
         * @since 2.0
         * @apioption subtitle.align
         */
        /**
         * When the subtitle is floating, the plot area will not move to make
         * space for it.
         *
         * @sample {highcharts} highcharts/subtitle/floating/
         *         Floating title and subtitle
         * @sample {highstock} stock/chart/subtitle-footnote
         *         Footnote floating at bottom right of plot area
         *
         * @type      {boolean}
         * @default   false
         * @since     2.1
         * @apioption subtitle.floating
         */
        /**
         * Whether to
         * [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
         * to render the text.
         *
         * @type      {boolean}
         * @default   false
         * @apioption subtitle.useHTML
         */
        /**
         * The vertical alignment of the title. Can be one of `"top"`,
         * `"middle"` and `"bottom"`. When middle, the subtitle behaves as
         * floating.
         *
         * @sample {highcharts} highcharts/subtitle/verticalalign/
         *         Footnote at the bottom right of plot area
         * @sample {highstock} stock/chart/subtitle-footnote
         *         Footnote at the bottom right of plot area
         *
         * @type      {Highcharts.VerticalAlignValue}
         * @since     2.1
         * @apioption subtitle.verticalAlign
         */
        /**
         * The x position of the subtitle relative to the alignment within
         * `chart.spacingLeft` and `chart.spacingRight`.
         *
         * @sample {highcharts} highcharts/subtitle/align/
         *         Footnote at right of plot area
         * @sample {highstock} stock/chart/subtitle-footnote
         *         Footnote at the bottom right of plot area
         *
         * @type      {number}
         * @default   0
         * @since     2.0
         * @apioption subtitle.x
         */
        /**
         * The y position of the subtitle relative to the alignment within
         * `chart.spacingTop` and `chart.spacingBottom`. By default the subtitle
         * is laid out below the title unless the title is floating.
         *
         * @sample {highcharts} highcharts/subtitle/verticalalign/
         *         Footnote at the bottom right of plot area
         * @sample {highstock} stock/chart/subtitle-footnote
         *         Footnote at the bottom right of plot area
         *
         * @type      {number}
         * @since     2.0
         * @apioption subtitle.y
         */
        /**
         * CSS styles for the title.
         *
         * In styled mode, the subtitle style is given in the
         * `.highcharts-subtitle` class.
         *
         * @sample {highcharts} highcharts/subtitle/style/
         *         Custom color and weight
         * @sample {highcharts} highcharts/css/titles/
         *         Styled mode
         * @sample {highstock} stock/chart/subtitle-style
         *         Custom color and weight
         * @sample {highstock} highcharts/css/titles/
         *         Styled mode
         * @sample {highmaps} highcharts/css/titles/
         *         Styled mode
         *
         * @type      {Highcharts.CSSObject}
         * @default   {"color": "#666666"}
         */
        style: {
            /**
             * @type {Highcharts.ColorType}
             */
            color: 'var(--highcharts-neutral-color-60)',
            /**
             * @type {number|string}
             */
            fontSize: '0.8em'
        },
        /**
         * The subtitle of the chart.
         *
         * @sample {highcharts|highstock} highcharts/subtitle/text/
         *         Custom subtitle
         * @sample {highcharts|highstock} highcharts/subtitle/text-formatted/
         *         Formatted and linked text.
         */
        text: ''
    },
    /**
     * The chart's caption, which will render below the chart and will be part
     * of exported charts. The caption can be updated after chart initialization
     * through the `Chart.update` or `Chart.caption.update` methods.
     *
     * @sample highcharts/caption/text/
     *         A chart with a caption
     * @since  7.2.0
     */
    caption: {
        /**
         * When the caption is floating, the plot area will not move to make
         * space for it.
         *
         * @type      {boolean}
         * @default   false
         * @apioption caption.floating
         */
        /**
         * The margin between the caption and the plot area.
         */
        margin: 15,
        /**
         * Whether to
         * [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
         * to render the text.
         *
         * @type      {boolean}
         * @default   false
         * @apioption caption.useHTML
         */
        /**
         * The x position of the caption relative to the alignment within
         * `chart.spacingLeft` and `chart.spacingRight`.
         *
         * @type      {number}
         * @default   0
         * @apioption caption.x
         */
        /**
         * The y position of the caption relative to the alignment within
         * `chart.spacingTop` and `chart.spacingBottom`.
         *
         * @type      {number}
         * @apioption caption.y
         */
        /**
         * CSS styles for the caption.
         *
         * In styled mode, the caption style is given in the
         * `.highcharts-caption` class.
         *
         * @sample {highcharts} highcharts/css/titles/
         *         Styled mode
         *
         * @type      {Highcharts.CSSObject}
         * @default   {"color": "#666666"}
         */
        style: {
            /**
             * @type {Highcharts.ColorType}
             */
            color: 'var(--highcharts-neutral-color-60)',
            /**
             * @type {number|string}
             */
            fontSize: '0.8em'
        },
        /**
         * The caption text of the chart.
         *
         * @sample {highcharts} highcharts/caption/text/
         *         Custom caption
         */
        text: '',
        /**
         * The horizontal alignment of the caption. Can be one of "left",
         *  "center" and "right".
         *
         * @type  {Highcharts.AlignValue}
         */
        align: 'left',
        /**
         * The vertical alignment of the caption. Can be one of `"top"`,
         * `"middle"` and `"bottom"`. When middle, the caption behaves as
         * floating.
         *
         * @type      {Highcharts.VerticalAlignValue}
         */
        verticalAlign: 'bottom'
    },
    /**
     * The plotOptions is a wrapper object for config objects for each series
     * type. The config objects for each series can also be overridden for
     * each series item as given in the series array.
     *
     * Configuration options for the series are given in three levels. Options
     * for all series in a chart are given in the [plotOptions.series](
     * #plotOptions.series) object. Then options for all series of a specific
     * type are given in the plotOptions of that type, for example
     * `plotOptions.line`. Next, options for one single series are given in
     * [the series array](#series).
     */
    plotOptions: {},
    /**
     * The legend is a box containing a symbol and name for each series
     * item or point item in the chart. Each series (or points in case
     * of pie charts) is represented by a symbol and its name in the legend.
     *
     * It is possible to override the symbol creator function and create
     * [custom legend symbols](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/studies/legend-custom-symbol/).
     *
     * @productdesc {highmaps}
     * A Highmaps legend by default contains one legend item per series, but if
     * a `colorAxis` is defined, the axis will be displayed in the legend.
     * Either as a gradient, or as multiple legend items for `dataClasses`.
     */
    legend: {
        /**
         * The background color of the legend.
         *
         * @see In styled mode, the legend background fill can be applied with
         *      the `.highcharts-legend-box` class.
         *
         * @sample {highcharts} highcharts/legend/backgroundcolor/
         *         Yellowish background
         * @sample {highstock} stock/legend/align/
         *         Various legend options
         * @sample {highmaps} maps/legend/border-background/
         *         Border and background options
         *
         * @type      {Highcharts.ColorType}
         * @apioption legend.backgroundColor
         */
        /**
         * The width of the drawn border around the legend.
         *
         * @see In styled mode, the legend border stroke width can be applied
         *      with the `.highcharts-legend-box` class.
         *
         * @sample {highcharts} highcharts/legend/borderwidth/
         *         2px border width
         * @sample {highstock} stock/legend/align/
         *         Various legend options
         * @sample {highmaps} maps/legend/border-background/
         *         Border and background options
         *
         * @type      {number}
         * @default   0
         * @apioption legend.borderWidth
         */
        /**
         * Enable or disable the legend. There is also a series-specific option,
         * [showInLegend](#plotOptions.series.showInLegend), that can hide the
         * series from the legend. In some series types this is `false` by
         * default, so it must set to `true` in order to show the legend for the
         * series.
         *
         * @sample {highcharts} highcharts/legend/enabled-false/ Legend disabled
         * @sample {highstock} stock/legend/align/ Various legend options
         * @sample {highmaps} maps/legend/enabled-false/ Legend disabled
         *
         * @default {highstock} false
         * @default {highmaps} true
         * @default {gantt} false
         */
        enabled: true,
        /**
         * The horizontal alignment of the legend box within the chart area.
         * Valid values are `left`, `center` and `right`.
         *
         * In the case that the legend is aligned in a corner position, the
         * `layout` option will determine whether to place it above/below
         * or on the side of the plot area.
         *
         * @sample {highcharts} highcharts/legend/align/
         *         Legend at the right of the chart
         * @sample {highstock} stock/legend/align/
         *         Various legend options
         * @sample {highmaps} maps/legend/alignment/
         *         Legend alignment
         *
         * @type  {Highcharts.AlignValue}
         * @since 2.0
         */
        align: 'center',
        /**
         * If the [layout](legend.layout) is `horizontal` and the legend items
         * span over two lines or more, whether to align the items into vertical
         * columns. Setting this to `false` makes room for more items, but will
         * look more messy.
         *
         * @sample highcharts/legend/aligncolumns
         *         Align columns
         *
         * @since 6.1.0
         */
        alignColumns: true,
        /**
         * A CSS class name to apply to the legend group.
         */
        className: 'highcharts-no-tooltip',
        /**
         * General event handlers for the legend. These event hooks can
         * also be attached to the legend at run time using the
         * `Highcharts.addEvent` function.
         *
         * @declare Highcharts.LegendEventsOptionsObject
         */
        events: {
        /**
         * Fires when the legend item belonging to the series is clicked.
         * One parameter, `event`, is passed to the function. The default
         * action is to toggle the visibility of the series, point or data
         * class. This can be prevented by returning `false` or calling
         * `event.preventDefault()`.
         *
         * @sample {highcharts} highcharts/legend/itemclick/
         *         Confirm hiding and showing
         * @sample {highcharts} highcharts/legend/pie-legend-itemclick/
         *         Confirm toggle visibility of pie slices
         *
         * @type      {Highcharts.LegendItemClickCallbackFunction}
         * @context   Highcharts.Legend
         * @apioption legend.events.itemClick
         */
        },
        /**
         * When the legend is floating, the plot area ignores it and is allowed
         * to be placed below it.
         *
         * @sample {highcharts} highcharts/legend/floating-false/
         *         False by default
         * @sample {highcharts} highcharts/legend/floating-true/
         *         True
         * @sample {highmaps} maps/legend/alignment/
         *         Floating legend
         *
         * @type      {boolean}
         * @default   false
         * @since     2.1
         * @apioption legend.floating
         */
        /**
         * The layout of the legend items. Can be one of `horizontal` or
         * `vertical` or `proximate`. When `proximate`, the legend items will be
         * placed as close as possible to the graphs they're representing,
         * except in inverted charts or when the legend position doesn't allow
         * it.
         *
         * @sample {highcharts} highcharts/legend/layout-horizontal/
         *         Horizontal by default
         * @sample {highcharts} highcharts/legend/layout-vertical/
         *         Vertical
         * @sample highcharts/legend/layout-proximate
         *         Labels proximate to the data
         * @sample {highstock} stock/legend/layout-horizontal/
         *         Horizontal by default
         * @sample {highmaps} maps/legend/padding-itemmargin/
         *         Vertical with data classes
         * @sample {highmaps} maps/legend/layout-vertical/
         *         Vertical with color axis gradient
         *
         * @validvalue ["horizontal", "vertical", "proximate"]
         */
        layout: 'horizontal',
        /**
         * In a legend with horizontal layout, the itemDistance defines the
         * pixel distance between each item.
         *
         * @sample {highcharts} highcharts/legend/itemwidth-default/
         *         40px item distance
         * @sample {highstock} highcharts/legend/itemwidth-default/
         *         40px item distance
         *
         * @type      {number}
         * @default   {highcharts} 20
         * @default   {highstock} 20
         * @default   {highmaps} 8
         * @since     3.0.3
         * @apioption legend.itemDistance
         */
        /**
         * The pixel bottom margin for each legend item.
         *
         * @sample {highcharts|highstock} highcharts/legend/padding-itemmargin/
         *         Padding and item margins demonstrated
         * @sample {highmaps} maps/legend/padding-itemmargin/
         *         Padding and item margins demonstrated
         *
         * @since     2.2.0
         */
        itemMarginBottom: 2,
        /**
         * The pixel top margin for each legend item.
         *
         * @sample {highcharts|highstock} highcharts/legend/padding-itemmargin/
         *         Padding and item margins demonstrated
         * @sample {highmaps} maps/legend/padding-itemmargin/
         *         Padding and item margins demonstrated
         *
         * @since     2.2.0
         */
        itemMarginTop: 2,
        /**
         * The width for each legend item. By default the items are laid out
         * successively. In a [horizontal layout](legend.layout), if the items
         * are laid out across two rows or more, they will be vertically aligned
         * depending on the [legend.alignColumns](legend.alignColumns) option.
         *
         * @sample {highcharts} highcharts/legend/itemwidth-default/
         *         Undefined by default
         * @sample {highcharts} highcharts/legend/itemwidth-80/
         *         80 for aligned legend items
         *
         * @type      {number}
         * @since     2.0
         * @apioption legend.itemWidth
         */
        /**
         * A [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
         * for each legend label. Available variables relates to properties on
         * the series, or the point in case of pies.
         *
         * @sample {highcharts} highcharts/legend/labelformat/
         *         Add text
         *
         * @type      {string}
         * @default   {name}
         * @since     1.3
         * @apioption legend.labelFormat
         */
        /**
         * Callback function to format each of the series' labels. The `this`
         * keyword refers to the series object, or the point object in case of
         * pie charts. By default the series or point name is printed. Since
         * v12.5.0, the callback also receives `ctx` as the first argument, so
         * that arrow functions can access the same context as regular
         * functions using `this`.
         *
         * @productdesc {highmaps}
         * In Highmaps the context can also be a data class in case of a
         * `colorAxis`.
         *
         * @sample {highcharts} highcharts/legend/labelformatter/
         *         Add text
         * @sample {highmaps} maps/legend/labelformatter/
         *         Data classes with label formatter
         *
         * @type {Highcharts.FormatterCallbackFunction<Highcharts.Point|Highcharts.Series>}
         */
        labelFormatter: function () {
            // eslint-enable valid-jsdoc
            return this.name;
        },
        /**
         * Line height for the legend items. Deprecated as of 2.1\. Instead,
         * the line height for each item can be set using
         * `itemStyle.lineHeight`, and the padding between items using
         * `itemMarginTop` and `itemMarginBottom`.
         *
         * @sample {highcharts} highcharts/legend/lineheight/
         *         Setting padding
         *
         * @deprecated 2.1.0
         *
         * @type      {number}
         * @default   16
         * @since     2.0
         * @product   highcharts gantt
         * @apioption legend.lineHeight
         */
        /**
         * If the plot area sized is calculated automatically and the legend is
         * not floating, the legend margin is the space between the legend and
         * the axis labels or plot area.
         *
         * @sample {highcharts} highcharts/legend/margin-default/
         *         12 pixels by default
         * @sample {highcharts} highcharts/legend/margin-30/
         *         30 pixels
         *
         * @type      {number}
         * @default   12
         * @since     2.1
         * @apioption legend.margin
         */
        /**
         * Maximum width for the legend. Can be a percentage of the chart width,
         * or an integer representing how many pixels wide the legend can be.
         *
         * @sample {highcharts} highcharts/legend/maxwidth/
         *         Max width set to 7%
         *
         * @type      {number|string}
         * @apioption legend.maxWidth
         */
        /**
         * Maximum pixel height for the legend. When the maximum height is
         * extended, navigation will show.
         *
         * @type      {number}
         * @since     2.3.0
         * @apioption legend.maxHeight
         */
        /**
         * The color of the drawn border around the legend.
         *
         * @see In styled mode, the legend border stroke can be applied with the
         *      `.highcharts-legend-box` class.
         *
         * @sample {highcharts} highcharts/legend/bordercolor/
         *         Brown border
         * @sample {highstock} stock/legend/align/
         *         Various legend options
         * @sample {highmaps} maps/legend/border-background/
         *         Border and background options
         *
         * @type {Highcharts.ColorType}
         */
        borderColor: 'var(--highcharts-neutral-color-40)',
        /**
         * The border corner radius of the legend.
         *
         * @sample {highcharts} highcharts/legend/borderradius-default/
         *         Square by default
         * @sample {highcharts} highcharts/legend/borderradius-round/
         *         5px rounded
         * @sample {highmaps} maps/legend/border-background/
         *         Border and background options
         */
        borderRadius: 0,
        /**
         * Options for the paging or navigation appearing when the legend is
         * overflown. Navigation works well on screen, but not in static
         * exported images. One way of working around that is to
         * [increase the chart height in
         * export](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/legend/navigation-enabled-false/).
         *
         * @sample highcharts/legend/scrollable-vertical/
         *         Legend with vertical scrollable extension
         * @sample highcharts/legend/scrollable-horizontal/
         *         Legend with horizontal scrollable extension
         *
         */
        navigation: {
            /**
             * How to animate the pages when navigating up or down. A value of
             * `true` applies the default navigation given in the
             * `chart.animation` option. Additional options can be given as an
             * object containing values for easing and duration.
             *
             * @sample {highcharts} highcharts/legend/navigation/
             *         Legend page navigation demonstrated
             * @sample {highstock} highcharts/legend/navigation/
             *         Legend page navigation demonstrated
             *
             * @type      {boolean|Partial<Highcharts.AnimationOptionsObject>}
             * @default   true
             * @since     2.2.4
             * @apioption legend.navigation.animation
             */
            /**
             * The pixel size of the up and down arrows in the legend paging
             * navigation.
             *
             * @sample {highcharts} highcharts/legend/navigation/
             *         Legend page navigation demonstrated
             * @sample {highstock} highcharts/legend/navigation/
             *         Legend page navigation demonstrated
             *
             * @type      {number}
             * @default   12
             * @since     2.2.4
             * @apioption legend.navigation.arrowSize
             */
            /**
             * Whether to enable the legend navigation. In most cases, disabling
             * the navigation results in an unwanted overflow.
             *
             * See also the
             * [adapt chart to legend](https://github.com/highcharts/adapt-chart-to-legend)
             * plugin for a solution to extend the chart height to make room for
             * the legend, optionally in exported charts only.
             *
             * @type      {boolean}
             * @default   true
             * @since     4.2.4
             * @apioption legend.navigation.enabled
             */
            /**
             * Text styles for the legend page navigation.
             *
             * @see In styled mode, the navigation items are styled with the
             *      `.highcharts-legend-navigation` class.
             *
             * @sample {highcharts} highcharts/legend/navigation/
             *         Legend page navigation demonstrated
             * @sample {highstock} highcharts/legend/navigation/
             *         Legend page navigation demonstrated
             *
             * @type      {Highcharts.CSSObject}
             * @since     2.2.4
             * @apioption legend.navigation.style
             */
            style: {
                /**
                 * @type {number|string}
                 */
                fontSize: '0.8em'
            },
            /**
             * The color for the active up or down arrow in the legend page
             * navigation.
             *
             * @see In styled mode, the active arrow be styled with the
             *      `.highcharts-legend-nav-active` class.
             *
             * @sample  {highcharts} highcharts/legend/navigation/
             *          Legend page navigation demonstrated
             * @sample  {highstock} highcharts/legend/navigation/
             *          Legend page navigation demonstrated
             *
             * @type  {Highcharts.ColorType}
             * @since 2.2.4
             */
            activeColor: 'var(--highcharts-highlight-color-100)',
            /**
             * The color of the inactive up or down arrow in the legend page
             * navigation. .
             *
             * @see In styled mode, the inactive arrow be styled with the
             *      `.highcharts-legend-nav-inactive` class.
             *
             * @sample {highcharts} highcharts/legend/navigation/
             *         Legend page navigation demonstrated
             * @sample {highstock} highcharts/legend/navigation/
             *         Legend page navigation demonstrated
             *
             * @type  {Highcharts.ColorType}
             * @since 2.2.4
             */
            inactiveColor: 'var(--highcharts-neutral-color-20)'
        },
        /**
         * The inner padding of the legend box.
         *
         * @sample {highcharts|highstock} highcharts/legend/padding-itemmargin/
         *         Padding and item margins demonstrated
         * @sample {highmaps} maps/legend/padding-itemmargin/
         *         Padding and item margins demonstrated
         *
         * @type      {number}
         * @default   8
         * @since     2.2.0
         * @apioption legend.padding
         */
        /**
         * Whether to reverse the order of the legend items compared to the
         * order of the series or points as defined in the configuration object.
         *
         * @see [yAxis.reversedStacks](#yAxis.reversedStacks),
         *      [series.legendIndex](#series.legendIndex)
         *
         * @sample {highcharts} highcharts/legend/reversed/
         *         Stacked bar with reversed legend
         *
         * @type      {boolean}
         * @default   false
         * @since     1.2.5
         * @apioption legend.reversed
         */
        /**
         * Whether to show the symbol on the right side of the text rather than
         * the left side. This is common in Arabic and Hebrew.
         *
         * @sample {highcharts} highcharts/legend/rtl/
         *         Symbol to the right
         *
         * @type      {boolean}
         * @default   false
         * @since     2.2
         * @apioption legend.rtl
         */
        /**
         * CSS styles for the legend area. In the 1.x versions the position
         * of the legend area was determined by CSS. In 2.x, the position is
         * determined by properties like `align`, `verticalAlign`, `x` and `y`,
         * but the styles are still parsed for backwards compatibility.
         *
         * @deprecated 2.0.0
         *
         * @type      {Highcharts.CSSObject}
         * @product   highcharts highstock
         * @apioption legend.style
         */
        /**
         * CSS styles for each legend item. Only a subset of CSS is supported,
         * notably those options related to text. The default `textOverflow`
         * property makes long texts truncate. Set it to `undefined` to wrap
         * text instead. A `width` property can be added to control the text
         * width.
         *
         * @see In styled mode, the legend items can be styled with the
         *      `.highcharts-legend-item` class.
         *
         * @sample {highcharts} highcharts/legend/itemstyle/
         *         Bold black text
         * @sample {highmaps} maps/legend/itemstyle/
         *         Item text styles
         *
         * @type    {Highcharts.CSSObject}
         * @default {"color": "#333333", "cursor": "pointer", "fontSize": "0.8em", "fontWeight": "bold", "textOverflow": "ellipsis"}
         */
        itemStyle: {
            /**
             * @ignore
             */
            color: 'var(--highcharts-neutral-color-80)',
            /**
             * @ignore
             */
            cursor: 'pointer',
            /**
             * @ignore
             */
            fontSize: '0.8em',
            /**
             * @ignore
             */
            textDecoration: 'none',
            /**
             * @ignore
             */
            textOverflow: 'ellipsis'
        },
        /**
         * CSS styles for each legend item in hover mode. Only a subset of
         * CSS is supported, notably those options related to text. Properties
         * are inherited from `style` unless overridden here.
         *
         * @see In styled mode, the hovered legend items can be styled with
         *      the `.highcharts-legend-item:hover` pseudo-class.
         *
         * @sample {highcharts} highcharts/legend/itemhoverstyle/
         *         Red on hover
         * @sample {highmaps} maps/legend/itemstyle/
         *         Item text styles
         *
         * @type    {Highcharts.CSSObject}
         * @default {"color": "#000000"}
         */
        itemHoverStyle: {
            /**
             * @ignore
             */
            color: 'var(--highcharts-neutral-color-100)'
        },
        /**
         * CSS styles for each legend item when the corresponding series or
         * point is hidden. Only a subset of CSS is supported, notably those
         * options related to text. Properties are inherited from `style`
         * unless overridden here.
         *
         * @see In styled mode, the hidden legend items can be styled with
         *      the `.highcharts-legend-item-hidden` class.
         *
         * @sample {highcharts} highcharts/legend/itemhiddenstyle/
         *         Darker gray color
         *
         * @type    {Highcharts.CSSObject}
         * @default {"color": "#cccccc"}
         */
        itemHiddenStyle: {
            /**
             * @ignore
             */
            color: 'var(--highcharts-neutral-color-60)',
            /**
             * @ignore
             */
            textDecoration: 'line-through'
        },
        /**
         * Whether to apply a drop shadow to the legend. A `backgroundColor`
         * also needs to be applied for this to take effect. The shadow can be
         * an object configuration containing `color`, `offsetX`, `offsetY`,
         * `opacity` and `width`.
         *
         * @sample {highcharts} highcharts/legend/shadow/
         *         White background and drop shadow
         * @sample {highstock} stock/legend/align/
         *         Various legend options
         * @sample {highmaps} maps/legend/border-background/
         *         Border and background options
         *
         * @type {boolean|Highcharts.CSSObject}
         */
        shadow: false,
        /**
         * Default styling for the checkbox next to a legend item when
         * `showCheckbox` is true.
         *
         * @type {Highcharts.CSSObject}
         * @default {"width": "13px", "height": "13px", "position":"absolute"}
         */
        itemCheckboxStyle: {
            /**
             * @ignore
             */
            position: 'absolute',
            /**
             * @ignore
             */
            width: '13px', // For IE precision
            /**
             * @ignore
             */
            height: '13px'
        },
        /// itemWidth: undefined,
        /**
         * When this is true, the legend symbol width will be the same as
         * the symbol height, which in turn defaults to the font size of the
         * legend items.
         *
         * @since 5.0.0
         */
        squareSymbol: true,
        /**
         * The pixel height of the symbol for series types that use a rectangle
         * in the legend. Defaults to the font size of legend items.
         *
         * Note: This option is a default source of color axis height, if the
         * [colorAxis.height](https://api.highcharts.com/highcharts/colorAxis.height)
         * option is not set.
         *
         * @productdesc {highmaps}
         * In Highmaps, when the symbol is the gradient of a vertical color
         * axis, the height defaults to 200.
         *
         * @sample {highmaps} maps/legend/layout-vertical-sized/
         *         Sized vertical gradient
         * @sample {highmaps} maps/legend/padding-itemmargin/
         *         No distance between data classes
         *
         * @type      {number}
         * @since     3.0.8
         * @apioption legend.symbolHeight
         */
        /**
         * The border radius of the symbol for series types that use a rectangle
         * in the legend. Defaults to half the `symbolHeight`, effectively
         * creating a circle.
         *
         * For color axis scales, it defaults to 3.
         *
         * @sample {highcharts} highcharts/legend/symbolradius/
         *         Round symbols
         * @sample {highstock} highcharts/legend/symbolradius/
         *         Round symbols
         * @sample {highmaps} highcharts/legend/symbolradius/
         *         Round symbols
         *
         * @type      {number}
         * @since     3.0.8
         * @apioption legend.symbolRadius
         */
        /**
         * The pixel width of the legend item symbol. When the `squareSymbol`
         * option is set, this defaults to the `symbolHeight`, otherwise 16.
         *
         * Note: This option is a default source of color axis width, if the
         * [colorAxis.width](https://api.highcharts.com/highcharts/colorAxis.width)
         * option is not set.
         *
         * @productdesc {highmaps}
         * In Highmaps, when the symbol is the gradient of a horizontal color
         * axis, the width defaults to 200.
         *
         * @sample {highcharts} highcharts/legend/symbolwidth/
         *         Greater symbol width and padding
         * @sample {highmaps} maps/legend/padding-itemmargin/
         *         Padding and item margins demonstrated
         * @sample {highmaps} maps/legend/layout-vertical-sized/
         *         Sized vertical gradient
         *
         * @type      {number}
         * @apioption legend.symbolWidth
         */
        /**
         * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
         * to render the legend item texts.
         *
         * Prior to 4.1.7, when using HTML, [legend.navigation](
         * #legend.navigation) was disabled.
         *
         * @sample highcharts/legend/scrollable-vertical/
         *         Legend with vertical scrollable extension
         * @sample highcharts/legend/scrollable-horizontal/
         *         Legend with horizontal scrollable extension
         *
         * @type      {boolean}
         * @default   false
         * @apioption legend.useHTML
         */
        /**
         * For a color axis with data classes, how many decimals to render in
         * the legend. The default preserves the decimals of the range numbers.
         *
         * @type      {number}
         * @default   -1
         * @product   highcharts highmaps
         * @apioption legend.valueDecimals
         */
        /**
         * For a color axis with data classes, a suffix for the range numbers in
         * the legend.
         *
         * @type      {string}
         * @default   ''
         * @product   highcharts highmaps
         * @apioption legend.valueSuffix
         */
        /**
         * The width of the legend box. If a number is set, it translates to
         * pixels. Since v7.0.2 it allows setting a percent string of the full
         * chart width, for example `40%`.
         *
         * Defaults to the full chart width for legends below or above the
         * chart, half the chart width for legends to the left and right.
         *
         * @sample {highcharts} highcharts/legend/width/
         *         Aligned to the plot area
         * @sample {highcharts} highcharts/legend/width-percent/
         *         A percent of the chart width
         *
         * @type      {number|string}
         * @since     2.0
         * @apioption legend.width
         */
        /**
         * The pixel padding between the legend item symbol and the legend
         * item text.
         *
         * @sample {highcharts} highcharts/legend/symbolpadding/
         *         Greater symbol width and padding
         */
        symbolPadding: 5,
        /**
         * The vertical alignment of the legend box. Can be one of `top`,
         * `middle` or `bottom`. Vertical position can be further determined
         * by the `y` option.
         *
         * In the case that the legend is aligned in a corner position, the
         * `layout` option will determine whether to place it above/below
         * or on the side of the plot area.
         *
         * When the [layout](#legend.layout) option is `proximate`, the
         * `verticalAlign` option doesn't apply.
         *
         * @sample {highcharts} highcharts/legend/verticalalign/
         *         Legend 100px from the top of the chart
         * @sample {highstock} stock/legend/align/
         *         Various legend options
         * @sample {highmaps} maps/legend/alignment/
         *         Legend alignment
         *
         * @type  {Highcharts.VerticalAlignValue}
         * @since 2.0
         */
        verticalAlign: 'bottom',
        // Width: undefined,
        /**
         * The x offset of the legend relative to its horizontal alignment
         * `align` within chart.spacingLeft and chart.spacingRight. Negative
         * x moves it to the left, positive x moves it to the right.
         *
         * @sample {highcharts} highcharts/legend/width/
         *         Aligned to the plot area
         *
         * @since 2.0
         */
        x: 0,
        /**
         * The vertical offset of the legend relative to it's vertical alignment
         * `verticalAlign` within chart.spacingTop and chart.spacingBottom.
         *  Negative y moves it up, positive y moves it down.
         *
         * @sample {highcharts} highcharts/legend/verticalalign/
         *         Legend 100px from the top of the chart
         * @sample {highstock} stock/legend/align/
         *         Various legend options
         * @sample {highmaps} maps/legend/alignment/
         *         Legend alignment
         *
         * @since 2.0
         */
        y: 0,
        /**
         * A title to be added on top of the legend.
         *
         * @sample {highcharts} highcharts/legend/title/
         *         Legend title
         * @sample {highmaps} maps/legend/alignment/
         *         Legend with title
         *
         * @since 3.0
         */
        title: {
            /**
             * A text or HTML string for the title.
             *
             * @type      {string}
             * @since     3.0
             * @apioption legend.title.text
             */
            /**
             * Generic CSS styles for the legend title.
             *
             * @see In styled mode, the legend title is styled with the
             *      `.highcharts-legend-title` class.
             *
             * @type    {Highcharts.CSSObject}
             * @default {"fontSize": "0.8em", "fontWeight": "bold"}
             * @since   3.0
             */
            style: {
                /**
                 * @ignore
                 */
                color: 'var(--highcharts-neutral-color-80)',
                /**
                 * @ignore
                 */
                fontSize: '0.8em',
                /**
                 * @ignore
                 */
                fontWeight: 'bold'
            }
        }
    },
    /**
     * The loading options control the appearance of the loading screen
     * that covers the plot area on chart operations. This screen only
     * appears after an explicit call to `chart.showLoading()`. It is a
     * utility for developers to communicate to the end user that something
     * is going on, for example while retrieving new data via an XHR connection.
     * The "Loading..." text itself is not part of this configuration
     * object, but part of the `lang` object.
     */
    loading: {
        /**
         * The duration in milliseconds of the fade out effect.
         *
         * @sample highcharts/loading/hideduration/
         *         Fade in and out over a second
         *
         * @type      {number}
         * @default   100
         * @since     1.2.0
         * @apioption loading.hideDuration
         */
        /**
         * The duration in milliseconds of the fade in effect.
         *
         * @sample highcharts/loading/hideduration/
         *         Fade in and out over a second
         *
         * @type      {number}
         * @default   100
         * @since     1.2.0
         * @apioption loading.showDuration
         */
        /**
         * CSS styles for the loading label `span`.
         *
         * @see In styled mode, the loading label is styled with the
         *      `.highcharts-loading-inner` class.
         *
         * @sample {highcharts|highmaps} highcharts/loading/labelstyle/
         *         Vertically centered
         * @sample {highstock} stock/loading/general/
         *         Label styles
         *
         * @type    {Highcharts.CSSObject}
         * @default {"fontWeight": "bold", "position": "relative", "top": "45%"}
         * @since   1.2.0
         */
        labelStyle: {
            /**
             * @ignore
             */
            fontWeight: 'bold',
            /**
             * @ignore
             */
            position: 'relative',
            /**
             * @ignore
             */
            top: '45%'
        },
        /**
         * CSS styles for the loading screen that covers the plot area.
         *
         * In styled mode, the loading label is styled with the
         * `.highcharts-loading` class.
         *
         * @sample  {highcharts|highmaps} highcharts/loading/style/
         *          Gray plot area, white text
         * @sample  {highstock} stock/loading/general/
         *          Gray plot area, white text
         *
         * @type    {Highcharts.CSSObject}
         * @default {"position": "absolute", "backgroundColor": "#ffffff", "opacity": 0.5, "textAlign": "center"}
         * @since   1.2.0
         */
        style: {
            /**
             * @ignore
             */
            position: 'absolute',
            /**
             * @ignore
             */
            backgroundColor: 'var(--highcharts-background-color)',
            /**
             * @ignore
             */
            opacity: 0.5,
            /**
             * @ignore
             */
            textAlign: 'center'
        }
    },
    /**
     * Options for the tooltip that appears when the user hovers over a
     * series or point.
     *
     * @declare Highcharts.TooltipOptions
     */
    tooltip: {
        /**
         * A CSS class name to apply to the tooltip, allowing unique CSS
         * styling for each chart.
         *
         * **Note:** The class is applied to the SVG element of the tooltip
         * (the tooltip label group), not to a container div. This allows you
         * to style the tooltip using CSS applicable to SVG elements.
         *
         * When [tooltip.outside](#tooltip.outside) is `true`, a separate
         * container div with class `highcharts-tooltip-container` is created
         * as the parent to the SVG tooltip element, but the `className` option
         * is still applied to the SVG element itself, not to the container.
         *
         * @type      {string}
         * @apioption tooltip.className
         */
        /**
         * Since 4.1, the crosshair definitions are moved to the Axis object
         * in order for a better separation from the tooltip. See
         * [xAxis.crosshair](#xAxis.crosshair).
         *
         * @sample {highcharts} highcharts/tooltip/crosshairs-x/
         *         Enable a crosshair for the x value
         *
         * @deprecated 4.1.0
         *
         * @type      {*}
         * @default   true
         * @apioption tooltip.crosshairs
         */
        /**
         * Distance from point to tooltip in pixels.
         *
         * @type      {number}
         * @default   16
         * @apioption tooltip.distance
         */
        /**
         * Whether the tooltip should be fixed to one position in the chart, or
         * located next to the point or mouse. When the tooltip is fixed, the
         * position can be further specified with the
         * [tooltip.position](#tooltip.position) options set.
         *
         * @sample    highcharts/tooltip/fixed/
         *            Fixed tooltip and position options
         * @sample    {highstock} stock/tooltip/fixed/
         *            Stock chart with fixed tooltip
         * @sample    {highmaps} maps/tooltip/fixed/
         *            Map with fixed tooltip
         *
         * @type      {boolean}
         * @default   false
         * @since 12.2.0
         * @apioption tooltip.fixed
         */
        /**
         * Whether the tooltip should follow the mouse as it moves across
         * columns, pie slices and other point types with an extent.
         * By default it behaves this way for pie, polygon, map, sankey
         * and wordcloud series by override in the `plotOptions`
         * for those series types.
         *
         * Does not apply if [split](#tooltip.split) is `true`.
         *
         * For touch moves to behave the same way, [followTouchMove](
         * #tooltip.followTouchMove) must be `true` also.
         *
         * @sample highcharts/tooltip/followpointer/
         *         Tooltip follow pointer comparison
         *
         * @type      {boolean}
         * @default   {highcharts} false
         * @default   {highstock} false
         * @default   {highmaps} true
         * @since     3.0
         * @apioption tooltip.followPointer
         */
        /**
         * Whether the tooltip should update as the finger moves on a touch
         * device. If this is `true` and [chart.panning](#chart.panning) is
         * set,`followTouchMove` will take over one-finger touches, so the user
         * needs to use two fingers for zooming and panning.
         *
         * Note the difference to [followPointer](#tooltip.followPointer) that
         * only defines the _position_ of the tooltip. If `followPointer` is
         * false in for example a column series, the tooltip will show above or
         * below the column, but as `followTouchMove` is true, the tooltip will
         * jump from column to column as the user swipes across the plot area.
         *
         * @type      {boolean}
         * @default   {highcharts} true
         * @default   {highstock} true
         * @default   {highmaps} false
         * @since     3.0.1
         * @apioption tooltip.followTouchMove
         */
        /**
         * A [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
         * for the whole shared tooltip. When format strings are a requirement,
         * it is usually more convenient to use `headerFormat`, `pointFormat`
         * and `footerFormat`, but the `format` option allows combining them
         * into one setting.
         *
         * The context of the format string is the same as that of the
         * `tooltip.formatter` callback.
         *
         * @sample {highcharts} highcharts/tooltip/format-shared/
         *         Format for shared tooltip
         *
         * @type      {string}
         * @default   undefined
         * @since     11.1.0
         * @apioption tooltip.format
         */
        /**
         * Callback function to format the text of the tooltip from scratch. In
         * case of single or [shared](#tooltip.shared) tooltips, a string should
         * be returned. In case of [split](#tooltip.split) tooltips, it should
         * return an array where the first item is the header, and subsequent
         * items are mapped to the points. Return `false` to disable tooltip for
         * a specific point on series.
         *
         * A subset of HTML is supported. Unless `useHTML` is true, the HTML of
         * the tooltip is parsed and converted to SVG, therefore this isn't a
         * complete HTML renderer. The following HTML tags are supported: `b`,
         * `br`, `em`, `i`, `span`, `strong`. Spans can be styled with a `style`
         * attribute, but only text-related CSS, that is shared with SVG, is
         * handled.
         *
         * The context of the formatter (since v12) is the
         * [Point](https://api.highcharts.com/class-reference/Highcharts.Point)
         * instance. If the tooltip is shared or split, an array `this.points`
         * contains all points of the hovered x-value.
         *
         * Common properties from the Point to use in the formatter include:
         *
         * - **Point.percentage**:
         *   Stacked series and pies only. The point's percentage of the total.
         *
         * - **Point.points**:
         *   In a shared or split tooltip, this is an array containing all the
         *   hovered points.
         *
         * - **this.series**:
         *   The series object. The series name is available through
         *   `this.series.name`.
         *
         * - **this.total**:
         *   The total value at this point's x value in a stacked series, or the
         *   sum of all slices in a pie series.
         *
         * - **this.x**:
         *   The x value.
         *
         * - **this.y**:
         *   The y value.
         *
         * @sample {highcharts} highcharts/tooltip/formatter-simple/
         *         Simple string formatting
         * @sample {highcharts} highcharts/tooltip/formatter-shared/
         *         Formatting with shared tooltip
         * @sample {highcharts|highstock} highcharts/tooltip/formatter-split/
         *         Formatting with split tooltip
         * @sample highcharts/tooltip/formatter-conditional-default/
         *         Extending default formatter
         * @sample {highstock} stock/tooltip/formatter/
         *         Formatting with shared tooltip
         * @sample {highmaps} maps/tooltip/formatter/
         *         String formatting
         *
         * Since v12.6.0, the callback also receives `ctx` as the second
         * argument, so that arrow functions can access the same context as
         * regular functions using `this`.
         *
         * @type      {Highcharts.TooltipFormatterCallbackFunction}
         * @apioption tooltip.formatter
         */
        /**
         * Callback function to format the text of the tooltip for
         * visible null points.
         * Works analogously to [formatter](#tooltip.formatter), including the
         * `ctx` callback argument added in v12.5.0.
         *
         * @sample highcharts/plotoptions/series-nullformat
         *         Format data label and tooltip for null point.
         *
         * @type      {Highcharts.TooltipFormatterCallbackFunction}
         * @apioption tooltip.nullFormatter
         */
        /**
         * Whether to allow the tooltip to render outside the chart's SVG
         * element box. By default (`false`), the tooltip is rendered within the
         * chart's SVG element, which results in the tooltip being aligned
         * inside the chart area. For small charts, this may result in clipping
         * or overlapping. When `true`, a separate SVG element is created and
         * overlaid on the page, allowing the tooltip to be aligned inside the
         * page itself. Beware that with this option active, CSS classes on the
         * chart's target container, with classnames matching the pattern
         * 'highcharts-*', will be set on the tooltip as well. This is done to
         * support theming for tooltips with this option.
         *
         * Defaults to `true` if `chart.scrollablePlotArea` is activated,
         * otherwise `false`.
         *
         * @sample highcharts/tooltip/outside
         *         Small charts with tooltips outside
         *
         * @type      {boolean|undefined}
         * @default   undefined
         * @since     6.1.1
         * @apioption tooltip.outside
         */
        /**
         * A callback function for formatting the HTML output for a single point
         * in the tooltip. Like the `pointFormat` string, but with more
         * flexibility. Since v12.6.0, the callback also receives `ctx` as the
         * first argument, so that arrow functions can access the same context
         * as regular functions using `this`.
         *
         * @type      {Highcharts.FormatterCallbackFunction<Highcharts.Point>}
         * @since     4.1.0
         * @context   Highcharts.Point
         * @apioption tooltip.pointFormatter
         */
        /**
         * A callback function to place the tooltip in a custom position. The
         * callback receives four parameters: `labelWidth`, `labelHeight`,
         * `point`, and `ctx`, where point contains values for `plotX` and
         * `plotY` telling where the reference point is in the plot area, and
         * `ctx` is the tooltip context (so that arrow-functions can access the
         * same context as a normal function using `this`). Add
         * `chart.plotLeft` and `chart.plotTop` to get the full coordinates.
         * Since v12.6.0, the callback receives `ctx`.
         *
         * To find the actual hovered `Point` instance, use
         * `this.chart.hoverPoint`. For shared or split tooltips, all the hover
         * points are available in `this.chart.hoverPoints`.
         *
         * Since v7, when [tooltip.split](#tooltip.split) option is enabled,
         * positioner is called for each of the boxes separately, including
         * xAxis header. xAxis header is not a point, instead `point` argument
         * contains info: `{ plotX: Number, plotY: Number, isHeader: Boolean }`
         *
         * Since v12.2, the [tooltip.fixed](#tooltip.fixed) option combined with
         * [tooltip.position](#tooltip.position) covers most of the use cases
         * for custom tooltip positioning.
         *
         * The return should be an object containing x and y values, for example
         * `{ x: 100, y: 100 }`.
         *
         * @sample {highcharts} highcharts/tooltip/positioner/
         *         A fixed tooltip position
         * @sample {highstock} stock/tooltip/positioner/
         *         A fixed tooltip position on top of the chart
         * @sample {highmaps} maps/tooltip/positioner/
         *         A fixed tooltip position
         * @sample {highstock} stock/tooltip/split-positioner/
         *         Split tooltip with fixed positions
         * @sample {highstock} stock/tooltip/positioner-scrollable-plotarea/
         *         Scrollable plot area combined with tooltip positioner
         *
         * @see [position](#tooltip.position)
         *
         * @type      {Highcharts.TooltipPositionerCallbackFunction}
         * @since     2.2.4
         * @apioption tooltip.positioner
         */
        /**
         * Shows tooltip for all points with the same X value. Splits the
         * tooltip into one label per series, with the header close to the axis.
         * This is recommended over [shared](#tooltip.shared)
         * tooltips for charts with multiple line series, generally making them
         * easier to read. This option takes precedence over `tooltip.shared`.
         *
         * Not supported for [polar](#chart.polar) and [inverted](#chart.inverted) charts.
         *
         * @productdesc {highstock} In Highcharts Stock, tooltips are split
         * by default since v6.0.0. Stock charts typically contain
         * multi-dimension points and multiple panes, making split tooltips
         * the preferred layout over
         * the previous `shared` tooltip.
         *
         * @sample highcharts/tooltip/split/
         *         Split tooltip
         * @sample {highcharts|highstock} highcharts/tooltip/formatter-split/
         *         Split tooltip and custom formatter callback
         *
         * @type      {boolean}
         * @default   {highcharts} false
         * @default   {highstock} true
         * @since     5.0.0
         * @product   highcharts highstock
         * @apioption tooltip.split
         */
        /**
         * How many decimals to show in each series' y value. This is
         * overridable in each series' tooltip options object. The default is to
         * preserve all decimals.
         *
         * @sample {highcharts|highstock} highcharts/tooltip/valuedecimals/
         *         Set decimals, prefix and suffix for the value
         * @sample {highmaps} maps/tooltip/valuedecimals/
         *         Set decimals, prefix and suffix for the value
         *
         * @type      {number|undefined}
         * @since     2.2
         * @apioption tooltip.valueDecimals
         */
        /**
         * A string to prepend to each series' y value. Overridable in each
         * series' tooltip options object.
         *
         * @sample {highcharts|highstock} highcharts/tooltip/valuedecimals/
         *         Set decimals, prefix and suffix for the value
         * @sample {highmaps} maps/tooltip/valuedecimals/
         *         Set decimals, prefix and suffix for the value
         *
         * @type      {string}
         * @since     2.2
         * @apioption tooltip.valuePrefix
         */
        /**
         * A string to append to each series' y value. Overridable in each
         * series' tooltip options object.
         *
         * @sample {highcharts|highstock} highcharts/tooltip/valuedecimals/
         *         Set decimals, prefix and suffix for the value
         * @sample {highmaps} maps/tooltip/valuedecimals/
         *         Set decimals, prefix and suffix for the value
         *
         * @type      {string}
         * @since     2.2
         * @apioption tooltip.valueSuffix
         */
        /**
         * The format for the date in the tooltip header if the X axis is a
         * datetime axis. The default is a best guess based on the smallest
         * distance between points in the chart.
         *
         * @sample {highcharts} highcharts/tooltip/xdateformat/
         *         A different format
         *
         * @type      {string|Highcharts.DateTimeFormatOptions}
         * @product   highcharts highstock gantt
         * @apioption tooltip.xDateFormat
         */
        /**
         * How many decimals to show for the `point.change`
         * or the `point.cumulativeSum` value when the `series.compare`
         * or the `series.cumulative` option is set.
         * This is overridable in each series' tooltip options object.
         *
         * @type      {number}
         * @default   2
         * @since     1.0.1
         * @product   highstock
         * @apioption tooltip.changeDecimals
         */
        /**
         * Enable or disable the tooltip.
         *
         * @sample {highcharts} highcharts/tooltip/enabled/
         *         Disabled
         * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/
         *         Disable tooltip and show values on chart instead
         */
        enabled: true,
        /**
         * Enable or disable animation of the tooltip.
         *
         * @type       {boolean|Partial<Highcharts.AnimationOptionsObject>}
         * @since      2.3.0
         */
        animation: {
            duration: 300,
            // EaseOutCirc
            easing: (x) => Math.sqrt(1 - Math.pow(x - 1, 2))
        },
        /**
         * The color of the tooltip border. When `undefined` or `null`, the
         * border takes the color of the corresponding series or point.
         *
         * By default, in light mode the border color matches the background
         * color because the shadow makes the tooltip stand out. In dark mode, a
         * visible line is used because the default shadow is too dark to be
         * visible on a dark background.
         *
         * @sample {highcharts} highcharts/tooltip/bordercolor-default/
         *         Default border color
         * @sample {highcharts} highcharts/tooltip/bordercolor-black/
         *         Black border
         * @sample {highstock} stock/tooltip/general/
         *         Styled tooltip
         * @sample {highmaps} maps/tooltip/background-border/
         *         Background and border demo
         *
         * @type {Highcharts.ColorType}
         */
        borderColor: 'light-dark(var(--highcharts-neutral-color-5), var(--highcharts-neutral-color-20))', // eslint-disable-line max-len
        /**
         * The radius of the rounded border corners.
         *
         * @sample {highcharts} highcharts/tooltip/bordercolor-default/
         *         Default border radius
         * @sample {highcharts} highcharts/tooltip/borderradius-0/
         *         Square borders
         * @sample {highmaps} maps/tooltip/background-border/
         *         Background and border demo
         */
        borderRadius: 5,
        /**
         * For series on datetime axes, the date format in the tooltip's
         * header will by default be guessed based on the closest data points.
         * This member gives the default string representations used for
         * each unit. For an overview of the string or object configuration, see
         * [dateFormat](/class-reference/Highcharts.Time#dateFormat).
         *
         * @see [xAxis.dateTimeLabelFormats](#xAxis.dateTimeLabelFormats)
         *
         * @type    {Highcharts.Dictionary<string|Highcharts.DateTimeFormatOptions>}
         * @product highcharts highstock gantt
         */
        dateTimeLabelFormats: {
            millisecond: '%[AebHMSL]',
            second: '%[AebHMS]',
            minute: '%[AebHM]',
            hour: '%[AebHM]',
            day: '%[AebY]',
            week: '%v %[AebY]',
            month: '%[BY]',
            year: '%[Y]'
        },
        /**
         * A string to append to the tooltip format.
         *
         * @sample {highcharts} highcharts/tooltip/footerformat/
         *         A table for value alignment
         * @sample {highmaps} maps/tooltip/format/
         *         Format demo
         *
         * @since 2.2
         */
        footerFormat: '',
        /**
         * The name of a symbol to use for the border around the tooltip
         * header. Applies only when [tooltip.split](#tooltip.split) is
         * enabled.
         *
         * Custom callbacks for symbol path generation can also be added to
         * `Highcharts.SVGRenderer.prototype.symbols` the same way as for
         * [series.marker.symbol](plotOptions.line.marker.symbol).
         *
         * @see [tooltip.shape](#tooltip.shape)
         *
         * @sample {highstock} stock/tooltip/split-positioner/
         *         Different shapes for header and split boxes
         *
         * @type       {Highcharts.TooltipShapeValue}
         * @validvalue ["callout", "rect"]
         * @since      7.0
         * @deprecated 13.0
         * @apioption tooltip.headerShape
         */
        /**
         * Options for the tooltip header when [tooltip.split](#tooltip.split)
         * is enabled. The header is the box containing the X value in a split
         * tooltip.
         *
         * @sample {highcharts} highcharts/tooltip/header
         *         Header options for split tooltip
         * @sample {highstock} stock/tooltip/header
         *         Header options for split tooltip
         * @since  13.0.0
         */
        header: {
            /**
             * Background color for the tooltip header when
             * [tooltip.split](#tooltip.split) is enabled.
             *
             * @sample {highcharts} highcharts/tooltip/header
             *         Header options for split tooltip
             * @sample {highstock} stock/tooltip/header
             *         Header options for split tooltip
             *
             * @type {Highcharts.ColorType}
             * @apioption tooltip.header.backgroundColor
             */
            /**
             * Border color for the tooltip header when
             * [tooltip.split](#tooltip.split) is enabled.
             *
             * @sample {highcharts} highcharts/tooltip/header
             *         Header options for split tooltip
             * @sample {highstock} stock/tooltip/header
             *         Header options for split tooltip
             * @type {Highcharts.ColorType}
             * @apioption tooltip.header.borderColor
             */
            /**
             * The width of the border for the tooltip header when
             * [tooltip.split](#tooltip.split) is enabled.
             *
             * @sample {highcharts} highcharts/tooltip/header
             *         Header options for split tooltip
             * @sample {highstock} stock/tooltip/header
             *         Header options for split tooltip
             * @type {number}
             * @apioption tooltip.header.borderWidth
             */
            /**
             * Distance between the plot area and the header (except the
             * chevron) in a split tooltip, in pixels. The default value makes
             * the header text align with the axis labels.
             *
             * @sample {highcharts} highcharts/tooltip/header
             *         Header options for split tooltip
             * @sample {highstock} stock/tooltip/header
             *         Header options for split tooltip
             */
            distance: 5,
            /**
             * The name of a symbol to use for the border around the tooltip
             * header. Applies only when [tooltip.split](#tooltip.split) is
             * enabled.
             *
             * Custom callbacks for symbol path generation can also be added to
             * `Highcharts.SVGRenderer.prototype.symbols` the same way as for
             * [series.marker.symbol](plotOptions.line.marker.symbol).
             *
             * @see [tooltip.shape](#tooltip.shape)
             *
             * @sample {highstock} stock/tooltip/split-positioner/
             *         Different shapes for header and split boxes
             */
            shape: 'callout',
            /**
             * CSS styles for the tooltip header. The default is `{ fontSize:
             * '1em' }`, ensuring that the header text is the same size as the
             * axis labels.
             *
             * @sample {highcharts} highcharts/tooltip/header
             *         Header options for split tooltip
             * @sample {highstock} stock/tooltip/header
             *         Header options for split tooltip
             */
            style: {
                /** @internal */
                fontSize: '1em'
            }
        },
        /**
         * The number of milliseconds to wait until the tooltip is hidden when
         * mouse out from a point or chart.
         *
         * @since 3.0
         */
        hideDelay: 500,
        /**
         * The number of milliseconds to wait until the tooltip is shown when
         * mouse over a point. Works on initial hover.
         *
         * @sample {highcharts|highstock} highcharts/tooltip/showdelay/
         *         Show tooltip after 2 seconds
         *
         * @since 12.6.0
         */
        showDelay: 0,
        /**
         * Padding inside the tooltip, in pixels.
         *
         * @since 5.0.0
         */
        padding: 8,
        /**
         * Positioning options for fixed tooltip, taking effect only when
         * [tooltip.fixed](#tooltip.fixed) is `true`.
         *
         * @sample {highcharts} highcharts/tooltip/fixed/
         *         Fixed tooltip and position options
         * @sample {highstock} stock/tooltip/fixed/
         *         Stock chart with fixed tooltip
         * @sample {highmaps} maps/tooltip/fixed/
         *         Map with fixed tooltip
         *
         * @since 12.2.0
         */
        position: {
            /**
             * The horizontal alignment of the fixed tooltip.
             *
             * @sample highcharts/tooltip/fixed/
             *         Fixed tooltip
             * @sample {highstock} stock/tooltip/fixed/
             *         Stock chart with fixed tooltip
             *
             * @type {Highcharts.AlignValue}
             * @default left
             * @apioption tooltip.position.align
             */
            /**
             * The vertical alignment of the fixed tooltip.
             *
             * @sample highcharts/tooltip/fixed/
             *         Fixed tooltip
             * @sample {highstock} stock/tooltip/fixed/
             *         Stock chart with fixed tooltip
             *
             * @type {Highcharts.VerticalAlignValue}
             * @default top
             * @apioption tooltip.position.verticalAlign
             */
            /**
             * What the fixed tooltip alignment should be relative to.
             *
             * The default, `pane`, means that it is aligned within the plot
             * area for that given series. If the tooltip is split (as default
             * in Stock charts), each partial tooltip is aligned within the
             * series' pane.
             *
             * @sample highcharts/tooltip/fixed/
             *         Fixed tooltip
             * @sample {highstock} stock/tooltip/fixed/
             *         Stock chart with fixed tooltip
             *
             * @type {string}
             * @default pane
             * @validvalue ["pane", "chart", "plotBox", "spacingBox"]
             * @apioption tooltip.position.relativeTo
             */
            /**
             * X pixel offset from the given position. Can be used to shy away
             * from axis lines, grid lines etc to avoid the tooltip overlapping
             * other elements.
             *
             * @sample highcharts/tooltip/fixed/
             *         Fixed tooltip
             * @sample {highstock} stock/tooltip/fixed/
             *         Stock chart with fixed tooltip
             */
            x: 0,
            /**
             * Y pixel offset from the given position. Can be used to shy away
             * from axis lines, grid lines etc to avoid the tooltip overlapping
             * other elements.
             *
             * @sample highcharts/tooltip/fixed/
             *         Fixed tooltip
             * @sample {highstock} stock/tooltip/fixed/
             *         Stock chart with fixed tooltip
             */
            y: 3
        },
        /**
         * The name of a symbol to use for the border around the tooltip. Can
         * be one of: `"callout"`, `"circle"` or `"rect"`. When
         * [tooltip.split](#tooltip.split)
         * option is enabled, shape is applied to all boxes except header, which
         * is controlled by
         * [tooltip.headerShape](#tooltip.headerShape).
         *
         * Custom callbacks for symbol path generation can also be added to
         * `Highcharts.SVGRenderer.prototype.symbols` the same way as for
         * [series.marker.symbol](plotOptions.line.marker.symbol).
         *
         * Defaults to `callout` for floating tooltip, `rect` for
         * [fixed](#tooltip.fixed) tooltip.
         *
         * @type  {Highcharts.TooltipShapeValue}
         * @since 4.0
         * @default undefined
         * @apioption tooltip.shape
         */
        /**
         * Shows information in the tooltip for all points with the same X
         * value. When the tooltip is shared, the entire plot area will capture
         * mouse movement or touch events. Tooltip texts for series types with
         * ordered data (not pie, scatter, flags etc) will be shown in a single
         * bubble. This is recommended for single series charts and for
         * tablet/mobile optimized charts.
         *
         * See also [tooltip.split](#tooltip.split), that is better suited for
         * charts with many series, especially line-type series. The
         * `tooltip.split` option takes precedence over `tooltip.shared`.
         *
         * @sample {highcharts} highcharts/tooltip/shared-false/
         *         False by default
         * @sample {highcharts} highcharts/tooltip/shared-true/
         *         True
         * @sample {highcharts} highcharts/tooltip/shared-x-crosshair/
         *         True with x axis crosshair
         * @sample {highcharts} highcharts/tooltip/shared-true-mixed-types/
         *         True with mixed series types
         *
         * @since   2.1
         * @product highcharts highstock
         */
        shared: false,
        /**
         * Proximity snap for graphs or single points. It defaults to 10 for
         * mouse-powered devices and 25 for touch devices.
         *
         * Note that in most cases the whole plot area captures the mouse
         * movement, and in these cases `tooltip.snap` doesn't make sense. This
         * applies when [stickyTracking](#plotOptions.series.stickyTracking)
         * is `true` (default) and when the tooltip is [shared](#tooltip.shared)
         * or [split](#tooltip.split).
         *
         * @sample {highcharts} highcharts/tooltip/bordercolor-default/
         *         10 px by default
         * @sample {highcharts} highcharts/tooltip/snap-50/
         *         50 px on graph
         *
         * @type    {number}
         * @default 10/25
         * @since   1.2.0
         * @product highcharts highstock
         */
        snap: isTouchDevice ? 25 : 10,
        /**
         * The HTML of the tooltip header line. The context is the
         * [Point class](https://api.highcharts.com/class-reference/Highcharts.Point).
         * Variables are enclosed in curly brackets. Examples of common
         * variables to include are `x`, `y`, `series.name` and `series.color`
         * and other properties on the same form. The `point.key` variable
         * contains the category name, x value or datetime string depending on
         * the type of axis. For datetime axes, the `point.key` date format can
         * be set using `tooltip.xDateFormat`.
         * In v12+, locale-aware date names follow the browser's casing and can
         * be lower-case, so use the `ucfirst` helper (for example
         * `{ucfirst point.key}`) if you want a capitalized header.
         *
         * @sample {highcharts} highcharts/tooltip/footerformat/
         *         An HTML table in the tooltip
         * @sample {highstock} highcharts/tooltip/footerformat/
         *         An HTML table in the tooltip
         * @sample {highmaps} maps/tooltip/format/
         *         Format demo
         *
         * @type      {string}
         * @apioption tooltip.headerFormat
         */
        headerFormat: '<span style="font-size: 0.8em">{ucfirst point.key}</span><br/>',
        /**
         * The HTML of the null point's line in the tooltip. Works analogously
         * to [pointFormat](#tooltip.pointFormat).
         *
         * @sample {highcharts} highcharts/series/null-interaction
         *         Line chart with null interaction
         * @sample {highcharts} highcharts/plotoptions/series-nullformat
         *         Heatmap with null interaction
         *
         * @type      {string}
         * @apioption tooltip.nullFormat
         */
        /**
         * The HTML of the point's line in the tooltip. The context is the
         * [Point class](https://api.highcharts.com/class-reference/Highcharts.Point).
         * Variables are enclosed in curly brackets. Examples of common
         * variables to include are `x`, `y`, `series.name` and `series.color`
         * and other properties on the same form. Furthermore, `y` can be
         * extended by the `tooltip.valuePrefix` and `tooltip.valueSuffix`
         * variables. This can also be overridden for each series, which makes
         * it a good hook for displaying units.
         *
         * In styled mode, the dot is colored by a class name rather than the
         * point color.
         *
         * @sample {highcharts} highcharts/tooltip/pointformat/
         *         A different point format with value suffix
         * @sample {highcharts|highstock} highcharts/tooltip/pointformat-extra-information/
         *         Show extra information about points in the tooltip
         * @sample {highmaps} maps/tooltip/format/
         *         Format demo
         *
         * @type       {string}
         * @since      2.2
         * @apioption  tooltip.pointFormat
         */
        pointFormat: '<span style="color:{point.color}">\u25CF</span> {series.name}: <b>{point.y}</b><br/>',
        /**
         * The background color or gradient for the tooltip.
         *
         * In styled mode, the stroke width is set in the
         * `.highcharts-tooltip-box` class.
         *
         * @sample {highcharts} highcharts/tooltip/backgroundcolor-solid/
         *         Yellowish background
         * @sample {highcharts} highcharts/tooltip/backgroundcolor-gradient/
         *         Gradient
         * @sample {highcharts} highcharts/css/tooltip-border-background/
         *         Tooltip in styled mode
         * @sample {highstock} stock/tooltip/general/
         *         Custom tooltip
         * @sample {highstock} highcharts/css/tooltip-border-background/
         *         Tooltip in styled mode
         * @sample {highmaps} maps/tooltip/background-border/
         *         Background and border demo
         * @sample {highmaps} highcharts/css/tooltip-border-background/
         *         Tooltip in styled mode
         *
         * @type {Highcharts.ColorType}
         */
        backgroundColor: 'var(--highcharts-background-color)',
        /**
         * The pixel width of the tooltip border. Defaults to 1, but with a
         * `borderColor` to match the background in light mode, and a visible
         * lighter border in dark mode.
         *
         * In styled mode, the stroke width is set in the
         * `.highcharts-tooltip-box` class.
         *
         * @sample {highcharts} highcharts/tooltip/bordercolor-default/
         *         2 pixels
         * @sample {highcharts} highcharts/tooltip/borderwidth/
         *         No border (shadow only)
         * @sample {highcharts} highcharts/css/tooltip-border-background/
         *         Tooltip in styled mode
         * @sample {highstock} stock/tooltip/general/
         *         Custom tooltip
         * @sample {highstock} highcharts/css/tooltip-border-background/
         *         Tooltip in styled mode
         * @sample {highmaps} maps/tooltip/background-border/
         *         Background and border demo
         * @sample {highmaps} highcharts/css/tooltip-border-background/
         *         Tooltip in styled mode
         *
         * @type {number}
         */
        borderWidth: void 0,
        /**
         * Whether to apply a drop shadow to the tooltip. Defaults to true,
         * unless the tooltip is [fixed](#tooltip.fixed).
         *
         * @sample {highcharts} highcharts/tooltip/bordercolor-default/
         *         True by default
         * @sample {highcharts} highcharts/tooltip/shadow/
         *         False
         * @sample {highmaps} maps/tooltip/positioner/
         *         Fixed tooltip position, border and shadow disabled
         *
         * @type {boolean|Highcharts.ShadowOptionsObject}
         * @default undefined
         * @apioption tooltip.shadow
         */
        /**
         * Prevents the tooltip from switching or closing, when touched or
         * pointed.
         *
         * @sample highcharts/tooltip/stickoncontact/
         *         Tooltip sticks on pointer contact
         * @sample highcharts/tooltip/stickoncontact-anchor-link/
         *         Tooltip with clickable links
         *
         * @type      {boolean}
         * @since     8.0.1
         */
        stickOnContact: false,
        /**
         * CSS styles for the tooltip. The tooltip can also be styled through
         * the CSS class `.highcharts-tooltip`.
         *
         * Note that the default `pointerEvents` style makes the tooltip ignore
         * mouse events, so in order to use clickable tooltips, this value must
         * be set to `auto`.
         *
         * @sample {highcharts} highcharts/tooltip/style/
         *         Greater padding, bold text
         *
         * @type {Highcharts.CSSObject}
         */
        style: {
            /** @type {Highcharts.ColorType} */
            color: 'var(--highcharts-neutral-color-80)',
            cursor: 'default',
            /** @type {number|string} */
            fontSize: '0.8em'
        },
        /**
         * Use HTML to render the contents of the tooltip instead of SVG. Using
         * HTML allows advanced formatting like tables and images in the
         * tooltip. It is also recommended for rtl languages as it works around
         * rtl bugs in early Firefox.
         *
         * @sample {highcharts|highstock} highcharts/tooltip/footerformat/
         *         A table for value alignment
         * @sample {highcharts|highstock} highcharts/tooltip/fullhtml/
         *         Full HTML tooltip
         * @sample {highmaps} maps/tooltip/usehtml/
         *         Pure HTML tooltip
         *
         * @since     2.2
         */
        useHTML: false
    },
    /**
     * Highcharts by default puts a credits label in the lower right corner
     * of the chart. This can be changed using these options.
     */
    credits: {
        /**
         * Credits for map source to be concatenated with conventional credit
         * text. By default this is a format string that collects copyright
         * information from the map if available.
         *
         * @see [mapTextFull](#credits.mapTextFull)
         * @see [text](#credits.text)
         *
         * @type      {string}
         * @default   \u00a9 <a href="{geojson.copyrightUrl}">{geojson.copyrightShort}</a>
         * @since     4.2.2
         * @product   highmaps
         * @apioption credits.mapText
         */
        /**
         * Detailed credits for map source to be displayed on hover of credits
         * text. By default this is a format string that collects copyright
         * information from the map if available.
         *
         * @see [mapText](#credits.mapText)
         * @see [text](#credits.text)
         *
         * @type      {string}
         * @default   {geojson.copyright}
         * @since     4.2.2
         * @product   highmaps
         * @apioption credits.mapTextFull
         */
        /**
         * Events for the credits label.
         *
         * @type     {object}
         * @since    12.6.0
         * @apioption credits.events
         */
        /**
         * Callback function to handle click events on the credits label.
         * The callback can call `event.preventDefault()` to prevent the
         * default navigation behavior. Alternatively, you can add a general
         * event handler using `Highcharts.addEvent(Chart, 'creditsClick',
         * callback)` instead of providing it in the options tree.
         *
         * @sample {highcharts} highcharts/credits/events-click/
         *         Custom click handler
         *
         * @param {Event} event
         *        The click event object.
         *
         * @type  {Function}
         * @since 12.6.0
         * @apioption credits.events.click
         */
        /**
         * Whether to render the credits as HTML
         *
         * @since     13.0.0
         * @sample    highcharts/palette/branding
         *            Branding with HTML credits
         * @type      {boolean}
         * @default   false
         * @apioption credits.useHTML
         */
        /**
         * Whether to show the credits text.
         *
         * @sample {highcharts} highcharts/credits/enabled-false/
         *         Credits disabled
         * @sample {highstock} stock/credits/enabled/
         *         Credits disabled
         * @sample {highmaps} maps/credits/enabled-false/
         *         Credits disabled
         */
        enabled: true,
        /**
         * The URL for the credits label.
         *
         * @sample {highcharts} highcharts/credits/href/
         *         Custom URL and text
         * @sample {highmaps} maps/credits/customized/
         *         Custom URL and text
         */
        href: 'https://www.highcharts.com?credits',
        /**
         * Position configuration for the credits label.
         *
         * @sample {highcharts} highcharts/credits/position-left/
         *         Left aligned
         * @sample {highmaps} maps/credits/customized/
         *         Left aligned
         *
         * @type  {Highcharts.AlignObject}
         * @since 2.1
         */
        position: {
            align: 'right',
            verticalAlign: 'bottom',
            x: -10,
            y: -5
        },
        /**
         * CSS styles for the credits label.
         *
         * @see In styled mode, credits styles can be set with the
         *      `.highcharts-credits` class.
         *
         * @type {Highcharts.CSSObject}
         */
        style: {
            cursor: 'pointer',
            /**
             * @type {Highcharts.ColorType}
             */
            color: 'var(--highcharts-neutral-color-40)',
            /**
             * @type {number|string}
             */
            fontSize: '0.6em'
        },
        /**
         * The text for the credits label.
         *
         * @productdesc {highmaps}
         * If a map is loaded as GeoJSON, the text defaults to
         * `Highcharts @ {map-credits}`. Otherwise, it defaults to
         * `Highcharts.com`.
         *
         * @sample {highcharts} highcharts/credits/href/
         *         Custom URL and text
         * @sample {highmaps} maps/credits/customized/
         *         Custom URL and text
         */
        text: 'Highcharts.com'
    }
};
const defaultTime = new Core_Time(defaultOptions.time, defaultOptions.lang);
/**
 * Get the updated default options. Until 3.0.7, merely exposing defaultOptions
 * for outside modules wasn't enough because the setOptions method created a new
 * object.
 *
 * @function Highcharts.getOptions
 *
 * @return {Highcharts.Options}
 * Default options.
 */
function getOptions() {
    return defaultOptions;
}
/**
 * Merge the default options with custom options and return the new options
 * structure. Commonly used for defining reusable templates.
 *
 * @sample highcharts/members/setoptions Applying a global theme
 *
 * @function Highcharts.setOptions
 *
 * @param {Highcharts.Options} options
 * The new custom chart options.
 *
 * @return {Highcharts.Options}
 * Updated options.
 */
function setOptions(options) {
    fireEvent(Core_Globals, 'setOptions', { options });
    // Copy in the default options
    merge(true, defaultOptions, options);
    // Update the time object
    if (options.time) {
        defaultTime.update(defaultOptions.time);
    }
    if (options.lang && 'locale' in options.lang) {
        defaultTime.update({
            locale: options.lang.locale
        });
    }
    if (options.lang?.chartTitle) {
        defaultOptions.title = {
            ...defaultOptions.title,
            text: options.lang.chartTitle
        };
    }
    return defaultOptions;
}
/* *
 *
 *  Default Export
 *
 * */
const DefaultOptions = {
    defaultOptions,
    defaultTime,
    getOptions,
    setOptions
};
/* harmony default export */ const Defaults = (DefaultOptions);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * @typedef {"plotBox"|"spacingBox"} Highcharts.ButtonRelativeToValue
 */
/**
 * Gets fired when a series is added to the chart after load time, using the
 * `addSeries` method. Returning `false` prevents the series from being added.
 *
 * @callback Highcharts.ChartAddSeriesCallbackFunction
 *
 * @param {Highcharts.Chart} this
 *        The chart on which the event occurred.
 *
 * @param {Highcharts.ChartAddSeriesEventObject} event
 *        The event that occurred.
 */
/**
 * Contains common event information. Through the `options` property you can
 * access the series options that were passed to the `addSeries` method.
 *
 * @interface Highcharts.ChartAddSeriesEventObject
 */ /**
* The series options that were passed to the `addSeries` method.
* @name Highcharts.ChartAddSeriesEventObject#options
* @type {Highcharts.SeriesOptionsType}
*/ /**
* Prevents the default behavior of the event.
* @name Highcharts.ChartAddSeriesEventObject#preventDefault
* @type {Function}
*/ /**
* The event target.
* @name Highcharts.ChartAddSeriesEventObject#target
* @type {Highcharts.Chart}
*/ /**
* The event type.
* @name Highcharts.ChartAddSeriesEventObject#type
* @type {"addSeries"}
*/
/**
 * Gets fired when clicking on the plot background.
 *
 * @callback Highcharts.ChartClickCallbackFunction
 *
 * @param {Highcharts.Chart} this
 *        The chart on which the event occurred.
 *
 * @param {Highcharts.PointerEventObject} event
 *        The event that occurred.
 */
/**
 * Contains an axes of the clicked spot.
 *
 * @interface Highcharts.ChartClickEventAxisObject
 */ /**
* Axis at the clicked spot.
* @name Highcharts.ChartClickEventAxisObject#axis
* @type {Highcharts.Axis}
*/ /**
* Axis value at the clicked spot.
* @name Highcharts.ChartClickEventAxisObject#value
* @type {number}
*/
/**
 * Contains information about the clicked spot on the chart. Remember the unit
 * of a datetime axis is milliseconds since 1970-01-01 00:00:00.
 *
 * @interface Highcharts.ChartClickEventObject
 * @extends Highcharts.PointerEventObject
 */ /**
* Information about the x-axis on the clicked spot.
* @name Highcharts.ChartClickEventObject#xAxis
* @type {Array<Highcharts.ChartClickEventAxisObject>}
*/ /**
* Information about the y-axis on the clicked spot.
* @name Highcharts.ChartClickEventObject#yAxis
* @type {Array<Highcharts.ChartClickEventAxisObject>}
*/ /**
* Information about the z-axis on the clicked spot.
* @name Highcharts.ChartClickEventObject#zAxis
* @type {Array<Highcharts.ChartClickEventAxisObject>|undefined}
*/
/**
 * Gets fired when the chart is finished loading.
 *
 * @callback Highcharts.ChartLoadCallbackFunction
 *
 * @param {Highcharts.Chart} this
 *        The chart on which the event occurred.
 *
 * @param {global.Event} event
 *        The event that occurred.
 */
/**
 * Fires when the chart is redrawn, either after a call to `chart.redraw()` or
 * after an axis, series or point is modified with the `redraw` option set to
 * `true`.
 *
 * @callback Highcharts.ChartRedrawCallbackFunction
 *
 * @param {Highcharts.Chart} this
 *        The chart on which the event occurred.
 *
 * @param {global.Event} event
 *        The event that occurred.
 */
/**
 * Gets fired after initial load of the chart (directly after the `load` event),
 * and after each redraw (directly after the `redraw` event).
 *
 * @callback Highcharts.ChartRenderCallbackFunction
 *
 * @param {Highcharts.Chart} this
 *        The chart on which the event occurred.
 *
 * @param {global.Event} event
 *        The event that occurred.
 */
/**
 * Gets fired when an area of the chart has been selected. The default action
 * for the selection event is to zoom the chart to the selected area. It can be
 * prevented by calling `event.preventDefault()` or return false.
 *
 * @callback Highcharts.ChartSelectionCallbackFunction
 *
 * @param {Highcharts.Chart} this
 *        The chart on which the event occurred.
 *
 * @param {Highcharts.SelectEventObject} event
 *        Event information
 *
 * @return {boolean|undefined}
 *         Return false to prevent the default action, usually zoom.
 */
(''); // Detach doclets above

;// ./code/es-modules/Core/Color/Color.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { win: Color_win } = Core_Globals;

/* *
 *
 *  Helpers
 *
 * */
const colorMix = (color1, color2, weight) => (weight === 0 ? color1 :
    weight === 1 ? color2 :
        `color-mix(in srgb,${color1},${color2} ${weight * 100}%)`);
const isStringColor = (color) => isString(color) && !!color && color !== 'none';
/* *
 *
 *  Class
 *
 * */
/**
 * Handle color operations. Some object methods are chainable.
 *
 * @class
 * @name Highcharts.Color
 *
 * @param {Highcharts.ColorType} input
 * The input color.
 */
class Color {
    /* *
     *
     *  Static Functions
     *
     * */
    /**
     * Creates a color instance out of a color string or object.
     *
     * @function Highcharts.Color.parse
     *
     * @param {Highcharts.ColorType} [input]
     * The input color.
     *
     * @return {Highcharts.Color}
     * Color instance.
     */
    static parse(input) {
        return input ? new Color(input) : Color.None;
    }
    /* *
     *
     *  Constructor
     *
     * */
    constructor(input) {
        /**
         * The RGBA color components, if the color is a solid color.
         * @name Highcharts.Color#rgba
         * @type {Highcharts.RGBA}
         */
        this.rgba = [NaN, NaN, NaN, NaN];
        this.input = input;
        const GlobalColor = Core_Globals.Color;
        // Backwards compatibility, allow class overwrite
        if (GlobalColor && GlobalColor !== Color) {
            return new GlobalColor(input);
        }
        let result, rgba, i, parser;
        // Gradients
        if (typeof input === 'object' &&
            typeof input.stops !== 'undefined') {
            this.stops = input.stops.map((stop) => new Color(stop[1]));
            // Solid colors
        }
        else if (typeof input === 'string') {
            this.input = input = (Color.names[input.toLowerCase()] || input);
            i = Color.parsers.length;
            while (i-- && !rgba) {
                parser = Color.parsers[i];
                result = parser.regex.exec(input);
                if (result) {
                    rgba = parser.parse(result);
                }
            }
        }
        if (rgba) {
            this.rgba = rgba;
        }
    }
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Return the color or gradient stops in the specified format
     *
     * @function Highcharts.Color#get
     *
     * @param {string} [format]
     * Possible values are 'a', 'rgb', 'rgba' (default).
     *
     * @return {Highcharts.ColorType}
     * This color as a string or gradient stops.
     */
    get(format) {
        const input = this.input, rgba = this.rgba;
        if (this.output) {
            return this.output;
        }
        if (typeof input === 'object' &&
            typeof this.stops !== 'undefined') {
            const ret = merge(input);
            ret.stops = [].slice.call(ret.stops);
            this.stops.forEach((stop, i) => {
                ret.stops[i] = [
                    ret.stops[i][0],
                    stop.get(format)
                ];
            });
            return ret;
        }
        // It's NaN if gradient colors on a column chart
        if (rgba && isNumber(rgba[0])) {
            if (format === 'rgb' || (!format && rgba[3] === 1)) {
                return 'rgb(' + rgba[0] + ',' + rgba[1] + ',' + rgba[2] + ')';
            }
            if (format === 'a') {
                return `${rgba[3]}`;
            }
            return 'rgba(' + rgba.join(',') + ')';
        }
        return input;
    }
    /**
     * Brighten the color instance.
     *
     * @function Highcharts.Color#brighten
     *
     * @param {number} alpha
     * The alpha value.
     *
     * @return {Highcharts.Color}
     * This color with modifications.
     */
    brighten(alpha) {
        const rgba = this.rgba;
        if (this.stops) {
            this.stops.forEach(function (stop) {
                stop.brighten(alpha);
            });
        }
        else if (isNumber(alpha) && alpha !== 0) {
            if (isNumber(rgba[0])) {
                for (let i = 0; i < 3; i++) {
                    rgba[i] += pInt(alpha * 255);
                    if (rgba[i] < 0) {
                        rgba[i] = 0;
                    }
                    if (rgba[i] > 255) {
                        rgba[i] = 255;
                    }
                }
            }
            else if (Color.useColorMix && isStringColor(this.input)) {
                this.output = colorMix(this.input, alpha > 0 ? 'white' : 'black', Math.abs(alpha));
            }
        }
        return this;
    }
    /**
     * Set the color's opacity to a given alpha value.
     *
     * @function Highcharts.Color#setOpacity
     *
     * @param {number} alpha
     *        Opacity between 0 and 1.
     *
     * @return {Highcharts.Color}
     *         Color with modifications.
     */
    setOpacity(alpha) {
        if (isNumber(this.rgba[0])) {
            this.rgba[3] = alpha;
        }
        else if (Color.useColorMix && isStringColor(this.input)) {
            this.output = colorMix(this.input, '#0000', 1 - alpha);
        }
        return this;
    }
    /**
     * Return an intermediate color between two colors.
     *
     * @function Highcharts.Color#tweenTo
     *
     * @param {Highcharts.Color} to
     * The color object to tween to.
     *
     * @param {number} pos
     * The intermediate position, where 0 is the from color (current color
     * item), and 1 is the `to` color.
     *
     * @return {Highcharts.ColorType}
     * The intermediate color in rgba notation, or unsupported type.
     */
    tweenTo(to, pos) {
        const fromRgba = this.rgba, toRgba = to.rgba;
        // Unsupported color, return to-color (#3920, #7034)
        if (!isNumber(fromRgba[0]) || !isNumber(toRgba[0])) {
            if (Color.useColorMix &&
                isStringColor(this.input) &&
                isStringColor(to.input) &&
                pos < 0.99) {
                return colorMix(this.input, to.input, pos);
            }
            return to.input || 'none';
        }
        // Check for has alpha, because rgba colors perform worse due to
        // lack of support in WebKit.
        const hasAlpha = (toRgba[3] !== 1 || fromRgba[3] !== 1), channel = (to, i) => to + (fromRgba[i] - to) * (1 - pos), rgba = toRgba.slice(0, 3).map(channel).map(Math.round);
        if (hasAlpha) {
            rgba.push(channel(toRgba[3], 3));
        }
        return (hasAlpha ? 'rgba(' : 'rgb(') + rgba.join(',') + ')';
    }
}
/* *
 *
 *  Static Properties
 *
 * */
/**
 * Collection of named colors. Can be extended from the outside by adding
 * colors to Highcharts.Color.names.
 * @internal
 */
Color.names = {
    white: '#ffffff',
    black: '#000000'
};
/**
 * Collection of parsers. This can be extended from the outside by pushing
 * parsers to `Color.parsers`.
 * @internal
 */
Color.parsers = [{
        // RGBA color
        // eslint-disable-next-line max-len
        regex: /rgba\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d?(?:\.\d+)?)\s*\)/,
        parse: function (result) {
            return [
                pInt(result[1]),
                pInt(result[2]),
                pInt(result[3]),
                parseFloat(result[4], 10)
            ];
        }
    }, {
        // RGB color
        regex: /rgb\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*\)/,
        parse: function (result) {
            return [pInt(result[1]), pInt(result[2]), pInt(result[3]), 1];
        }
    }, {
        // RGBA 3 & 4 digit hex color, e.g. #F0F, #F0FA
        regex: /^#([a-f0-9])([a-f0-9])([a-f0-9])([a-f0-9])?$/i,
        parse: function (result) {
            // #abcd => #aabbccdd, hence result + result.
            return [
                (pInt(result[1] + result[1], 16)),
                (pInt(result[2] + result[2], 16)),
                (pInt(result[3] + result[3], 16)),
                !defined(result[4]) ?
                    1 :
                    (pInt(result[4] + result[4], 16) / 255)
            ];
        }
    }, {
        // RGBA 6 & 8 digit hex color, e.g. #FFCC00, #FFCC00FF
        regex: /^#([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})([a-f0-9]{2})?$/i,
        parse: function (result) {
            return [
                pInt(result[1], 16),
                pInt(result[2], 16),
                pInt(result[3], 16),
                !defined(result[4]) ?
                    1 :
                    (pInt(result[4], 16) / 255)
            ];
        }
    }];
/**
 * Whether to use CSS `color-mix` for color handling (brightening,
 * tweening). This can be disabled from the outside.
 * @internal
 */
Color.useColorMix = Color_win.CSS?.supports('color', 'color-mix(in srgb,red,blue 9%)');
/**
 * A static Color instance representing no color.
 * @name Highcharts.Color.None
 * @type {Highcharts.Color}
 * @internal
 */
Color.None = new Color(''); // Must be last static for init
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Color_Color = (Color);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * A valid color to be parsed and handled by Highcharts.
 *
 * @typedef {string} Highcharts.ColorString
 */
/**
 * A valid color type than can be parsed and handled by Highcharts. It can be a
 * color string, a gradient object, or a pattern object.
 *
 * @typedef {Highcharts.ColorString|Highcharts.GradientColorObject|Highcharts.PatternObject} Highcharts.ColorType
 */
/**
 * Gradient options instead of a solid color.
 *
 * @example
 * // Linear gradient used as a color option
 * color: {
 *     linearGradient: { x1: 0, x2: 0, y1: 0, y2: 1 },
 *     stops: [
 *         [0, '#003399'], // start
 *         [0.5, '#ffffff'], // middle
 *         [1, '#3366AA'] // end
 *     ]
 * }
 *
 * @interface Highcharts.GradientColorObject
 */ /**
* Holds an object that defines the start position and the end position relative
* to the shape.
* @name Highcharts.GradientColorObject#linearGradient
* @type {Highcharts.LinearGradientColorObject|undefined}
*/ /**
* Holds an object that defines the center position and the radius.
* @name Highcharts.GradientColorObject#radialGradient
* @type {Highcharts.RadialGradientColorObject|undefined}
*/ /**
* The first item in each tuple is the position in the gradient, where 0 is the
* start of the gradient and 1 is the end of the gradient. Multiple stops can be
* applied. The second item is the color for each stop. This color can also be
* given in the rgba format.
* @name Highcharts.GradientColorObject#stops
* @type {Array<Highcharts.GradientColorStopObject>}
*/
/**
 * Color stop tuple.
 *
 * @see Highcharts.GradientColorObject
 *
 * @interface Highcharts.GradientColorStopObject
 */ /**
* @name Highcharts.GradientColorStopObject#0
* @type {number}
*/ /**
* @name Highcharts.GradientColorStopObject#1
* @type {Highcharts.ColorString}
*/ /**
* @name Highcharts.GradientColorStopObject#color
* @type {Highcharts.Color|undefined}
*/
/**
 * Defines the start position and the end position for a gradient relative
 * to the shape. Start position (x1, y1) and end position (x2, y2) are relative
 * to the shape, where 0 means top/left and 1 is bottom/right.
 *
 * @interface Highcharts.LinearGradientColorObject
 */ /**
* Start horizontal position of the gradient. Float ranges 0-1.
* @name Highcharts.LinearGradientColorObject#x1
* @type {number}
*/ /**
* End horizontal position of the gradient. Float ranges 0-1.
* @name Highcharts.LinearGradientColorObject#x2
* @type {number}
*/ /**
* Start vertical position of the gradient. Float ranges 0-1.
* @name Highcharts.LinearGradientColorObject#y1
* @type {number}
*/ /**
* End vertical position of the gradient. Float ranges 0-1.
* @name Highcharts.LinearGradientColorObject#y2
* @type {number}
*/
/**
 * Defines the center position and the radius for a gradient.
 *
 * @interface Highcharts.RadialGradientColorObject
 */ /**
* Center horizontal position relative to the shape. Float ranges 0-1.
* @name Highcharts.RadialGradientColorObject#cx
* @type {number}
*/ /**
* Center vertical position relative to the shape. Float ranges 0-1.
* @name Highcharts.RadialGradientColorObject#cy
* @type {number}
*/ /**
* Radius relative to the shape. Float ranges 0-1.
* @name Highcharts.RadialGradientColorObject#r
* @type {number}
*/
/**
 * @interface Highcharts.RGBA
 * @extends Array<number>
 */ /**
* @name Highcharts.RGBA#length
* @type {4}
*/
/**
 * Creates a color instance out of a color string.
 *
 * @function Highcharts.color
 *
 * @param {Highcharts.ColorType} input
 *        The input color.
 *
 * @return {Highcharts.Color}
 *         Color instance
 */
(''); // Detach doclets above

;// ./code/es-modules/Core/Animation/Fx.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { parse: color } = Color_Color;

const { win: Fx_win } = Core_Globals;

/* eslint-disable no-invalid-this, valid-jsdoc */
/* *
 *
 *  Class
 *
 * */
/**
 * An animator object used internally. One instance applies to one property
 * (attribute or style prop) on one element. Animation is always initiated
 * through {@link SVGElement#animate}.
 *
 * @example
 * let rect = renderer.rect(0, 0, 10, 10).add();
 * rect.animate({ width: 100 });
 *
 * @internal
 * @class
 * @name Highcharts.Fx
 *
 * @param {Highcharts.HTMLDOMElement|Highcharts.SVGElement} elem
 * The element to animate.
 *
 * @param {Partial<Highcharts.AnimationOptionsObject>} options
 * Animation options.
 *
 * @param {string} prop
 * The single attribute or CSS property to animate.
 */
class Fx {
    /* *
     *
     *  Constructors
     *
     * */
    constructor(elem, options, prop) {
        /**
         * Current position of the animation, a value between 0 and 1.
         * @internal
         */
        this.pos = NaN;
        this.options = options;
        this.elem = elem;
        this.prop = prop;
    }
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Set the current step of a path definition on SVGElement.
     *
     * @function Highcharts.Fx#dSetter
     *
     */
    dSetter() {
        const paths = this.paths, start = paths?.[0], end = paths?.[1], now = this.now || 0;
        let path = [];
        // Land on the final path without adjustment points appended in the ends
        if (now === 1 || !start || !end) {
            path = this.toD || [];
        }
        else if (start.length === end.length && now < 1) {
            for (let i = 0; i < end.length; i++) {
                // Tween between the start segment and the end segment. Start
                // with a copy of the end segment and tween the appropriate
                // numerics
                const startSeg = start[i];
                const endSeg = end[i];
                const tweenSeg = [];
                for (let j = 0; j < endSeg.length; j++) {
                    const startItem = startSeg[j];
                    const endItem = endSeg[j];
                    // Tween numbers
                    if (isNumber(startItem) &&
                        isNumber(endItem) &&
                        // Arc boolean flags
                        !(endSeg[0] === 'A' && (j === 4 || j === 5))) {
                        tweenSeg[j] = startItem + now * (endItem - startItem);
                        // Strings, take directly from the end segment
                    }
                    else {
                        tweenSeg[j] = endItem;
                    }
                }
                path.push(tweenSeg);
            }
            // If animation is finished or length not matching, land on right value
        }
        else {
            path = end;
        }
        this.elem.attr('d', path, void 0, true);
    }
    /**
     * Update the element with the current animation step.
     *
     * @function Highcharts.Fx#update
     *
     */
    update() {
        const elem = this.elem, prop = this.prop, // If destroyed, it is null
        now = this.now, step = this.options.step;
        // Animation setter defined from outside
        if (this[prop + 'Setter']) {
            this[prop + 'Setter']();
            // Other animations on SVGElement
        }
        else if (elem.attr) {
            if (elem.element) {
                elem.attr(prop, now, null, true);
            }
            // HTML styles, raw HTML content like container size
        }
        else {
            elem.style[prop] = now + this.unit;
        }
        if (step) {
            step.call(elem, now, this);
        }
    }
    /**
     * Run an animation.
     *
     * @function Highcharts.Fx#run
     *
     * @param {number} from
     *        The current value, value to start from.
     *
     * @param {number} to
     *        The end value, value to land on.
     *
     * @param {string} unit
     *        The property unit, for example `px`.
     *
     */
    run(from, to, unit) {
        const self = this, options = self.options, timer = function (gotoEnd) {
            return timer.stopped ? false : self.step(gotoEnd);
        }, requestAnimationFrame = Fx_win.requestAnimationFrame ||
            function (step) {
                setTimeout(step, 13);
            }, step = function () {
            for (let i = 0; i < Fx.timers.length; i++) {
                if (!Fx.timers[i]()) {
                    Fx.timers.splice(i--, 1);
                }
            }
            if (Fx.timers.length) {
                requestAnimationFrame(step);
            }
        };
        if (from === to && !this.elem['forceAnimate:' + this.prop]) {
            delete options.curAnim[this.prop];
            if (options.complete &&
                Object.keys(options.curAnim).length === 0) {
                options.complete.call(this.elem);
            }
        }
        else { // #7166
            this.startTime = +new Date();
            this.start = from;
            this.end = to;
            this.unit = unit;
            this.now = this.start;
            this.pos = 0;
            timer.elem = this.elem;
            timer.prop = this.prop;
            if (timer() && Fx.timers.push(timer) === 1) {
                requestAnimationFrame(step);
            }
        }
    }
    /**
     * Run a single step in the animation.
     *
     * @function Highcharts.Fx#step
     *
     * @param {boolean} [gotoEnd]
     *        Whether to go to the endpoint of the animation after abort.
     *
     * @return {boolean}
     *         Returns `true` if animation continues.
     */
    step(gotoEnd) {
        const t = +new Date(), options = this.options, elem = this.elem, complete = options.complete, duration = options.duration, curAnim = options.curAnim;
        let ret, done;
        if (!!elem.attr && !elem.element) { // #2616, element is destroyed
            ret = false;
        }
        else if (gotoEnd || t >= duration + this.startTime) {
            this.now = this.end;
            this.pos = 1;
            this.update();
            curAnim[this.prop] = true;
            done = true;
            objectEach(curAnim, function (val) {
                if (val !== true) {
                    done = false;
                }
            });
            if (done && complete) {
                complete.call(elem);
            }
            ret = false;
        }
        else {
            this.pos = options.easing((t - this.startTime) / duration);
            this.now = this.start + ((this.end -
                this.start) * this.pos);
            this.update();
            ret = true;
        }
        return ret;
    }
    /**
     * Prepare start and end values so that the path can be animated one to one.
     *
     * @function Highcharts.Fx#initPath
     *
     * @param {Highcharts.SVGElement} elem
     *        The SVGElement item.
     *
     * @param {Highcharts.SVGPathArray|undefined} fromD
     *        Starting path definition.
     *
     * @param {Highcharts.SVGPathArray} toD
     *        Ending path definition.
     *
     * @return {Array<Highcharts.SVGPathArray,Highcharts.SVGPathArray>}
     *         An array containing start and end paths in array form so that
     *         they can be animated in parallel.
     */
    initPath(elem, fromD, toD) {
        const startX = elem.startX, endX = elem.endX, end = toD.slice(), // Copy
        isArea = elem.isArea, positionFactor = isArea ? 2 : 1, disableAnimation = fromD &&
            toD.length > fromD.length &&
            toD.hasStackedCliffs; // #16925
        let shift, fullLength, i, reverse, start = fromD?.slice(); // Copy
        if (!start || disableAnimation) {
            return [end, end];
        }
        /**
         * If shifting points, prepend a dummy point to the end path.
         * @internal
         */
        function prepend(arr, other) {
            while (arr.length < fullLength) {
                // Move to, line to or curve to?
                const moveSegment = arr[0], otherSegment = other[fullLength - arr.length];
                if (otherSegment && moveSegment[0] === 'M') {
                    if (otherSegment[0] === 'C') {
                        arr[0] = [
                            'C',
                            moveSegment[1],
                            moveSegment[2],
                            moveSegment[1],
                            moveSegment[2],
                            moveSegment[1],
                            moveSegment[2]
                        ];
                    }
                    else {
                        arr[0] = ['L', moveSegment[1], moveSegment[2]];
                    }
                }
                // Prepend a copy of the first point
                arr.unshift(moveSegment);
                // For areas, the bottom path goes back again to the left, so we
                // need to append a copy of the last point.
                if (isArea) {
                    const z = arr.pop();
                    arr.push(arr[arr.length - 1], z); // Append point and the Z
                }
            }
        }
        /**
         * Copy and append last point until the length matches the end length.
         * @internal
         */
        function append(arr) {
            while (arr.length < fullLength) {
                // Pull out the slice that is going to be appended or inserted.
                // In a line graph, the positionFactor is 1, and the last point
                // is sliced out. In an area graph, the positionFactor is 2,
                // causing the middle two points to be sliced out, since an area
                // path starts at left, follows the upper path then turns and
                // follows the bottom back.
                const segmentToAdd = arr[Math.floor(arr.length / positionFactor) - 1].slice();
                // Disable the first control point of curve segments
                if (segmentToAdd[0] === 'C') {
                    segmentToAdd[1] = segmentToAdd[5];
                    segmentToAdd[2] = segmentToAdd[6];
                }
                if (!isArea) {
                    arr.push(segmentToAdd);
                }
                else {
                    const lowerSegmentToAdd = arr[Math.floor(arr.length / positionFactor)].slice();
                    arr.splice(arr.length / 2, 0, segmentToAdd, lowerSegmentToAdd);
                }
            }
        }
        // For sideways animation, find out how much we need to shift to get the
        // start path Xs to match the end path Xs.
        if (startX && endX && endX.length) {
            for (i = 0; i < startX.length; i++) {
                // Moving left, new points coming in on right
                if (startX[i] === endX[0]) {
                    shift = i;
                    break;
                    // Moving right
                }
                else if (startX[0] ===
                    endX[endX.length - startX.length + i]) {
                    shift = i;
                    reverse = true;
                    break;
                    // Fixed from the right side, "scaling" left
                }
                else if (startX[startX.length - 1] ===
                    endX[endX.length - startX.length + i]) {
                    shift = startX.length - i;
                    break;
                }
            }
            if (typeof shift === 'undefined') {
                start = [];
            }
        }
        if (start.length && isNumber(shift)) {
            // The common target length for the start and end array, where both
            // arrays are padded in opposite ends
            fullLength = end.length + shift * positionFactor;
            if (!reverse) {
                prepend(end, start);
                append(start);
            }
            else {
                prepend(start, end);
                append(end);
            }
        }
        return [start, end];
    }
    /**
     * Handle animation of the color attributes directly.
     *
     * @function Highcharts.Fx#fillSetter
     *
     */
    fillSetter() {
        Fx.prototype.strokeSetter.apply(this, arguments);
    }
    /**
     * Handle animation of the color attributes directly.
     *
     * @function Highcharts.Fx#strokeSetter
     *
     */
    strokeSetter() {
        this.elem.attr(this.prop, color(this.start).tweenTo(color(this.end), this.pos), void 0, true);
    }
}
/* *
 *
 *  Static Properties
 *
 * */
/** @internal */
Fx.timers = [];
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
/* harmony default export */ const Animation_Fx = (Fx);

;// ./code/es-modules/Core/Animation/AnimationUtilities.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */



/* *
 *
 *  Functions
 *
 * */
/**
 * Set the global animation to either a given value, or fall back to the given
 * chart's animation option.
 *
 * @function Highcharts.setAnimation
 *
 * @param {boolean|Partial<Highcharts.AnimationOptionsObject>|undefined} animation
 *        The animation object.
 *
 * @param {Highcharts.Chart} chart
 *        The chart instance.
 *
 * @todo
 * This function always relates to a chart, and sets a property on the renderer,
 * so it should be moved to the SVGRenderer.
 */
function setAnimation(animation, chart) {
    chart.renderer.globalAnimation = pick(animation, chart.options.chart.animation, true);
}
/**
 * Get the animation in object form, where a disabled animation is always
 * returned as `{ duration: 0 }`.
 *
 * @function Highcharts.animObject
 *
 * @param {boolean|Highcharts.AnimationOptionsObject} [animation=0]
 *        An animation setting. Can be an object with duration, complete and
 *        easing properties, or a boolean to enable or disable.
 *
 * @return {Highcharts.AnimationOptionsObject}
 *         An object with at least a duration property.
 */
function animObject(animation) {
    return isObject(animation) ?
        merge({ duration: 500, defer: 0 }, animation) :
        { duration: animation ? 500 : 0, defer: 0 };
}
/**
 * Get the defer as a number value from series animation options.
 *
 * @function Highcharts.getDeferredAnimation
 *
 * @param {Highcharts.Chart} chart
 *        The chart instance.
 *
 * @param {boolean|Highcharts.AnimationOptionsObject} animation
 *        An animation setting. Can be an object with duration, complete and
 *        easing properties, or a boolean to enable or disable.
 *
 * @param {Highcharts.Series} [series]
 *        Series to defer animation.
 *
 * @return {number}
 *        The numeric value.
 */
function getDeferredAnimation(chart, animation, series) {
    const labelAnimation = animObject(animation), s = series ? [series] : chart.series;
    let defer = 0, duration = 0;
    s.forEach((series) => {
        const seriesAnim = animObject(series.options.animation);
        defer = isObject(animation) && defined(animation.defer) ?
            labelAnimation.defer :
            Math.max(defer, seriesAnim.duration + seriesAnim.defer);
        duration = Math.min(labelAnimation.duration, seriesAnim.duration);
    });
    // Disable defer for exporting
    if (chart.renderer.forExport) {
        defer = 0;
    }
    const anim = {
        defer: Math.max(0, defer - duration),
        duration: Math.min(defer, duration)
    };
    return anim;
}
/**
 * The global animate method, which uses Fx to create individual animators.
 *
 * @function Highcharts.animate
 *
 * @param {Highcharts.HTMLDOMElement|Highcharts.SVGElement} el
 *        The element to animate.
 *
 * @param {Highcharts.CSSObject|Highcharts.SVGAttributes} params
 *        An object containing key-value pairs of the properties to animate.
 *        Supports numeric as pixel-based CSS properties for HTML objects and
 *        attributes for SVGElements.
 *
 * @param {Partial<Highcharts.AnimationOptionsObject>} [opt]
 *        Animation options.
 *
 * @return {void}
 */
function animate(el, params, opt) {
    let start, unit = '', end, fx, args;
    if (!isObject(opt)) { // Number or undefined/null
        args = arguments;
        opt = {
            duration: args[2],
            easing: args[3],
            complete: args[4]
        };
    }
    if (!isNumber(opt.duration)) {
        opt.duration = 400;
    }
    opt.easing = typeof opt.easing === 'function' ?
        opt.easing :
        (Math[opt.easing] || Math.easeInOutSine);
    opt.curAnim = merge(params);
    objectEach(params, function (val, prop) {
        // Stop current running animation of this property
        stop(el, prop);
        fx = new Animation_Fx(el, opt, prop);
        end = void 0;
        if (prop === 'd' && isArray(params.d)) {
            fx.paths = fx.initPath(el, el.pathArray, params.d);
            fx.toD = params.d;
            start = 0;
            end = 1;
        }
        else if (el.attr) {
            start = el.attr(prop);
        }
        else {
            start = parseFloat(getStyle(el, prop)) || 0;
            if (prop !== 'opacity') {
                unit = 'px';
            }
        }
        if (!end) {
            end = val;
        }
        if (typeof end === 'string' && end.match('px')) {
            end = end.replace(/px/g, ''); // #4351
        }
        fx.run(start, end, unit);
    });
}
/**
 * Stop running animation.
 *
 * @function Highcharts.stop
 *
 * @param {Highcharts.SVGElement} el
 *        The SVGElement to stop animation on.
 *
 * @param {string} [prop]
 *        The property to stop animating. If given, the stop method will stop a
 *        single property from animating, while others continue.
 *
 * @return {void}
 *
 * @todo
 * A possible extension to this would be to stop a single property, when
 * we want to continue animating others. Then assign the prop to the timer
 * in the Fx.run method, and check for the prop here. This would be an
 * improvement in all cases where we stop the animation from .attr. Instead of
 * stopping everything, we can just stop the actual attributes we're setting.
 */
function stop(el, prop) {
    let i = Animation_Fx.timers.length;
    // Remove timers related to this element (#4519)
    while (i--) {
        if (Animation_Fx.timers[i].elem === el && (!prop || prop === Animation_Fx.timers[i].prop)) {
            Animation_Fx.timers[i].stopped = true; // #4667
        }
    }
}
const animationExports = {
    animate,
    animObject,
    getDeferredAnimation,
    setAnimation,
    stop
};
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const AnimationUtilities = (animationExports);
/* *
 *
 *  API Options
 *
 * */
/**
 * An animation configuration. Animation configurations can also be defined as
 * booleans, where `false` turns off animation and `true` defaults to a duration
 * of 500ms and defer of 0ms.
 *
 * @interface Highcharts.AnimationOptionsObject
 */ /**
* A callback function to execute when the animation finishes.
* @name Highcharts.AnimationOptionsObject#complete
* @type {Function|undefined}
*/ /**
* The animation defer in milliseconds.
* @name Highcharts.AnimationOptionsObject#defer
* @type {number|undefined}
*/ /**
* The animation duration in milliseconds.
* @name Highcharts.AnimationOptionsObject#duration
* @type {number|undefined}
*/ /**
* The name of an easing function as defined on the `Math` object.
* @name Highcharts.AnimationOptionsObject#easing
* @type {string|Function|undefined}
*/ /**
* A callback function to execute on each step of each attribute or CSS property
* that's being animated. The first argument contains information about the
* animation and progress.
* @name Highcharts.AnimationOptionsObject#step
* @type {Function|undefined}
*/
''; // Keeps doclets in JS file

;// ./code/es-modules/Core/Renderer/HTML/AST.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { SVG_NS, win: AST_win } = Core_Globals;


const { trustedTypes } = AST_win;
/* *
 *
 *  Constants
 *
 * */
// Create the trusted type policy. This should not be exposed.
const trustedTypesPolicy = (trustedTypes &&
    isFunction(trustedTypes.createPolicy) &&
    trustedTypes.createPolicy('highcharts', {
        createHTML: (s) => s
    }));
const emptyHTML = trustedTypesPolicy ?
    trustedTypesPolicy.createHTML('') :
    '';
/* *
 *
 *  Class
 *
 * */
/**
 * The AST class represents an abstract syntax tree of HTML or SVG content. It
 * can take HTML as an argument, parse it, optionally transform it to SVG, then
 * perform sanitation before inserting it into the DOM.
 *
 * @class
 * @name Highcharts.AST
 *
 * @param {string|Array<Highcharts.ASTNode>} source
 * Either an HTML string or an ASTNode list to populate the tree.
 */
class AST {
    /* *
     *
     *  Static Functions
     *
     * */
    /**
     * Filter an object of SVG or HTML attributes against the allow list.
     *
     * @static
     *
     * @function Highcharts.AST#filterUserAttributes
     *
     * @param {Highcharts.SVGAttributes} attributes The attributes to filter
     *
     * @return {Highcharts.SVGAttributes}
     * The filtered attributes
     */
    static filterUserAttributes(attributes) {
        objectEach(attributes, (val, key) => {
            let valid = true;
            if (AST.allowedAttributes.indexOf(key) === -1) {
                valid = false;
            }
            if (['background', 'dynsrc', 'href', 'lowsrc', 'src']
                .indexOf(key) !== -1) {
                valid = isString(val) && AST.allowedReferences.some((ref) => val.indexOf(ref) === 0);
            }
            if (!valid) {
                error(33, false, void 0, {
                    'Invalid attribute in config': `${key}`
                });
                delete attributes[key];
            }
            // #17753, < is not allowed in SVG attributes
            if (isString(val) && attributes[key]) {
                attributes[key] = val.replace(/</g, '&lt;');
            }
        });
        return attributes;
    }
    /**
     * Utility function to parse a style string to a CSSObject.
     *
     * @internal
     * @param {string} style
     * The style string to parse.
     * @return {Highcharts.CSSObject}
     * The parsed CSSObject.
     */
    static parseStyle(style) {
        return style
            .split(';')
            .reduce((styles, line) => {
            const pair = line.split(':').map((s) => s.trim()), key = pair.shift();
            if (key && pair.length) {
                styles[key.replace(/-([a-z])/g, (g) => g[1].toUpperCase())] = pair.join(':'); // #17146
            }
            return styles;
        }, {});
    }
    /**
     * Utility function to set html content for an element by passing in a
     * markup string. The markup is safely parsed by the AST class to avoid
     * XSS vulnerabilities. This function should be used instead of setting
     * `innerHTML` in all cases where the content is not fully trusted.
     *
     * @static
     * @function Highcharts.AST#setElementHTML
     *
     * @param {SVGDOMElement|HTMLDOMElement} el
     * Node to set content of.
     *
     * @param {string} html
     * Markup string
     */
    static setElementHTML(el, html) {
        el.innerHTML = AST.emptyHTML; // Clear previous
        if (html) {
            const ast = new AST(html);
            ast.addToDOM(el);
        }
    }
    /* *
     *
     *  Constructor
     *
     * */
    // Construct an AST from HTML markup, or wrap an array of existing AST nodes
    constructor(source) {
        this.nodes = typeof source === 'string' ?
            this.parseMarkup(source) : source;
    }
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Add the tree defined as a hierarchical JS structure to the DOM
     *
     * @function Highcharts.AST#addToDOM
     *
     * @param {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement} parent
     * The node where it should be added
     *
     * @return {Highcharts.HTMLDOMElement|Highcharts.SVGDOMElement}
     * The inserted node.
     */
    addToDOM(parent) {
        /**
         * @internal
         * @param {Highcharts.ASTNode} subtree
         * HTML/SVG definition.
         * @param {Element} [subParent]
         * Parent node.
         * @return {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement}
         * The inserted node.
         */
        function recurse(subtree, subParent) {
            let ret;
            splat(subtree).forEach(function (item) {
                const tagName = item.tagName;
                const textNode = item.textContent ?
                    Core_Globals.doc.createTextNode(item.textContent) :
                    void 0;
                // Whether to ignore the AST filtering totally, #15345
                const bypassHTMLFiltering = AST.bypassHTMLFiltering;
                let node;
                if (tagName) {
                    if (tagName === '#text') {
                        node = textNode;
                    }
                    else if (AST.allowedTags.indexOf(tagName) !== -1 ||
                        bypassHTMLFiltering) {
                        const NS = tagName === 'svg' ?
                            SVG_NS :
                            (subParent.namespaceURI || SVG_NS);
                        const element = Core_Globals.doc.createElementNS(NS, tagName);
                        const attributes = item.attributes || {};
                        // Apply attributes from root of AST node, legacy from
                        // from before TextBuilder
                        objectEach(item, function (val, key) {
                            if (key !== 'tagName' &&
                                key !== 'attributes' &&
                                key !== 'children' &&
                                key !== 'style' &&
                                key !== 'textContent') {
                                attributes[key] = val;
                            }
                        });
                        attr(element, bypassHTMLFiltering ?
                            attributes :
                            AST.filterUserAttributes(attributes));
                        if (item.style) {
                            css(element, item.style);
                        }
                        // Add text content
                        if (textNode) {
                            element.appendChild(textNode);
                        }
                        // Recurse
                        recurse(item.children || [], element);
                        node = element;
                    }
                    else {
                        error(33, false, void 0, {
                            'Invalid tagName in config': tagName
                        });
                    }
                }
                // Add to the tree
                if (node) {
                    subParent.appendChild(node);
                }
                ret = node;
            });
            // Return last node added (on top level it's the only one)
            return ret;
        }
        return recurse(this.nodes, parent);
    }
    /**
     * Parse HTML/SVG markup into AST Node objects. Used internally from the
     * constructor.
     *
     * @internal
     * @param {string} markup
     * The markup string.
     * @return {Array<Highcharts.ASTNode>}
     * The parsed nodes.
     */
    parseMarkup(markup) {
        const nodes = [];
        markup = markup
            .trim()
            // The style attribute throws a warning when parsing when CSP is
            // enabled (#6884), so use an alias and pick it up below
            // Make all quotation marks parse correctly to DOM (#17627)
            .replace(/ style=(["'])/g, ' data-style=$1');
        let doc;
        try {
            doc = new DOMParser().parseFromString(trustedTypesPolicy ?
                trustedTypesPolicy.createHTML(markup) :
                markup, 'text/html');
        }
        catch {
            // There are two cases where this fails:
            // 1. IE9 and PhantomJS, where the DOMParser only supports parsing
            //    XML
            // 2. Due to a Chromium issue where chart redraws are triggered by
            //    a `beforeprint` event (#16931),
            //    https://issues.chromium.org/issues/40222135
        }
        if (!doc) {
            const body = createElement('div');
            body.innerHTML = markup;
            doc = { body };
        }
        const appendChildNodes = (node, addTo) => {
            const tagName = node.nodeName.toLowerCase();
            // Add allowed tags
            const astNode = {
                tagName
            };
            if (tagName === '#text') {
                astNode.textContent = node.textContent || '';
            }
            const parsedAttributes = node.attributes;
            // Add attributes
            if (parsedAttributes) {
                const attributes = {};
                [].forEach.call(parsedAttributes, (attrib) => {
                    if (attrib.name === 'data-style') {
                        astNode.style = AST.parseStyle(attrib.value);
                    }
                    else {
                        attributes[attrib.name] = attrib.value;
                    }
                });
                astNode.attributes = attributes;
            }
            // Handle children
            if (node.childNodes.length) {
                const children = [];
                [].forEach.call(node.childNodes, (childNode) => {
                    appendChildNodes(childNode, children);
                });
                if (children.length) {
                    astNode.children = children;
                }
            }
            addTo.push(astNode);
        };
        [].forEach.call(doc.body.childNodes, (childNode) => appendChildNodes(childNode, nodes));
        return nodes;
    }
}
/* *
 *
 *  Static Properties
 *
 * */
/**
 * The list of allowed SVG or HTML attributes, used for sanitizing
 * potentially harmful content from the chart configuration before adding to
 * the DOM.
 *
 * @see [Source code with default values](
 * https://github.com/highcharts/highcharts/blob/master/ts/Core/Renderer/HTML/AST.ts#:~:text=public%20static%20allowedAttributes)
 *
 * @example
 * // Allow a custom, trusted attribute
 * Highcharts.AST.allowedAttributes.push('data-value');
 *
 * @name Highcharts.AST.allowedAttributes
 * @type {Array<string>}
 */
AST.allowedAttributes = [
    'alt',
    'aria-controls',
    'aria-describedby',
    'aria-expanded',
    'aria-haspopup',
    'aria-hidden',
    'aria-label',
    'aria-labelledby',
    'aria-live',
    'aria-pressed',
    'aria-readonly',
    'aria-roledescription',
    'aria-selected',
    'aria-sort',
    'class',
    'clip-path',
    'color',
    'colspan',
    'cx',
    'cy',
    'd',
    'disabled',
    'dx',
    'dy',
    'fill',
    'filterUnits',
    'flood-color',
    'flood-opacity',
    'height',
    'href',
    'id',
    'in',
    'in2',
    'markerHeight',
    'markerWidth',
    'offset',
    'opacity',
    'operator',
    'orient',
    'padding',
    'paddingLeft',
    'paddingRight',
    'patternUnits',
    'r',
    'radius',
    'refX',
    'refY',
    'result',
    'role',
    'rowspan',
    'scope',
    'slope',
    'src',
    'startOffset',
    'stdDeviation',
    'stroke-linecap',
    'stroke-width',
    'stroke',
    'style',
    'summary',
    'tabindex',
    'tableValues',
    'target',
    'text-align',
    'text-anchor',
    'textAnchor',
    'textLength',
    'title',
    'type',
    'valign',
    'width',
    'x',
    'x1',
    'x2',
    'xlink:href',
    'y',
    'y1',
    'y2',
    'zIndex'
];
/**
 * The list of allowed references for referring attributes like `href` and
 * `src`. Attribute values will only be allowed if they start with one of
 * these strings.
 *
 * @see [Source code with default values](
 * https://github.com/highcharts/highcharts/blob/master/ts/Core/Renderer/HTML/AST.ts#:~:text=public%20static%20allowedReferences)
 *
 * @example
 * // Allow tel:
 * Highcharts.AST.allowedReferences.push('tel:');
 *
 * @name    Highcharts.AST.allowedReferences
 * @type    {Array<string>}
 */
AST.allowedReferences = [
    'https://',
    'http://',
    'mailto:',
    '/',
    '../',
    './',
    '#'
];
/**
 * The list of allowed SVG or HTML tags, used for sanitizing potentially
 * harmful content from the chart configuration before adding to the DOM.
 *
 * @see [Source code with default values](
 * https://github.com/highcharts/highcharts/blob/master/ts/Core/Renderer/HTML/AST.ts#:~:text=public%20static%20allowedTags)
 *
 * @example
 * // Allow a custom, trusted tag
 * Highcharts.AST.allowedTags.push('blink'); // ;)
 *
 * @name    Highcharts.AST.allowedTags
 * @type    {Array<string>}
 */
AST.allowedTags = [
    '#text',
    'a',
    'abbr',
    'b',
    'br',
    'button',
    'caption',
    'circle',
    'clipPath',
    'code',
    'dd',
    'defs',
    'div',
    'dl',
    'dt',
    'em',
    'feComponentTransfer',
    'feComposite',
    'feDropShadow',
    'feFlood',
    'feFuncA',
    'feFuncB',
    'feFuncG',
    'feFuncR',
    'feGaussianBlur',
    'feMerge',
    'feMergeNode',
    'feMorphology',
    'feOffset',
    'filter',
    'h1',
    'h2',
    'h3',
    'h4',
    'h5',
    'h6',
    'hr',
    'i',
    'img',
    'li',
    'linearGradient',
    'marker',
    'ol',
    'p',
    'path',
    'pattern',
    'pre',
    'rect',
    'small',
    'span',
    'stop',
    'strong',
    'style',
    'sub',
    'sup',
    'svg',
    'table',
    'tbody',
    'td',
    'text',
    'textPath',
    'th',
    'thead',
    'title',
    'tr',
    'tspan',
    'u',
    'ul'
];
/** @internal */
AST.emptyHTML = emptyHTML;
/**
 * Allow all custom SVG and HTML attributes, references and tags (together
 * with potentially harmful ones) to be added to the DOM from the chart
 * configuration. In other words, disable the allow-listing which is the
 * primary functionality of the AST.
 *
 * WARNING: Setting this property to `true` while allowing untrusted user
 * data in the chart configuration will expose your application to XSS
 * security risks!
 *
 * Note that in case you want to allow a known set of tags or attributes,
 * you should allow-list them instead of disabling the filtering totally.
 * See [allowedAttributes](Highcharts.AST#.allowedAttributes),
 * [allowedReferences](Highcharts.AST#.allowedReferences) and
 * [allowedTags](Highcharts.AST#.allowedTags). The `bypassHTMLFiltering`
 * setting is intended only for those cases where allow-listing is not
 * practical, and the chart configuration already comes from a secure
 * source.
 *
 * @example
 * // Allow all custom attributes, references and tags (disable DOM XSS
 * // filtering)
 * Highcharts.AST.bypassHTMLFiltering = true;
 *
 * @name Highcharts.AST.bypassHTMLFiltering
 * @static
 */
AST.bypassHTMLFiltering = false;
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const HTML_AST = (AST);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * Serialized form of an SVG/HTML definition, including children.
 *
 * @interface Highcharts.ASTNode
 */ /**
* @name Highcharts.ASTNode#attributes
* @type {Highcharts.SVGAttributes|undefined}
*/ /**
* @name Highcharts.ASTNode#children
* @type {Array<Highcharts.ASTNode>|undefined}
*/ /**
* @name Highcharts.ASTNode#tagName
* @type {string|undefined}
*/ /**
* @name Highcharts.ASTNode#textContent
* @type {string|undefined}
*/
(''); // Keeps doclets above in file

;// ./code/es-modules/Core/Templating.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { defaultOptions: Templating_defaultOptions, defaultTime: Templating_defaultTime } = Defaults;

const { pageLang: Templating_pageLang } = Core_Globals;

/** @internal */
const helpers = {
    // Built-in helpers
    add: (a, b) => a + b,
    divide: (a, b) => (b !== 0 ? correctFloat(a / b) : ''),
    // eslint-disable-next-line eqeqeq
    eq: (a, b) => a == b,
    each: function (arr) {
        const match = arguments[arguments.length - 1];
        return isArray(arr) ?
            arr.map((item, i) => format(match.body, extend(isObject(item) ? item : { '@this': item }, {
                '@index': i,
                '@first': i === 0,
                '@last': i === arr.length - 1
            }))).join('') :
            false;
    },
    ge: (a, b) => a >= b,
    gt: (a, b) => a > b,
    'if': (condition) => !!condition,
    le: (a, b) => a <= b,
    lt: (a, b) => a < b,
    multiply: (a, b) => correctFloat(a * b, 15),
    // eslint-disable-next-line eqeqeq
    ne: (a, b) => a != b,
    subtract: (a, b) => a - b,
    ucfirst: ucfirst,
    unless: (condition) => !condition
};
const numberFormatCache = {};
/* *
 *
 *  Functions
 *
 * */
/**
 * Internal convenience function.
 * @internal
 */
const isQuotedString = (str) => /^["'].+["']$/.test(str);
/**
 * Formats a JavaScript date timestamp (milliseconds since Jan 1st 1970) into a
 * human readable date string. The format is a subset of the formats for PHP's
 * [strftime](https://www.php.net/manual/en/function.strftime.php) function.
 * Additional formats can be given in the {@link Highcharts.dateFormats} hook.
 *
 * Since v6.0.5, all internal dates are formatted through the
 * {@link Highcharts.Chart#time} instance to respect chart-level time settings.
 * The `Highcharts.dateFormat` function only reflects global time settings set
 * with `setOptions`.
 *
 * Supported format keys:
 * - `%a`: Short weekday, like 'Mon'
 * - `%A`: Long weekday, like 'Monday'
 * - `%d`: Two digit day of the month, 01 to 31
 * - `%e`: Day of the month, 1 through 31
 * - `%w`: Day of the week, 0 through 6
 * - `%b`: Short month, like 'Jan'
 * - `%B`: Long month, like 'January'
 * - `%m`: Two digit month number, 01 through 12
 * - `%y`: Two digits year, like 09 for 2009
 * - `%Y`: Four digits year, like 2009
 * - `%H`: Two digits hours in 24h format, 00 through 23
 * - `%k`: Hours in 24h format, 0 through 23
 * - `%I`: Two digits hours in 12h format, 00 through 11
 * - `%l`: Hours in 12h format, 1 through 12
 * - `%M`: Two digits minutes, 00 through 59
 * - `%p`: Upper case AM or PM
 * - `%P`: Lower case AM or PM
 * - `%S`: Two digits seconds, 00 through 59
 * - `%L`: Milliseconds (naming from Ruby)
 *
 * @function Highcharts.dateFormat
 *
 * @param {string} format
 *        The desired format where various time representations are prefixed
 *        with `%`.
 *
 * @param {number} timestamp
 *        The JavaScript timestamp.
 *
 * @param {boolean} [upperCaseFirst=false]
 *        Upper case first letter in the return.
 *
 * @return {string}
 *         The formatted date.
 */
function dateFormat(format, timestamp, upperCaseFirst) {
    return Templating_defaultTime.dateFormat(format, timestamp, upperCaseFirst);
}
/**
 * Format a string according to a subset of the rules of Python's String.format
 * method.
 *
 * @example
 * let s = Highcharts.format(
 *     'The {color} fox was {len:.2f} feet long',
 *     { color: 'red', len: Math.PI }
 * );
 * // => The red fox was 3.14 feet long
 *
 * @function Highcharts.format
 *
 * @param {string} str
 *        The string to format.
 *
 * @param {Record<string, *>} ctx
 *        The context, a collection of key-value pairs where each key is
 *        replaced by its value.
 *
 * @param {Highcharts.Chart} [owner]
 *        A `Chart` or `Grid` instance used to get numberFormatter and time.
 *
 * @return {string}
 *         The formatted string.
 */
function format(str = '', ctx, owner) {
    const regex = /\{([^{}]+)\}/g, 
    // The sub expression regex is the same as the top expression regex,
    // but except parens and block helpers (#), and surrounded by parens
    // instead of curly brackets.
    subRegex = /\(([^()]+)\)/g, matches = [], floatRegex = /f$/, decRegex = /\.(\d)/, lang = owner?.options?.lang || Templating_defaultOptions.lang, time = owner?.time || Templating_defaultTime, numberFormatter = owner?.numberFormatter || numberFormat.bind(owner);
    /*
     * Get a literal or variable value inside a template expression. May be
     * extended with other types like string or null if needed, but keep it
     * small for now.
     */
    const resolveProperty = (key = '') => {
        let n;
        // Literals
        if (key === 'true') {
            return true;
        }
        if (key === 'false') {
            return false;
        }
        if ((n = Number(key)).toString() === key) {
            return n;
        }
        if (isQuotedString(key)) {
            return key.slice(1, -1);
        }
        // Variables and constants
        return getNestedProperty(key, ctx);
    };
    let match, currentMatch, depth = 0, hasSub;
    // Parse and create tree
    while ((match = regex.exec(str)) !== null) {
        // When a sub expression is found, it is evaluated first, and the
        // results recursively evaluated until no subexpression exists.
        const mainMatch = match, subMatch = subRegex.exec(match[1]);
        if (subMatch) {
            match = subMatch;
            hasSub = true;
        }
        if (!currentMatch?.isBlock) {
            currentMatch = {
                ctx,
                expression: match[1],
                find: match[0],
                isBlock: match[1].charAt(0) === '#',
                start: match.index,
                startInner: match.index + match[0].length,
                length: match[0].length
            };
        }
        // Identify helpers
        const fn = (currentMatch.isBlock ? mainMatch : match)[1].split(' ')[0].replace('#', '');
        if (helpers[fn]) {
            // Block helper, only 0 level is handled
            if (currentMatch.isBlock && fn === currentMatch.fn) {
                depth++;
            }
            if (!currentMatch.fn) {
                currentMatch.fn = fn;
            }
        }
        // Closing a block helper
        const startingElseSection = match[1] === 'else';
        if (currentMatch.isBlock &&
            currentMatch.fn && (match[1] === `/${currentMatch.fn}` ||
            startingElseSection)) {
            if (!depth) { // === 0
                const start = currentMatch.startInner, body = str.substr(start, match.index - start);
                // Either closing without an else section, or when encountering
                // an else section
                if (currentMatch.body === void 0) {
                    currentMatch.body = body;
                    currentMatch.startInner = match.index + match[0].length;
                    // The body exists already, so this is the else section
                }
                else {
                    currentMatch.elseBody = body;
                }
                currentMatch.find += body + match[0];
                if (!startingElseSection) {
                    matches.push(currentMatch);
                    currentMatch = void 0;
                }
            }
            else if (!startingElseSection) {
                depth--;
            }
            // Common expression
        }
        else if (!currentMatch.isBlock) {
            matches.push(currentMatch);
        }
        // Evaluate sub-matches one by one to prevent orphaned block closers
        if (subMatch && !currentMatch?.isBlock) {
            break;
        }
    }
    // Execute
    matches.forEach((match) => {
        const { body, elseBody, expression, fn } = match;
        let replacement, i;
        // Helper function
        if (fn) {
            // Pass the helpers the amount of arguments defined by the function,
            // then the match as the last argument.
            const args = [match], parts = [], len = expression.length;
            let start = 0, startChar;
            for (i = 0; i <= len; i++) {
                const char = expression.charAt(i);
                // Start of string
                if (!startChar && (char === '"' || char === '\'')) {
                    startChar = char;
                    // End of string
                }
                else if (startChar === char) {
                    startChar = '';
                }
                if (!startChar &&
                    (char === ' ' || i === len)) {
                    parts.push(expression.substr(start, i - start));
                    start = i + 1;
                }
            }
            i = helpers[fn].length;
            while (i--) {
                args.unshift(resolveProperty(parts[i + 1]));
            }
            replacement = helpers[fn].apply(ctx, args);
            // Block helpers may return true or false. They may also return a
            // string, like the `each` helper.
            if (match.isBlock && typeof replacement === 'boolean') {
                replacement = format(replacement ? body : elseBody, ctx, owner);
            }
            // Simple variable replacement
        }
        else {
            const valueAndFormat = isQuotedString(expression) ?
                [expression] : expression.split(':');
            replacement = resolveProperty(valueAndFormat.shift() || '');
            // Format the replacement
            if (valueAndFormat.length && typeof replacement === 'number') {
                const segment = valueAndFormat.join(':');
                if (floatRegex.test(segment)) { // Float
                    const decimals = parseInt((segment.match(decRegex) || ['', '-1'])[1], 10);
                    if (replacement !== null) {
                        replacement = numberFormatter(replacement, decimals, lang.decimalPoint, segment.indexOf(',') > -1 ? lang.thousandsSep : '');
                    }
                }
                else {
                    replacement = time.dateFormat(segment, replacement);
                }
            }
            // Use string literal in order to be preserved in the outer
            // expression
            subRegex.lastIndex = 0;
            if (subRegex.test(match.find) && isString(replacement)) {
                replacement = `"${replacement}"`;
            }
        }
        str = str.replace(match.find, pick(replacement, ''));
    });
    return hasSub ? format(str, ctx, owner) : str;
}
/**
 * Format a number and return a string based on input settings.
 *
 * @sample highcharts/members/highcharts-numberformat/
 *         Custom number format
 *
 * @function Highcharts.numberFormat
 *
 * @param {number} number
 *        The input number to format.
 *
 * @param {number} decimals
 *        The amount of decimals. A value of -1 preserves the amount in the
 *        input number.
 *
 * @param {string} [decimalPoint]
 *        The decimal point, defaults to the one given in the lang options, or
 *        a dot.
 *
 * @param {string} [thousandsSep]
 *        The thousands separator, defaults to the one given in the lang
 *        options, or a space character.
 *
 * @return {string}
 *         The formatted number.
 */
function numberFormat(number, decimals, decimalPoint, thousandsSep) {
    number = +number || 0;
    decimals = +decimals;
    let ret, fractionDigits, [mantissa, exp] = number.toString().split('e').map(Number);
    const lang = this?.options?.lang || Templating_defaultOptions.lang, origDec = (number.toString().split('.')[1] || '').split('e')[0].length, firstDecimals = decimals, options = {};
    decimalPoint ?? (decimalPoint = lang.decimalPoint);
    thousandsSep ?? (thousandsSep = lang.thousandsSep);
    if (decimals === -1) {
        // Preserve decimals. Not huge numbers (#3793).
        decimals = Math.min(origDec, 20);
    }
    else if (!isNumber(decimals)) {
        decimals = 2;
    }
    else if (decimals && exp < 0) {
        // Expose decimals from exponential notation (#7042)
        fractionDigits = decimals + exp;
        if (fractionDigits >= 0) {
            // Remove too small part of the number while keeping the notation
            mantissa = +mantissa.toExponential(fractionDigits).split('e')[0];
            decimals = fractionDigits;
        }
        else {
            // `fractionDigits < 0`
            mantissa = Math.floor(mantissa);
            if (decimals < 20) {
                // Use number instead of exponential notation (#7405)
                number = +(mantissa * Math.pow(10, exp)).toFixed(decimals);
            }
            else {
                // Or zero
                number = 0;
            }
            exp = 0;
        }
    }
    if (exp) {
        decimals ?? (decimals = 2);
        number = mantissa;
    }
    if (isNumber(decimals) && decimals >= 0) {
        options.minimumFractionDigits = decimals;
        options.maximumFractionDigits = decimals;
    }
    if (thousandsSep === '') {
        options.useGrouping = false;
    }
    const hasSeparators = thousandsSep || decimalPoint, locale = hasSeparators ?
        'en' : (this?.locale || lang.locale || Templating_pageLang), cacheKey = JSON.stringify(options) + locale, nf = numberFormatCache[cacheKey] ?? (numberFormatCache[cacheKey] = new Intl.NumberFormat(locale, options));
    ret = nf.format(number);
    // If thousandsSep or decimalPoint are set, fall back to using English
    // format with string replacement for the separators.
    if (hasSeparators) {
        ret = ret
            // Preliminary step to avoid re-swapping (#22402)
            .replace(/([,\.])/g, '_$1')
            .replace(/_\,/g, thousandsSep ?? ',')
            .replace('_.', decimalPoint ?? '.');
    }
    if (
    // Remove signed zero (#20564)
    (!decimals && +ret === 0) ||
        // Small numbers, no decimals (#14023)
        (exp < 0 && !firstDecimals)) {
        ret = '0';
    }
    if (exp && +ret !== 0) {
        ret += 'e' + (exp < 0 ? '' : '+') + exp;
    }
    return ret;
}
/* *
 *
 *  Default Export
 *
 * */
const Templating = {
    dateFormat,
    format,
    helpers,
    numberFormat
};
/* harmony default export */ const Core_Templating = (Templating);
/* *
 * API Declarations
 * */
/**
 * @interface Highcharts.Templating
 *
 * The Highcharts.Templating interface provides a structure for defining
 * helpers. Helpers can be used as conditional blocks or functions within
 * expressions. Highcharts includes several built-in helpers and supports
 * the addition of custom helpers.
 *
 * @see [More information](
 * https://www.highcharts.com/docs/chart-concepts/templating#helpers)
 *
 * @example
 * // Define a custom helper to return the absolute value of a number
 * Highcharts.Templating.helpers.abs = value => Math.abs(value);
 *
 * // Usage in a format string
 * format: 'Absolute value: {abs point.y}'
 *
 * @name Highcharts.Templating#helpers
 * @type {Record<string, Function>}
 */
(''); // Keeps doclets above in file

;// ./code/es-modules/Core/Renderer/RendererUtilities.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */

/* *
 *
 *  Imports
 *
 * */

/* *
 *
 *  Namespace
 *
 * */
/** @internal */
var RendererUtilities;
(function (RendererUtilities) {
    /* *
     *
     *  Declarations
     *
     * */
    /* *
     *
     *  Functions
     *
     * */
    /**
     * General distribution algorithm for distributing labels of differing size
     * along a confined length in two dimensions. The algorithm takes an array
     * of objects containing a size, a target and a rank. It will place the
     * labels as close as possible to their targets, skipping the lowest ranked
     * labels if necessary.
     * @internal
     */
    function distribute(boxes, len, maxDistance) {
        // Original array will be altered with added .pos
        const origBoxes = boxes, reducedLen = origBoxes.reducedLen || len, sortByRank = (a, b) => (b.rank || 0) - (a.rank || 0), sortByTarget = (a, b) => a.target - b.target, restBoxes = [], // The outranked overshoot
        boxesLength = boxes.length, forDeletion = [], push = restBoxes.push;
        let i, overlapping = true, box, target, total = 0, equalRank;
        // If the total size exceeds the len, remove those boxes with the lowest
        // rank
        i = boxesLength;
        while (i--) {
            total += boxes[i].size;
        }
        // Sort by rank, then slice away overshoot
        if (total > reducedLen) {
            stableSort(boxes, sortByRank);
            equalRank = boxes[0].rank === boxes[boxes.length - 1].rank;
            // When all boxes have equal rank (pie data labels, flags - #10073),
            // decimate from the center outwards by repeatedly splitting index
            // ranges. This guarantees progress and avoids cursor/step stalling
            // on repeated indices (#23541). When ranks differ, remove the
            // lowest ranked boxes from the end.
            if (equalRank) {
                const ranges = [[0, boxesLength - 1]];
                while (ranges.length && total > reducedLen) {
                    const range = ranges.shift();
                    if (!range) {
                        break;
                    }
                    i = Math.floor((range[0] + range[1]) / 2);
                    box = boxes[i];
                    if (pushUnique(forDeletion, i)) {
                        total -= box.size;
                    }
                    if (range[0] < i) {
                        ranges.push([range[0], i - 1]);
                    }
                    if (i < range[1]) {
                        ranges.push([i + 1, range[1]]);
                    }
                }
            }
            else {
                i = boxesLength - 1;
                while (total > reducedLen && i >= 0) {
                    box = boxes[i];
                    if (pushUnique(forDeletion, i)) {
                        total -= box.size;
                    }
                    i--;
                }
            }
            // Clean out the boxes marked for deletion
            forDeletion
                .sort((a, b) => b - a)
                .forEach((i) => push.apply(restBoxes, boxes.splice(i, 1)));
        }
        // Order by target
        stableSort(boxes, sortByTarget);
        // So far we have been mutating the original array. Now
        // create a copy with target arrays
        boxes = boxes.map((box) => ({
            size: box.size,
            targets: [box.target],
            align: pick(box.align, 0.5)
        }));
        while (overlapping) {
            // Initial positions: target centered in box
            i = boxes.length;
            while (i--) {
                box = boxes[i];
                // Composite box, average of targets
                target = (Math.min.apply(0, box.targets) +
                    Math.max.apply(0, box.targets)) / 2;
                box.pos = clamp(target - box.size * box.align, 0, len - box.size);
            }
            // Detect overlap and join boxes
            i = boxes.length;
            overlapping = false;
            while (i--) {
                // Overlap
                if (i > 0 &&
                    boxes[i - 1].pos + boxes[i - 1].size >
                        boxes[i].pos) {
                    // Add this size to the previous box
                    boxes[i - 1].size += boxes[i].size;
                    boxes[i - 1].targets = boxes[i - 1]
                        .targets
                        .concat(boxes[i].targets);
                    boxes[i - 1].align = 0.5;
                    // Overlapping right, push left
                    if (boxes[i - 1].pos + boxes[i - 1].size > len) {
                        boxes[i - 1].pos = len - boxes[i - 1].size;
                    }
                    boxes.splice(i, 1); // Remove this item
                    overlapping = true;
                }
            }
        }
        // Add the rest (hidden boxes)
        push.apply(origBoxes, restBoxes);
        // Now the composite boxes are placed, we need to put the original boxes
        // within them
        i = 0;
        boxes.some((box) => {
            let posInCompositeBox = 0;
            // Exceeded maxDistance => abort
            return (box.targets || []).some(() => {
                origBoxes[i].pos = box.pos + posInCompositeBox;
                // If the distance between the position and the target exceeds
                // maxDistance, abort the loop and decrease the length in
                // increments of 10% to recursively reduce the number of
                // visible boxes by rank. Once all boxes are within the
                // maxDistance, we're good.
                if (typeof maxDistance !== 'undefined' &&
                    Math.abs(origBoxes[i].pos - origBoxes[i].target) > maxDistance) {
                    // Reset the positions that are already set
                    origBoxes
                        .slice(0, i + 1)
                        .forEach((box) => delete box.pos);
                    // Try with a smaller length
                    origBoxes.reducedLen =
                        (origBoxes.reducedLen || len) - (len * 0.1);
                    // Recurse
                    if (origBoxes.reducedLen > len * 0.1) {
                        distribute(origBoxes, len, maxDistance);
                    }
                    // Exceeded maxDistance => abort
                    return true;
                }
                posInCompositeBox += origBoxes[i].size;
                i++;
                return false;
            });
        });
        // Add the rest (hidden) boxes and sort by target
        stableSort(origBoxes, sortByTarget);
        return origBoxes;
    }
    RendererUtilities.distribute = distribute;
})(RendererUtilities || (RendererUtilities = {}));
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
/* harmony default export */ const Renderer_RendererUtilities = (RendererUtilities);

;// ./code/es-modules/Core/Renderer/SVG/SVGElement.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { animate: SVGElement_animate, animObject: SVGElement_animObject, stop: SVGElement_stop } = AnimationUtilities;


const { deg2rad, doc: SVGElement_doc, svg, SVG_NS: SVGElement_SVG_NS, win: SVGElement_win, isFirefox } = Core_Globals;


/* *
 *
 *  Class
 *
 * */
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
 * The SVGElement prototype is a JavaScript wrapper for SVG elements used in the
 * rendering layer of Highcharts. Combined with the
 * {@link Highcharts.SVGRenderer}
 * object, these prototypes allow freeform annotation in the charts or even in
 * HTML pages without instantiating a chart. The SVGElement can also wrap HTML
 * labels, when `text` or `label` elements are created with the `useHTML`
 * parameter.
 *
 * The SVGElement instances are created through factory functions on the
 * {@link Highcharts.SVGRenderer}
 * object, like
 * {@link Highcharts.SVGRenderer#rect|rect},
 * {@link Highcharts.SVGRenderer#path|path},
 * {@link Highcharts.SVGRenderer#text|text},
 * {@link Highcharts.SVGRenderer#label|label},
 * {@link Highcharts.SVGRenderer#g|g}
 * and more.
 *
 * See [How to use the SVG Renderer](
 * https://www.highcharts.com/docs/advanced-chart-features/renderer) for a
 * comprehensive tutorial on how to draw SVG elements on a chart.
 *
 * @class
 * @name Highcharts.SVGElement
 */
class SVGElement {
    // @todo public zIndex?: number;
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Get the current value of an attribute or pseudo attribute,
     * used mainly for animation. Called internally from
     * the {@link Highcharts.SVGRenderer#attr} function.
     *
     * @internal
     * @function Highcharts.SVGElement#_defaultGetter
     *
     * @param {string} key
     * Property key.
     *
     * @return {number|string}
     * Property value.
     */
    _defaultGetter(key) {
        let ret = pick(this[key + 'Value'], // Align getter
        this[key], this.element ? this.element.getAttribute(key) : null, 0);
        if (/^-?[\d\.]+$/.test(ret)) { // Is numerical
            ret = parseFloat(ret);
        }
        return ret;
    }
    /**
     * @internal
     * @function Highcharts.SVGElement#_defaultSetter
     * @param {string} value
     * @param {string} key
     * @param {Highcharts.SVGDOMElement} element
     */
    _defaultSetter(value, key, element) {
        element.setAttribute(key, value);
    }
    /**
     * Add the element to the DOM. All elements must be added this way.
     *
     * @sample highcharts/members/renderer-g
     *         Elements added to a group
     *
     * @function Highcharts.SVGElement#add
     *
     * @param {Highcharts.SVGElement} [parent]
     * The parent item to add it to. If undefined, the element is added to the
     * {@link Highcharts.SVGRenderer.box}.
     *
     * @return {Highcharts.SVGElement}
     * Returns the SVGElement for chaining.
     */
    add(parent) {
        const renderer = this.renderer, element = this.element;
        let inserted;
        if (parent) {
            this.parentGroup = parent;
        }
        // Build formatted text
        if (typeof this.textStr !== 'undefined' &&
            this.element.nodeName === 'text' // Not for SVGLabel instances
        ) {
            renderer.buildText(this);
        }
        // Mark as added
        this.added = true;
        // If we're adding to renderer root, or other elements in the group
        // have a z index, we need to handle it
        if (!parent || parent.handleZ || this.zIndex) {
            inserted = this.zIndexSetter();
        }
        // If zIndex is not handled, append at the end
        if (!inserted) {
            (parent ?
                parent.element :
                renderer.box).appendChild(element);
        }
        // Fire an event for internal hooks
        if (this.onAdd) {
            this.onAdd();
        }
        return this;
    }
    /**
     * Add a class name to an element.
     *
     * @function Highcharts.SVGElement#addClass
     *
     * @param {string} className
     * The new class name to add.
     *
     * @param {boolean} [replace=false]
     * When true, the existing class name(s) will be overwritten with the new
     * one. When false, the new one is added.
     *
     * @return {Highcharts.SVGElement}
     * Return the SVG element for chainability.
     */
    addClass(className, replace) {
        const currentClassName = replace ? '' : (this.attr('class') || '');
        // Trim the string and remove duplicates
        className = (className || '')
            .split(/ /g)
            .reduce(function (newClassName, name) {
            if (currentClassName.indexOf(name) === -1) {
                newClassName.push(name);
            }
            return newClassName;
        }, (currentClassName ?
            [currentClassName] :
            []))
            .join(' ');
        if (className !== currentClassName) {
            this.attr('class', className);
        }
        return this;
    }
    /**
     * This method is executed in the end of `attr()`, after setting all
     * attributes in the hash. In can be used to efficiently consolidate
     * multiple attributes in one SVG property -- e.g., translate, rotate and
     * scale are merged in one "transform" attribute in the SVG node.
     *
     * @internal
     * @function Highcharts.SVGElement#afterSetters
     */
    afterSetters() {
        // Update transform. Do this outside the loop to prevent redundant
        // updating for batch setting of attributes.
        if (this.doTransform) {
            this.updateTransform();
            this.doTransform = false;
        }
    }
    /**
     * Align the element relative to the chart or another box.
     *
     * @function Highcharts.SVGElement#align
     *
     * @param {Highcharts.AlignObject} [alignOptions]
     * The alignment options. The function can be called without this parameter
     * in order to re-align an element after the box has been updated.
     *
     * @param {boolean} [alignByTranslate]
     * Align element by translation.
     *
     * @param {string|Highcharts.BBoxObject} [alignTo]
     * The box to align to, needs a width and height. When the box is a string,
     * it refers to an object in the Renderer. For example, when box is
     * `spacingBox`, it refers to `Renderer.spacingBox` which holds `width`,
     * `height`, `x` and `y` properties.
     *
     * @param {boolean} [redraw]
     * Decide if SVGElement should be redrawn with new alignment or just change
     * its attributes.
     *
     * @return {Highcharts.SVGElement}
     * Returns the SVGElement for chaining.
     */
    align(alignOptions, alignByTranslate, alignTo, redraw = true) {
        const renderer = this.renderer, alignedObjects = renderer.alignedObjects, initialAlignment = Boolean(alignOptions);
        // First call on instantiate
        if (alignOptions) {
            this.alignOptions = alignOptions;
            this.alignByTranslate = alignByTranslate;
            this.alignTo = alignTo;
            // When called on resize, no arguments are supplied
        }
        else {
            alignOptions = this.alignOptions || {};
            alignByTranslate = this.alignByTranslate;
            alignTo = this.alignTo;
        }
        const alignToKey = !alignTo || isString(alignTo) ?
            alignTo || 'renderer' :
            void 0;
        // When aligned to a key, automatically re-align on redraws
        if (alignToKey) {
            // Prevent duplicates, like legendGroup after resize
            if (initialAlignment) {
                pushUnique(alignedObjects, this);
            }
            alignTo = void 0; // Do not use the box
        }
        const alignToBox = pick(alignTo, renderer[alignToKey], renderer), 
        // Default: left align
        x = (alignToBox.x || 0) + (alignOptions.x || 0) +
            ((alignToBox.width || 0) - (alignOptions.width || 0)) *
                getAlignFactor(alignOptions.align), 
        // Default: top align
        y = (alignToBox.y || 0) + (alignOptions.y || 0) +
            ((alignToBox.height || 0) - (alignOptions.height || 0)) *
                getAlignFactor(alignOptions.verticalAlign), attribs = {};
        // Add text-align attribute only if option is defined, #22698
        if (alignOptions.align) {
            attribs['text-align'] = alignOptions.align;
        }
        attribs[alignByTranslate ? 'translateX' : 'x'] = Math.round(x);
        attribs[alignByTranslate ? 'translateY' : 'y'] = Math.round(y);
        // Animate only if already placed
        if (redraw) {
            this[this.placed ? 'animate' : 'attr'](attribs);
            this.placed = true;
        }
        this.alignAttr = attribs;
        return this;
    }
    /**
     * @internal
     * @function Highcharts.SVGElement#alignSetter
     * @param {"left"|"center"|"right"} value
     */
    alignSetter(value) {
        const convert = {
            left: 'start',
            center: 'middle',
            right: 'end'
        };
        if (convert[value]) {
            this.alignValue = value;
            this.element.setAttribute('text-anchor', convert[value]);
        }
    }
    /**
     * Animate to given attributes or CSS properties.
     *
     * @sample highcharts/members/element-on/
     *         Setting some attributes by animation
     *
     * @function Highcharts.SVGElement#animate
     *
     * @param {Highcharts.SVGAttributes} params
     * SVG attributes or CSS to animate.
     *
     * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [options]
     * Animation options.
     *
     * @param {Function} [complete]
     * Function to perform at the end of animation.
     *
     * @return {Highcharts.SVGElement}
     * Returns the SVGElement for chaining.
     */
    animate(params, options, complete) {
        const animOptions = SVGElement_animObject(pick(options, this.renderer.globalAnimation, true)), deferTime = animOptions.defer;
        // When the page is hidden save resources in the background by not
        // running animation at all (#9749).
        if (SVGElement_doc.hidden) {
            animOptions.duration = 0;
        }
        if (animOptions.duration !== 0) {
            // Allows using a callback with the global animation without
            // overwriting it
            if (complete) {
                animOptions.complete = complete;
            }
            // If defer option is defined delay the animation #12901
            syncTimeout(() => {
                if (this.element) {
                    SVGElement_animate(this, params, animOptions);
                }
            }, deferTime);
        }
        else {
            this.attr(params, void 0, complete || animOptions.complete);
            // Call the end step synchronously
            objectEach(params, function (val, prop) {
                if (animOptions.step) {
                    animOptions.step.call(this, val, { prop: prop, pos: 1, elem: this });
                }
            }, this);
        }
        return this;
    }
    /**
     * Apply a text outline through a custom CSS property, by copying the text
     * element and apply stroke to the copy. Used internally. Contrast checks at
     * [example](https://jsfiddle.net/highcharts/43soe9m1/2/).
     *
     * @example
     * // Specific color
     * text.css({
     *    textOutline: '1px black'
     * });
     * // Automatic contrast
     * text.css({
     *    color: '#000000', // black text
     *    textOutline: '1px contrast' // => white outline
     * });
     *
     * @internal
     * @function Highcharts.SVGElement#applyTextOutline
     *
     * @param {string} textOutline
     * A custom CSS `text-outline` setting, defined by `width color`.
     */
    applyTextOutline(textOutline) {
        const elem = this.element, hasContrast = textOutline.indexOf('contrast') !== -1, styles = {};
        // When the text shadow is set to contrast, use dark stroke for light
        // text and vice versa.
        if (hasContrast) {
            styles.textOutline = textOutline = textOutline.replace(/contrast/g, this.renderer.getContrast(elem.style.fill));
        }
        // Extract the stroke width and color
        const spacePos = textOutline.indexOf(' '), color = textOutline.substring(spacePos + 1);
        let strokeWidth = textOutline.substring(0, spacePos);
        if (strokeWidth && strokeWidth !== 'none' && Core_Globals.svg) {
            this.fakeTS = true; // Fake text shadow
            // Since the stroke is applied on center of the actual outline, we
            // need to double it to get the correct stroke-width outside the
            // glyphs.
            strokeWidth = strokeWidth.replace(/(^[\d\.]+)(.*?)$/g, function (match, digit, unit) {
                return (2 * Number(digit)) + unit;
            });
            // Remove shadows from previous runs.
            this.removeTextOutline();
            const outline = SVGElement_doc.createElementNS(SVGElement_SVG_NS, 'tspan');
            attr(outline, {
                'class': 'highcharts-text-outline',
                fill: color,
                stroke: color,
                'stroke-width': strokeWidth,
                'stroke-linejoin': 'round'
            });
            // For each of the tspans and text nodes, create a copy in the
            // outline.
            const parentElem = elem.querySelector('textPath') || elem;
            [].forEach.call(parentElem.childNodes, (childNode) => {
                const clone = childNode.cloneNode(true);
                if (clone.removeAttribute) {
                    ['fill', 'stroke', 'stroke-width', 'stroke'].forEach((prop) => clone
                        .removeAttribute(prop));
                }
                outline.appendChild(clone);
            });
            // Collect the sum of dy from all children, included nested ones
            let totalHeight = 0;
            [].forEach.call(parentElem.querySelectorAll('text tspan'), (element) => {
                totalHeight += Number(element.getAttribute('dy'));
            });
            // Insert an absolutely positioned break before the original text
            // to keep it in place
            const br = SVGElement_doc.createElementNS(SVGElement_SVG_NS, 'tspan');
            br.textContent = '\u200B';
            // Reset the position for the following text
            attr(br, {
                x: Number(elem.getAttribute('x')),
                dy: -totalHeight
            });
            // Insert the outline
            outline.appendChild(br);
            parentElem.insertBefore(outline, parentElem.firstChild);
        }
    }
    /**
     * @function Highcharts.SVGElement#attr
     * @param {string} key
     * @return {number|string}
     */ /**
    * Apply native and custom attributes to the SVG elements.
    *
    * In order to set the rotation center for rotation, set x and y to 0 and
    * use `translateX` and `translateY` attributes to position the element
    * instead.
    *
    * Attributes frequently used in Highcharts are `fill`, `stroke`,
    * `stroke-width`.
    *
    * @sample highcharts/members/renderer-rect/
    *         Setting some attributes
    *
    * @example
    * // Set multiple attributes
    * element.attr({
    *     stroke: 'red',
    *     fill: 'blue',
    *     x: 10,
    *     y: 10
    * });
    *
    * // Set a single attribute
    * element.attr('stroke', 'red');
    *
    * // Get an attribute
    * element.attr('stroke'); // => 'red'
    *
    * @function Highcharts.SVGElement#attr
    *
    * @param {string|Highcharts.SVGAttributes} [hash]
    * The native and custom SVG attributes.
    *
    * @param {number|string|Highcharts.SVGPathArray} [val]
    * If the type of the first argument is `string`, the second can be a value,
    * which will serve as a single attribute setter. If the first argument is a
    * string and the second is undefined, the function serves as a getter and
    * the current value of the property is returned.
    *
    * @param {Function} [complete]
    * A callback function to execute after setting the attributes. This makes
    * the function compliant and interchangeable with the
    * {@link SVGElement#animate} function.
    *
    * @param {boolean} [continueAnimation=true]
    * Used internally when `.attr` is called as part of an animation step.
    * Otherwise, calling `.attr` for an attribute will stop animation for that
    * attribute.
    *
    * @return {Highcharts.SVGElement}
    * If used as a setter, it returns the current {@link Highcharts.SVGElement}
    * so the calls can be chained. If used as a getter, the current value of
    * the attribute is returned.
    */
    attr(hash, val, complete, continueAnimation) {
        const { element } = this, symbolCustomAttribs = SVGElement.symbolCustomAttribs;
        let key, hasSetSymbolSize, ret = this, skipAttr, setter;
        // Single key-value pair
        if (typeof hash === 'string' && typeof val !== 'undefined') {
            key = hash;
            hash = {};
            hash[key] = val;
        }
        // Used as a getter: first argument is a string, second is undefined
        if (typeof hash === 'string') {
            ret = (this[hash + 'Getter'] ||
                this._defaultGetter).call(this, hash, element);
            // Setter
        }
        else {
            objectEach(hash, function eachAttribute(val, key) {
                skipAttr = false;
                // Unless .attr is from the animator update, stop current
                // running animation of this property
                if (!continueAnimation) {
                    SVGElement_stop(this, key);
                }
                // Special handling of symbol attributes
                if (this.symbolName &&
                    symbolCustomAttribs.indexOf(key) !== -1) {
                    if (!hasSetSymbolSize) {
                        this.symbolAttr(hash);
                        hasSetSymbolSize = true;
                    }
                    skipAttr = true;
                }
                if (this.rotation && (key === 'x' || key === 'y')) {
                    this.doTransform = true;
                }
                if (!skipAttr) {
                    setter = (this[key + 'Setter'] ||
                        this._defaultSetter);
                    setter.call(this, val, key, element);
                }
            }, this);
            this.afterSetters();
        }
        // In accordance with animate, run a complete callback
        if (complete) {
            complete.call(this);
        }
        return ret;
    }
    /**
     * Apply a clipping shape to this element.
     *
     * @function Highcharts.SVGElement#clip
     *
     * @param {SVGElement} [clipElem]
     * The clipping shape. If skipped, the current clip is removed.
     *
     * @return {Highcharts.SVGElement}
     * Returns the SVG element to allow chaining.
     */
    clip(clipElem) {
        if (clipElem && !clipElem.clipPath) {
            // Add a hyphen at the end to avoid confusion in testing indexes
            // -1 and -10, -11 etc (#6550)
            const id = uniqueKey() + '-', clipPath = this.renderer.createElement('clipPath')
                .attr({ id })
                .add(this.renderer.defs);
            extend(clipElem, { clipPath, id, count: 0 });
            clipElem.add(clipPath);
        }
        return this.attr('clip-path', clipElem ?
            `url(${this.renderer.url}#${clipElem.id})` :
            'none');
    }
    /**
     * Calculate the coordinates needed for drawing a rectangle crisply and
     * return the calculated attributes.
     *
     * @function Highcharts.SVGElement#crisp
     *
     * @param {Highcharts.RectangleObject} rect
     * Rectangle to crisp.
     *
     * @param {number} [strokeWidth]
     * The stroke width to consider when computing crisp positioning. It can
     * also be set directly on the rect parameter.
     *
     * @return {Highcharts.RectangleObject}
     * The modified rectangle arguments.
     */
    crisp(rect, strokeWidth) {
        // Math.round because strokeWidth can sometimes have roundoff errors
        strokeWidth = Math.round(strokeWidth || rect.strokeWidth || 0);
        const x1 = rect.x || this.x || 0, y1 = rect.y || this.y || 0, x2 = (rect.width || this.width || 0) + x1, y2 = (rect.height || this.height || 0) + y1, 
        // Find all the rounded coordinates for corners
        x = crisp(x1, strokeWidth), y = crisp(y1, strokeWidth), x2Crisp = crisp(x2, strokeWidth), y2Crisp = crisp(y2, strokeWidth);
        extend(rect, {
            x,
            y,
            width: x2Crisp - x,
            height: y2Crisp - y
        });
        if (defined(rect.strokeWidth)) {
            rect.strokeWidth = strokeWidth;
        }
        return rect;
    }
    /**
     * Build and apply an SVG gradient out of a common JavaScript configuration
     * object. This function is called from the attribute setters. An event
     * hook is added for supporting other complex color types.
     *
     * @internal
     * @function Highcharts.SVGElement#complexColor
     * @param {Highcharts.GradientColorObject|Highcharts.PatternObject} colorOptions
     * The gradient or pattern options structure.
     * @param {string} prop
     * The property to apply, can either be `fill` or `stroke`.
     * @param {Highcharts.SVGDOMElement} elem
     * SVG element to apply the gradient on.
     */
    complexColor(colorOptions, prop, elem) {
        const renderer = this.renderer;
        let colorObject, gradName, gradAttr, radAttr, gradients, stops, stopOpacity, radialReference, id, key = [], value;
        fireEvent(this.renderer, 'complexColor', {
            args: arguments
        }, () => {
            // Apply linear or radial gradients
            if (colorOptions.radialGradient) {
                gradName = 'radialGradient';
            }
            else if (colorOptions.linearGradient) {
                gradName = 'linearGradient';
            }
            if (gradName) {
                gradAttr = colorOptions[gradName];
                gradients = renderer.gradients;
                stops = colorOptions.stops;
                radialReference = elem.radialReference;
                // Keep < 2.2 compatibility
                if (isArray(gradAttr)) {
                    colorOptions[gradName] = gradAttr = {
                        x1: gradAttr[0],
                        y1: gradAttr[1],
                        x2: gradAttr[2],
                        y2: gradAttr[3],
                        gradientUnits: 'userSpaceOnUse'
                    };
                }
                // Correct the radial gradient for the radial reference system
                if (gradName === 'radialGradient' &&
                    radialReference &&
                    !defined(gradAttr.gradientUnits)) {
                    // Save the radial attributes for updating
                    radAttr = gradAttr;
                    gradAttr = merge(gradAttr, renderer.getRadialAttr(radialReference, radAttr), { gradientUnits: 'userSpaceOnUse' });
                }
                // Build the unique key to detect whether we need to create a
                // new element (#1282)
                objectEach(gradAttr, function (value, n) {
                    if (n !== 'id') {
                        key.push(n, value);
                    }
                });
                objectEach(stops, function (val) {
                    key.push(val);
                });
                key = key.join(',');
                // Check if a gradient object with the same config object is
                // created within this renderer
                if (gradients[key]) {
                    id = gradients[key].attr('id');
                }
                else {
                    // Set the id and create the element
                    gradAttr.id = id = uniqueKey();
                    const gradientObject = gradients[key] =
                        renderer.createElement(gradName)
                            .attr(gradAttr)
                            .add(renderer.defs);
                    gradientObject.radAttr = radAttr;
                    // The gradient needs to keep a list of stops to be able to
                    // destroy them
                    gradientObject.stops = [];
                    stops.forEach(([offset, stopColor]) => {
                        if (stopColor.indexOf('rgba') === 0) {
                            colorObject = Color_Color.parse(stopColor);
                            stopColor = colorObject.get('rgb');
                            stopOpacity = colorObject.get('a');
                        }
                        else {
                            stopOpacity = 1;
                        }
                        const stopObject = renderer.createElement('stop').attr({
                            offset,
                            'stop-color': stopColor,
                            'stop-opacity': stopOpacity
                        }).add(gradientObject);
                        // Add the stop element to the gradient
                        gradientObject.stops.push(stopObject);
                    });
                }
                // Set the reference to the gradient object
                value = 'url(' + renderer.url + '#' + id + ')';
                elem.setAttribute(prop, value);
                elem.gradient = key;
                // Allow the color to be concatenated into tooltips formatters
                // etc. (#2995)
                colorOptions.toString = function () {
                    return value;
                };
            }
        });
    }
    /**
     * Set styles for the element. In addition to CSS styles supported by
     * native SVG and HTML elements, there are also some custom made for
     * Highcharts, like `width`, `ellipsis` and `textOverflow` for SVG text
     * elements.
     *
     * @sample highcharts/members/renderer-text-on-chart/
     *         Styled text
     *
     * @function Highcharts.SVGElement#css
     *
     * @param {Highcharts.CSSObject} styles
     * The new CSS styles.
     *
     * @return {Highcharts.SVGElement}
     * Return the SVG element for chaining.
     */
    css(styles) {
        const oldStyles = this.styles, newStyles = {}, elem = this.element, renderer = this.renderer;
        let textWidth, hasNew = !oldStyles;
        // Filter out existing styles to increase performance (#2640)
        if (oldStyles) {
            objectEach(styles, function (value, n) {
                if (oldStyles && oldStyles[n] !== value) {
                    newStyles[n] = value;
                    hasNew = true;
                }
            });
        }
        if (hasNew) {
            // Merge the new styles with the old ones
            if (oldStyles) {
                styles = extend(oldStyles, newStyles);
            }
            // Get the text width from style
            // Previously set, unset it (#8234)
            if (styles.width === null || styles.width === 'auto') {
                delete this.textWidth;
                // Apply new
            }
            else if (elem.nodeName.toLowerCase() === 'text' &&
                styles.width) {
                textWidth = this.textWidth = pInt(styles.width);
            }
            // Store object
            extend(this.styles, styles);
            if (textWidth && (!svg && renderer.forExport)) {
                delete styles.width;
            }
            const fontSize = isFirefox && styles.fontSize || null;
            // Necessary in firefox to be able to set font-size, #22124
            if (fontSize && (isNumber(fontSize) ||
                /^\d+$/.test(fontSize))) {
                styles.fontSize += 'px';
            }
            const stylesToApply = merge(styles);
            if (elem.namespaceURI === this.SVG_NS) {
                // These CSS properties are interpreted internally by the SVG
                // renderer, but are not supported by SVG and should not be
                // added to the DOM. In styled mode, no CSS should find its way
                // to the DOM whatsoever (#6173, #6474).
                ['textOutline', 'textOverflow', 'whiteSpace', 'width'].forEach((key) => (stylesToApply &&
                    delete stylesToApply[key]));
                // SVG requires fill for text
                if (stylesToApply.color) {
                    stylesToApply.fill = stylesToApply.color;
                    delete stylesToApply.color;
                }
            }
            css(elem, stylesToApply);
        }
        if (this.added) {
            // Rebuild text after added. Cache mechanisms in the buildText will
            // prevent building if there are no significant changes.
            if (this.element.nodeName === 'text') {
                renderer.buildText(this);
            }
            // Apply text outline after added
            if (styles.textOutline) {
                this.applyTextOutline(styles.textOutline);
            }
        }
        return this;
    }
    /**
     * @internal
     * @function Highcharts.SVGElement#dashstyleSetter
     * @param {string} value
     */
    dashstyleSetter(value) {
        let i, strokeWidth = this['stroke-width'];
        // If "inherit", like maps in IE, assume 1 (#4981). With HC5 and the new
        // strokeWidth function, we should be able to use that instead.
        if (strokeWidth === 'inherit') {
            strokeWidth = 1;
        }
        if (value) {
            value = value.toLowerCase();
            const v = value
                .replace('shortdashdotdot', '3,1,1,1,1,1,')
                .replace('shortdashdot', '3,1,1,1')
                .replace('shortdot', '1,1,')
                .replace('shortdash', '3,1,')
                .replace('longdash', '8,3,')
                .replace(/dot/g, '1,3,')
                .replace('dash', '4,3,')
                .replace(/,$/, '')
                .split(','); // Ending comma
            i = v.length;
            while (i--) {
                v[i] = '' + (pInt(v[i]) * pick(strokeWidth, NaN));
            }
            value = v.join(',').replace(/NaN/g, 'none'); // #3226
            this.element.setAttribute('stroke-dasharray', value);
        }
    }
    /**
     * Destroy the element and element wrapper and clear up the DOM and event
     * hooks.
     *
     * @function Highcharts.SVGElement#destroy
     */
    destroy() {
        const wrapper = this, { element = {}, renderer, stops } = wrapper, ownerSVGElement = element.ownerSVGElement;
        // Remove events
        element.onclick = element.onmouseout = element.onmouseover =
            element.onmousemove = element.point = null;
        SVGElement_stop(wrapper); // Stop running animations
        if (wrapper.clipPath && ownerSVGElement) {
            const clipPath = wrapper.clipPath;
            // Look for existing references to this clipPath and remove them
            // before destroying the element (#6196).
            // The upper case version is for Edge
            [].forEach.call(ownerSVGElement.querySelectorAll('[clip-path],[CLIP-PATH]'), function (el) {
                if (el.getAttribute('clip-path').indexOf(clipPath.element.id) > -1) {
                    el.removeAttribute('clip-path');
                }
            });
            wrapper.clipPath = clipPath.destroy();
        }
        // Destroy stops in case this is a gradient object @todo old code?
        if (stops) {
            for (const stop of stops) {
                stop.destroy();
            }
            stops.length = 0;
        }
        // Remove element
        wrapper.safeRemoveChild(element);
        // Remove from alignObjects
        if (wrapper.alignOptions) {
            erase(renderer.alignedObjects, wrapper);
        }
        objectEach(wrapper, (val, key) => {
            if (
            // Destroy child elements of a group
            wrapper[key]?.parentGroup === wrapper ||
                // Destroy own elements
                ['connector', 'foreignObject'].indexOf(key) !== -1) {
                wrapper[key]?.destroy?.();
            }
            // Delete all properties
            delete wrapper[key];
        });
        return;
    }
    /**
     * @internal
     * @function Highcharts.SVGElement#dSetter
     * @param {number|string|Highcharts.SVGPathArray} value
     * @param {string} key
     * @param {Highcharts.SVGDOMElement} element
     */
    dSetter(value, key, element) {
        if (isArray(value)) {
            // Backwards compatibility, convert one-dimensional array into an
            // array of segments
            if (typeof value[0] === 'string') {
                value = this.renderer.pathToSegments(value);
            }
            this.pathArray = value;
            value = value.reduce((acc, seg, i) => {
                if (!seg?.join) {
                    return (seg || '').toString();
                }
                return (i ? acc + ' ' : '') + seg.join(' ');
            }, '');
        }
        if (/(NaN| {2}|^$)/.test(value)) {
            value = 'M 0 0';
        }
        // Check for cache before resetting. Resetting causes disturbance in the
        // DOM, causing flickering in some cases in Edge/IE (#6747). Also
        // possible performance gain.
        if (this[key] !== value) {
            element.setAttribute(key, value);
            this[key] = value;
        }
    }
    /**
     * @internal
     * @function Highcharts.SVGElement#fillSetter
     * @param {Highcharts.ColorType} value
     * @param {string} key
     * @param {Highcharts.SVGDOMElement} element
     */
    fillSetter(value, key, element) {
        if (typeof value === 'string') {
            element.setAttribute(key, value);
        }
        else if (value) {
            this.complexColor(value, key, element);
        }
    }
    /**
     * @internal
     * @function Highcharts.SVGElement#hrefSetter
     * @param {Highcharts.ColorType} value
     * @param {string} key
     * @param {Highcharts.SVGDOMElement} element
     */
    hrefSetter(value, key, element) {
        // Namespace is needed for offline export, #19106
        element.setAttributeNS('http://www.w3.org/1999/xlink', key, value);
    }
    /**
     * Get the bounding box (width, height, x and y) for the element. Generally
     * used to get rendered text size. Since this is called a lot in charts,
     * the results are cached based on text properties, in order to save DOM
     * traffic. The returned bounding box includes the rotation, so for example
     * a single text line of rotation 90 will report a greater height, and a
     * width corresponding to the line-height.
     *
     * @sample highcharts/members/renderer-on-chart/
     *         Draw a rectangle based on a text's bounding box
     *
     * @function Highcharts.SVGElement#getBBox
     *
     * @param {boolean} [reload]
     *        Skip the cache and get the updated DOM bounding box.
     *
     * @param {number} [rot]
     *        Override the element's rotation. This is internally used on axis
     *        labels with a value of 0 to find out what the bounding box would
     *        be have been if it were not rotated.
     *
     * @return {Highcharts.BBoxObject}
     *         The bounding box with `x`, `y`, `width` and `height` properties.
     */
    getBBox(reload, rot) {
        const wrapper = this, { element, renderer, styles, textStr } = wrapper, { cache, cacheKeys } = renderer, isSVG = element.namespaceURI === wrapper.SVG_NS, rotation = pick(rot, wrapper.rotation, 0), fontSize = renderer.styledMode ? (element &&
            SVGElement.prototype.getStyle.call(element, 'font-size')) : (styles.fontSize), cacheKey = this.getBBoxCacheKey([
            renderer.rootFontSize,
            this.textWidth, // #7874, also useHTML
            this.alignValue,
            styles.fontWeight, // #12163
            styles.lineClamp,
            styles.textOverflow, // #5968
            fontSize,
            rotation
        ]);
        let bBox, height, toggleTextShadowShim;
        if (cacheKey && !reload) {
            bBox = cache[cacheKey];
        }
        // No cache found
        if (!bBox || bBox.polygon) {
            // SVG elements
            if (isSVG || renderer.forExport) {
                try { // Fails in Firefox if the container has display: none.
                    // When the text shadow shim is used, we need to hide the
                    // fake shadows to get the correct bounding box (#3872)
                    toggleTextShadowShim = this.fakeTS && function (display) {
                        const outline = element.querySelector('.highcharts-text-outline');
                        if (outline) {
                            css(outline, { display });
                        }
                    };
                    // Workaround for #3842, Firefox reporting wrong bounding
                    // box for shadows
                    if (isFunction(toggleTextShadowShim)) {
                        toggleTextShadowShim('none');
                    }
                    bBox = element.getBBox ?
                        // SVG: use extend because IE9 is not allowed to change
                        // width and height in case of rotation (below)
                        extend({}, element.getBBox()) : {
                        // HTML elements with `exporting.allowHTML` and
                        // legacy IE in export mode
                        width: element.offsetWidth,
                        height: element.offsetHeight,
                        x: 0,
                        y: 0
                    };
                    // #3842
                    if (isFunction(toggleTextShadowShim)) {
                        toggleTextShadowShim('');
                    }
                }
                catch {
                    // Ignore error
                }
                // If the bBox is not set, the try-catch block above failed. The
                // other condition is for Opera that returns a width of
                // -Infinity on hidden elements.
                if (!bBox || bBox.width < 0) {
                    bBox = { x: 0, y: 0, width: 0, height: 0 };
                }
                // Use HTML within SVG
            }
            else {
                bBox = wrapper.htmlGetBBox();
            }
            // True SVG elements as well as HTML elements in modern browsers
            // using the .useHTML option need to compensated for rotation
            height = bBox.height;
            // Workaround for wrong bounding box in IE, Edge and Chrome on
            // Windows. With Highcharts' default font, IE and Edge report
            // a box height of 16.899 and Chrome rounds it to 17. If this
            // stands uncorrected, it results in more padding added below
            // the text than above when adding a label border or background.
            // Also vertical positioning is affected.
            // https://jsfiddle.net/highcharts/em37nvuj/
            // (#1101, #1505, #1669, #2568, #6213).
            if (isSVG) {
                bBox.height = height = ({
                    '11px,17': 14,
                    '13px,20': 16
                }[`${fontSize || ''},${Math.round(height)}`] ||
                    height);
            }
            // Adjust for rotated text
            if (rotation) {
                bBox = this.getRotatedBox(bBox, rotation);
            }
            // Create a reference to catch changes to bBox
            const e = { bBox };
            fireEvent(this, 'afterGetBBox', e);
            // Pick up any changes after the fired event
            bBox = e.bBox;
        }
        // Cache it. When loading a chart in a hidden iframe in Firefox and
        // IE/Edge, the bounding box height is 0, so don't cache it (#5620).
        if (cacheKey && (textStr === '' || bBox.height > 0)) {
            // Rotate (#4681)
            while (cacheKeys.length > 250) {
                delete cache[cacheKeys.shift()];
            }
            if (!cache[cacheKey]) {
                cacheKeys.push(cacheKey);
            }
            cache[cacheKey] = bBox;
        }
        return bBox;
    }
    /**
     * Overridable method to get a cache key for the bounding box of this
     * element.
     *
     * @example
     * // Plugin to let the getBBox function respond to font family changes
     * (({ SVGElement }) => {
     * const getBBoxCacheKey = SVGElement.prototype.getBBoxCacheKey;
     *   SVGElement.prototype.getBBoxCacheKey = function (keys) {
     *     const key = getBBoxCacheKey.call(this, keys);
     *     const fontFamily = this.styles.fontFamily;
     *     return key + (fontFamily ? `,${fontFamily}` : '');
     *   };
     * })(Highcharts);
     *
     * @function Highcharts.SVGElement#getBBoxCacheKey
     *
     * @return {string|void} The cache key based on the text properties.
     */
    getBBoxCacheKey(keys) {
        // Avoid undefined and null (#7316)
        if (defined(this.textStr)) {
            let textStr = '' + this.textStr;
            // Since numerical labels appear a lot in a chart, we approximate
            // that a label of n characters has the same bounding box as others
            // of the same length. Unless there is inner HTML in the label. In
            // that case, leave the numbers as is (#5899).
            if (textStr.indexOf('<') === -1) {
                textStr = textStr.replace(/\d/g, '0');
            }
            // Properties that affect bounding box
            return [
                textStr,
                ...keys
            ].join(',');
        }
    }
    /**
     * Get the rotated box.
     * @internal
     */
    getRotatedBox(box, rotation) {
        const { x: boxX, y: boxY, width, height } = box, { alignValue, translateY, rotationOriginX = 0, rotationOriginY = 0 } = this, alignFactor = getAlignFactor(alignValue), baseline = Number(this.element.getAttribute('y') || 0) -
            (translateY ? 0 : boxY), rad = rotation * deg2rad, rad90 = (rotation - 90) * deg2rad, cosRad = Math.cos(rad), sinRad = Math.sin(rad), wCosRad = width * cosRad, wSinRad = width * sinRad, cosRad90 = Math.cos(rad90), sinRad90 = Math.sin(rad90), [[xOriginCosRad, xOriginSinRad], [yOriginCosRad, yOriginSinRad]] = [
            rotationOriginX,
            rotationOriginY
        ].map((rotOrigin) => [
            rotOrigin - (rotOrigin * cosRad),
            rotOrigin * sinRad
        ]), 
        // Find the starting point on the left side baseline of
        // the text
        pX = ((boxX + alignFactor * (width - wCosRad)) +
            xOriginCosRad + yOriginSinRad), pY = ((boxY + baseline - alignFactor * wSinRad) -
            xOriginSinRad + yOriginCosRad), 
        // Find all corners
        aX = pX + baseline * cosRad90, bX = aX + wCosRad, cX = bX - height * cosRad90, dX = cX - wCosRad, aY = pY + baseline * sinRad90, bY = aY + wSinRad, cY = bY - height * sinRad90, dY = cY - wSinRad;
        // Deduct the bounding box from the corners
        const x = Math.min(aX, bX, cX, dX), y = Math.min(aY, bY, cY, dY), boxWidth = Math.max(aX, bX, cX, dX) - x, boxHeight = Math.max(aY, bY, cY, dY) - y;
        /* Uncomment to visualize boxes
        this.bBoxViz ??= this.renderer.path()
            .attr({
                stroke: 'red',
                'stroke-width': 1,
                zIndex: 20
            })
            .add();
        this.bBoxViz.attr('d', [
            ['M', aX, aY],
            ['L', bX, bY],
            ['L', cX, cY],
            ['L', dX, dY],
            ['Z']
        ]);
        // */
        return {
            x,
            y,
            width: boxWidth,
            height: boxHeight,
            polygon: [
                [aX, aY],
                [bX, bY],
                [cX, cY],
                [dX, dY]
            ]
        };
    }
    /**
     * Get the computed style. Only in styled mode.
     *
     * @example
     * chart.series[0].points[0].graphic.getStyle('stroke-width'); // => '1px'
     *
     * @function Highcharts.SVGElement#getStyle
     *
     * @param {string} prop
     *        The property name to check for.
     *
     * @return {string}
     *         The current computed value.
     */
    getStyle(prop) {
        return SVGElement_win
            .getComputedStyle(this.element || this, '')
            .getPropertyValue(prop);
    }
    /**
     * Check if an element has the given class name.
     *
     * @function Highcharts.SVGElement#hasClass
     *
     * @param {string} className
     * The class name to check for.
     *
     * @return {boolean}
     * Whether the class name is found.
     */
    hasClass(className) {
        return ('' + this.attr('class'))
            .split(' ')
            .indexOf(className) !== -1;
    }
    /**
     * Hide the element, similar to setting the `visibility` attribute to
     * `hidden`.
     *
     * @function Highcharts.SVGElement#hide
     *
     * @return {Highcharts.SVGElement}
     *         Returns the SVGElement for chaining.
     */
    hide() {
        return this.attr({ visibility: 'hidden' });
    }
    /** @internal */
    htmlGetBBox() {
        return { height: 0, width: 0, x: 0, y: 0 };
    }
    /**
     * Initialize the SVG element. This function only exists to make the
     * initialization process overridable. It should not be called directly.
     *
     * @function Highcharts.SVGElement#init
     *
     * @param {Highcharts.SVGRenderer} renderer
     * The SVGRenderer instance to initialize to.
     *
     * @param {string} nodeName
     * The SVG node name.
     */
    constructor(renderer, nodeName) {
        /** @internal */
        this.onEvents = {};
        /** @internal */
        this.opacity = 1; // Default base for animation
        /** @internal */
        this.SVG_NS = SVGElement_SVG_NS;
        /**
         * The primary DOM node. Each `SVGElement` instance wraps a main DOM
         * node, but may also represent more nodes.
         *
         * @name Highcharts.SVGElement#element
         * @type {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement}
         */
        this.element = nodeName === 'div' || nodeName === 'body' ?
            createElement(nodeName) :
            SVGElement_doc.createElementNS(this.SVG_NS, nodeName);
        /**
         * The renderer that the SVGElement belongs to.
         *
         * @name Highcharts.SVGElement#renderer
         * @type {Highcharts.SVGRenderer}
         */
        this.renderer = renderer;
        this.styles = {};
        fireEvent(this, 'afterInit');
    }
    /**
     * Add an event listener. This is a simple setter that replaces the
     * previous event of the same type added by this function, as opposed to
     * the {@link Highcharts#addEvent} function.
     *
     * @sample highcharts/members/element-on/
     *         A clickable rectangle
     *
     * @function Highcharts.SVGElement#on
     *
     * @param {string} eventType
     * The event type.
     *
     * @param {Function} handler
     * The handler callback.
     *
     * @return {Highcharts.SVGElement}
     * The SVGElement for chaining.
     */
    on(eventType, handler) {
        const { onEvents } = this;
        if (onEvents[eventType]) {
            // Unbind existing event
            onEvents[eventType]();
        }
        onEvents[eventType] = addEvent(this.element, eventType, handler);
        return this;
    }
    /**
     * @internal
     * @function Highcharts.SVGElement#opacitySetter
     * @param {string} value
     * @param {string} key
     * @param {Highcharts.SVGDOMElement} element
     */
    opacitySetter(value, key, element) {
        // Round off to avoid float errors, like tests where opacity lands on
        // 9.86957e-06 instead of 0
        const opacity = Number(Number(value).toFixed(3));
        this.opacity = opacity;
        element.setAttribute(key, opacity);
    }
    /**
     * Re-align an aligned text or label after setting the text.
     *
     * @internal
     * @function Highcharts.SVGElement#reAlign
     *
     */
    reAlign() {
        if (this.alignOptions?.width && this.alignOptions.align !== 'left') {
            this.alignOptions.width = this.getBBox().width;
            this.placed = false; // Block animation
            this.align();
        }
    }
    /**
     * Remove a class name from the element.
     *
     * @function Highcharts.SVGElement#removeClass
     *
     * @param {string|RegExp} className
     *        The class name to remove.
     *
     * @return {Highcharts.SVGElement} Returns the SVG element for chainability.
     */
    removeClass(className) {
        return this.attr('class', ('' + this.attr('class'))
            .replace(isString(className) ?
            new RegExp(`(^| )${className}( |$)`) : // #12064, #13590
            className, ' ')
            .replace(/ +/g, ' ')
            .trim());
    }
    /**
     *
     * @internal
     */
    removeTextOutline() {
        const outline = this.element
            .querySelector('tspan.highcharts-text-outline');
        if (outline) {
            this.safeRemoveChild(outline);
        }
    }
    /**
     * Removes an element from the DOM.
     *
     * @internal
     * @function Highcharts.SVGElement#safeRemoveChild
     *
     * @param {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement} element
     * The DOM node to remove.
     */
    safeRemoveChild(element) {
        const parentNode = element.parentNode;
        if (parentNode) {
            parentNode.removeChild(element);
        }
    }
    /**
     * Set the coordinates needed to draw a consistent radial gradient across
     * a shape regardless of positioning inside the chart. Used on pie slices
     * to make all the slices have the same radial reference point.
     *
     * @function Highcharts.SVGElement#setRadialReference
     *
     * @param {Array<number>} coordinates
     * The center reference. The format is `[centerX, centerY, diameter]` in
     * pixels.
     *
     * @return {Highcharts.SVGElement}
     * Returns the SVGElement for chaining.
     */
    setRadialReference(coordinates) {
        const existingGradient = (this.element.gradient &&
            this.renderer.gradients[this.element.gradient]) || void 0;
        this.element.radialReference = coordinates;
        // On redrawing objects with an existing gradient, the gradient needs
        // to be repositioned (#3801)
        if (existingGradient?.radAttr) {
            existingGradient.animate(this.renderer.getRadialAttr(coordinates, existingGradient.radAttr));
        }
        return this;
    }
    /**
     * Add a shadow to the element. In styled mode, this method is not used,
     * instead use `defs` and filters.
     *
     * @example
     * renderer.rect(10, 100, 100, 100)
     *     .attr({ fill: 'red' })
     *     .shadow(true);
     *
     * @function Highcharts.SVGElement#shadow
     *
     * @param {boolean|Highcharts.ShadowOptionsObject} [shadowOptions] The
     *        shadow options. If `true`, the default options are applied. If
     *        `false`, the current shadow will be removed.
     *
     * @return {Highcharts.SVGElement} Returns the SVGElement for chaining.
     */
    shadow(shadowOptions) {
        const { renderer } = this, options = merge(this.parentGroup?.rotation === 90 ? {
            offsetX: -1,
            offsetY: -1
        } : {}, isObject(shadowOptions) ? shadowOptions : {}), id = renderer.shadowDefinition(options);
        return this.attr({
            filter: shadowOptions ?
                `url(${renderer.url}#${id})` :
                'none'
        });
    }
    /**
     * Show the element after it has been hidden.
     *
     * @function Highcharts.SVGElement#show
     *
     * @param {boolean} [inherit=true]
     *        Set the visibility attribute to `inherit` rather than `visible`.
     *        The difference is that an element with `visibility="visible"`
     *        will be visible even if the parent is hidden.
     *
     * @return {Highcharts.SVGElement}
     *         Returns the SVGElement for chaining.
     */
    show(inherit = true) {
        return this.attr({ visibility: inherit ? 'inherit' : 'visible' });
    }
    /**
     * Set the stroke-width and record it on the SVGElement
     *
     * @internal
     * @function Highcharts.SVGElement#strokeSetter
     * @param {number|string|ColorType} value
     * @param {string} key
     * @param {Highcharts.SVGDOMElement} element
     */
    'stroke-widthSetter'(value, key, element) {
        // Record it for quick access in getter
        this[key] = value;
        element.setAttribute(key, value);
    }
    /**
     * Get the computed stroke width in pixel values. This is used extensively
     * when drawing shapes to ensure the shapes are rendered crisp and
     * positioned correctly relative to each other. Using
     * `shape-rendering: crispEdges` leaves us less control over positioning,
     * for example when we want to stack columns next to each other, or position
     * things pixel-perfectly within the plot box.
     *
     * The common pattern when placing a shape is:
     * - Create the SVGElement and add it to the DOM. In styled mode, it will
     *   now receive a stroke width from the style sheet. In classic mode we
     *   will add the `stroke-width` attribute.
     * - Read the computed `elem.strokeWidth()`.
     * - Place it based on the stroke width.
     *
     * @function Highcharts.SVGElement#strokeWidth
     *
     * @return {number}
     * The stroke width in pixels. Even if the given stroke width (in CSS or by
     * attributes) is based on `em` or other units, the pixel size is returned.
     */
    strokeWidth() {
        // In non-styled mode, read the stroke width as set by .attr
        if (!this.renderer.styledMode) {
            return this['stroke-width'] || 0;
        }
        // In styled mode, read computed stroke width
        const val = this.getStyle('stroke-width');
        let ret = 0, tempElement;
        // Read pixel values directly
        if (/px$/.test(val)) {
            ret = pInt(val);
            // Other values like em, pt etc need to be measured
        }
        else if (val !== '') {
            tempElement = SVGElement_doc.createElementNS(SVGElement_SVG_NS, 'rect');
            attr(tempElement, {
                width: val,
                'stroke-width': 0
            });
            this.element.parentNode.appendChild(tempElement);
            ret = tempElement.getBBox().width;
            tempElement.parentNode.removeChild(tempElement);
        }
        return ret;
    }
    /**
     * If one of the symbol size affecting parameters are changed,
     * check all the others only once for each call to an element's
     * .attr() method
     *
     * @internal
     * @function Highcharts.SVGElement#symbolAttr
     *
     * @param {Highcharts.SVGAttributes} hash
     * The attributes to set.
     */
    symbolAttr(hash) {
        const wrapper = this;
        SVGElement.symbolCustomAttribs.forEach(function (key) {
            wrapper[key] = pick(hash[key], wrapper[key]);
        });
        wrapper.attr({
            d: wrapper.renderer.symbols[wrapper.symbolName](wrapper.x, wrapper.y, wrapper.width, wrapper.height, wrapper)
        });
    }
    /**
     * @internal
     * @function Highcharts.SVGElement#textSetter
     * @param {string} value
     */
    textSetter(value) {
        if (value !== this.textStr) {
            // Delete size caches when the text changes
            delete this.textPxLength;
            this.textStr = value;
            if (this.added) {
                this.renderer.buildText(this);
            }
            this.reAlign();
        }
    }
    /**
     * @internal
     * @function Highcharts.SVGElement#titleSetter
     * @param {string} value
     */
    titleSetter(value) {
        const el = this.element;
        const titleNode = el.getElementsByTagName('title')[0] ||
            SVGElement_doc.createElementNS(this.SVG_NS, 'title');
        // Move to first child
        if (el.insertBefore) {
            el.insertBefore(titleNode, el.firstChild);
        }
        else {
            el.appendChild(titleNode);
        }
        // Replace text content and escape markup
        titleNode.textContent = replaceNested(// Scan #[73]
        pick(value, ''), // #3276, #3895
        [/<[^>]*>/g, '']).replace(/&lt;/g, '<').replace(/&gt;/g, '>');
    }
    /**
     * Bring the element to the front. Alternatively, a new zIndex can be set.
     *
     * @sample highcharts/members/element-tofront/
     *         Click an element to bring it to front
     *
     * @function Highcharts.SVGElement#toFront
     *
     * @return {Highcharts.SVGElement}
     * Returns the SVGElement for chaining.
     */
    toFront() {
        const element = this.element;
        element.parentNode.appendChild(element);
        return this;
    }
    /**
     * Move an object and its children by x and y values.
     *
     * @function Highcharts.SVGElement#translate
     *
     * @param {number} x
     * The x value.
     *
     * @param {number} y
     * The y value.
     *
     * @return {Highcharts.SVGElement}
     * Translated element.
     */
    translate(x, y) {
        return this.attr({
            translateX: x,
            translateY: y
        });
    }
    /**
     * Update the transform attribute based on internal properties. Deals with
     * the custom `translateX`, `translateY`, `rotation`, `scaleX` and `scaleY`
     * attributes and updates the SVG `transform` attribute.
     *
     * @internal
     * @function Highcharts.SVGElement#updateTransform
     */
    updateTransform(attrib = 'transform') {
        const { element, foreignObject, matrix, rotation = 0, rotationOriginX, rotationOriginY, scaleX, scaleY, text, translateX = 0, translateY = 0 } = this;
        // Apply translate. Nearly all transformed elements have translation,
        // so instead of checking for translate = 0, do it always (#1767,
        // #1846).
        const transform = [`translate(${translateX},${translateY})`];
        // Apply matrix
        if (defined(matrix)) {
            transform.push('matrix(' + matrix.join(',') + ')');
        }
        // Apply rotation
        if (rotation) {
            transform.push('rotate(' + rotation + ' ' +
                (rotationOriginX ?? element.getAttribute('x') ?? this.x ?? 0) +
                ' ' +
                (rotationOriginY ?? element.getAttribute('y') ?? this.y ?? 0) +
                ')');
        }
        // Apply scale
        if (defined(scaleX) || defined(scaleY)) {
            transform.push(`scale(${scaleX ?? 1} ${scaleY ?? 1})`);
        }
        if (transform.length && !(text || this).textPath) {
            (foreignObject?.element || element)
                .setAttribute(attrib, transform.join(' '));
        }
    }
    /**
     * @internal
     * @function Highcharts.SVGElement#visibilitySetter
     *
     * @param {string} value
     *
     * @param {string} key
     *
     * @param {Highcharts.SVGDOMElement} element
     *
     */
    visibilitySetter(value, key, element) {
        // IE9-11 doesn't handle visibility:inherit well, so we remove the
        // attribute instead (#2881, #3909)
        if (value === 'inherit') {
            element.removeAttribute(key);
        }
        else if (this[key] !== value) { // #6747
            element.setAttribute(key, value);
        }
        this[key] = value;
    }
    /**
     * @internal
     * @function Highcharts.SVGElement#xGetter
     */
    xGetter(key) {
        if (this.element.nodeName === 'circle') {
            if (key === 'x') {
                key = 'cx';
            }
            else if (key === 'y') {
                key = 'cy';
            }
        }
        return this._defaultGetter(key);
    }
    /**
     * @internal
     * @function Highcharts.SVGElement#zIndexSetter
     */
    zIndexSetter(value, key) {
        const { element, parentGroup, renderer } = this, parentNode = parentGroup?.element || renderer.box, svgParent = parentNode === renderer.box;
        let childNodes, otherElement, otherZIndex, inserted = false, undefinedOtherZIndex, run = this.added, i;
        if (defined(value)) {
            // So we can read it for other elements in the group
            element.setAttribute('data-z-index', value);
            value = +value;
            if (this[key] === value) {
                // Only update when needed (#3865)
                run = false;
            }
        }
        else if (defined(this[key])) {
            element.removeAttribute('data-z-index');
        }
        this[key] = value;
        // Insert according to this and other elements' zIndex. Before .add() is
        // called, nothing is done. Then on add, or by later calls to
        // zIndexSetter, the node is placed on the right place in the DOM.
        if (run) {
            value = this.zIndex;
            if (value && parentGroup) {
                parentGroup.handleZ = true;
            }
            childNodes = parentNode.childNodes;
            for (i = childNodes.length - 1; i >= 0 && !inserted; i--) {
                otherElement = childNodes[i];
                otherZIndex = otherElement.getAttribute('data-z-index');
                undefinedOtherZIndex = !defined(otherZIndex);
                if (otherElement !== element) {
                    if (
                    // Negative zIndex versus no zIndex:
                    // On all levels except the highest. If the parent is
                    // <svg>, then we don't want to put items before <desc>
                    // or <defs>
                    defined(value) && value < 0 &&
                        undefinedOtherZIndex &&
                        !svgParent &&
                        !i) {
                        parentNode.insertBefore(element, childNodes[i]);
                        inserted = true;
                    }
                    else if (
                    // Insert after the first element with a lower zIndex
                    (defined(value) &&
                        parseFloat(otherZIndex || '') <= value) ||
                        // If negative zIndex, add this before first undefined
                        // zIndex element
                        (undefinedOtherZIndex &&
                            (!defined(value) || value >= 0))) {
                        parentNode.insertBefore(element, childNodes[i + 1]);
                        inserted = true;
                    }
                }
            }
            if (!inserted) {
                parentNode.insertBefore(element, childNodes[svgParent ? 3 : 0]);
                inserted = true;
            }
        }
        return inserted;
    }
}
/* *
 *
 *  Properties
 *
 * */
/**
 * Custom attributes used for symbols, these should be filtered out when
 * setting SVGElement attributes (#9375).
 * @internal
 */
SVGElement.symbolCustomAttribs = [
    'anchorX',
    'anchorY',
    'clockwise',
    'end',
    'height',
    'innerR',
    'r',
    'start',
    'width',
    'x',
    'y'
];
// Some shared setters and getters
SVGElement.prototype.strokeSetter = SVGElement.prototype.fillSetter;
SVGElement.prototype.yGetter = SVGElement.prototype.xGetter;
SVGElement.prototype.matrixSetter =
    SVGElement.prototype.rotationOriginXSetter =
        SVGElement.prototype.rotationOriginYSetter =
            SVGElement.prototype.rotationSetter =
                SVGElement.prototype.scaleXSetter =
                    SVGElement.prototype.scaleYSetter =
                        SVGElement.prototype.translateXSetter =
                            SVGElement.prototype.translateYSetter =
                                SVGElement.prototype.verticalAlignSetter = function (value, key) {
                                    this[key] = value;
                                    this.doTransform = true;
                                };
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const SVG_SVGElement = (SVGElement);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * Reference to the global SVGElement class as a workaround for a name conflict
 * in the Highcharts namespace.
 *
 * @global
 * @typedef {global.SVGElement} GlobalSVGElement
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGElement
 */
/**
 * The horizontal alignment of an element.
 *
 * @typedef {"center"|"left"|"right"} Highcharts.AlignValue
 */
/**
 * Options to align the element relative to the chart or another box.
 *
 * @interface Highcharts.AlignObject
 */ /**
* Horizontal alignment. Can be one of `left`, `center` and `right`.
*
* @name Highcharts.AlignObject#align
* @type {Highcharts.AlignValue|undefined}
*
* @default left
*/ /**
* Vertical alignment. Can be one of `top`, `middle` and `bottom`.
*
* @name Highcharts.AlignObject#verticalAlign
* @type {Highcharts.VerticalAlignValue|undefined}
*
* @default top
*/ /**
* Horizontal pixel offset from alignment.
*
* @name Highcharts.AlignObject#x
* @type {number|undefined}
*
* @default 0
*/ /**
* Vertical pixel offset from alignment.
*
* @name Highcharts.AlignObject#y
* @type {number|undefined}
*
* @default 0
*/ /**
* Use the `transform` attribute with translateX and translateY custom
* attributes to align this elements rather than `x` and `y` attributes.
*
* @name Highcharts.AlignObject#alignByTranslate
* @type {boolean|undefined}
*
* @default false
*/
/**
 * Bounding box of an element.
 *
 * @interface Highcharts.BBoxObject
 * @extends Highcharts.PositionObject
 */ /**
* Height of the bounding box.
*
* @name Highcharts.BBoxObject#height
* @type {number}
*/ /**
* Width of the bounding box.
*
* @name Highcharts.BBoxObject#width
* @type {number}
*/ /**
* Horizontal position of the bounding box.
*
* @name Highcharts.BBoxObject#x
* @type {number}
*/ /**
* Vertical position of the bounding box.
*
* @name Highcharts.BBoxObject#y
* @type {number}
*/
/**
 * An object of key-value pairs for SVG attributes. Attributes in Highcharts
 * elements for the most parts correspond to SVG, but some are specific to
 * Highcharts, like `zIndex`, `rotation`, `rotationOriginX`,
 * `rotationOriginY`, `translateX`, `translateY`, `scaleX` and `scaleY`. SVG
 * attributes containing a hyphen are _not_ camel-cased, they should be
 * quoted to preserve the hyphen.
 *
 * @example
 * {
 *     'stroke': '#ff0000', // basic
 *     'stroke-width': 2, // hyphenated
 *     'rotation': 45 // custom
 *     'd': ['M', 10, 10, 'L', 30, 30, 'z'] // path definition, note format
 * }
 *
 * @interface Highcharts.SVGAttributes
 */ /**
* @name Highcharts.SVGAttributes#[key:string]
* @type {*}
*/ /**
* @name Highcharts.SVGAttributes#d
* @type {string|Highcharts.SVGPathArray|undefined}
*/ /**
* @name Highcharts.SVGAttributes#dx
* @type {number|undefined}
*/ /**
* @name Highcharts.SVGAttributes#dy
* @type {number|undefined}
*/ /**
* @name Highcharts.SVGAttributes#fill
* @type {Highcharts.ColorType|undefined}
*/ /**
* @name Highcharts.SVGAttributes#inverted
* @type {boolean|undefined}
*/ /**
* @name Highcharts.SVGAttributes#matrix
* @type {Array<number>|undefined}
*/ /**
* @name Highcharts.SVGAttributes#rotation
* @type {number|undefined}
*/ /**
* @name Highcharts.SVGAttributes#rotationOriginX
* @type {number|undefined}
*/ /**
* @name Highcharts.SVGAttributes#rotationOriginY
* @type {number|undefined}
*/ /**
* @name Highcharts.SVGAttributes#scaleX
* @type {number|undefined}
*/ /**
* @name Highcharts.SVGAttributes#scaleY
* @type {number|undefined}
*/ /**
* @name Highcharts.SVGAttributes#stroke
* @type {Highcharts.ColorType|undefined}
*/ /**
* @name Highcharts.SVGAttributes#style
* @type {string|Highcharts.CSSObject|undefined}
*/ /**
* @name Highcharts.SVGAttributes#translateX
* @type {number|undefined}
*/ /**
* @name Highcharts.SVGAttributes#translateY
* @type {number|undefined}
*/ /**
* @name Highcharts.SVGAttributes#zIndex
* @type {number|undefined}
*/
/**
 * An SVG DOM element. The type is a reference to the regular SVGElement in the
 * global scope.
 *
 * @typedef {globals.GlobalSVGElement} Highcharts.SVGDOMElement
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/SVGElement
 */
/**
 * The vertical alignment of an element.
 *
 * @typedef {"bottom"|"middle"|"top"} Highcharts.VerticalAlignValue
 */
''; // Keeps doclets above in JS file

;// ./code/es-modules/Core/Color/Palette.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Honsi
 *
 *  A commercial license may be required depending on use.
 *  See www.highcharts.com/license
 *
 *
 * */



const { charts: Palette_charts } = Core_Globals;


/* *
 *
 *  Constants
 *
 * */
const defaultColors = [
    '#2caffe',
    '#544fc5',
    '#00e272',
    '#fe6a35',
    '#6b8abc',
    '#d568fb',
    '#2ee0ca',
    '#fa4b42',
    '#feb56a',
    '#91e8e1'
];
const defaultDarkOverrideColors = [
    null, // Use the same color for the first item in dark mode
    '#00e272',
    '#efdf00'
];
/* *
 * Build the text content for the style tag
 */
const getStyles = (specifier, cssVars) => {
    const reduceToCSS = (
    // For browsers that don't support color-scheme and light-dark, we need
    // to first set the CSS variables as the light scheme.
    supportsColorScheme) => {
        let css = '';
        objectEach(cssVars.light, (value, key) => {
            css += supportsColorScheme && value !== cssVars.dark[key] ?
                `  ${key}: light-dark(${value}, ${cssVars.dark[key]});\n` :
                `  ${key}: ${value};\n`;
        });
        return css;
    };
    const legacy = reduceToCSS(), lightDark = reduceToCSS(true);
    const css = `${specifier || ':root'} {
${legacy}
}
@supports (color: light-dark(#fff, #000)) {
  ${specifier || ':root'} {
${lightDark}
  }
}
.highcharts-container {
  color-scheme: light dark;
}
.highcharts-light .highcharts-container {
  color-scheme: light;
}
.highcharts-dark .highcharts-container {
  color-scheme: dark;
}`;
    return css;
};
/* *
 *
 *  Class
 *
 * */
/* eslint-disable valid-jsdoc */
/**
 * A Palette class holding the palette colors and lifecycle methods for each
 * chart.
 *
 * @class
 * @name Highcharts.Palette
 *
 * @param {Highcharts.Chart} chart
 *        The chart instance
 * @param {Highcharts.PaletteOptions} options
 *        Palette options
 */
class Palette {
    /* *
     *
     *  Constructor
     *
     * */
    constructor(renderer, options) {
        this.defaultOptions = PaletteDefaults;
        this.options = merge(PaletteDefaults);
        this.cssVars = { light: {}, dark: {} };
        this.renderer = renderer;
        this.update(options);
    }
    /**
     * Inject the CSS for the palette into the document head or chart
     * container.
     *
     * @internal
     *
     * @param {Highcharts.PaletteOptions} options
     *        Palette options
     */
    injectCSS(options) {
        const { cssVars, renderer } = this, hasSpecificPalette = Object.keys(diffObjects(options, this.defaultOptions)).length > 0, colorCount = Math.max(options.colors?.length || 0, defaultColors.length);
        let colorScheme = 'light';
        const addKebab = (color, key) => {
            if (isString(color)) {
                // Kebab-case the key. Sequences of numbers should be kept
                // but with a preceding dash.
                key = key
                    .replace(/([0-9]+)/g, '-$1')
                    .replace(/[A-Z]/g, (match) => `-${match.toLowerCase()}`);
                cssVars[colorScheme][`--highcharts-${key}`] = color;
            }
        };
        for (const cScheme of ['light', 'dark']) {
            const paletteColors = options[cScheme] || {}, interpolated = {}, neutralColor = new Color_Color(paletteColors?.neutralColor || ''), backgroundColor = new Color_Color(paletteColors?.backgroundColor || ''), highlightColor = new Color_Color(paletteColors?.highlightColor || '');
            colorScheme = cScheme;
            // Interpolate keys
            [3, 5, 10, 20, 40, 60, 80, 100].forEach((fraction) => {
                interpolated[`neutralColor${fraction}`] = backgroundColor
                    .tweenTo(neutralColor, fraction / 100);
                interpolated[`highlightColor${fraction}`] = backgroundColor
                    .tweenTo(highlightColor, fraction / 100);
            });
            // Extended data colors per scheme
            for (let i = 0; i < colorCount; i++) {
                const color = 
                // 1. priority to scheme-specific colors
                paletteColors?.colors?.[i] ||
                    // 2. then fall back to palette-level colors
                    options.colors?.[i] ||
                    // 3. then fall back to dark default colors
                    (cScheme === 'dark' ? defaultDarkOverrideColors : [])?.[i] ||
                    // 4. finally, use default colors
                    defaultColors[i];
                addKebab(color || '#888a', `color${i}`);
            }
            // The rest are stored as named properties
            objectEach(paletteColors, addKebab);
            objectEach(interpolated, addKebab);
            // Default to the light scheme, the dark scheme doesn't define
            // all colors.
            if (cScheme === 'light') {
                extend(cssVars.dark, cssVars.light);
            }
        }
        // Add a style tag to the chart renderer box
        const defs = renderer.defs.element, specifier = hasSpecificPalette ?
            `*[data-highcharts-chart="${renderer.chartIndex}"]` :
            '', style = defs
            .querySelector('style.highcharts-palette') ||
            renderer.box.ownerDocument.createElement('style');
        if (!style.parentNode) {
            style.nonce = 'highcharts';
            style.className = 'highcharts-palette';
            defs.appendChild(style);
        }
        style.textContent = getStyles(specifier, cssVars);
    }
    /**
     * Update the palette with new options. May be called directly as
     * `chart.palette.update()` or indirectly from `chart.update({ palette })`.
     *
     * @function Highcharts.Palette#update
     *
     * @param {Highcharts.PaletteOptions} options
     *        New palette options
     */
    update(options) {
        const renderTo = this.renderer.box.parentElement, chart = Palette_charts?.[this.renderer.chartIndex];
        options = merge(true, this.options, options);
        if (chart) {
            chart.options.palette = options;
        }
        if (options.injectCSS !== false) {
            this.injectCSS(options);
        }
        if (renderTo) {
            if (isString(options.colorScheme) &&
                ['light', 'dark', 'inherit'].includes(options.colorScheme)) {
                renderTo.style.colorScheme = options.colorScheme;
            }
            else {
                renderTo.style.removeProperty('color-scheme');
            }
        }
    }
}

;// ./code/es-modules/Core/Renderer/SVG/SVGLabel.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


/* *
 *
 *  Class
 *
 * */
/**
 * SVG label to render text.
 *
 * @class
 * @name Highcharts.SVGLabel
 * @augments Highcharts.SVGElement
 */
class SVGLabel extends SVG_SVGElement {
    /* *
     *
     *  Constructor
     *
     * */
    constructor(renderer, str, x, y, shape, // @todo (SymbolKey|string),
    anchorX, anchorY, useHTML, baseline, className) {
        super(renderer, 'g');
        /** @internal */
        this.paddingLeftSetter = this.paddingSetter;
        /** @internal */
        this.paddingRightSetter = this.paddingSetter;
        /** @internal */
        this.doUpdate = false;
        this.textStr = str;
        this.x = x;
        this.y = y;
        this.anchorX = anchorX;
        this.anchorY = anchorY;
        this.baseline = baseline;
        this.className = className;
        this.addClass(className === 'button' ?
            'highcharts-no-tooltip' :
            'highcharts-label');
        if (className) {
            this.addClass('highcharts-' + className);
        }
        // Create the text element. An undefined text content prevents redundant
        // box calculation (#16121)
        this.text = renderer.text(void 0, 0, 0, useHTML).attr({ zIndex: 1 });
        // Validate the shape argument
        let hasBGImage;
        if (typeof shape === 'string') {
            hasBGImage = /^url\((.*?)\)$/.test(shape);
            if (hasBGImage || this.renderer.symbols[shape]) {
                this.symbolKey = shape;
            }
        }
        this.bBox = SVGLabel.emptyBBox;
        this.padding = 3;
        this.baselineOffset = 0;
        this.needsBox = renderer.styledMode || hasBGImage;
        this.deferredAttr = {};
        this.alignFactor = 0;
    }
    /* *
     *
     *  Functions
     *
     * */
    /** @internal */
    alignSetter(value) {
        const alignFactor = getAlignFactor(value);
        this.textAlign = value;
        if (alignFactor !== this.alignFactor) {
            this.alignFactor = alignFactor;
            // Bounding box exists, means we're dynamically changing
            if (this.bBox && isNumber(this.xSetting)) {
                this.attr({ x: this.xSetting }); // #5134
            }
            this.updateTextPadding(); // #23595
        }
    }
    /** @internal */
    anchorXSetter(value, key) {
        this.anchorX = value;
        this.boxAttr(key, Math.round(value) - this.getCrispAdjust() - this.xSetting);
    }
    /** @internal */
    anchorYSetter(value, key) {
        this.anchorY = value;
        this.boxAttr(key, value - this.ySetting);
    }
    /**
     * Set a box attribute, or defer it, if the box is not yet created.
     * @internal
     */
    boxAttr(key, value) {
        if (this.box) {
            this.box.attr(key, value);
        }
        else {
            this.deferredAttr[key] = value;
        }
    }
    /**
     * Pick up some properties and apply them to the text instead of the
     * wrapper.
     * @internal
     */
    css(styles) {
        if (styles) {
            const textStyles = {};
            // Create a copy to avoid altering the original object
            // (#537)
            styles = merge(styles);
            SVGLabel.textProps.forEach((prop) => {
                if (typeof styles[prop] !== 'undefined') {
                    textStyles[prop] = styles[prop];
                    delete styles[prop];
                }
            });
            this.text.css(textStyles);
            // Update existing text, box (#9400, #12163, #18212, #23595)
            if ('fontSize' in textStyles ||
                'fontWeight' in textStyles ||
                'width' in textStyles) {
                this.updateTextPadding();
            }
            else if ('textOverflow' in textStyles) {
                this.updateBoxSize();
            }
            if ('color' in textStyles) {
                this.updateBackground();
            }
        }
        return SVG_SVGElement.prototype.css.call(this, styles);
    }
    /**
     * Destroy and release memory.
     * @internal
     */
    destroy() {
        // Added by button implementation
        removeEvent(this.element, 'mouseenter');
        removeEvent(this.element, 'mouseleave');
        if (this.text) {
            this.text.destroy();
        }
        if (this.box) {
            this.box = this.box.destroy();
        }
        // Call base implementation to destroy the rest
        SVG_SVGElement.prototype.destroy.call(this);
        return void 0;
    }
    /** @internal */
    fillSetter(value, key) {
        if (value) {
            this.needsBox = true;
        }
        // For animation getter (#6776)
        this.fill = value;
        this.boxAttr(key, value);
    }
    /**
     * Return the bounding box of the box, not the group.
     * @internal
     */
    getBBox(reload, rot) {
        // If we have a text string and the DOM bBox was 0, it typically means
        // that the label was first rendered hidden, so we need to update the
        // bBox (#15246)
        if ((this.textStr && this.bBox.width === 0 && this.bBox.height === 0) || this.rotation) {
            this.updateBoxSize();
        }
        const { padding, height = 0, translateX = 0, translateY = 0, width = 0 } = this, paddingLeft = pick(this.paddingLeft, padding), rotation = rot ?? (this.rotation || 0);
        let bBox = {
            width,
            height,
            x: translateX + this.bBox.x - paddingLeft,
            y: translateY + this.bBox.y - padding + this.baselineOffset
        };
        if (rotation) {
            bBox = this.getRotatedBox(bBox, rotation);
        }
        return bBox;
    }
    /** @internal */
    getCrispAdjust() {
        return (this.renderer.styledMode && this.box ?
            this.box.strokeWidth() :
            (this['stroke-width'] ?
                parseInt(this['stroke-width'], 10) :
                0)) % 2 / 2;
    }
    /** @internal */
    heightSetter(value) {
        this.heightSetting = value;
        this.doUpdate = true;
    }
    /**
     * This method is executed in the end of `attr()`, after setting all
     * attributes in the hash. In can be used to efficiently consolidate
     * multiple attributes in one SVG property -- e.g., translate, rotate and
     * scale are merged in one "transform" attribute in the SVG node.
     * Also updating height or width should trigger update of the box size.
     *
     * @internal
     * @function Highcharts.SVGLabel#afterSetters
     */
    afterSetters() {
        super.afterSetters();
        if (this.doUpdate) {
            this.updateBoxSize();
            this.doUpdate = false;
        }
        this.updateBackground();
    }
    /**
     * After the text element is added, get the desired size of the border
     * box and add it before the text in the DOM.
     * @internal
     */
    onAdd() {
        this.text.add(this);
        this.attr({
            // Alignment is available now  (#3295, 0 not rendered if given
            // as a value)
            text: pick(this.textStr, ''),
            x: this.x || 0,
            y: this.y || 0
        });
        if (this.box && defined(this.anchorX)) {
            this.attr({
                anchorX: this.anchorX,
                anchorY: this.anchorY
            });
        }
    }
    /** @internal */
    paddingSetter(value, key) {
        if (!isNumber(value)) {
            this[key] = void 0;
        }
        else if (value !== this[key]) {
            this[key] = value;
            this.updateTextPadding();
        }
    }
    /** @internal */
    rSetter(value, key) {
        this.boxAttr(key, value);
    }
    /** @internal */
    strokeSetter(value, key) {
        // For animation getter (#6776)
        this.stroke = value;
        this.boxAttr(key, value);
    }
    /** @internal */
    'stroke-widthSetter'(value, key) {
        if (value) {
            this.needsBox = true;
        }
        this['stroke-width'] = value;
        this.boxAttr(key, value);
    }
    /** @internal */
    'text-alignSetter'(value) {
        // The text-align variety is for the pre-animation getter. The code
        // should be unified to either textAlign or text-align.
        this.textAlign = this['text-align'] = value;
        this.updateTextPadding();
    }
    /** @internal */
    textSetter(text) {
        if (typeof text !== 'undefined') {
            // Must use .attr to ensure transforms are done (#10009)
            this.text.attr({ text });
        }
        this.updateTextPadding();
        this.reAlign();
    }
    updateBackground() {
        if (this.fill === 'contrast') {
            this.box?.attr({
                fill: this.renderer.getContrast(this.text.styles.color || '#000'),
                'fill-opacity': 0.65
            });
        }
    }
    /**
     * This function runs after the label is added to the DOM (when the bounding
     * box is available), and after the text of the label is updated to detect
     * the new bounding box and reflect it in the border box.
     * @internal
     */
    updateBoxSize() {
        const text = this.text, attribs = {}, padding = this.padding, 
        // #12165 error when width is null (auto)
        // #12163 when fontweight: bold, recalculate bBox without cache
        // #3295 && 3514 box failure when string equals 0
        bBox = this.bBox = (((!isNumber(this.widthSetting) ||
            !isNumber(this.heightSetting) ||
            this.textAlign) && defined(text.textStr)) ?
            text.getBBox(void 0, 0) :
            SVGLabel.emptyBBox);
        let crispAdjust;
        this.width = this.getPaddedWidth();
        this.height = (this.heightSetting || bBox.height || 0) + 2 * padding;
        const metrics = this.renderer.fontMetrics(text);
        // Update the label-scoped y offset. Math.min because of inline
        // style (#9400)
        this.baselineOffset = padding + Math.min(
        // When applicable, use the font size of the first line (#15707)
        (this.text.firstLineMetrics || metrics).b, 
        // When the height is 0, there is no bBox, so go with the font
        // metrics. Highmaps CSS demos.
        bBox.height || Infinity);
        // #15491: Vertical centering
        if (this.heightSetting) {
            this.baselineOffset += (this.heightSetting - metrics.h) / 2;
        }
        if (this.needsBox && !text.textPath) {
            // Create the border box if it is not already present
            if (!this.box) {
                // Symbol definition exists (#5324)
                const box = this.box = this.symbolKey ?
                    this.renderer.symbol(this.symbolKey) :
                    this.renderer.rect();
                box.addClass(// Don't use label className for buttons
                (this.className === 'button' ?
                    '' : 'highcharts-label-box') +
                    (this.className ?
                        ' highcharts-' + this.className + '-box' : ''));
                box.add(this);
            }
            crispAdjust = this.getCrispAdjust();
            attribs.x = crispAdjust;
            attribs.y = ((this.baseline ? -this.baselineOffset : 0) + crispAdjust);
            // Apply the box attributes
            attribs.width = Math.round(this.width);
            attribs.height = Math.round(this.height);
            this.box.attr(extend(attribs, this.deferredAttr));
            this.deferredAttr = {};
        }
    }
    /**
     * This function runs after setting text or padding, but only if padding
     * is changed.
     * @internal
     */
    updateTextPadding() {
        const text = this.text, textAlign = text.styles.textAlign || this.textAlign;
        if (!text.textPath) {
            this.updateBoxSize();
            // Determine y based on the baseline
            const textY = this.baseline ? 0 : this.baselineOffset, textX = (this.paddingLeft ?? this.padding) +
                // Compensate for alignment
                getAlignFactor(textAlign) * (this.widthSetting ?? this.bBox.width);
            // Update if anything changed
            if (textX !== text.x || textY !== text.y) {
                text.attr({
                    align: textAlign,
                    x: textX
                });
                if (typeof textY !== 'undefined') {
                    text.attr('y', textY);
                }
            }
            // Record current values
            text.x = textX;
            text.y = textY;
        }
    }
    /** @internal */
    widthSetter(value) {
        // `width:auto` => null
        this.widthSetting = isNumber(value) ? value : void 0;
        this.doUpdate = true;
    }
    /** @internal */
    getPaddedWidth() {
        const padding = this.padding;
        const paddingLeft = pick(this.paddingLeft, padding);
        const paddingRight = pick(this.paddingRight, padding);
        return ((this.widthSetting || this.bBox.width || 0) +
            paddingLeft +
            paddingRight);
    }
    /** @internal */
    xSetter(value) {
        this.x = value; // For animation getter
        if (this.alignFactor) {
            value -= this.alignFactor * this.getPaddedWidth();
            // Force animation even when setting to the same value (#7898)
            this['forceAnimate:x'] = true;
        }
        if (this.anchorX) {
            // #22907, force anchorX to animate after x set
            this['forceAnimate:anchorX'] = true;
        }
        this.xSetting = Math.round(value);
        this.attr('translateX', this.xSetting);
    }
    /** @internal */
    ySetter(value) {
        if (this.anchorY) {
            // #22907, force anchorY to animate after y set
            this['forceAnimate:anchorY'] = true;
        }
        this.ySetting = this.y = Math.round(value);
        this.attr('translateY', this.ySetting);
    }
}
/* *
 *
 *  Static Properties
 *
 * */
/** @internal */
SVGLabel.emptyBBox = {
    width: 0,
    height: 0,
    x: 0,
    y: 0
};
/**
 * For labels, these CSS properties are applied to the `text` node directly.
 *
 * @internal
 * @name Highcharts.SVGLabel#textProps
 * @type {Array<string>}
 */
SVGLabel.textProps = [
    'color', 'direction', 'fontFamily', 'fontSize', 'fontStyle',
    'fontWeight', 'lineClamp', 'lineHeight', 'textAlign', 'textDecoration',
    'textOutline', 'textOverflow', 'whiteSpace', 'width'
];
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const SVG_SVGLabel = (SVGLabel);

;// ./code/es-modules/Core/Renderer/SVG/Symbols.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


/* *
 *
 *  Functions
 *
 * */
/* eslint-disable require-jsdoc, valid-jsdoc */
/**
 * Arc symbol path.
 *
 * @param {number} cx
 * Center X
 * @param {number} cy
 * Center Y
 * @param {number} w
 * Width
 * @param {number} h
 * Height
 * @param {Highcharts.SymbolOptions} [options]
 * Options
 * @return {Highcharts.SVGPathArray}
 * Path
 */
function arc(cx, cy, w, h, options) {
    const arc = [];
    if (options) {
        let start = options.start || 0, end = options.end || 0;
        const rx = pick(options.r, w), ry = pick(options.r, h || w), 
        // Subtract a small number to prevent cos and sin of start and end
        // from becoming equal on 360 arcs (#1561). See "Arc proximity"
        // tests at samples/unit-tests/svgrenderer/symbol/demo.js
        proximity = 0.0001, fullCircle = (Math.abs(end - start - 2 * Math.PI) <
            proximity);
        if (fullCircle) {
            start = Math.PI / 2;
            end = Math.PI * 2.5 - proximity;
        }
        const innerRadius = options.innerR, open = pick(options.open, fullCircle), cosStart = fullCircle ? 0 : Math.cos(start), sinStart = fullCircle ? 1 : Math.sin(start), cosEnd = fullCircle ? 0 : Math.cos(end), sinEnd = fullCircle ? 1 : Math.sin(end), 
        // Proximity takes care of rounding errors around PI (#6971)
        longArc = pick(options.longArc, end - start - Math.PI < proximity ? 0 : 1);
        let arcSegment = [
            'A', // ArcTo
            rx, // X radius
            ry, // Y radius
            0, // Slanting
            longArc, // Long or short arc
            pick(options.clockwise, 1), // Clockwise
            // Use a static pixel offset for full circle (#21701)
            cx + (fullCircle ? 0.001 : rx * cosEnd),
            cy + ry * sinEnd
        ];
        arcSegment.params = { start, end, cx, cy }; // Memo for border radius
        arc.push([
            'M',
            cx + rx * cosStart,
            cy + ry * sinStart
        ], arcSegment);
        if (defined(innerRadius)) {
            arcSegment = [
                'A', // ArcTo
                innerRadius, // X radius
                innerRadius, // Y radius
                0, // Slanting
                longArc, // Long or short arc
                // Clockwise - opposite to the outer arc clockwise
                defined(options.clockwise) ? 1 - options.clockwise : 0,
                cx + (fullCircle ? -0.001 : innerRadius * cosStart),
                cy + innerRadius * sinStart
            ];
            // Memo for border radius
            arcSegment.params = {
                start: end,
                end: start,
                cx,
                cy
            };
            arc.push(open ?
                [
                    'M',
                    cx + innerRadius * cosEnd,
                    cy + innerRadius * sinEnd
                ] : [
                'L',
                cx + innerRadius * cosEnd,
                cy + innerRadius * sinEnd
            ], arcSegment);
        }
        if (!open) {
            arc.push(['Z']);
        }
    }
    return arc;
}
/**
 * Callout shape used for default tooltips.
 *
 * @param {number} x
 * Center X
 * @param {number} y
 * Center Y
 * @param {number} w
 * Width
 * @param {number} h
 * Height
 * @param {Highcharts.SymbolOptions} [options]
 * Options
 * @return {Highcharts.SVGPathArray}
 * Path
 */
function callout(x, y, w, h, options) {
    const arrowLength = 6, halfDistance = 6, r = Math.min((options?.r) || 0, w, h), safeDistance = r + halfDistance, anchorX = options?.anchorX, anchorY = options?.anchorY || 0;
    const path = roundedRect(x, y, w, h, { r });
    if (!isNumber(anchorX)) {
        return path;
    }
    // Do not render a connector, if anchor starts inside the label
    if (anchorX < w && anchorX > 0 && anchorY < h && anchorY > 0) {
        return path;
    }
    // Anchor on right side
    if (x + anchorX > w - safeDistance) {
        // Chevron
        if (anchorY > y + safeDistance &&
            anchorY < y + h - safeDistance) {
            path.splice(3, 1, ['L', x + w, anchorY - halfDistance], ['L', x + w + arrowLength, anchorY], ['L', x + w, anchorY + halfDistance], ['L', x + w, y + h - r]);
            // Simple connector
        }
        else {
            if (anchorX < w) { // Corner connector
                const isTopCorner = anchorY < y + safeDistance, cornerY = isTopCorner ? y : y + h, sliceStart = isTopCorner ? 2 : 5;
                path.splice(sliceStart, 0, ['L', anchorX, anchorY], ['L', x + w - r, cornerY]);
            }
            else { // Side connector
                path.splice(3, 1, ['L', x + w, h / 2], ['L', anchorX, anchorY], ['L', x + w, h / 2], ['L', x + w, y + h - r]);
            }
        }
        // Anchor on left side
    }
    else if (x + anchorX < safeDistance) {
        // Chevron
        if (anchorY > y + safeDistance &&
            anchorY < y + h - safeDistance) {
            path.splice(7, 1, ['L', x, anchorY + halfDistance], ['L', x - arrowLength, anchorY], ['L', x, anchorY - halfDistance], ['L', x, y + r]);
            // Simple connector
        }
        else {
            if (anchorX > 0) { // Corner connector
                const isTopCorner = anchorY < y + safeDistance, cornerY = isTopCorner ? y : y + h, sliceStart = isTopCorner ? 1 : 6;
                path.splice(sliceStart, 0, ['L', anchorX, anchorY], ['L', x + r, cornerY]);
            }
            else { // Side connector
                path.splice(7, 1, ['L', x, h / 2], ['L', anchorX, anchorY], ['L', x, h / 2], ['L', x, y + r]);
            }
        }
    }
    else if ( // Replace bottom
    anchorY > h &&
        anchorX < w - safeDistance) {
        path.splice(5, 1, ['L', anchorX + halfDistance, y + h], ['L', anchorX, y + h + arrowLength], ['L', anchorX - halfDistance, y + h], ['L', x + r, y + h]);
    }
    else if ( // Replace top
    anchorY < 0 &&
        anchorX > safeDistance) {
        path.splice(1, 1, ['L', anchorX - halfDistance, y], ['L', anchorX, y - arrowLength], ['L', anchorX + halfDistance, y], ['L', w - r, y]);
    }
    return path;
}
/**
 * Circle symbol path.
 *
 * @param {number} x
 * X coordinate
 * @param {number} y
 * Y coordinate
 * @param {number} w
 * Width
 * @param {number} h
 * Height
 * @return {Highcharts.SVGPathArray}
 * Path
 */
function circle(x, y, w, h) {
    // Return a full arc
    return arc(x + w / 2, y + h / 2, w / 2, h / 2, {
        start: Math.PI * 0.5,
        end: Math.PI * 2.5,
        open: false
    });
}
/**
 * Diamond symbol path.
 *
 * @param {number} x
 * X coordinate
 * @param {number} y
 * Y coordinate
 * @param {number} w
 * Width
 * @param {number} h
 * Height
 * @return {Highcharts.SVGPathArray}
 * Path
 */
function diamond(x, y, w, h) {
    return [
        ['M', x + w / 2, y],
        ['L', x + w, y + h / 2],
        ['L', x + w / 2, y + h],
        ['L', x, y + h / 2],
        ['Z']
    ];
}
// #15291
/**
 * Rect symbol path.
 *
 * @param {number} x
 * X coordinate
 * @param {number} y
 * Y coordinate
 * @param {number} w
 * Width
 * @param {number} h
 * Height
 * @param {Highcharts.SymbolOptions} [options]
 * Options
 * @return {Highcharts.SVGPathArray}
 * Path
 */
function rect(x, y, w, h, options) {
    if (options?.r) {
        return roundedRect(x, y, w, h, options);
    }
    return [
        ['M', x, y],
        ['L', x + w, y],
        ['L', x + w, y + h],
        ['L', x, y + h],
        ['Z']
    ];
}
/**
 * Rounded rectangle symbol path.
 *
 * @param {number} x
 * X coordinate
 * @param {number} y
 * Y coordinate
 * @param {number} w
 * Width
 * @param {number} h
 * Height
 * @param {Highcharts.SymbolOptions} [options]
 * Options
 * @return {Highcharts.SVGPathArray}
 * Path
 */
function roundedRect(x, y, w, h, options) {
    const r = options?.r || 0;
    return [
        ['M', x + r, y],
        ['L', x + w - r, y], // Top side
        ['A', r, r, 0, 0, 1, x + w, y + r], // Top-right corner
        ['L', x + w, y + h - r], // Right side
        ['A', r, r, 0, 0, 1, x + w - r, y + h], // Bottom-right corner
        ['L', x + r, y + h], // Bottom side
        ['A', r, r, 0, 0, 1, x, y + h - r], // Bottom-left corner
        ['L', x, y + r], // Left side
        ['A', r, r, 0, 0, 1, x + r, y],
        ['Z'] // Top-left corner
    ];
}
/**
 * Triangle symbol path.
 *
 * @param {number} x
 * X coordinate
 * @param {number} y
 * Y coordinate
 * @param {number} w
 * Width
 * @param {number} h
 * Height
 * @return {Highcharts.SVGPathArray}
 * Path
 */
function triangle(x, y, w, h) {
    return [
        ['M', x + w / 2, y],
        ['L', x + w, y + h],
        ['L', x, y + h],
        ['Z']
    ];
}
/**
 * Inverted triangle symbol path.
 *
 * @param {number} x
 * X coordinate
 * @param {number} y
 * Y coordinate
 * @param {number} w
 * Width
 * @param {number} h
 * Height
 * @return {Highcharts.SVGPathArray}
 * Path
 */
function triangleDown(x, y, w, h) {
    return [
        ['M', x, y],
        ['L', x + w, y],
        ['L', x + w / 2, y + h],
        ['Z']
    ];
}
const Symbols = {
    /**
     * Arc symbol path.
     *
     * @param {number} cx
     * Center X
     * @param {number} cy
     * Center Y
     * @param {number} w
     * Width
     * @param {number} h
     * Height
     * @param {Highcharts.SymbolOptions} [options]
     * Options
     * @return {Highcharts.SVGPathArray}
     * Path
     */
    arc,
    /**
     * Callout shape used for default tooltips.
     *
     * @param {number} cx
     * Center X
     * @param {number} cy
     * Center Y
     * @param {number} w
     * Width
     * @param {number} h
     * Height
     * @param {Highcharts.SymbolOptions} [options]
     * Options
     * @return {Highcharts.SVGPathArray}
     * Path
     */
    callout,
    /**
     * Circle symbol path.
     *
     * @param {number} x
     * X coordinate
     * @param {number} y
     * Y coordinate
     * @param {number} w
     * Width
     * @param {number} h
     * Height
     * @return {Highcharts.SVGPathArray}
     * Path
     */
    circle,
    /**
     * Diamond symbol path.
     *
     * @param {number} x
     * X coordinate
     * @param {number} y
     * Y coordinate
     * @param {number} w
     * Width
     * @param {number} h
     * Height
     * @return {Highcharts.SVGPathArray}
     * Path
     */
    diamond,
    /**
     * Rect symbol path.
     *
     * @param {number} x
     * X coordinate
     * @param {number} y
     * Y coordinate
     * @param {number} w
     * Width
     * @param {number} h
     * Height
     * @param {Highcharts.SymbolOptions} [options]
     * Options
     * @return {Highcharts.SVGPathArray}
     * Path
     */
    rect,
    /**
     * Rounded rectangle symbol path.
     *
     * @param {number} x
     * X coordinate
     * @param {number} y
     * Y coordinate
     * @param {number} w
     * Width
     * @param {number} h
     * Height
     * @param {Highcharts.SymbolOptions} [options]
     * Options
     * @return {Highcharts.SVGPathArray}
     * Path
     */
    roundedRect,
    /**
     * Rect symbol path.
     *
     * @param {number} x
     * X coordinate
     * @param {number} y
     * Y coordinate
     * @param {number} w
     * Width
     * @param {number} h
     * Height
     * @param {Highcharts.SymbolOptions} [options]
     * Options
     * @return {Highcharts.SVGPathArray}
     * Path
     */
    square: rect,
    /**
     * Triangle symbol path.
     *
     * @param {number} x
     * X coordinate
     * @param {number} y
     * Y coordinate
     * @param {number} w
     * Width
     * @param {number} h
     * Height
     * @return {Highcharts.SVGPathArray}
     * Path
     */
    triangle,
    /**
     * Inverted triangle symbol path.
     *
     * @param {number} x
     * X coordinate
     * @param {number} number
     * Y coordinate
     * @param {number} w
     * Width
     * @param {number} h
     * Height
     * @return {Highcharts.SVGPathArray}
     * Path
     */
    'triangle-down': triangleDown
};
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const SVG_Symbols = (Symbols);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * @interface Highcharts.SymbolOptions
 */ /**
* @name anchorX
* @type {number|undefined}
*/ /**
* @name anchorY
* @type {number|undefined}
*/ /**
* @name backgroundSize
* @type {"contain"|"cover"|"within"}
*/ /**
* @name clockwise
* @type {0|1|undefined}
*/ /**
* @name context
* @type {string|undefined}
*/ /**
* @name end
* @type {number|undefined}
*/ /**
* @name height
* @type {number|undefined}
*/ /**
* @name innerR
* @type {number|undefined}
*/ /**
* @name longArc
* @type {0|1|undefined}
*/ /**
* @name open
* @type {boolean|undefined}
*/ /**
* @name r
* @type {number|undefined}
*/ /**
* @name start
* @type {number|undefined}
*/ /**
* @name width
* @type {number|undefined}
*/ /**
* @name x
* @type {number|undefined}
*/ /**
* @name y
* @type {number|undefined}
*/
''; // Keeps doclets above in file

;// ./code/es-modules/Core/Renderer/SVG/TextBuilder.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */



const { doc: TextBuilder_doc, SVG_NS: TextBuilder_SVG_NS, win: TextBuilder_win } = Core_Globals;

// Function used to test string length including an ellipsis
const stringWithEllipsis = (text, currentIndex) => text.substring(0, currentIndex) + '\u2026';
/* *
 *
 *  Class
 *
 * */
/**
 * SVG Text Builder
 * @internal
 * @class
 * @name Highcharts.TextBuilder
 */
class TextBuilder {
    /**
     * Constructs a new TextBuilder instance.
     * @internal
     */
    constructor(svgElement) {
        const textStyles = svgElement.styles;
        this.renderer = svgElement.renderer;
        this.svgElement = svgElement;
        this.width = svgElement.textWidth;
        this.textLineHeight = textStyles?.lineHeight;
        this.textOutline = textStyles?.textOutline;
        this.ellipsis = Boolean(textStyles?.textOverflow === 'ellipsis');
        this.lineClamp = textStyles?.lineClamp;
        this.noWrap = Boolean(textStyles?.whiteSpace === 'nowrap');
    }
    /**
     * Build an SVG representation of the pseudo HTML given in the object's
     * svgElement.
     *
     * @internal
     */
    buildSVG() {
        const wrapper = this.svgElement, textNode = wrapper.element, renderer = wrapper.renderer, textStr = pick(wrapper.textStr, '').toString(), hasMarkup = textStr.indexOf('<') !== -1, childNodes = textNode.childNodes, tempParent = !wrapper.added && renderer.box, regexMatchBreaks = /<br.*?>/g, 
        // The buildText code is quite heavy, so if we're not changing
        // something that affects the text, skip it (#6113).
        textCache = [
            textStr,
            this.ellipsis,
            this.noWrap,
            this.textLineHeight,
            this.textOutline,
            wrapper.getStyle('font-size'),
            wrapper.styles.lineClamp,
            this.width
        ].join(',');
        if (textCache === wrapper.textCache) {
            return;
        }
        wrapper.textCache = textCache;
        delete wrapper.actualWidth;
        // Remove old text
        for (let i = childNodes.length; i--;) {
            textNode.removeChild(childNodes[i]);
        }
        // Simple strings, add text directly and return
        if (!hasMarkup &&
            !this.ellipsis &&
            !this.width &&
            !wrapper.textPath &&
            (textStr.indexOf(' ') === -1 ||
                (this.noWrap && !regexMatchBreaks.test(textStr)))) {
            textNode.appendChild(TextBuilder_doc.createTextNode(this.unescapeEntities(textStr)));
            // Complex strings, add more logic
        }
        else if (textStr !== '') {
            if (tempParent) {
                // Attach it to the DOM to read offset width and font size
                tempParent.appendChild(textNode);
            }
            // Step 1. Parse the markup safely and directly into a tree
            // structure.
            const ast = new HTML_AST(textStr);
            // Step 2. Do as many as we can of the modifications to the tree
            // structure before it is added to the DOM
            this.modifyTree(ast.nodes);
            ast.addToDOM(textNode);
            // Step 3. Some modifications can't be done until the structure is
            // in the DOM, because we need to read computed metrics.
            this.modifyDOM();
            // Add title if an ellipsis was added
            if (this.ellipsis &&
                (textNode.textContent || '').indexOf('\u2026') !== -1) {
                wrapper.attr('title', this.unescapeEntities(wrapper.textStr || '', ['&lt;', '&gt;']) // #7179
                );
            }
            if (tempParent) {
                tempParent.removeChild(textNode);
            }
        }
        // Apply the text outline
        if (isString(this.textOutline) && wrapper.applyTextOutline) {
            wrapper.applyTextOutline(this.textOutline);
        }
    }
    /**
     * Modify the DOM of the generated SVG structure. This function only does
     * operations that cannot be done until the elements are attached to the
     * DOM, like doing layout based on rendered metrics of the added elements.
     * @internal
     */
    modifyDOM() {
        const wrapper = this.svgElement;
        const x = attr(wrapper.element, 'x');
        wrapper.firstLineMetrics = void 0;
        // Remove empty tspans (including breaks) from the beginning because
        // SVG's getBBox doesn't count empty lines. The use case is tooltip
        // where the header is empty. By doing this in the DOM rather than in
        // the AST, we can inspect the textContent directly and don't have to
        // recurse down to look for valid content.
        let firstChild;
        while ((firstChild = wrapper.element.firstChild)) {
            if (/^[\s\u200B]*$/.test(firstChild.textContent || ' ')) {
                wrapper.element.removeChild(firstChild);
            }
            else {
                break;
            }
        }
        // Modify hard line breaks by applying the rendered line height
        [].forEach.call(wrapper.element.querySelectorAll('tspan.highcharts-br'), (br, i) => {
            if (br.nextSibling && br.previousSibling) { // #5261
                if (i === 0 && br.previousSibling.nodeType === 1) {
                    wrapper.firstLineMetrics = wrapper.renderer
                        .fontMetrics(br.previousSibling);
                }
                attr(br, {
                    // Since the break is inserted in front of the next
                    // line, we need to use the next sibling for the line
                    // height
                    dy: this.getLineHeight(br.nextSibling),
                    x
                });
            }
        });
        // Constrain the line width, either by ellipsis or wrapping
        const width = this.width || 0;
        if (!width) {
            return;
        }
        // Insert soft line breaks into each text node
        const modifyTextNode = (textNode, parentElement) => {
            const text = textNode.textContent || '';
            const words = text
                .replace(/([^\^])-/g, '$1- ') // Split on hyphens
                // .trim()
                .split(' '); // #1273
            const hasWhiteSpace = !this.noWrap && (words.length > 1 || wrapper.element.childNodes.length > 1);
            const dy = this.getLineHeight(parentElement), ellipsisWidth = Math.max(0, 
            // Subtract the font face to make room for
            // the ellipsis itself
            width - 0.8 * dy);
            let lineNo = 0;
            let startAt = wrapper.actualWidth;
            if (hasWhiteSpace) {
                const lines = [];
                // Remove preceding siblings in order to make the text length
                // calculation correct in the truncate function
                const precedingSiblings = [];
                while (parentElement.firstChild &&
                    parentElement.firstChild !== textNode) {
                    precedingSiblings.push(parentElement.firstChild);
                    parentElement.removeChild(parentElement.firstChild);
                }
                while (words.length) {
                    // Apply the previous line
                    if (words.length && !this.noWrap && lineNo > 0) {
                        lines.push(textNode.textContent || '');
                        textNode.textContent = words.join(' ')
                            .replace(/- /g, '-');
                    }
                    // For each line, truncate the remaining
                    // words into the line length.
                    this.truncate(textNode, void 0, words, lineNo === 0 ? (startAt || 0) : 0, width, ellipsisWidth, 
                    // Build the text to test for
                    (t, currentIndex) => words
                        .slice(0, currentIndex)
                        .join(' ')
                        .replace(/- /g, '-'));
                    startAt = wrapper.actualWidth;
                    lineNo++;
                    // Line clamp. Break out after n lines and append an
                    // ellipsis regardless of the text length.
                    if (this.lineClamp && lineNo >= this.lineClamp) {
                        // Only if there are remaining words that should have
                        // been rendered.
                        if (words.length) {
                            this.truncate(textNode, textNode.textContent || '', void 0, 0, 
                            // Target width
                            width, ellipsisWidth, stringWithEllipsis);
                            textNode.textContent = textNode.textContent
                                ?.replace('\u2026', '') + '\u2026';
                        }
                        break;
                    }
                }
                // Reinsert the preceding child nodes
                precedingSiblings.forEach((childNode) => {
                    parentElement.insertBefore(childNode, textNode);
                });
                // Insert the previous lines before the original text node
                lines.forEach((line) => {
                    // Insert the line
                    parentElement.insertBefore(TextBuilder_doc.createTextNode(line), textNode);
                    // Insert a break
                    const br = TextBuilder_doc.createElementNS(TextBuilder_SVG_NS, 'tspan');
                    br.textContent = '\u200B'; // Zero-width space
                    attr(br, { dy, x });
                    parentElement.insertBefore(br, textNode);
                });
            }
            else if (this.ellipsis) {
                if (text) {
                    this.truncate(textNode, text, void 0, 0, 
                    // Target width
                    width, ellipsisWidth, stringWithEllipsis);
                }
            }
        };
        // Recurse down the DOM tree and handle line breaks for each text node
        const modifyChildren = ((node) => {
            const childNodes = [].slice.call(node.childNodes);
            childNodes.forEach((childNode) => {
                if (childNode.nodeType === TextBuilder_win.Node.TEXT_NODE) {
                    modifyTextNode(childNode, node);
                }
                else {
                    // Reset word-wrap width readings after hard breaks
                    if (childNode.className.baseVal
                        .indexOf('highcharts-br') !== -1) {
                        wrapper.actualWidth = 0;
                    }
                    // Recurse down to child node
                    modifyChildren(childNode);
                }
            });
        });
        modifyChildren(wrapper.element);
    }
    /**
     * Get the rendered line height of a <text>, <tspan> or pure text node.
     * @internal
     * @param {DOMElementType|Text} node
     * The node to check for.
     * @return {number}
     * The rendered line height.
     */
    getLineHeight(node) {
        // If the node is a text node, use its parent
        const element = (node.nodeType === TextBuilder_win.Node.TEXT_NODE) ?
            node.parentElement :
            node;
        return this.textLineHeight ?
            parseInt(this.textLineHeight.toString(), 10) :
            this.renderer.fontMetrics(element || this.svgElement.element).h;
    }
    /**
     * Transform a pseudo HTML AST node tree into an SVG structure. We do as
     * much heavy lifting as we can here, before doing the final processing in
     * the modifyDOM function. The original data is mutated.
     *
     * @param {ASTNode[]} nodes The AST nodes
     */
    modifyTree(nodes) {
        const modifyChild = (node, i) => {
            const { attributes = {}, children, style = {}, tagName } = node, styledMode = this.renderer.styledMode;
            // Apply styling to text tags
            if (tagName === 'b' || tagName === 'strong') {
                if (styledMode) {
                    // eslint-disable-next-line dot-notation
                    attributes['class'] = 'highcharts-strong';
                }
                else {
                    style.fontWeight = 'bold';
                }
            }
            else if (tagName === 'i' || tagName === 'em') {
                if (styledMode) {
                    // eslint-disable-next-line dot-notation
                    attributes['class'] = 'highcharts-emphasized';
                }
                else {
                    style.fontStyle = 'italic';
                }
            }
            // Modify styling
            if (style?.color) {
                style.fill = style.color;
            }
            // Handle breaks
            if (tagName === 'br') {
                attributes['class'] = 'highcharts-br'; // eslint-disable-line dot-notation
                node.textContent = '\u200B'; // Zero-width space
                // Trim whitespace off the beginning of new lines
                const nextNode = nodes[i + 1];
                if (nextNode?.textContent) {
                    nextNode.textContent =
                        nextNode.textContent.replace(/^ +/gm, '');
                }
                // If an anchor has direct text node children, the text is unable to
                // wrap because there is no `getSubStringLength` function on the
                // element. Therefore we need to wrap the child text node or nodes
                // in a tspan. #16173.
            }
            else if (tagName === 'a' &&
                children &&
                children.some((child) => child.tagName === '#text')) {
                node.children = [{ children, tagName: 'tspan' }];
            }
            if (tagName !== '#text' && tagName !== 'a') {
                node.tagName = 'tspan';
            }
            extend(node, { attributes, style });
            // Recurse
            if (children) {
                children
                    .filter((c) => c.tagName !== '#text')
                    .forEach(modifyChild);
            }
        };
        nodes.forEach(modifyChild);
        fireEvent(this.svgElement, 'afterModifyTree', { nodes });
    }
    /*
     * Truncate the text node contents to a given length. Used when the css
     * width is set. If the `textOverflow` is `ellipsis`, the text is truncated
     * character by character to the given length. If not, the text is
     * word-wrapped line by line.
     */
    truncate(textNode, text, words, startAt, width, ellipsisWidth, getString) {
        const svgElement = this.svgElement;
        const { rotation } = svgElement;
        // Cache the lengths to avoid checking the same twice
        const lengths = [];
        // Word wrap cannot be truncated to shorter than one word, ellipsis
        // text can be completely blank.
        let minIndex = words && !startAt ? 1 : 0;
        let maxIndex = (text || words || '').length;
        let currentIndex = maxIndex;
        let str;
        let actualWidth;
        if (!words) {
            width = ellipsisWidth;
        }
        const getSubStringLength = function (charEnd, concatenatedEnd) {
            // `charEnd` is used when finding the character-by-character
            // break for ellipsis, concatenatedEnd is used for word-by-word
            // break for word wrapping.
            const end = concatenatedEnd || charEnd;
            const parentNode = textNode.parentNode;
            if (parentNode && typeof lengths[end] === 'undefined') {
                // Modern browsers
                if (parentNode.getSubStringLength) {
                    // Fails with DOM exception on unit-tests/legend/members
                    // of unknown reason. Desired width is 0, text content
                    // is "5" and end is 1.
                    try {
                        lengths[end] = startAt +
                            parentNode.getSubStringLength(0, words ? end + 1 : end);
                    }
                    catch {
                        // Ignore error
                    }
                }
            }
            return lengths[end];
        };
        svgElement.rotation = 0; // Discard rotation when computing box
        actualWidth = getSubStringLength(textNode.textContent.length);
        if (startAt + actualWidth > width) {
            // Do a binary search for the index where to truncate the text
            while (minIndex <= maxIndex) {
                currentIndex = Math.ceil((minIndex + maxIndex) / 2);
                // When checking words for word-wrap, we need to build the
                // string and measure the subStringLength at the concatenated
                // word length.
                if (words) {
                    str = getString(words, currentIndex);
                }
                actualWidth = getSubStringLength(currentIndex, str && str.length - 1);
                if (minIndex === maxIndex) {
                    // Complete
                    minIndex = maxIndex + 1;
                }
                else if (actualWidth > width) {
                    // Too large. Set max index to current.
                    maxIndex = currentIndex - 1;
                }
                else {
                    // Within width. Set min index to current.
                    minIndex = currentIndex;
                }
            }
            // If max index was 0 it means the shortest possible text was also
            // too large. For ellipsis that means only the ellipsis, while for
            // word wrap it means the whole first word.
            if (maxIndex === 0) {
                // Remove ellipsis
                textNode.textContent = '';
                // If the new text length is one less than the original, we don't
                // need the ellipsis
            }
            else if (!(text && maxIndex === text.length - 1)) {
                textNode.textContent = str || getString(text || words, currentIndex);
            }
            // Add ellipsis on individual lines
            if (this.ellipsis && actualWidth > width) {
                this.truncate(textNode, textNode.textContent || '', void 0, 0, width, ellipsisWidth, stringWithEllipsis);
            }
        }
        // When doing line wrapping, prepare for the next line by removing the
        // items from this line.
        if (words) {
            words.splice(0, currentIndex);
        }
        svgElement.actualWidth = actualWidth;
        svgElement.rotation = rotation; // Apply rotation again.
    }
    /**
     * Un-escape HTML entities based on the public `renderer.escapes` list
     *
     * @param {string} inputStr
     * The string to unescape
     *
     * @param {Array<string>} [except]
     * Exceptions
     *
     * @return {string}
     * The processed string
     */
    unescapeEntities(inputStr, except) {
        objectEach(this.renderer.escapes, function (value, key) {
            if (!except || except.indexOf(value) === -1) {
                inputStr = inputStr.toString().replace(new RegExp(value, 'g'), key);
            }
        });
        return inputStr;
    }
}
/** @internal */
/* harmony default export */ const SVG_TextBuilder = (TextBuilder);

;// ./code/es-modules/Core/Renderer/SVG/SVGRenderer.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */



const { defaultOptions: SVGRenderer_defaultOptions } = Defaults;


const { charts: SVGRenderer_charts, deg2rad: SVGRenderer_deg2rad, doc: SVGRenderer_doc, isFirefox: SVGRenderer_isFirefox, isMS, isWebKit, noop, SVG_NS: SVGRenderer_SVG_NS, symbolSizes, win: SVGRenderer_win } = Core_Globals;







/* *
 *
 *  Variables
 *
 * */
let hasInternalReferenceBug;
/* *
 *
 *  Class
 *
 * */
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
 * Allows direct access to the Highcharts rendering layer in order to draw
 * primitive shapes like circles, rectangles, paths or text directly on a chart,
 * or independent from any chart. The SVGRenderer represents a wrapper object
 * for SVG in modern browsers.
 *
 * An existing chart's renderer can be accessed through {@link Chart.renderer}.
 * The renderer can also be used completely decoupled from a chart.
 *
 * See [How to use the SVG Renderer](
 * https://www.highcharts.com/docs/advanced-chart-features/renderer) for a
 * comprehensive tutorial.
 *
 * @sample highcharts/members/renderer-on-chart
 *         Annotating a chart programmatically.
 * @sample highcharts/members/renderer-basic
 *         Independent SVG drawing.
 *
 * @example
 * // Use directly without a chart object.
 * let renderer = new Highcharts.Renderer(parentNode, 600, 400);
 *
 * @class
 * @name Highcharts.SVGRenderer
 *
 * @param {Highcharts.HTMLDOMElement} container
 *        Where to put the SVG in the web page.
 *
 * @param {number} width
 *        The width of the SVG.
 *
 * @param {number} height
 *        The height of the SVG.
 *
 * @param {Highcharts.CSSObject} [style]
 *        The box style, if not in styleMode
 *
 * @param {boolean} [forExport=false]
 *        Whether the rendered content is intended for export.
 *
 * @param {boolean} [allowHTML=true]
 *        Whether the renderer is allowed to include HTML text, which will be
 *        projected on top of the SVG.
 *
 * @param {boolean} [styledMode=false]
 *        Whether the renderer belongs to a chart that is in styled mode.
 *        If it does, it will avoid setting presentational attributes in
 *        some cases, but not when set explicitly through `.attr` and `.css`
 *        etc.
 */
class SVGRenderer {
    /**
     * The root `svg` node of the renderer.
     *
     * @name Highcharts.SVGRenderer#box
     * @type {Highcharts.SVGDOMElement}
     */
    /**
     * The wrapper for the root `svg` node of the renderer.
     *
     * @name Highcharts.SVGRenderer#boxWrapper
     * @type {Highcharts.SVGElement}
     */
    /**
     * A pointer to the `defs` node of the root SVG.
     *
     * @name Highcharts.SVGRenderer#defs
     * @type {Highcharts.SVGElement}
     */
    /**
     * Whether the rendered content is intended for export.
     *
     * @name Highcharts.SVGRenderer#forExport
     * @type {boolean | undefined}
     */
    /**
     * Page url used for internal references.
     *
     * @internal
     * @name Highcharts.SVGRenderer#url
     * @type {string}
     */
    /**
     * Initialize the SVGRenderer. Overridable initializer function that takes
     * the same parameters as the constructor.
     *
     * @function Highcharts.SVGRenderer#init
     *
     * @param {Highcharts.HTMLDOMElement} container
     * Where to put the SVG in the web page.
     *
     * @param {number} width
     * The width of the SVG.
     *
     * @param {number} height
     * The height of the SVG.
     *
     * @param {Highcharts.CSSObject} [style]
     * The box style, if not in styleMode
     *
     * @param {boolean} [forExport=false]
     * Whether the rendered content is intended for export.
     *
     * @param {boolean} [allowHTML=true]
     * Whether the renderer is allowed to include HTML text, which will be
     * projected on top of the SVG.
     *
     * @param {boolean} [styledMode=false]
     * Whether the renderer belongs to a chart that is in styled mode. If it
     * does, it will avoid setting presentational attributes in some cases, but
     * not when set explicitly through `.attr` and `.css` etc.
     */
    constructor(container, width, height, style, forExport, allowHTML, styledMode, palette, chartIndex) {
        /** @internal */
        this.x = 0;
        /** @internal */
        this.y = 0;
        const renderer = this, boxWrapper = renderer
            .createElement('svg')
            .attr({
            version: '1.1',
            'class': 'highcharts-root'
        }), element = boxWrapper.element;
        container.appendChild(element);
        // Always use ltr on the container, otherwise text-anchor will be
        // flipped and text appear outside labels, buttons, tooltip etc (#3482)
        attr(container, 'dir', 'ltr');
        // For browsers other than IE, add the namespace attribute (#1978)
        if (container.innerHTML.indexOf('xmlns') === -1) {
            attr(element, 'xmlns', this.SVG_NS);
        }
        this.box = element;
        this.boxWrapper = boxWrapper;
        this.alignedObjects = [];
        this.url = this.getReferenceURL();
        // Add description
        const desc = this.createElement('desc').add();
        desc.element.appendChild(SVGRenderer_doc.createTextNode('Created with Highcharts 13.0.0'));
        this.defs = this.createElement('defs').add();
        this.allowHTML = allowHTML;
        this.forExport = forExport;
        this.styledMode = styledMode;
        this.chartIndex = chartIndex || 0;
        this.gradients = {}; // Object where gradient SvgElements are stored
        this.cache = {}; // Cache for numerical bounding boxes
        this.cacheKeys = [];
        this.asyncCounter = 0;
        this.rootFontSize = boxWrapper.getStyle('font-size');
        renderer.setSize(width, height, false);
        if (!styledMode) {
            boxWrapper.css(this.getStyle(style || {}));
            // Create the palette
            this.palette = new Palette(this, palette || SVGRenderer_defaultOptions.palette);
        }
        // Issue 110 workaround:
        // In Firefox, if a div is positioned by percentage, its pixel position
        // may land between pixels. The container itself doesn't display this,
        // but an SVG element inside this container will be drawn at subpixel
        // precision. In order to draw sharp lines, this must be compensated
        // for. This doesn't seem to work inside iframes though (like in
        // jsFiddle).
        let subPixelFix, rect;
        if (SVGRenderer_isFirefox && container.getBoundingClientRect) {
            subPixelFix = function () {
                css(container, { left: 0, top: 0 });
                rect = container.getBoundingClientRect();
                css(container, {
                    left: (Math.ceil(rect.left) - rect.left) + 'px',
                    top: (Math.ceil(rect.top) - rect.top) + 'px'
                });
            };
            // Run the fix now
            subPixelFix();
            // Run it on resize
            renderer.unSubPixelFix = addEvent(SVGRenderer_win, 'resize', subPixelFix);
        }
    }
    /* *
     *
     *  Functions
     *
     * */
    /**
     * General method for adding a definition to the SVG `defs` tag. Can be used
     * for gradients, fills, filters etc. Styled mode only. A hook for adding
     * general definitions to the SVG's defs tag. Definitions can be referenced
     * from the CSS by its `id`. Read more in
     * [gradients, shadows and patterns](https://www.highcharts.com/docs/chart-design-and-style/gradients-shadows-and-patterns).
     * Styled mode only.
     *
     * @function Highcharts.SVGRenderer#definition
     *
     * @param {Highcharts.ASTNode} def
     * A serialized form of an SVG definition, including children.
     *
     * @return {Highcharts.SVGElement}
     * The inserted node.
     */
    definition(def) {
        const ast = new HTML_AST([def]);
        return ast.addToDOM(this.defs.element);
    }
    /**
     * Get the prefix needed for internal URL references to work in certain
     * cases. Some older browser versions had a bug where internal url
     * references in SVG attributes, on the form `url(#some-id)`, would fail if
     * a base tag was present in the page. There were also issues with
     * `history.pushState` related to this prefix.
     *
     * Related issues: #24, #672, #1070, #5244.
     *
     * The affected browsers are:
     * - Chrome <= 53 (May 2018)
     * - Firefox <= 51 (January 2017)
     * - Safari/Mac <= 12.1 (2018 or 2019)
     * - Safari/iOS <= 13
     *
     * @internal
     *
     * @todo Remove this hack when time has passed. All the affected browsers
     * are evergreens, so it is increasingly unlikely that users are affected by
     * the bug.
     *
     * @return {string}
     * The prefix to use. An empty string for modern browsers.
     */
    getReferenceURL() {
        if ((SVGRenderer_isFirefox || isWebKit) &&
            SVGRenderer_doc.getElementsByTagName('base').length) {
            // Detect if a clip path is taking effect by performing a hit test
            // outside the clipped area. If the hit element is the rectangle
            // that was supposed to be clipped, the bug is present. This only
            // has to be performed once per page load, so we store the result
            // locally in the module.
            if (!defined(hasInternalReferenceBug)) {
                const id = uniqueKey();
                const ast = new HTML_AST([{
                        tagName: 'svg',
                        attributes: {
                            width: 8,
                            height: 8
                        },
                        children: [{
                                tagName: 'defs',
                                children: [{
                                        tagName: 'clipPath',
                                        attributes: {
                                            id
                                        },
                                        children: [{
                                                tagName: 'rect',
                                                attributes: {
                                                    width: 4,
                                                    height: 4
                                                }
                                            }]
                                    }]
                            }, {
                                tagName: 'rect',
                                attributes: {
                                    id: 'hitme',
                                    width: 8,
                                    height: 8,
                                    'clip-path': `url(#${id})`,
                                    fill: 'rgba(0,0,0,0.001)'
                                }
                            }]
                    }]);
                const svg = ast.addToDOM(SVGRenderer_doc.body);
                css(svg, {
                    position: 'fixed',
                    top: 0,
                    left: 0,
                    zIndex: 9e5
                });
                const hitElement = SVGRenderer_doc.elementFromPoint(6, 6);
                hasInternalReferenceBug = hitElement?.id === 'hitme';
                SVGRenderer_doc.body.removeChild(svg);
            }
            if (hasInternalReferenceBug) {
                // Scan alert #[72]: Loop for nested patterns
                return replaceNested(SVGRenderer_win.location.href.split('#')[0], // Remove hash
                [/<[^>]*>/g, ''], // Wing cut HTML
                [/([\('\)])/g, '\\$1'], // Escape parentheses and quotes
                [/ /g, '%20'] // Replace spaces (needed for Safari only)
                );
            }
        }
        return '';
    }
    /**
     * Get the global style setting for the renderer.
     *
     * @internal
     * @function Highcharts.SVGRenderer#getStyle
     * @param {Highcharts.CSSObject} style
     * Style settings.
     * @return {Highcharts.CSSObject}
     * The style settings mixed with defaults.
     */
    getStyle(style) {
        this.style = extend({
            fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", ' +
                'Roboto, Helvetica, Arial, "Apple Color Emoji", ' +
                '"Segoe UI Emoji", "Segoe UI Symbol", sans-serif',
            fontSize: '1rem'
        }, style);
        return this.style;
    }
    /**
     * Apply the global style on the renderer, mixed with the default styles.
     *
     * @function Highcharts.SVGRenderer#setStyle
     *
     * @param {Highcharts.CSSObject} style
     * CSS to apply.
     */
    setStyle(style) {
        this.boxWrapper.css(this.getStyle(style));
    }
    /**
     * Detect whether the renderer is hidden. This happens when one of the
     * parent elements has `display: none`. Used internally to detect when we
     * need to render preliminarily in another div to get the text bounding
     * boxes right.
     *
     * @function Highcharts.SVGRenderer#isHidden
     *
     * @return {boolean}
     * True if it is hidden.
     */
    isHidden() {
        return !this.boxWrapper.getBBox().width;
    }
    /**
     * Destroys the renderer and its allocated members.
     *
     * @function Highcharts.SVGRenderer#destroy
     *
     * @return {null}
     * Pass through value.
     */
    destroy() {
        const renderer = this, rendererDefs = renderer.defs;
        renderer.box = null;
        renderer.boxWrapper = renderer.boxWrapper.destroy();
        // Call destroy on all gradient elements
        destroyObjectProperties(renderer.gradients || {});
        renderer.gradients = null;
        renderer.defs = rendererDefs.destroy();
        // Remove sub pixel fix handler (#982)
        if (renderer.unSubPixelFix) {
            renderer.unSubPixelFix();
        }
        renderer.alignedObjects.length = 0;
        return null;
    }
    /**
     * Create a wrapper for an SVG element. Serves as a factory for
     * {@link SVGElement}, but this function is itself mostly called from
     * primitive factories like {@link SVGRenderer#path}, {@link
     * SVGRenderer#rect} or {@link SVGRenderer#text}.
     *
     * @function Highcharts.SVGRenderer#createElement
     *
     * @param {string} nodeName
     * The node name, for example `rect`, `g` etc.
     *
     * @return {Highcharts.SVGElement}
     * The generated SVGElement.
     */
    createElement(nodeName) {
        return new this.Element(this, nodeName);
    }
    /**
     * Get converted radial gradient attributes according to the radial
     * reference. Used internally from the {@link SVGElement#colorGradient}
     * function.
     *
     * @internal
     * @function Highcharts.SVGRenderer#getRadialAttr
     */
    getRadialAttr(radialReference, gradAttr) {
        return {
            cx: (radialReference[0] - radialReference[2] / 2) +
                (gradAttr.cx || 0) * radialReference[2],
            cy: (radialReference[1] - radialReference[2] / 2) +
                (gradAttr.cy || 0) * radialReference[2],
            r: (gradAttr.r || 0) * radialReference[2]
        };
    }
    /**
     * Create a drop shadow definition and return its id
     *
     * @internal
     * @function Highcharts.SVGRenderer#shadowDefinition
     * @param {boolean|Highcharts.ShadowOptionsObject} [shadowOptions]
     * The shadow options. If `true`, the default options are applied.
     */
    shadowDefinition(shadowOptions) {
        const id = [
            `highcharts-drop-shadow-${this.chartIndex}`,
            ...Object.keys(shadowOptions)
                .map((key) => `${key}-${shadowOptions[key]}`)
        ].join('-').toLowerCase().replace(/[^a-z\d\-]/g, ''), options = merge({
            color: '#000000',
            offsetX: 0,
            offsetY: 2,
            opacity: 0.05,
            width: 6
        }, shadowOptions);
        if (!this.defs.element.querySelector(`#${id}`)) {
            this.definition({
                tagName: 'filter',
                attributes: {
                    id,
                    filterUnits: options.filterUnits
                },
                children: this.getShadowFilterContent(options)
            });
        }
        return id;
    }
    /**
     * Get shadow filter content.
     * NOTE! Overridden in es5 module for IE11 compatibility.
     *
     * @internal
     * @function Highcharts.SVGRenderer#getShadowFilterContent
     * @param {ShadowOptionsObject} options
     * The shadow options.
     * @return {Array<AST.Node>}
     * The shadow filter content.
     */
    getShadowFilterContent(options) {
        return [{
                tagName: 'feDropShadow',
                attributes: {
                    dx: options.offsetX,
                    dy: options.offsetY,
                    'flood-color': options.color,
                    // Tuned and modified to keep a preserve compatibility
                    // with the old settings
                    'flood-opacity': Math.min(options.opacity * 5, 1),
                    stdDeviation: options.width / 2
                }
            }];
    }
    /**
     * Parse a simple HTML string into SVG tspans. Called internally when text
     * is set on an SVGElement. The function supports a subset of HTML tags, CSS
     * text features like `width`, `text-overflow`, `white-space`, and also
     * attributes like `href` and `style`.
     *
     * @internal
     * @function Highcharts.SVGRenderer#buildText
     * @param {Highcharts.SVGElement} wrapper
     * The parent SVGElement.
     */
    buildText(wrapper) {
        new SVG_TextBuilder(wrapper).buildSVG();
    }
    /**
     * Returns white for dark colors and black for bright colors, based on W3C's
     * definition of [Relative luminance](
     * https://www.w3.org/WAI/GL/wiki/Relative_luminance).
     *
     * @function Highcharts.SVGRenderer#getContrast
     *
     * @param {Highcharts.ColorString} color
     * The color to get the contrast for.
     *
     * @return {Highcharts.ColorString}
     * The contrast color, either `#000000` or `#FFFFFF`.
     */
    getContrast(color) {
        if (color === 'transparent') {
            return '#000000';
        }
        // #6216, #17273
        const rgba256 = Color_Color.parse(color).rgba, 
        // For each rgb channel, compute the luminosity based on all
        // channels. Subtract this from 0.5 and multiply by a huge number,
        // so that all colors with luminosity < 0.5 result in a negative
        // number, and all colors > 0.5 end up very high. This is then
        // clamped into the range 0-1, to result in either black or white.
        // The subtraction of 0.5, multiplication by 9e9, and clamping are
        // workarounds for lack of support for the round() function. As of
        // 2025, it is too fresh in Chrome, and doesn't work in Safari.
        channelFunc = ' clamp(0,calc(9e9*(0.5 - (0.2126*r + 0.7152*g + 0.0722*b))),1)';
        // The color is parsable by the Color class parsers
        if (isNumber(rgba256[0]) || !Color_Color.useColorMix) {
            const rgba = rgba256.map((b8) => {
                const c = b8 / 255;
                return c <= 0.04 ?
                    c / 12.92 :
                    Math.pow((c + 0.055) / 1.055, 2.4);
            }), 
            // Relative luminance
            l = 0.2126 * rgba[0] + 0.7152 * rgba[1] + 0.0722 * rgba[2];
            // Use white or black based on which provides more contrast
            return 1.05 / (l + 0.05) > (l + 0.05) / 0.05 ?
                '#FFFFFF' :
                '#000000';
        }
        // Not parsable, use CSS functions instead
        return 'color(' +
            'from ' + color + ' srgb' +
            channelFunc + channelFunc + channelFunc +
            ')';
    }
    /**
     * Create a button with preset states. Styles for the button can either be
     * set as arguments, or a general theme for all buttons can be set by the
     * `global.buttonTheme` option.
     *
     * @function Highcharts.SVGRenderer#button
     *
     * @param {string} text
     * The text or HTML to draw.
     *
     * @param {number} x
     * The x position of the button's left side.
     *
     * @param {number} y
     * The y position of the button's top side.
     *
     * @param {Highcharts.EventCallbackFunction<Highcharts.SVGElement>} callback
     * The function to execute on button click or touch.
     *
     * @param {Highcharts.SVGAttributes} [theme]
     * SVG attributes for the normal state.
     *
     * @param {Highcharts.SVGAttributes} [hoverState]
     * SVG attributes for the hover state.
     *
     * @param {Highcharts.SVGAttributes} [selectState]
     * SVG attributes for the pressed state.
     *
     * @param {Highcharts.SVGAttributes} [disabledState]
     * SVG attributes for the disabled state.
     *
     * @param {Highcharts.SymbolKeyValue} [shape=rect]
     * The shape type.
     *
     * @param {boolean} [useHTML=false]
     * Whether to use HTML to render the label.
     *
     * @return {Highcharts.SVGElement}
     * The button element.
     */
    button(text, x, y, callback, theme = {}, hoverState, selectState, disabledState, shape, useHTML) {
        const label = this.label(text, x, y, shape, void 0, void 0, useHTML, void 0, 'button'), styledMode = this.styledMode, args = arguments;
        let curState = 0;
        theme = merge(SVGRenderer_defaultOptions.global.buttonTheme, theme);
        // @todo Consider moving this to a lower level, like .attr
        if (styledMode) {
            delete theme.fill;
            delete theme.stroke;
            delete theme['stroke-width'];
        }
        const states = theme.states || {}, normalStyle = theme.style || {};
        delete theme.states;
        delete theme.style;
        // Presentational
        const stateAttribs = [
            HTML_AST.filterUserAttributes(theme)
        ], 
        // The string type is a mistake, it is just for compliance with
        // SVGAttribute and is not used in button theme.
        stateStyles = [normalStyle];
        if (!styledMode) {
            ['hover', 'select', 'disabled'].forEach((stateName, i) => {
                stateAttribs.push(merge(stateAttribs[0], HTML_AST.filterUserAttributes(args[i + 5] || states[stateName] || {})));
                stateStyles.push(stateAttribs[i + 1].style);
                delete stateAttribs[i + 1].style;
            });
        }
        // Add the events. IE9 and IE10 need mouseover and mouseout to function
        // (#667).
        addEvent(label.element, isMS ? 'mouseover' : 'mouseenter', function () {
            if (curState !== 3) {
                label.setState(1);
            }
        });
        addEvent(label.element, isMS ? 'mouseout' : 'mouseleave', function () {
            if (curState !== 3) {
                label.setState(curState);
            }
        });
        label.setState = (state = 0) => {
            // Hover state is temporary, don't record it
            if (state !== 1) {
                label.state = curState = state;
            }
            // Update visuals
            label
                .removeClass(/highcharts-button-(normal|hover|pressed|disabled)/)
                .addClass('highcharts-button-' +
                ['normal', 'hover', 'pressed', 'disabled'][state]);
            if (!styledMode) {
                label.attr(stateAttribs[state]);
                const css = stateStyles[state];
                if (isObject(css)) {
                    label.css(css);
                }
            }
        };
        label.attr(stateAttribs[0]);
        // Presentational attributes
        if (!styledMode) {
            label.css(extend({ cursor: 'default' }, normalStyle));
            // HTML labels don't need to handle pointer events because click and
            // mouseenter/mouseleave is bound to the underlying <g> element.
            // Should this be reconsidered, we need more complex logic to share
            // events between the <g> and its <div> counterpart, and avoid
            // triggering mouseenter/mouseleave when hovering from one to the
            // other (#17440).
            if (useHTML) {
                label.text.css({ pointerEvents: 'none' });
            }
        }
        return label
            .on('touchstart', (e) => e.stopPropagation())
            .on('click', function (e) {
            if (curState !== 3) {
                callback?.call(label, e);
            }
        });
    }
    /**
     * Make a straight line crisper by not spilling out to neighbor pixels.
     *
     * @function Highcharts.SVGRenderer#crispLine
     *
     * @param {Highcharts.SVGPathArray} points
     *        The original points on the format `[['M', 0, 0], ['L', 100, 0]]`.
     *
     * @param {number} width
     *        The width of the line.
     *
     * @return {Highcharts.SVGPathArray}
     *         The original points array, but modified to render crisply.
     */
    crispLine(points, width) {
        const [start, end] = points;
        // Normalize to a crisp line
        if (defined(start[1]) && start[1] === end[1]) {
            start[1] = end[1] = crisp(start[1], width);
        }
        if (defined(start[2]) && start[2] === end[2]) {
            start[2] = end[2] = crisp(start[2], width);
        }
        return points;
    }
    /**
     * Draw a path, wraps the SVG `path` element.
     *
     * @sample highcharts/members/renderer-path-on-chart/
     *         Draw a path in a chart
     * @sample highcharts/members/renderer-path/
     *         Draw a path independent from a chart
     *
     * @example
     * let path = renderer.path(['M', 10, 10, 'L', 30, 30, 'z'])
     *     .attr({ stroke: '#ff00ff' })
     *     .add();
     *
     * @function Highcharts.SVGRenderer#path
     *
     * @param {Highcharts.SVGPathArray} [path]
     * An SVG path definition in array form.
     *
     * @return {Highcharts.SVGElement}
     * The generated wrapper element.
     *
     */ /**
    * Draw a path, wraps the SVG `path` element.
    *
    * @function Highcharts.SVGRenderer#path
    *
    * @param {Highcharts.SVGAttributes|Highcharts.SVGPathArray} [path]
    * The initial attributes.
    *
    * @return {Highcharts.SVGElement}
    * The generated wrapper element.
    */
    path(path) {
        const attribs = (this.styledMode ? {} : {
            fill: 'none'
        });
        if (isArray(path)) {
            attribs.d = path;
        }
        else if (isObject(path)) { // Attributes
            extend(attribs, path);
        }
        return this.createElement('path').attr(attribs);
    }
    /* eslint-disable jsdoc/check-param-names */
    /**
     * Draw a circle, wraps the SVG `circle` element.
     *
     * @sample highcharts/members/renderer-circle/
     *         Drawing a circle
     *
     * @function Highcharts.SVGRenderer#circle
     *
     * @param {number} [x]
     * The center x position.
     *
     * @param {number} [y]
     * The center y position.
     *
     * @param {number} [r]
     * The radius.
     *
     * @return {Highcharts.SVGElement}
     * The generated wrapper element.
     */ /**
    * Draw a circle, wraps the SVG `circle` element.
    *
    * @function Highcharts.SVGRenderer#circle
    *
    * @param {Highcharts.SVGAttributes} [attribs]
    * The initial attributes.
    *
    * @return {Highcharts.SVGElement}
    * The generated wrapper element.
    */
    circle(x, y, r) {
        const attribs = (isObject(x) ?
            x :
            typeof x === 'undefined' ? {} : { x: x, y: y, r: r }), wrapper = this.createElement('circle');
        // Setting x or y translates to cx and cy
        wrapper.xSetter = wrapper.ySetter = function (value, key, element) {
            element.setAttribute('c' + key, value);
        };
        return wrapper.attr(attribs);
    }
    /* eslint-disable jsdoc/check-param-names */
    /**
     * Draw and return an arc.
     *
     * @sample highcharts/members/renderer-arc/
     *         Drawing an arc
     *
     * @function Highcharts.SVGRenderer#arc
     *
     * @param {number} [x=0]
     * Center X position.
     *
     * @param {number} [y=0]
     * Center Y position.
     *
     * @param {number} [r=0]
     * The outer radius' of the arc.
     *
     * @param {number} [innerR=0]
     * Inner radius like used in donut charts.
     *
     * @param {number} [start=0]
     * The starting angle of the arc in radians, where 0 is to the right and
     * `-Math.PI/2` is up.
     *
     * @param {number} [end=0]
     * The ending angle of the arc in radians, where 0 is to the right and
     * `-Math.PI/2` is up.
     *
     * @return {Highcharts.SVGElement}
     * The generated wrapper element.
     */ /**
    * Draw and return an arc. Overloaded function that takes arguments object.
    *
    * @function Highcharts.SVGRenderer#arc
    *
    * @param {Highcharts.SVGAttributes} attribs
    * Initial SVG attributes.
    *
    * @return {Highcharts.SVGElement}
    * The generated wrapper element.
    */
    arc(x, y, r, innerR, start, end) {
        let options;
        if (isObject(x)) {
            options = x;
            y = options.y;
            r = options.r;
            innerR = options.innerR;
            start = options.start;
            end = options.end;
            x = options.x;
        }
        else {
            options = { innerR, start, end };
        }
        // Arcs are defined as symbols for the ability to set
        // attributes in attr and animate
        const arc = this.symbol('arc', x, y, r, r, options);
        arc.r = r; // #959
        return arc;
    }
    /* eslint-enable jsdoc/check-param-names */
    /* eslint-disable jsdoc/check-param-names */
    /**
     * Draw and return a rectangle.
     *
     * @function Highcharts.SVGRenderer#rect
     *
     * @param {number} [x]
     * Left position.
     *
     * @param {number} [y]
     * Top position.
     *
     * @param {number} [width]
     * Width of the rectangle.
     *
     * @param {number} [height]
     * Height of the rectangle.
     *
     * @param {number} [r]
     * Border corner radius.
     *
     * @param {number} [strokeWidth]
     * A stroke width can be supplied to allow crisp drawing.
     *
     * @return {Highcharts.SVGElement}
     * The generated wrapper element.
     */ /**
    * Draw and return a rectangle.
    *
    * @sample highcharts/members/renderer-rect-on-chart/
    *         Draw a rectangle in a chart
    * @sample highcharts/members/renderer-rect/
    *         Draw a rectangle independent from a chart
    *
    * @function Highcharts.SVGRenderer#rect
    *
    * @param {Highcharts.SVGAttributes} [attributes]
    * General SVG attributes for the rectangle.
    *
    * @return {Highcharts.SVGElement}
    * The generated wrapper element.
    */
    rect(x, y, width, height, r, strokeWidth) {
        const attribs = (isObject(x) ?
            x :
            typeof x === 'undefined' ?
                {} :
                {
                    x,
                    y,
                    r,
                    width: Math.max(width || 0, 0),
                    height: Math.max(height || 0, 0)
                }), wrapper = this.createElement('rect');
        if (!this.styledMode) {
            if (typeof strokeWidth !== 'undefined') {
                attribs['stroke-width'] = strokeWidth;
                extend(attribs, wrapper.crisp(attribs));
            }
            attribs.fill = 'none';
        }
        wrapper.rSetter = function (value, _key, element) {
            wrapper.r = value;
            attr(element, {
                rx: value,
                ry: value
            });
        };
        wrapper.rGetter = function () {
            return wrapper.r || 0;
        };
        return wrapper.attr(attribs);
    }
    /* eslint-enable jsdoc/check-param-names */
    /**
     * Draw and return a rectangle with advanced corner rounding options.
     *
     * @function Highcharts.SVGRenderer#roundedRect
     *
     * @param {Highcharts.SVGAttributes} attribs
     * Attributes.
     *
     * @return {Highcharts.SVGElement}
     * The generated wrapper element.
     */
    roundedRect(attribs) {
        return this.symbol('roundedRect').attr(attribs);
    }
    /**
     * Resize the {@link SVGRenderer#box} and re-align all aligned child
     * elements.
     *
     * @sample highcharts/members/renderer-g/
     *         Show and hide grouped objects
     *
     * @function Highcharts.SVGRenderer#setSize
     *
     * @param {number} width
     * The new pixel width.
     *
     * @param {number} height
     * The new pixel height.
     *
     * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animate=true]
     * Whether and how to animate.
     */
    setSize(width, height, animate) {
        const renderer = this;
        renderer.width = width;
        renderer.height = height;
        renderer.boxWrapper.animate({
            width: width,
            height: height
        }, {
            step: function () {
                this.attr({
                    viewBox: '0 0 ' + this.attr('width') + ' ' +
                        this.attr('height')
                });
            },
            duration: pick(animate, true) ? void 0 : 0
        });
        renderer.alignElements();
    }
    /**
     * Create and return an svg group element. Child
     * {@link Highcharts.SVGElement} objects are added to the group by using the
     * group as the first parameter in {@link Highcharts.SVGElement#add|add()}.
     *
     * @function Highcharts.SVGRenderer#g
     *
     * @param {string} [name]
     * The group will be given a class name of `highcharts-{name}`. This can be
     * used for styling and scripting.
     *
     * @return {Highcharts.SVGElement}
     * The generated wrapper element.
     */
    g(name) {
        const elem = this.createElement('g');
        return name ?
            elem.attr({ 'class': 'highcharts-' + name }) :
            elem;
    }
    /**
     * Display an image.
     *
     * @sample highcharts/members/renderer-image-on-chart/
     *         Add an image in a chart
     * @sample highcharts/members/renderer-image/
     *         Add an image independent of a chart
     *
     * @function Highcharts.SVGRenderer#image
     *
     * @param {string} href
     * The image source.
     *
     * @param {number} [x]
     * The X position.
     *
     * @param {number} [y]
     * The Y position.
     *
     * @param {number} [width]
     * The image width. If omitted, it defaults to the image file width.
     *
     * @param {number} [height]
     * The image height. If omitted it defaults to the image file height.
     *
     * @param {Function} [onload]
     * Event handler for image load.
     *
     * @return {Highcharts.SVGElement}
     * The generated wrapper element.
     */
    image(href, x, y, width, height, onload) {
        const attribs = { preserveAspectRatio: 'none' };
        // Optional properties (#11756)
        if (isNumber(x)) {
            attribs.x = x;
        }
        if (isNumber(y)) {
            attribs.y = y;
        }
        if (isNumber(width)) {
            attribs.width = width;
        }
        if (isNumber(height)) {
            attribs.height = height;
        }
        const elemWrapper = this.createElement('image').attr(attribs), onDummyLoad = function (e) {
            elemWrapper.attr({ href });
            onload.call(elemWrapper, e);
        };
        // Add load event if supplied
        if (onload) {
            // We have to use a dummy HTML image since IE support for SVG image
            // load events is very buggy. First set a transparent src, wait for
            // dummy to load, and then add the real src to the SVG image.
            elemWrapper.attr({
                /* eslint-disable-next-line max-len */
                href: 'data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw=='
            });
            const dummy = new SVGRenderer_win.Image();
            addEvent(dummy, 'load', onDummyLoad);
            dummy.src = href;
            if (dummy.complete) {
                onDummyLoad({});
            }
        }
        else {
            elemWrapper.attr({ href });
        }
        return elemWrapper;
    }
    /**
     * Draw a symbol out of pre-defined shape paths from
     * {@link SVGRenderer#symbols}.
     * It is used in Highcharts for point makers, which cake a `symbol` option,
     * and label and button backgrounds like in the tooltip and stock flags.
     *
     * @function Highcharts.SVGRenderer#symbol
     *
     * @param {string} symbol
     * The symbol name.
     *
     * @param {number} [x]
     * The X coordinate for the top left position.
     *
     * @param {number} [y]
     * The Y coordinate for the top left position.
     *
     * @param {number} [width]
     * The pixel width.
     *
     * @param {number} [height]
     * The pixel height.
     *
     * @param {Highcharts.SymbolOptionsObject} [options]
     * Additional options, depending on the actual symbol drawn.
     *
     * @return {Highcharts.SVGElement}
     * SVG symbol.
     */
    symbol(symbol, x, y, width, height, options) {
        const ren = this, imageRegex = /^url\((.*?)\)$/, isImage = imageRegex.test(symbol), sym = (!isImage && (this.symbols[symbol] ? symbol : 'circle')), 
        // Get the symbol definition function
        symbolFn = (sym && this.symbols[sym]);
        let obj, path, imageSrc, centerImage;
        if (symbolFn) {
            // Check if there's a path defined for this symbol
            if (typeof x === 'number') {
                path = symbolFn.call(this.symbols, x || 0, y || 0, width || 0, height || 0, options);
            }
            obj = this.path(path);
            if (!ren.styledMode) {
                obj.attr('fill', 'none');
            }
            // Expand properties for use in animate and attr
            extend(obj, {
                symbolName: (sym || void 0),
                x: x,
                y: y,
                width: width,
                height: height
            });
            if (options) {
                extend(obj, options);
            }
            // Image symbols
        }
        else if (isImage) {
            imageSrc = symbol.match(imageRegex)[1];
            // Create the image synchronously, add attribs async
            const img = obj = this.image(imageSrc);
            // The image width is not always the same as the symbol width. The
            // image may be centered within the symbol, as is the case when
            // image shapes are used as label backgrounds, for example in flags.
            img.imgwidth = pick(options?.width, symbolSizes[imageSrc]?.width);
            img.imgheight = pick(options?.height, symbolSizes[imageSrc]?.height);
            /**
             * Set the size and position
             */
            centerImage = (obj) => obj.attr({
                width: obj.width,
                height: obj.height
            });
            /**
             * Width and height setters that take both the image's physical size
             * and the label size into consideration, and translates the image
             * to center within the label.
             */
            ['width', 'height'].forEach((key) => {
                img[`${key}Setter`] = function (value, key) {
                    this[key] = value;
                    const { alignByTranslate, element, width, height, imgwidth, imgheight } = this, imgSize = key === 'width' ? imgwidth : imgheight;
                    let scale = 1;
                    // Scale and center the image within its container. The name
                    // `backgroundSize` is taken from the CSS spec, but the
                    // value `within` is made up. Other possible values in the
                    // spec, `cover` and `contain`, can be implemented if
                    // needed.
                    if (options &&
                        options.backgroundSize === 'within' &&
                        width &&
                        height &&
                        imgwidth &&
                        imgheight) {
                        scale = Math.min(width / imgwidth, height / imgheight);
                        // Update both width and height to keep the ratio
                        // correct (#17315)
                        attr(element, {
                            width: Math.round(imgwidth * scale),
                            height: Math.round(imgheight * scale)
                        });
                    }
                    else if (element && imgSize) {
                        element.setAttribute(key, imgSize);
                    }
                    if (!alignByTranslate && imgwidth && imgheight) {
                        this.translate(((width || 0) - (imgwidth * scale)) / 2, ((height || 0) - (imgheight * scale)) / 2);
                    }
                };
            });
            if (defined(x)) {
                img.attr({
                    x: x,
                    y: y
                });
            }
            img.isImg = true;
            if (defined(img.imgwidth) && defined(img.imgheight)) {
                centerImage(img);
            }
            else {
                // Initialize image to be 0 size so export will still function
                // if there's no cached sizes.
                img.attr({ width: 0, height: 0 });
                // Create a dummy JavaScript image to get the width and height.
                createElement('img', {
                    onload: function () {
                        const chart = SVGRenderer_charts[ren.chartIndex];
                        // Special case for SVGs on IE11, the width is not
                        // accessible until the image is part of the DOM
                        // (#2854).
                        if (this.width === 0) {
                            css(this, {
                                position: 'absolute',
                                top: '-999em'
                            });
                            SVGRenderer_doc.body.appendChild(this);
                        }
                        // Center the image
                        symbolSizes[imageSrc] = {
                            width: this.width,
                            height: this.height
                        };
                        img.imgwidth = this.width;
                        img.imgheight = this.height;
                        if (img.element) {
                            centerImage(img);
                        }
                        // Clean up after #2854 workaround.
                        if (this.parentNode) {
                            this.parentNode.removeChild(this);
                        }
                        // Fire the load event when all external images are
                        // loaded
                        ren.asyncCounter--;
                        if (!ren.asyncCounter && chart && !chart.hasLoaded) {
                            chart.onload();
                        }
                    },
                    src: imageSrc
                });
                this.asyncCounter++;
            }
        }
        return obj;
    }
    /**
     * Define a clipping rectangle. The clipping rectangle is later applied
     * to {@link SVGElement} objects through the {@link SVGElement#clip}
     * function.
     *
     * This function is deprecated as of v11.2. Instead, use a regular shape
     * (`rect`, `path` etc), and the `SVGElement.clipTo` function.
     *
     * @example
     * let circle = renderer.circle(100, 100, 100)
     *     .attr({ fill: 'red' })
     *     .add();
     * let clipRect = renderer.clipRect(100, 100, 100, 100);
     *
     * // Leave only the lower right quarter visible
     * circle.clip(clipRect);
     *
     * @deprecated 11.2.0
     *
     * @function Highcharts.SVGRenderer#clipRect
     *
     * @param {number} [x]
     *
     * @param {number} [y]
     *
     * @param {number} [width]
     *
     * @param {number} [height]
     *
     * @return {Highcharts.ClipRectElement}
     * A clipping rectangle.
     */
    clipRect(x, y, width, height) {
        return this.rect(x, y, width, height, 0);
    }
    /**
     * Draw text. The text can contain a subset of HTML, like spans and anchors
     * and some basic text styling of these. For more advanced features like
     * border and background, use {@link Highcharts.SVGRenderer#label} instead.
     * To update the text after render, run `text.attr({ text: 'New text' })`.
     *
     * @sample highcharts/members/renderer-text-on-chart/
     *         Annotate the chart freely
     * @sample highcharts/members/renderer-on-chart/
     *         Annotate with a border and in response to the data
     * @sample highcharts/members/renderer-text/
     *         Formatted text
     *
     * @function Highcharts.SVGRenderer#text
     *
     * @param {string} [str]
     * The text of (subset) HTML to draw.
     *
     * @param {number} [x]
     * The x position of the text's lower left corner.
     *
     * @param {number} [y]
     * The y position of the text's lower left corner.
     *
     * @param {boolean} [useHTML=false]
     * Use HTML to render the text.
     *
     * @return {Highcharts.SVGElement}
     * The text object.
     */
    text(str, x, y, useHTML) {
        const renderer = this, attribs = {};
        if (useHTML && (renderer.allowHTML || !renderer.forExport)) {
            return renderer.html(str, x, y);
        }
        attribs.x = Math.round(x || 0); // X always needed for line-wrap logic
        if (y) {
            attribs.y = Math.round(y);
        }
        if (defined(str)) {
            attribs.text = str;
        }
        const wrapper = renderer.createElement('text').attr(attribs);
        if (!useHTML || (renderer.forExport && !renderer.allowHTML)) {
            wrapper.xSetter = function (value, key, element) {
                const tspans = element.getElementsByTagName('tspan'), parentVal = element.getAttribute(key);
                for (let i = 0, tspan; i < tspans.length; i++) {
                    tspan = tspans[i];
                    // If the x values are equal, the tspan represents a line
                    // break
                    if (tspan.getAttribute(key) === parentVal) {
                        tspan.setAttribute(key, value);
                    }
                }
                element.setAttribute(key, value);
            };
        }
        return wrapper;
    }
    /**
     * Utility to return the baseline offset and total line height from the font
     * size.
     *
     * @function Highcharts.SVGRenderer#fontMetrics
     *
     * @param {Highcharts.SVGElement|Highcharts.SVGDOMElement|number} ref
     * The element to inspect for a current font size. If a number is given,
     * it's used as a fall back for direct font size in pixels.
     *
     * @return {Highcharts.FontMetricsObject}
     * The font metrics.
     */
    fontMetrics(ref) {
        const f = isNumber(ref) ? ref : pInt((SVG_SVGElement.prototype.getStyle.call(ref, 'font-size') || 0));
        // Empirical values found by comparing font size and bounding box
        // height. Applies to the default font family.
        // https://jsfiddle.net/highcharts/7xvn7/
        const h = f < 24 ? f + 3 : Math.round(f * 1.2), b = Math.round(h * 0.8);
        return {
            // Line height
            h,
            // Baseline
            b,
            // Font size
            f
        };
    }
    /**
     * Correct X and Y positioning of a label for rotation (#1764).
     *
     * @internal
     * @function Highcharts.SVGRenderer#rotCorr
     */
    rotCorr(baseline, rotation, alterY) {
        let y = baseline;
        if (rotation && alterY) {
            y = Math.max(y * Math.cos(rotation * SVGRenderer_deg2rad), 4);
        }
        return {
            x: (-baseline / 3) * Math.sin(rotation * SVGRenderer_deg2rad),
            y: y
        };
    }
    /**
     * Compatibility function to convert the legacy one-dimensional path array
     * into an array of segments.
     *
     * It is used in maps to parse the `path` option, and in SVGRenderer.dSetter
     * to support legacy paths from demos.
     *
     * @internal
     * @function Highcharts.SVGRenderer#pathToSegments
     */
    pathToSegments(path) {
        const ret = [];
        const segment = [];
        const commandLength = {
            A: 8,
            C: 7,
            H: 2,
            L: 3,
            M: 3,
            Q: 5,
            S: 5,
            T: 3,
            V: 2
        };
        // Short, non-typesafe parsing of the one-dimensional array. It splits
        // the path on any string. This is not type checked against the tuple
        // types, but is shorter, and doesn't require specific checks for any
        // command type in SVG.
        for (let i = 0; i < path.length; i++) {
            // Command skipped, repeat previous or insert L/l for M/m
            if (isString(segment[0]) &&
                isNumber(path[i]) &&
                segment.length === commandLength[(segment[0].toUpperCase())]) {
                path.splice(i, 0, segment[0].replace('M', 'L').replace('m', 'l'));
            }
            // Split on string
            if (typeof path[i] === 'string') {
                if (segment.length) {
                    ret.push(segment.slice(0));
                }
                segment.length = 0;
            }
            segment.push(path[i]);
        }
        ret.push(segment.slice(0));
        return ret;
        /*
        // Fully type-safe version where each tuple type is checked. The
        // downside is filesize and a lack of flexibility for unsupported
        // commands
        const ret: SVGPath = [],
            commands = {
                A: 7,
                C: 6,
                H: 1,
                L: 2,
                M: 2,
                Q: 4,
                S: 4,
                T: 2,
                V: 1,
                Z: 0
            };

        let i = 0,
            lastI = 0,
            lastCommand;

        while (i < path.length) {
            const item = path[i];

            let command;

            if (typeof item === 'string') {
                command = item;
                i += 1;
            } else {
                command = lastCommand || 'M';
            }

            // Upper case
            const commandUC = command.toUpperCase();

            if (commandUC in commands) {

                // No numeric parameters
                if (command === 'Z' || command === 'z') {
                    ret.push([command]);

                // One numeric parameter
                } else {
                    const val0 = path[i];
                    if (typeof val0 === 'number') {

                        // Horizontal line to
                        if (command === 'H' || command === 'h') {
                            ret.push([command, val0]);
                            i += 1;

                        // Vertical line to
                        } else if (command === 'V' || command === 'v') {
                            ret.push([command, val0]);
                            i += 1;

                        // Two numeric parameters
                        } else {
                            const val1 = path[i + 1];
                            if (typeof val1 === 'number') {
                                // lineTo
                                if (command === 'L' || command === 'l') {
                                    ret.push([command, val0, val1]);
                                    i += 2;

                                // moveTo
                                } else if (command === 'M' || command === 'm') {
                                    ret.push([command, val0, val1]);
                                    i += 2;

                                // Smooth quadratic bezier
                                } else if (command === 'T' || command === 't') {
                                    ret.push([command, val0, val1]);
                                    i += 2;

                                // Four numeric parameters
                                } else {
                                    const val2 = path[i + 2],
                                        val3 = path[i + 3];
                                    if (
                                        typeof val2 === 'number' &&
                                        typeof val3 === 'number'
                                    ) {
                                        // Quadratic bezier to
                                        if (
                                            command === 'Q' ||
                                            command === 'q'
                                        ) {
                                            ret.push([
                                                command,
                                                val0,
                                                val1,
                                                val2,
                                                val3
                                            ]);
                                            i += 4;

                                        // Smooth cubic bezier to
                                        } else if (
                                            command === 'S' ||
                                            command === 's'
                                        ) {
                                            ret.push([
                                                command,
                                                val0,
                                                val1,
                                                val2,
                                                val3
                                            ]);
                                            i += 4;

                                        // Six numeric parameters
                                        } else {
                                            const val4 = path[i + 4],
                                                val5 = path[i + 5];

                                            if (
                                                typeof val4 === 'number' &&
                                                typeof val5 === 'number'
                                            ) {
                                                // Curve to
                                                if (
                                                    command === 'C' ||
                                                    command === 'c'
                                                ) {
                                                    ret.push([
                                                        command,
                                                        val0,
                                                        val1,
                                                        val2,
                                                        val3,
                                                        val4,
                                                        val5
                                                    ]);
                                                    i += 6;

                                                // Seven numeric parameters
                                                } else {
                                                    const val6 = path[i + 6];

                                                    // Arc to
                                                    if (
                                                        typeof val6 ===
                                                        'number' &&
                                                        (
                                                            command === 'A' ||
                                                            command === 'a'
                                                        )
                                                    ) {
                                                        ret.push([
                                                            command,
                                                            val0,
                                                            val1,
                                                            val2,
                                                            val3,
                                                            val4,
                                                            val5,
                                                            val6
                                                        ]);
                                                        i += 7;

                                                    }

                                                }
                                            }
                                        }
                                    }
                                }
                            }

                        }
                    }
                }
            }

            // An unmarked command following a moveTo is a lineTo
            lastCommand = command === 'M' ? 'L' : command;

            if (i === lastI) {
                break;
            }
            lastI = i;
        }
        return ret;
        */
    }
    /**
     * Draw a label, which is an extended text element with support for border
     * and background. Highcharts creates a `g` element with a text and a `path`
     * or `rect` inside, to make it behave somewhat like a HTML div. Border and
     * background are set through `stroke`, `stroke-width` and `fill` attributes
     * using the {@link Highcharts.SVGElement#attr|attr} method. To update the
     * text after render, run `label.attr({ text: 'New text' })`.
     *
     * @sample highcharts/members/renderer-label-on-chart/
     *         A label on the chart
     *
     * @function Highcharts.SVGRenderer#label
     *
     * @param {string} str
     * The initial text string or (subset) HTML to render.
     *
     * @param {number} x
     * The x position of the label's left side.
     *
     * @param {number} [y]
     * The y position of the label's top side or baseline, depending on the
     * `baseline` parameter.
     *
     * @param {string} [shape='rect']
     * The shape of the label's border/background, if any. Defaults to `rect`.
     * Other possible values are `callout` or other shapes defined in
     * {@link Highcharts.SVGRenderer#symbols}.
     *
     * @param {number} [anchorX]
     * In case the `shape` has a pointer, like a flag, this is the coordinates
     * it should be pinned to.
     *
     * @param {number} [anchorY]
     * In case the `shape` has a pointer, like a flag, this is the coordinates
     * it should be pinned to.
     *
     * @param {boolean} [useHTML=false]
     * Whether to use HTML to render the label.
     *
     * @param {boolean} [baseline=false]
     * Whether to position the label relative to the text baseline, like
     * {@link Highcharts.SVGRenderer#text|renderer.text}, or to the upper border
     * of the rectangle.
     *
     * @param {string} [className]
     * Class name for the group.
     *
     * @return {Highcharts.SVGElement}
     * The generated label.
     */
    label(str, x, y, shape, anchorX, anchorY, useHTML, baseline, className) {
        return new SVG_SVGLabel(this, str, x, y, shape, anchorX, anchorY, useHTML, baseline, className);
    }
    /**
     * Re-align all aligned elements.
     *
     * @internal
     * @function Highcharts.SVGRenderer#alignElements
     */
    alignElements() {
        this.alignedObjects.forEach((el) => el.align());
    }
}
extend(SVGRenderer.prototype, {
    /**
     * A pointer to the renderer's associated Element class.
     *
     * @name Highcharts.SVGRenderer#Element
     * @type {Highcharts.SVGElement}
     */
    Element: SVG_SVGElement,
    SVG_NS: SVGRenderer_SVG_NS,
    /**
     * A collection of characters mapped to HTML entities. When `useHTML` on an
     * element is true, these entities will be rendered correctly by HTML. In
     * the SVG pseudo-HTML, they need to be unescaped back to simple characters,
     * so for example `&lt;` will render as `<`.
     *
     * @example
     * // Add support for unescaping quotes
     * Highcharts.SVGRenderer.prototype.escapes['"'] = '&quot;';
     *
     * @name Highcharts.SVGRenderer#escapes
     * @type {Highcharts.Dictionary<string>}
     */
    escapes: {
        '&': '&amp;',
        '<': '&lt;',
        '>': '&gt;',
        "'": '&#39;', // eslint-disable-line quotes
        '"': '&quot;'
    },
    /**
     * An extendable collection of functions for defining symbol paths. Each
     * symbol function takes five parameters: `x`, `y`, `width`, `height` and
     * `options`, and returns an `SVGPath` array.
     *
     * @sample highcharts/members/renderer-symbols
     *         Renderer symbols overview
     * @sample highcharts/plotoptions/series-marker-symbol
     *         Custom marker symbol
     *
     * @name Highcharts.SVGRenderer#symbols
     * @type {Highcharts.SymbolDictionary}
     */
    symbols: SVG_Symbols,
    /**
     * Dummy function for plugins, called every time the renderer is updated.
     * Prior to Highcharts 5, this was used for the canvg renderer.
     *
     * @deprecated 5.0.0
     * @function Highcharts.SVGRenderer#draw
     */
    draw: noop
});
/* *
 *
 *  Compatibility
 *
 * */
Core_Globals.Renderer = SVGRenderer;
/* *
 *
 *  Export Default
 *
 * */
/* harmony default export */ const SVG_SVGRenderer = (SVGRenderer);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * A clipping rectangle that can be applied to one or more {@link SVGElement}
 * instances. It is instantiated with the {@link SVGRenderer#clipRect} function
 * and applied with the {@link SVGElement#clip} function.
 *
 * @example
 * let circle = renderer.circle(100, 100, 100)
 *     .attr({ fill: 'red' })
 *     .add();
 * let clipRect = renderer.clipRect(100, 100, 100, 100);
 *
 * // Leave only the lower right quarter visible
 * circle.clip(clipRect);
 *
 * @typedef {Highcharts.SVGElement} Highcharts.ClipRectElement
 */
/**
 * The font metrics.
 *
 * @interface Highcharts.FontMetricsObject
 */ /**
* The baseline relative to the top of the box.
*
* @name Highcharts.FontMetricsObject#b
* @type {number}
*/ /**
* The font size.
*
* @name Highcharts.FontMetricsObject#f
* @type {number}
*/ /**
* The line height.
*
* @name Highcharts.FontMetricsObject#h
* @type {number}
*/
/**
 * An object containing `x` and `y` properties for the position of an element.
 *
 * @interface Highcharts.PositionObject
 */ /**
* X position of the element.
* @name Highcharts.PositionObject#x
* @type {number}
*/ /**
* Y position of the element.
* @name Highcharts.PositionObject#y
* @type {number}
*/
/**
 * A rectangle.
 *
 * @interface Highcharts.RectangleObject
 */ /**
* Height of the rectangle.
* @name Highcharts.RectangleObject#height
* @type {number}
*/ /**
* Width of the rectangle.
* @name Highcharts.RectangleObject#width
* @type {number}
*/ /**
* Horizontal position of the rectangle.
* @name Highcharts.RectangleObject#x
* @type {number}
*/ /**
* Vertical position of the rectangle.
* @name Highcharts.RectangleObject#y
* @type {number}
*/
/**
 * The shadow options.
 *
 * @interface Highcharts.ShadowOptionsObject
 */ /**
* The shadow color.
* @name    Highcharts.ShadowOptionsObject#color
* @type    {Highcharts.ColorString|undefined}
* @default var(--highcharts-neutral-color-100)
*/ /**
* The horizontal offset from the element.
*
* @name    Highcharts.ShadowOptionsObject#offsetX
* @type    {number|undefined}
* @default 1
*/ /**
* The vertical offset from the element.
* @name    Highcharts.ShadowOptionsObject#offsetY
* @type    {number|undefined}
* @default 1
*/ /**
* The shadow opacity.
*
* @name    Highcharts.ShadowOptionsObject#opacity
* @type    {number|undefined}
* @default 0.15
*/ /**
* The shadow width or distance from the element.
* @name    Highcharts.ShadowOptionsObject#width
* @type    {number|undefined}
* @default 3
*/
/**
 * @interface Highcharts.SizeObject
 */ /**
* @name Highcharts.SizeObject#height
* @type {number}
*/ /**
* @name Highcharts.SizeObject#width
* @type {number}
*/
/**
 * Array of path commands, that will go into the `d` attribute of an SVG
 * element.
 *
 * @typedef {Array<(Array<Highcharts.SVGPathCommand>|Array<Highcharts.SVGPathCommand,number>|Array<Highcharts.SVGPathCommand,number,number>|Array<Highcharts.SVGPathCommand,number,number,number,number>|Array<Highcharts.SVGPathCommand,number,number,number,number,number,number>|Array<Highcharts.SVGPathCommand,number,number,number,number,number,number,number>)>} Highcharts.SVGPathArray
 */
/**
 * Possible path commands in an SVG path array. Valid values are `A`, `C`, `H`,
 * `L`, `M`, `Q`, `S`, `T`, `V`, `Z`.
 *
 * @typedef {string} Highcharts.SVGPathCommand
 * @validvalue ["a","c","h","l","m","q","s","t","v","z","A","C","H","L","M","Q","S","T","V","Z"]
 */
/**
 * An extendable collection of functions for defining symbol paths. Symbols are
 * used internally for point markers, button and label borders and backgrounds,
 * or custom shapes. Extendable by adding to {@link SVGRenderer#symbols}.
 *
 * @interface Highcharts.SymbolDictionary
 */ /**
* @name Highcharts.SymbolDictionary#[key:string]
* @type {Function|undefined}
*/ /**
* @name Highcharts.SymbolDictionary#arc
* @type {Function|undefined}
*/ /**
* @name Highcharts.SymbolDictionary#callout
* @type {Function|undefined}
*/ /**
* @name Highcharts.SymbolDictionary#circle
* @type {Function|undefined}
*/ /**
* @name Highcharts.SymbolDictionary#diamond
* @type {Function|undefined}
*/ /**
* @name Highcharts.SymbolDictionary#square
* @type {Function|undefined}
*/ /**
* @name Highcharts.SymbolDictionary#triangle
* @type {Function|undefined}
*/
/**
 * Can be one of `arc`, `callout`, `circle`, `diamond`, `square`, `triangle`,
 * and `triangle-down`. Symbols are used internally for point markers, button
 * and label borders and backgrounds, or custom shapes. Extendable by adding to
 * {@link SVGRenderer#symbols}.
 *
 * @typedef {"arc"|"callout"|"circle"|"diamond"|"square"|"triangle"|"triangle-down"} Highcharts.SymbolKeyValue
 */
/**
 * Additional options, depending on the actual symbol drawn.
 *
 * @interface Highcharts.SymbolOptionsObject
 */ /**
* The anchor X position for the `callout` symbol. This is where the chevron
* points to.
*
* @name Highcharts.SymbolOptionsObject#anchorX
* @type {number|undefined}
*/ /**
* The anchor Y position for the `callout` symbol. This is where the chevron
* points to.
*
* @name Highcharts.SymbolOptionsObject#anchorY
* @type {number|undefined}
*/ /**
* The end angle of an `arc` symbol.
*
* @name Highcharts.SymbolOptionsObject#end
* @type {number|undefined}
*/ /**
* Whether to draw `arc` symbol open or closed.
*
* @name Highcharts.SymbolOptionsObject#open
* @type {boolean|undefined}
*/ /**
* The radius of an `arc` symbol, or the border radius for the `callout` symbol.
*
* @name Highcharts.SymbolOptionsObject#r
* @type {number|undefined}
*/ /**
* The start angle of an `arc` symbol.
*
* @name Highcharts.SymbolOptionsObject#start
* @type {number|undefined}
*/
(''); // Keeps doclets above in transpiled file

;// ./code/es-modules/Core/Renderer/HTML/HTMLElement.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */



const { composed, isFirefox: HTMLElement_isFirefox } = Core_Globals;


/* *
 *
 *  Class
 *
 * */
class HTMLElement extends SVG_SVGElement {
    /**
     * Compose
     * @internal
     */
    static compose(SVGRendererClass) {
        if (pushUnique(composed, 'HTMLElement')) {
            /**
             * Create a HTML text node. This is used by the SVG renderer `text`
             * and `label` functions through the `useHTML` parameter.
             *
             * @internal
             */
            SVGRendererClass.prototype.html = function (str, x, y) {
                return new HTMLElement(this, 'div')
                    // Set the default attributes
                    .attr({
                    text: str,
                    x: Math.round(x),
                    y: Math.round(y)
                });
            };
        }
    }
    /* *
     *
     *  Functions
     *
     * */
    constructor(renderer, nodeName) {
        super(renderer, nodeName);
        this.foreignObject = renderer.createElement('foreignObject')
            .attr({
            zIndex: 2
        });
        this.element.style.whiteSpace = 'nowrap';
    }
    /**
     * Get the correction in X and Y positioning as the element is rotated.
     * @internal
     */
    getSpanCorrection(width, baseline, alignCorrection) {
        this.xCorr = -width * alignCorrection;
        this.yCorr = -baseline;
    }
    /**
     * Apply CSS to HTML elements. This is used in text within SVG rendering.
     * @internal
     */
    css(styles) {
        const { element } = this, 
        // When setting or unsetting the width style, we need to update
        // transform (#8809)
        isSettingWidth = styles && 'width' in styles, textWidth = isSettingWidth && styles.width;
        let doTransform;
        if (isSettingWidth) {
            delete styles.width;
            this.textWidth = pInt(textWidth) || void 0;
            doTransform = true;
        }
        // Some properties require other properties to be set
        if (styles?.textOverflow === 'ellipsis') {
            styles.overflow = 'hidden';
            styles.whiteSpace = 'nowrap';
        }
        // Apply line clamp
        if (styles?.lineClamp) {
            styles.display = '-webkit-box';
            styles.WebkitLineClamp = styles.lineClamp;
            styles.WebkitBoxOrient = 'vertical';
            styles.overflow = 'hidden';
        }
        else if (styles?.lineClamp === 0) {
            // Disable the clamp by breaking the -webkit-box context (#22961)
            styles.display = 'inline-block';
        }
        // SVG natively supports setting font size as numbers. With HTML, the
        // font size should behave in the same way (#21624).
        if (isNumber(Number(styles?.fontSize))) {
            styles.fontSize += 'px';
        }
        extend(this.styles, styles);
        css(element, styles);
        // Now that all styles are applied, to the transform
        if (doTransform) {
            this.updateTransform();
        }
        return this;
    }
    /**
     * The useHTML method for calculating the bounding box based on offsets.
     * Called internally from the `SVGElement.getBBox` function and subsequently
     * rotated.
     *
     * @internal
     */
    htmlGetBBox() {
        const { element } = this;
        return {
            x: element.offsetLeft,
            y: element.offsetTop,
            width: element.offsetWidth,
            height: element.offsetHeight
        };
    }
    /**
     * Batch update styles and attributes related to transform
     *
     * @internal
     */
    updateTransform() {
        // Aligning non added elements is expensive
        if (!this.added) {
            this.alignOnAdd = true;
            return;
        }
        const { element, foreignObject, oldTextWidth, renderer, rotation, scaleX, styles: { display = 'inline-block', whiteSpace }, textAlign = 'left', textWidth, x = 0, y = 0 } = this;
        // Get the pixel length of the text
        const getTextPxLength = () => {
            if (this.textPxLength) {
                return this.textPxLength;
            }
            // Reset multiline/ellipsis in order to read width (#4928, #5417)
            css(element, {
                width: '',
                whiteSpace: whiteSpace || 'nowrap'
            });
            return element.offsetWidth;
        };
        const currentTextTransform = [
            rotation,
            textAlign,
            element.innerHTML,
            textWidth,
            this.textAlign
        ].join(',');
        let baseline;
        css(element, {
            // Inline block must be set before we can read the offset width
            display: 'inline-block',
            verticalAlign: 'top'
        });
        // Update textWidth. Use the memoized textPxLength if possible, to avoid
        // the getTextPxLength function using elem.offsetWidth. Calling
        // offsetWidth affects rendering time as it forces layout (#7656).
        if (textWidth !== oldTextWidth) { // #983, #1254
            const textPxLength = getTextPxLength(), textWidthNum = textWidth || 0, willOverWrap = !renderer.styledMode &&
                element.style.textOverflow === '' &&
                element.style.webkitLineClamp;
            if ((textWidthNum > oldTextWidth ||
                textPxLength > textWidthNum ||
                willOverWrap) && (
            // Only set the width if the text is able to word-wrap, or
            // text-overflow is ellipsis (#9537)
            /[\-\s\u00AD]/.test(element.textContent || element.innerText) ||
                element.style.textOverflow === 'ellipsis')) {
                const usePxWidth = rotation ||
                    scaleX ||
                    textPxLength > textWidthNum;
                css(element, {
                    // #16261
                    width: usePxWidth && isNumber(textWidth) ?
                        textWidth + 'px' :
                        // Set width to prevent over-wrapping (#22609)
                        (willOverWrap ?
                            Math.min(
                            // +1 for rounding errors
                            textPxLength + 1, textWidthNum) + 'px' :
                            'auto'),
                    display,
                    whiteSpace: whiteSpace || 'normal' // #3331
                });
                this.oldTextWidth = textWidth;
            }
        }
        // In many cases (Firefox always, others on title layout) we need the
        // foreign object to have a larger width and height than its content, in
        // order to read its content's size
        foreignObject.attr({
            width: renderer.width,
            height: renderer.height
        });
        // Do the calculations and DOM access only if properties changed
        if (currentTextTransform !== this.cTT) {
            baseline = renderer.fontMetrics(element).b;
            this.getSpanCorrection(
            // Avoid elem.offsetWidth if we can, it affects rendering time
            // heavily (#7656)
            ((!defined(rotation) &&
                !this.textWidth &&
                this.textPxLength) || // #7920
                element.offsetWidth), baseline, getAlignFactor(textAlign));
        }
        // Move the foreign object
        super.updateTransform();
        if (isNumber(x) && isNumber(y)) {
            foreignObject.attr({
                x: x + (this.xCorr || 0),
                y: y + (this.yCorr || 0),
                // Add 4px to avoid ellipsis, since the body adds 3 px right
                // margin. We need one more because of rounding.
                width: element.offsetWidth + 4,
                // Add 1px to account for subpixel bounding boxes
                height: element.offsetHeight + 1,
                'transform-origin': element
                    .getAttribute('transform-origin') || '0 0'
            });
            // Reset, otherwise lineClamp will not work
            css(element, { display, textAlign });
        }
        else if (HTMLElement_isFirefox) {
            foreignObject.attr({
                width: 0,
                height: 0
            });
        }
        // Record current text transform
        this.cTT = currentTextTransform;
    }
    /**
     * Add the element to a group wrapper. For HTML elements, a parallel div
     * will be created for each ancestor SVG `g` element.
     *
     * @internal
     */
    add(parentGroup) {
        const { foreignObject, renderer } = this;
        // Foreign object
        foreignObject.add(parentGroup);
        super.add(
        // Create a body inside the foreignObject
        renderer.createElement('body')
            .attr({ xmlns: 'http://www.w3.org/1999/xhtml' })
            .css({
            background: 'transparent',
            // 3px is to avoid clipping on the right
            margin: '0 3px 0 0'
        })
            .add(foreignObject));
        if (this.alignOnAdd) {
            this.updateTransform();
        }
        return this;
    }
    /**
     * Text setter
     * @internal
     */
    textSetter(value) {
        if (value !== this.textStr) {
            delete this.oldTextWidth;
            HTML_AST.setElementHTML(this.element, value ?? '');
            this.textStr = value;
            this.doTransform = true;
        }
    }
    /**
     * Align setter
     *
     * @internal
     */
    alignSetter(value) {
        this.alignValue = this.textAlign = value;
        this.doTransform = true;
    }
    /**
     * Various setters which rely on update transform
     * @internal
     */
    xSetter(value, key) {
        this[key] = value;
        this.doTransform = true;
    }
}
// Some shared setters
const proto = HTMLElement.prototype;
proto.ySetter = proto.xSetter;
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const HTML_HTMLElement = (HTMLElement);

;// ./code/es-modules/Core/Axis/AxisDefaults.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */

/* *
 *
 *  Namespace
 *
 * */
var AxisDefaults;
(function (AxisDefaults) {
    /* *
     *
     *  Constants
     *
     * */
    /**
     * The X axis or category axis. Normally this is the horizontal axis,
     * though if the chart is inverted this is the vertical axis. In case of
     * multiple axes, the xAxis node is an array of configuration objects.
     *
     * See the [Axis class](/class-reference/Highcharts.Axis) for programmatic
     * access to the axis.
     *
     * @productdesc {highmaps}
     * In Highmaps, the axis is hidden, but it is used behind the scenes to
     * control features like zooming and panning. Zooming is in effect the same
     * as setting the extremes of one of the exes.
     *
     * @type         {*|Array<*>}
     * @optionparent xAxis
     */
    AxisDefaults.xAxis = {
        /**
         * When using multiple axis, the ticks of two or more opposite axes
         * will automatically be aligned by adding ticks to the axis or axes
         * with the least ticks, as if `tickAmount` were specified.
         *
         * This can be prevented by setting `alignTicks` to false. If the grid
         * lines look messy, it's a good idea to hide them for the secondary
         * axis by setting `gridLineWidth` to 0.
         *
         * If `startOnTick` or `endOnTick` in an Axis options are set to false,
         * then the `alignTicks ` will be disabled for the Axis.
         *
         * Disabled for logarithmic axes.
         *
         * @product   highcharts highstock gantt
         */
        alignTicks: true,
        /**
         * Whether to allow decimals in this axis' ticks. When counting
         * integers, like persons or hits on a web page, decimals should
         * be avoided in the labels. By default, decimals are allowed on small
         * scale axes.
         *
         * @see [minTickInterval](#xAxis.minTickInterval)
         *
         * @sample {highcharts|highstock} highcharts/yaxis/allowdecimals-true/
         *         True by default
         * @sample {highcharts|highstock} highcharts/yaxis/allowdecimals-false/
         *         False
         *
         * @type      {boolean|undefined}
         * @default   undefined
         * @since     2.0
         */
        allowDecimals: void 0,
        /**
         * When using an alternate grid color, a band is painted across the
         * plot area between every other grid line.
         *
         * @sample {highcharts} highcharts/yaxis/alternategridcolor/
         *         Alternate grid color on the Y axis
         * @sample {highstock} stock/xaxis/alternategridcolor/
         *         Alternate grid color on the Y axis
         *
         * @type      {Highcharts.ColorType}
         * @apioption xAxis.alternateGridColor
         */
        /**
         * An array defining breaks in the axis, the sections defined will be
         * left out and all the points shifted closer to each other.
         *
         * @productdesc {highcharts}
         * Requires that the broken-axis.js module is loaded.
         *
         * @sample {highcharts} highcharts/axisbreak/break-simple/
         *         Simple break
         * @sample {highcharts|highstock} highcharts/axisbreak/break-visualized/
         *         Advanced with callback
         * @sample {highstock} stock/demo/intraday-breaks/
         *         Break on nights and weekends
         *
         * @type      {Array<*>}
         * @since     4.1.0
         * @product   highcharts highstock gantt
         * @apioption xAxis.breaks
         */
        /**
         * A number indicating how much space should be left between the start
         * and the end of the break. The break size is given in axis units,
         * so for instance on a `datetime` axis, a break size of 3600000 would
         * indicate the equivalent of an hour.
         *
         * @type      {number}
         * @default   0
         * @since     4.1.0
         * @product   highcharts highstock gantt
         * @apioption xAxis.breaks.breakSize
         */
        /**
         * The axis value where the break starts. On datetime axes, this may be
         * a date string.
         *
         * @type      {number|string}
         * @since     4.1.0
         * @product   highcharts highstock gantt
         * @apioption xAxis.breaks.from
         */
        /**
         * Defines an interval after which the break appears again. By default
         * the breaks do not repeat.
         *
         * @type      {number}
         * @default   0
         * @since     4.1.0
         * @product   highcharts highstock gantt
         * @apioption xAxis.breaks.repeat
         */
        /**
         * The axis value where the break ends. On datetime axes, this may be
         * a date string.
         *
         * @type      {number|string}
         * @since     4.1.0
         * @product   highcharts highstock gantt
         * @apioption xAxis.breaks.to
         */
        /**
         * If categories are present for the xAxis, names are used instead of
         * numbers for that axis.
         *
         * Since Highcharts 3.0, categories can also
         * be extracted by giving each point a [name](#series.data) and setting
         * axis [type](#xAxis.type) to `category`. However, if you have multiple
         * series, best practice remains defining the `categories` array.
         *
         * Example: `categories: ['Apples', 'Bananas', 'Oranges']`
         *
         * @sample {highcharts} highcharts/demo/line-labels/
         *         With
         * @sample {highcharts} highcharts/xaxis/categories/
         *         Without
         *
         * @type      {Array<string>}
         * @product   highcharts gantt
         * @apioption xAxis.categories
         */
        /**
         * The highest allowed value for automatically computed axis extremes.
         *
         * @see [floor](#xAxis.floor)
         *
         * @sample {highcharts|highstock} highcharts/yaxis/floor-ceiling/
         *         Floor and ceiling
         *
         * @type       {number}
         * @since      4.0
         * @product    highcharts highstock gantt
         * @apioption  xAxis.ceiling
         */
        /**
         * A class name that opens for styling the axis by CSS, especially in
         * Highcharts styled mode. The class name is applied to group elements
         * for the grid, axis elements and labels.
         *
         * @sample {highcharts|highstock|highmaps} highcharts/css/axis/
         *         Multiple axes with separate styling
         *
         * @type      {string}
         * @since     5.0.0
         * @apioption xAxis.className
         */
        /**
         * Configure a crosshair that follows either the mouse pointer or the
         * hovered point.
         *
         * Support boolean or object definition. If `true`, a crosshair with
         * default settings will be displayed.
         *
         * In styled mode, the crosshairs are styled in the
         * `.highcharts-crosshair`, `.highcharts-crosshair-thin` or
         * `.highcharts-xaxis-category` classes.
         *
         * @productdesc {highstock}
         * In Highcharts stock, by default, the crosshair is enabled on the
         * X axis and disabled on the Y axis.
         *
         * @sample {highcharts} highcharts/xaxis/crosshair-both/
         *         Crosshair on both axes
         * @sample {highstock} stock/xaxis/crosshairs-xy/
         *         Crosshair on both axes, with y axis label
         * @sample {highmaps} highcharts/xaxis/crosshair-both/
         *         Crosshair on both axes
         *
         * @declare   Highcharts.AxisCrosshairOptions
         * @type      {boolean|*}
         * @default   false
         * @since     4.1
         * @apioption xAxis.crosshair
         */
        /**
         * A class name for the crosshair, especially as a hook for styling.
         *
         * @type      {string}
         * @since     5.0.0
         * @apioption xAxis.crosshair.className
         */
        /**
         * The color of the crosshair. Defaults to `#cccccc` for numeric and
         * datetime axes, and `rgba(204,214,235,0.25)` for category axes, where
         * the crosshair by default highlights the whole category.
         *
         * @sample {highcharts|highstock|highmaps} highcharts/xaxis/crosshair-customized/
         *         Customized crosshairs
         *
         * @type      {Highcharts.ColorType}
         * @default   #cccccc
         * @since     4.1
         * @apioption xAxis.crosshair.color
         */
        /**
         * The dash style for the crosshair. See
         * [plotOptions.series.dashStyle](#plotOptions.series.dashStyle)
         * for possible values.
         *
         * @sample {highcharts|highmaps} highcharts/xaxis/crosshair-dotted/
         *         Dotted crosshair
         * @sample {highstock} stock/xaxis/crosshair-dashed/
         *         Dashed X axis crosshair
         *
         * @type      {Highcharts.DashStyleValue}
         * @default   Solid
         * @since     4.1
         * @apioption xAxis.crosshair.dashStyle
         */
        /**
         * A label on the axis next to the crosshair.
         *
         * In styled mode, the label is styled with the
         * `.highcharts-crosshair-label` class.
         *
         * @sample {highstock} stock/xaxis/crosshair-label/
         *         Crosshair labels
         * @sample {highstock} highcharts/css/crosshair-label/
         *         Style mode
         *
         * @declare   Highcharts.AxisCrosshairLabelOptions
         * @since     2.1
         * @product   highstock
         * @apioption xAxis.crosshair.label
         */
        /**
         * Alignment of the label compared to the axis. Defaults to `"left"` for
         * right-side axes, `"right"` for left-side axes and `"center"` for
         * horizontal axes.
         *
         * @type      {Highcharts.AlignValue}
         * @since     2.1
         * @product   highstock
         * @apioption xAxis.crosshair.label.align
         */
        /**
         * The background color for the label. Defaults to the related series
         * color, or `#666666` if that is not available.
         *
         * @type      {Highcharts.ColorType}
         * @since     2.1
         * @product   highstock
         * @apioption xAxis.crosshair.label.backgroundColor
         */
        /**
         * The border color for the crosshair label
         *
         * @type      {Highcharts.ColorType}
         * @since     2.1
         * @product   highstock
         * @apioption xAxis.crosshair.label.borderColor
         */
        /**
         * The border corner radius of the crosshair label.
         *
         * @type      {number}
         * @default   3
         * @since     2.1.10
         * @product   highstock
         * @apioption xAxis.crosshair.label.borderRadius
         */
        /**
         * The border width for the crosshair label.
         *
         * @type      {number}
         * @default   0
         * @since     2.1
         * @product   highstock
         * @apioption xAxis.crosshair.label.borderWidth
         */
        /**
         * Flag to enable crosshair's label.
         *
         * @sample {highstock} stock/xaxis/crosshairs-xy/
         *         Enabled label for yAxis' crosshair
         *
         * @type      {boolean}
         * @default   false
         * @since     2.1
         * @product   highstock
         * @apioption xAxis.crosshair.label.enabled
         */
        /**
         * A format string for the crosshair label. Defaults to `{value}` for
         * numeric axes and `{value:%b %d, %Y}` for datetime axes.
         *
         * @type      {string}
         * @since     2.1
         * @product   highstock
         * @apioption xAxis.crosshair.label.format
         */
        /**
         * Formatter function for the label text.
         * Since v12.6.0, the callback also receives `ctx` as the second
         * argument, so that arrow functions can access the same context as
         * regular functions using `this`.
         *
         * @type      {Highcharts.XAxisCrosshairLabelFormatterCallbackFunction}
         * @since     2.1
         * @product   highstock
         * @apioption xAxis.crosshair.label.formatter
         */
        /**
         * Padding inside the crosshair label.
         *
         * @type      {number}
         * @default   8
         * @since     2.1
         * @product   highstock
         * @apioption xAxis.crosshair.label.padding
         */
        /**
         * The shape to use for the label box.
         *
         * @type      {string}
         * @default   callout
         * @since     2.1
         * @product   highstock
         * @apioption xAxis.crosshair.label.shape
         */
        /**
         * Text styles for the crosshair label.
         *
         * @type      {Highcharts.CSSObject}
         * @default   {"color": "white", "fontWeight": "normal", "fontSize": "11px", "textAlign": "center"}
         * @since     2.1
         * @product   highstock
         * @apioption xAxis.crosshair.label.style
         */
        /**
         * The number of milliseconds to wait until the crosshair is shown when
         * the mouse is over a point. Works on initial hover.
         *
         * @sample {highcharts|highstock} highcharts/tooltip/showdelay/
         *         Show crosshair after 2 seconds
         *
         * @type      {number}
         * @default   0
         * @since     12.6.0
         * @apioption xAxis.crosshair.showDelay
         */
        /**
         * Whether the crosshair should snap to the point or follow the pointer
         * independent of points.
         *
         * @sample {highcharts|highstock} highcharts/xaxis/crosshair-snap-false/
         *         True by default
         * @sample {highmaps} maps/demo/latlon-advanced/
         *         Snap is false
         *
         * @type      {boolean}
         * @default   true
         * @since     4.1
         * @apioption xAxis.crosshair.snap
         */
        /**
         * The pixel width of the crosshair. Defaults to 1 for numeric or
         * datetime axes, and for one category width for category axes.
         *
         * @sample {highcharts} highcharts/xaxis/crosshair-customized/
         *         Customized crosshairs
         * @sample {highstock} highcharts/xaxis/crosshair-customized/
         *         Customized crosshairs
         * @sample {highmaps} highcharts/xaxis/crosshair-customized/
         *         Customized crosshairs
         *
         * @type      {number}
         * @default   1
         * @since     4.1
         * @apioption xAxis.crosshair.width
         */
        /**
         * The Z index of the crosshair. Higher Z indices allow drawing the
         * crosshair on top of the series or behind the grid lines.
         *
         * @type      {number}
         * @default   2
         * @since     4.1
         * @apioption xAxis.crosshair.zIndex
         */
        /**
         * The value on a perpendicular axis where this axis should cross. This
         * is typically used on mathematical plots where the axes cross at 0.
         * When `crossing` is set, space will not be reserved at the sides of
         * the chart for axis labels and title, so those may be clipped. In this
         * case it is better to place the axes without the `crossing` option.
         *
         * @type      {number}
         * @sample    highcharts/xaxis/crossing
         *            Function plot with axes crossing at 0
         * @since 11.0.1
         * @apioption xAxis.crossing
         */
        /**
         * Whether to pan axis. If `chart.panning` is enabled, the option
         * allows to disable panning on an individual axis.
         */
        panningEnabled: true,
        /**
         * The Z index for the axis group.
         *
         * @see [axis.gridZIndex](#xAxis.gridZIndex)
         * @see [axis.labels.zIndex](#xAxis.labels.zIndex)
         */
        zIndex: 2,
        /**
         * Whether to zoom axis. If `chart.zoomType` is set, the option allows
         * to disable zooming on an individual axis.
         *
         * @sample {highcharts} highcharts/xaxis/zoomenabled/
         *         Zoom enabled is false
         */
        zoomEnabled: true,
        /**
         * For a datetime axis, the scale will automatically adjust to the
         * appropriate unit. This member gives the default representations used
         * for each unit. For easier data interpretation, `hour`, `day`, `month`
         * and `year` units can be used and formatted as boundary tick values.
         * Check boundaries map below to see where to format boundary ticks on
         * given main time units.
         *
         * ```js
         *     baseUnit: boundaryUnit
         *     ----------------------
         *     millisecond: 'hour',
         *     second: 'hour',
         *     minute: 'hour',
         *     hour: 'day',
         *     day: 'month',
         *     week: 'month',
         *     month: 'year',
         *     year: 'year'
         * ```
         *
         * For an overview of the date time label formats configuration, see
         * [dateFormat](/class-reference/Highcharts.Time#dateFormat).
         *
         * Defaults to:
         * ```js
         * {
         *     millisecond: { main: '%[HMSL]' },
         *     second: { main: '%[HMS]' },
         *     minute: { main: '%[HM]' },
         *     hour: { main: '%[HM]', boundary: undefined },
         *     day: { main: '%[eb]', boundary: '%[eb]' },
         *     week: { main: '%[eb]' },
         *     month: { main: '%[bY]', boundary: undefined },
         *     year: { main: '%Y', boundary: undefined }
         * }
         * ```
         *
         * @productdesc {gantt}
         * For grid axes (like in Gantt charts),
         * it is possible to declare as a list to provide different
         * formats depending on available space.
         *
         * Defaults to:
         * ```js
         * {
         *     hour: { list: ['%H:%M', '%H'] },
         *     day: { list: ['%A, %e. %B', '%a, %e. %b', '%E'] },
         *     week: { list: ['Week %W', 'W%W'] },
         *     month: { list: ['%B', '%b', '%o'] }
         * }
         * ```
         *
         * @sample {highcharts} highcharts/xaxis/datetimelabelformats-object/
         *         Object day format on X axis
         * @sample {highcharts} highcharts/xaxis/datetimelabelformats/
         *         String day format on X axis
         * @sample {highcharts} highcharts/xaxis/labels-boundary/
         *         Month boundary on daily ticks
         * @sample {highcharts} highcharts/xaxis/labels-boundary-format/
         *         Using axis labels format to format boundary labels
         * @sample {highstock} stock/xaxis/datetimelabelformats/
         *         More information in x axis labels
         * @sample {highstock} stock/xaxis/labels-boundary/
         *         Year boundary on monthly ticks
         * @sample {gantt} gantt/grid-axis/date-time-label-formats
         *         Gantt chart with custom axis date format.
         *
         * @declare Highcharts.AxisDateTimeLabelFormatsOptions
         * @product highcharts highstock gantt
         */
        dateTimeLabelFormats: {
            /**
             * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject
             */
            millisecond: {
                /**
                 * List of possible format strings used for this unit.
                 *
                 * @type {Array<string|Highcharts.DateTimeFormatOptions>}
                 * @apioption xAxis.dateTimeLabelFormats.millisecond.list
                 */
                /**
                 * @apioption xAxis.dateTimeLabelFormats.millisecond.main
                 */
                main: '%[HMSL]',
                /**
                 * When `false`, this time unit is treated as a point in time
                 * rather than a range. In Gantt charts, when grid axis is
                 * enabled, point-in-time ticks get left-aligned in the grid
                 * cell by default.
                 *
                 * @product gantt
                 * @apioption xAxis.dateTimeLabelFormats.millisecond.range
                 */
                range: false
            },
            /**
             * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject
             */
            second: {
                /**
                 * List of possible format strings used for this unit.
                 *
                 * @type {Array<string|Highcharts.DateTimeFormatOptions>}
                 * @apioption xAxis.dateTimeLabelFormats.second.list
                 */
                /**
                 * @apioption xAxis.dateTimeLabelFormats.second.main
                 */
                main: '%[HMS]',
                /**
                 * When `false`, this time unit is treated as a point in time
                 * rather than a range. In Gantt charts, when grid axis is
                 * enabled, point-in-time ticks get left-aligned in the grid
                 * cell by default.
                 *
                 * @product gantt
                 * @apioption xAxis.dateTimeLabelFormats.second.range
                 */
                range: false
            },
            /**
             * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject
             */
            minute: {
                /**
                 * List of possible format strings used for this unit.
                 *
                 * @type {Array<string|Highcharts.DateTimeFormatOptions>}
                 * @apioption xAxis.dateTimeLabelFormats.minute.list
                 */
                /**
                 * @apioption xAxis.dateTimeLabelFormats.minute.main
                 */
                main: '%[HM]',
                /**
                 * When `false`, this time unit is treated as a point in time
                 * rather than a range. In Gantt charts, when grid axis is
                 * enabled, point-in-time ticks get left-aligned in the grid
                 * cell by default.
                 *
                 * @product gantt
                 * @apioption xAxis.dateTimeLabelFormats.minute.range
                 */
                range: false
            },
            /**
             * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject
             */
            hour: {
                /**
                 * List of possible format strings used for this unit.
                 *
                 * @type {Array<string|Highcharts.DateTimeFormatOptions>}
                 * @apioption xAxis.dateTimeLabelFormats.hour.list
                 */
                /**
                 * @apioption xAxis.dateTimeLabelFormats.hour.main
                 */
                main: '%[HM]',
                /**
                 * Label format that should be used when a tick is a boundary
                 * tick, e.g. start of day, start of year, etc.
                 *
                 * @apioption xAxis.dateTimeLabelFormats.hour.boundary
                 */
                boundary: void 0,
                /**
                 * When `false`, this time unit is treated as a point in time
                 * rather than a range. In Gantt charts, when grid axis is
                 * enabled, point-in-time ticks get left-aligned in the grid
                 * cell by default.
                 *
                 * @product gantt
                 * @apioption xAxis.dateTimeLabelFormats.hour.range
                 */
                range: false
            },
            /**
             * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject
             */
            day: {
                /**
                 * List of possible format strings used for this unit.
                 *
                 * @type {Array<string|Highcharts.DateTimeFormatOptions>}
                 * @apioption xAxis.dateTimeLabelFormats.day.list
                 */
                /**
                 * @apioption xAxis.dateTimeLabelFormats.day.main
                 */
                main: '%[eb]',
                /**
                 * Label format that should be used when a tick is a boundary
                 * tick, e.g. start of day, start of year, etc.
                 *
                 * @apioption xAxis.dateTimeLabelFormats.day.boundary
                 */
                boundary: '%[eb]'
            },
            /**
             * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject
             */
            week: {
                /**
                 * List of possible format strings used for this unit.
                 *
                 * @type {Array<string|Highcharts.DateTimeFormatOptions>}
                 * @apioption xAxis.dateTimeLabelFormats.week.list
                 */
                /**
                 * @apioption xAxis.dateTimeLabelFormats.week.main
                 */
                main: '%[eb]'
            },
            /**
             * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject
             */
            month: {
                /**
                 * List of possible format strings used for this unit.
                 *
                 * @type {Array<string|Highcharts.DateTimeFormatOptions>}
                 * @apioption xAxis.dateTimeLabelFormats.month.list
                 */
                /**
                 * @apioption xAxis.dateTimeLabelFormats.month.main
                 */
                main: '%[bY]',
                /**
                 * Label format that should be used when a tick is a boundary
                 * tick, e.g. start of day, start of year, etc.
                 *
                 * @apioption xAxis.dateTimeLabelFormats.month.boundary
                 */
                boundary: void 0
            },
            /**
             * @declare Highcharts.AxisDateTimeLabelFormatsOptionsObject
             */
            year: {
                /**
                 * List of possible format strings used for this unit.
                 *
                 * @type {Array<string|Highcharts.DateTimeFormatOptions>}
                 * @apioption xAxis.dateTimeLabelFormats.year.list
                 */
                /**
                 * @apioption xAxis.dateTimeLabelFormats.year.main
                 */
                main: '%[Y]',
                /**
                 * Label format that should be used when a tick is a boundary
                 * tick, e.g. start of day, start of year, etc.
                 *
                 * @apioption xAxis.dateTimeLabelFormats.year.boundary
                 */
                boundary: void 0
            }
        },
        /**
         * Whether to force the axis to end on a tick. Use this option with
         * the `maxPadding` option to control the axis end.
         *
         * @productdesc {highstock}
         * In Highcharts Stock, `endOnTick` is always `false` when the navigator
         * is enabled, to prevent jumpy scrolling. With disabled navigator
         * enabling `endOnTick` may lead to extending the xAxis to show the last
         * tick, therefore range selector buttons may not have an active state
         * if the axis gets extended.
         *
         * @sample {highcharts} highcharts/yaxis/endontick/
         *         True by default
         * @sample {highcharts} highcharts/yaxis/endontick-false/
         *         False
         * @sample {highstock} stock/demo/basic-line/
         *         True by default
         * @sample {highstock} stock/xaxis/endontick/
         *         False
         *
         * @since 1.2.0
         */
        endOnTick: false,
        /**
         * Event handlers for the axis.
         *
         * @type      {*}
         * @apioption xAxis.events
         */
        /**
         * An event fired after the breaks have rendered.
         *
         * @see [breaks](#xAxis.breaks)
         *
         * @sample {highcharts} highcharts/axisbreak/break-event/
         *         AfterBreak Event
         *
         * @type      {Highcharts.AxisEventCallbackFunction}
         * @since     4.1.0
         * @product   highcharts gantt
         * @apioption xAxis.events.afterBreaks
         */
        /**
         * As opposed to the `setExtremes` event, this event fires after the
         * final min and max values are computed and corrected for `minRange`.
         *
         * Fires when the minimum and maximum is set for the axis, either by
         * calling the `.setExtremes()` method or by selecting an area in the
         * chart. One parameter, `event`, is passed to the function, containing
         * common event information.
         *
         * The new user set minimum and maximum values can be found by
         * `event.min` and `event.max`. These reflect the axis minimum and
         * maximum in axis values. The actual data extremes are found in
         * `event.dataMin` and `event.dataMax`.
         *
         * @type      {Highcharts.AxisSetExtremesEventCallbackFunction}
         * @since     2.3
         * @context   Highcharts.Axis
         * @apioption xAxis.events.afterSetExtremes
         */
        /**
         * An event fired when a break from this axis occurs on a point.
         *
         * @see [breaks](#xAxis.breaks)
         *
         * @sample {highcharts} highcharts/axisbreak/break-visualized/
         *         Visualization of a Break
         *
         * @type      {Highcharts.AxisPointBreakEventCallbackFunction}
         * @since     4.1.0
         * @product   highcharts gantt
         * @context   Highcharts.Axis
         * @apioption xAxis.events.pointBreak
         */
        /**
         * An event fired when a point falls inside a break from this axis.
         *
         * @type      {Highcharts.AxisPointBreakEventCallbackFunction}
         * @product   highcharts highstock gantt
         * @context   Highcharts.Axis
         * @apioption xAxis.events.pointInBreak
         */
        /**
         * Fires when the minimum and maximum is set for the axis, either by
         * calling the `.setExtremes()` method or by selecting an area in the
         * chart. One parameter, `event`, is passed to the function,
         * containing common event information.
         *
         * The new user set minimum and maximum values can be found by
         * `event.min` and `event.max`. These reflect the axis minimum and
         * maximum in data values. When an axis is zoomed all the way out from
         * the "Reset zoom" button, `event.min` and `event.max` are null, and
         * the new extremes are set based on `this.dataMin` and `this.dataMax`.
         *
         * @sample {highstock} stock/xaxis/events-setextremes/
         *         Log new extremes on x axis
         *
         * @type      {Highcharts.AxisSetExtremesEventCallbackFunction}
         * @since     1.2.0
         * @context   Highcharts.Axis
         * @apioption xAxis.events.setExtremes
         */
        /**
         * The lowest allowed value for automatically computed axis extremes.
         *
         * @see [ceiling](#yAxis.ceiling)
         *
         * @sample {highcharts} highcharts/yaxis/floor-ceiling/
         *         Floor and ceiling
         * @sample {highstock} stock/demo/lazy-loading/
         *         Prevent negative stock price on Y axis
         *
         * @type      {number}
         * @since     4.0
         * @product   highcharts highstock gantt
         * @apioption xAxis.floor
         */
        /**
         * The dash or dot style of the grid lines. For possible values, see
         * [this demonstration](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/).
         *
         * @sample {highcharts} highcharts/yaxis/gridlinedashstyle/
         *         Long dashes
         * @sample {highstock} stock/xaxis/gridlinedashstyle/
         *         Long dashes
         *
         * @type      {Highcharts.DashStyleValue}
         * @since     1.2
         */
        gridLineDashStyle: 'Solid',
        /**
         * The Z index of the grid lines.
         *
         * @sample {highcharts|highstock} highcharts/xaxis/gridzindex/
         *         A Z index of 4 renders the grid above the graph
         *
         * @product   highcharts highstock gantt
         *
         * @see [axis.zIndex](#xAxis.zIndex)
         * @see [axis.labels.zIndex](#xAxis.labels.zIndex)
         */
        gridZIndex: 1,
        /**
         * An id for the axis. This can be used after render time to get
         * a pointer to the axis object through `chart.get()`.
         *
         * @sample {highcharts} highcharts/xaxis/id/
         *         Get the object
         * @sample {highstock} stock/xaxis/id/
         *         Get the object
         *
         * @type      {string}
         * @since     1.2.0
         * @apioption xAxis.id
         */
        /**
         * The axis labels show the number or category for each tick.
         *
         * Since v8.0.0: Labels are animated in categorized x-axis with
         * updating data if `tickInterval` and `step` is set to 1.
         *
         * @productdesc {highmaps}
         * X and Y axis labels are by default disabled in Highmaps, but the
         * functionality is inherited from Highcharts and used on `colorAxis`,
         * and can be enabled on X and Y axes too.
         */
        labels: {
            /**
             * What part of the string the given position is anchored to.
             * If `left`, the left side of the string is at the axis position.
             * Can be one of `"left"`, `"center"` or `"right"`. Defaults to
             * an intelligent guess based on which side of the chart the axis
             * is on and the rotation of the label.
             *
             * @see [reserveSpace](#xAxis.labels.reserveSpace)
             *
             * @sample {highcharts} highcharts/xaxis/labels-align-left/
             *         Left
             * @sample {highcharts} highcharts/xaxis/labels-align-right/
             *         Right
             * @sample {highcharts} highcharts/xaxis/labels-reservespace-true/
             *         Left-aligned labels on a vertical category axis
             *
             * @type      {Highcharts.AlignValue}
             * @apioption xAxis.labels.align
             */
            /**
             * Whether to allow the axis labels to overlap. When false,
             * overlapping labels are hidden.
             *
             * @sample {highcharts} highcharts/xaxis/labels-allowoverlap-true/
             *         X axis labels overlap enabled
             *
             * @type      {boolean}
             * @default   false
             * @apioption xAxis.labels.allowOverlap
             */
            /**
             * For horizontal axes, the allowed degrees of label rotation
             * to prevent overlapping labels. If there is enough space,
             * labels are not rotated. As the chart gets narrower, it
             * will start rotating the labels -45 degrees, then remove
             * every second label and try again with rotations 0 and -45 etc.
             * Set it to `undefined` to disable rotation, which will
             * cause the labels to word-wrap if possible. Defaults to `[-45]``
             * on bottom and top axes, `undefined` on left and right axes.
             *
             * @sample {highcharts|highstock} highcharts/xaxis/labels-autorotation-default/
             *         Default auto rotation of 0 or -45
             * @sample {highcharts|highstock} highcharts/xaxis/labels-autorotation-0-90/
             *         Custom graded auto rotation
             *
             * @type      {Array<number>}
             * @default   undefined
             * @since     4.1.0
             * @product   highcharts highstock gantt
             * @apioption xAxis.labels.autoRotation
             */
            /**
             * When each category width is more than this many pixels, we don't
             * apply auto rotation. Instead, we lay out the axis label with word
             * wrap. A lower limit makes sense when the label contains multiple
             * short words that don't extend the available horizontal space for
             * each label.
             *
             * @sample {highcharts} highcharts/xaxis/labels-autorotationlimit/
             *         Lower limit
             *
             * @since     4.1.5
             * @product   highcharts gantt
             */
            autoRotationLimit: 80,
            /**
             * The label's pixel distance from the perimeter of the plot area.
             * On cartesian charts, this is overridden if the `labels.y` setting
             * is set.
             *
             * @sample {highcharts} highcharts/yaxis/labels-distance/
             *         Polar chart, labels centered under the arc
             *
             * @type      {number}
             * @product   highcharts gantt
             */
            distance: 15,
            /**
             * Enable or disable the axis labels.
             *
             * @sample {highcharts} highcharts/xaxis/labels-enabled/
             *         X axis labels disabled
             * @sample {highstock} stock/xaxis/labels-enabled/
             *         X axis labels disabled
             *
             */
            enabled: true,
            /**
             * A format string for the axis label. The context is available as
             * format string variables. For example, you can use `{text}` to
             * insert the default formatted text. The recommended way of adding
             * units for the label is using `text`, for example `{text} km`.
             *
             * To add custom numeric or datetime formatting, use `{value}` with
             * formatting, for example `{value:.1f}` or `{value:%Y-%m-%d}`.
             *
             * See
             * [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
             * for more examples of formatting.
             *
             * The default value is not specified due to the dynamic
             * nature of the default implementation.
             *
             * @sample {highcharts|highstock} highcharts/yaxis/labels-format/
             *         Add units to Y axis label
             * @sample {highcharts} highcharts/xaxis/labels-format-linked/
             *         Linked category names
             * @sample {highcharts} highcharts/xaxis/labels-format-custom/
             *         Custom number format
             * @sample {highstock} stock/xaxis/labels-format/
             *         Added units on Y axis
             *
             * @type      {string}
             * @since     3.0
             * @apioption xAxis.labels.format
             */
            /**
             * Callback JavaScript function to format the label. The value
             * is given by `this.value`. Additional properties for `this` are
             * `axis`, `chart`, `isFirst`, `isLast` and `text` which holds the
             * value of the default formatter. Since v12.6.0, the callback also
             * receives `ctx` as the first argument, so that arrow functions can
             * access the same context as regular functions using `this`.
             *
             * Defaults to a built in function returning a formatted string
             * depending on whether the axis is `category`, `datetime`,
             * `numeric` or other.
             *
             * @sample {highcharts} highcharts/xaxis/labels-formatter-linked/
             *         Linked category names
             * @sample {highcharts} highcharts/xaxis/labels-formatter-extended/
             *         Modified numeric labels
             * @sample {highstock} stock/xaxis/labels-formatter/
             *         Added units on Y axis
             *
             * @type      {Highcharts.AxisLabelsFormatterCallbackFunction}
             * @apioption xAxis.labels.formatter
             */
            /**
             * The number of pixels to indent the labels per level in a treegrid
             * axis.
             *
             * @sample gantt/treegrid-axis/demo
             *         Indentation 10px by default.
             * @sample gantt/treegrid-axis/indentation-0px
             *         Indentation set to 0px.
             *
             * @product gantt
             */
            indentation: 10,
            /**
             * Horizontal axis only. When `staggerLines` is not set,
             * `maxStaggerLines` defines how many lines the axis is allowed to
             * add to automatically avoid overlapping X labels. Set to `1` to
             * disable overlap detection.
             *
             * @deprecated 6.0.0
             * @type      {number}
             * @default   5
             * @since     1.3.3
             * @apioption xAxis.labels.maxStaggerLines
             */
            /**
             * How to handle overflowing labels on horizontal axis. If set to
             * `"allow"`, it will not be aligned at all. By default it
             * `"justify"` labels inside the chart area. If there is room to
             * move it, it will be aligned to the edge, else it will be removed.
             *
             * @since      2.2.5
             * @validvalue ["allow", "justify"]
             */
            overflow: 'justify',
            /**
             * The pixel padding for axis labels, to ensure white space between
             * them. Defaults to 4 for horizontal axes, 1 for vertical.
             *
             * @type      {number}
             * @default   undefined
             * @product   highcharts gantt
             * @apioption xAxis.labels.padding
             */
            /**
             * Whether to reserve space for the labels. By default, space is
             * reserved for the labels in these cases:
             *
             * * On all horizontal axes.
             * * On vertical axes if `label.align` is `right` on a left-side
             * axis or `left` on a right-side axis.
             * * On vertical axes if `label.align` is `center`.
             *
             * This can be turned off when for example the labels are rendered
             * inside the plot area instead of outside.
             *
             * @see [labels.align](#xAxis.labels.align)
             *
             * @sample {highcharts} highcharts/xaxis/labels-reservespace/
             *         No reserved space, labels inside plot
             * @sample {highcharts} highcharts/xaxis/labels-reservespace-true/
             *         Left-aligned labels on a vertical category axis
             *
             * @type      {boolean}
             * @since     4.1.10
             * @product   highcharts highstock gantt
             * @apioption xAxis.labels.reserveSpace
             */
            reserveSpace: void 0,
            /**
             * Rotation of the labels in degrees. When `undefined`, the
             * `autoRotation` option takes precedence.
             *
             * @sample {highcharts} highcharts/xaxis/labels-rotation/
             *         X axis labels rotated 90°
             *
             * @type      {number}
             * @default   0
             * @apioption xAxis.labels.rotation
             */
            rotation: void 0,
            /**
             * Horizontal axes only. The number of lines to spread the labels
             * over to make room or tighter labels. 0 disables staggering.
             *
             * @sample {highcharts} highcharts/xaxis/labels-staggerlines/
             *         Show labels over two lines
             * @sample {highstock} stock/xaxis/labels-staggerlines/
             *         Show labels over two lines
             *
             * @since     2.1
             */
            staggerLines: 0,
            /**
             * To show only every _n_'th label on the axis, set the step to _n_.
             * Setting the step to 2 shows every other label.
             *
             * By default, when 0, the step is calculated automatically to avoid
             * overlap. To prevent this, set it to 1\. This usually only
             * happens on a category axis, and is often a sign that you have
             * chosen the wrong axis type.
             *
             * Read more at
             * [Axis docs](https://www.highcharts.com/docs/chart-concepts/axes)
             * => What axis should I use?
             *
             * @sample {highcharts} highcharts/xaxis/labels-step/
             *         Showing only every other axis label on a categorized
             *         x-axis
             * @sample {highcharts} highcharts/xaxis/labels-step-auto/
             *         Auto steps on a category axis
             *
             * @since     2.1
             */
            step: 0,
            /**
             * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
             * to render the labels.
             */
            useHTML: false,
            /**
             * The x position offset of all labels relative to the tick
             * positions on the axis. Overrides the `labels.distance` option.
             *
             * @type      {number}
             * @apioption xAxis.labels.x
             */
            /**
             * The y position offset of all labels relative to the tick
             * positions on the axis. Overrides the `labels.distance` option.
             *
             * @sample {highcharts} highcharts/xaxis/labels-x/
             *         X axis labels placed on grid lines
             *
             * @type      {number}
             * @apioption xAxis.labels.y
             */
            /**
             * The Z index for the axis labels.
             *
             * @see [axis.zIndex](#xAxis.zIndex)
             * @see [axis.gridZIndex](#xAxis.gridZIndex)
             */
            zIndex: 7,
            /**
             * CSS styles for the label. Use `lineClamp` to control wrapping of
             * category labels. Use `textOverflow: 'none'` to prevent ellipsis
             * (dots).
             *
             * In styled mode, the labels are styled with the
             * `.highcharts-axis-labels` class.
             *
             * @sample {highcharts} highcharts/xaxis/labels-style/
             *         Red X axis labels
             *
             * @type      {Highcharts.CSSObject}
             */
            style: {
                /**
                 * @type {Highcharts.ColorType}
                 */
                color: 'var(--highcharts-neutral-color-80)',
                cursor: 'default',
                fontSize: '0.8em',
                textOverflow: 'ellipsis'
            }
        },
        /**
         * The left position as the horizontal axis. If it's a number, it is
         * interpreted as pixel position relative to the chart.
         *
         * Since Highcharts v5.0.13: If it's a percentage string, it is
         * interpreted as percentages of the plot width, offset from plot area
         * left.
         *
         * @sample {highcharts} highcharts/xaxis/axis-position-properties
         *         Different axis position properties
         *
         * @type      {number|string}
         * @product   highcharts highstock
         * @apioption xAxis.left
         */
        /**
         * The top position as the vertical axis. If it's a number, it is
         * interpreted as pixel position relative to the chart.
         *
         * Since Highcharts 2: If it's a percentage string, it is interpreted
         * as percentages of the plot height, offset from plot area top.
         *
         * @sample {highcharts} highcharts/xaxis/axis-position-properties
         *         Different axis position properties
         *
         * @type      {number|string}
         * @product   highcharts highstock
         * @apioption xAxis.top
         */
        /**
         * Index of another axis that this axis is linked to. When an axis is
         * linked to a master axis, it will take the same extremes as
         * the master, but as assigned by min or max or by setExtremes.
         * It can be used to show additional info, or to ease reading the
         * chart by duplicating the scales.
         *
         * @sample {highcharts} highcharts/xaxis/linkedto/
         *         Different string formats of the same date
         * @sample {highcharts} highcharts/yaxis/linkedto/
         *         Y values on both sides
         *
         * @type      {number}
         * @since     2.0.2
         * @product   highcharts highstock gantt
         * @apioption xAxis.linkedTo
         */
        /**
         * The maximum value of the axis. If `undefined`, the max value is
         * automatically calculated.
         *
         * If a datetime string is passed, it is parsed into epoch time
         * according to the time zone given in [time.timezone](#time.timezone).
         *
         * If the [endOnTick](#yAxis.endOnTick) option is true, the `max` value
         * might be rounded up.
         *
         * If a [tickAmount](#yAxis.tickAmount) is set, the axis may be extended
         * beyond the set max in order to reach the given number of ticks. The
         * same may happen in a chart with multiple axes, determined by [chart.
         * alignTicks](#chart), where a `tickAmount` is applied internally.
         *
         * @sample {highcharts} highcharts/yaxis/max-200/
         *         Y axis max of 200
         * @sample {highcharts} highcharts/yaxis/max-logarithmic/
         *         Y axis max on logarithmic axis
         * @sample {highstock} stock/xaxis/min-max/
         *         Fixed min and max on X axis
         *
         * @type      {number|string|null}
         * @apioption xAxis.max
         */
        /**
         * Padding of the max value relative to the length of the axis. A
         * padding of 0.05 will make a 100px axis 5px longer. This is useful
         * when you don't want the highest data value to appear on the edge
         * of the plot area. When the axis' `max` option is set or a max extreme
         * is set using `axis.setExtremes()`, the maxPadding will be ignored.
         *
         * @productdesc {highstock}
         * For an [ordinal](#xAxis.ordinal) axis, `minPadding` and `maxPadding`
         * are ignored. Use [overscroll](#xAxis.overscroll) instead.
         *
         * @sample {highcharts} highcharts/yaxis/maxpadding/
         *         Max padding of 0.25 on y axis
         * @sample {highstock} stock/xaxis/minpadding-maxpadding/
         *         Greater min- and maxPadding
         * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/
         *         Add some padding
         *
         * @default   {highcharts} 0.01
         * @default   {highstock|highmaps} 0
         * @since     1.2.0
         */
        maxPadding: 0.01,
        /**
         * Deprecated. Use `minRange` instead.
         *
         * @deprecated 6.0.0
         * @type      {number}
         * @product   highcharts highstock
         * @apioption xAxis.maxZoom
         */
        /**
         * The minimum value of the axis. If `undefined`, the min value is
         * automatically calculated.
         *
         * If a datetime string is passed, it is parsed into epoch time
         * according to the time zone given in [time.timezone](#time.timezone).
         *
         * If the [startOnTick](#yAxis.startOnTick) option is true (default),
         * the `min` value might be rounded down.
         *
         * The automatically calculated minimum value is also affected by
         * [floor](#yAxis.floor), [softMin](#yAxis.softMin),
         * [minPadding](#yAxis.minPadding), [minRange](#yAxis.minRange)
         * as well as [series.threshold](#plotOptions.series.threshold)
         * and [series.softThreshold](#plotOptions.series.softThreshold).
         *
         * @sample {highcharts} highcharts/yaxis/min-startontick-false/
         *         -50 with startOnTick to false
         * @sample {highcharts} highcharts/yaxis/min-startontick-true/
         *         -50 with startOnTick true by default
         * @sample {highstock} stock/xaxis/min-max/
         *         Set min and max on X axis
         *
         * @type      {number|string|null}
         * @apioption xAxis.min
         */
        /**
         * The dash or dot style of the minor grid lines. For possible values,
         * see [this demonstration](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/).
         *
         * @sample {highcharts} highcharts/yaxis/minorgridlinedashstyle/
         *         Long dashes on minor grid lines
         * @sample {highstock} stock/xaxis/minorgridlinedashstyle/
         *         Long dashes on minor grid lines
         *
         * @type      {Highcharts.DashStyleValue}
         * @since     1.2
         */
        minorGridLineDashStyle: 'Solid',
        /**
         * Specific tick interval in axis units for the minor ticks. On a linear
         * axis, if `"auto"`, the minor tick interval is calculated as a fifth
         * of the tickInterval. If `undefined`, minor ticks are not shown.
         *
         * On logarithmic axes, the unit is the power of the value. For example,
         * setting the minorTickInterval to 1 puts one tick on each of 0.1, 1,
         * 10, 100 etc. Setting the minorTickInterval to 0.1 produces 9 ticks
         * between 1 and 10, 10 and 100 etc.
         *
         * If user settings dictate minor ticks to become too dense, they don't
         * make sense, and will be ignored to prevent performance problems.
         *
         * @sample {highcharts} highcharts/yaxis/minortickinterval-null/
         *         Undefined by default
         * @sample {highcharts} highcharts/yaxis/minortickinterval-5/ 5 units
         * @sample {highcharts} highcharts/yaxis/minortickinterval-log-auto/
         *         "auto"
         * @sample {highcharts} highcharts/yaxis/minortickinterval-log/ 0.1
         * @sample {highstock} stock/demo/basic-line/ Null by default
         * @sample {highstock} stock/xaxis/minortickinterval-auto/ "auto"
         *
         * @type      {number|'auto'}
         * @apioption xAxis.minorTickInterval
         */
        /**
         * The pixel length of the minor tick marks.
         *
         * @sample {highcharts} highcharts/yaxis/minorticklength/
         *         Minor ticks on Y axis
         * @sample {highstock} stock/xaxis/minorticks/
         *         10px on Y axis
         */
        minorTickLength: 2,
        /**
         * The position of the minor tick marks relative to the axis line.
         *  Can be one of `inside` and `outside`.
         *
         * @sample {highcharts} highcharts/yaxis/minortickposition-outside/
         *         Outside by default
         * @sample {highcharts} highcharts/yaxis/minortickposition-inside/
         *         Inside
         * @sample {highstock} stock/xaxis/minorticks/
         *         Inside
         *
         * @validvalue ["inside", "outside"]
         */
        minorTickPosition: 'outside',
        /**
         * Enable or disable minor ticks. The interval between the minor ticks
         * can be controlled either by the
         * [minorTicksPerMajor](#xAxis.minorTicksPerMajor) setting, or as an
         * absolute [minorTickInterval](#xAxis.minorTickInterval) value.
         *
         * On a logarithmic axis, minor ticks are laid out based on a best
         * guess, attempting to enter an approximate number of minor ticks
         * between each major tick based on
         * [minorTicksPerMajor](#xAxis.minorTicksPerMajor).
         *
         * Prior to v6.0.0, ticks were enabled in auto layout by setting
         * `minorTickInterval` to `"auto"`.
         *
         * @productdesc {highcharts} On axes using
         * [categories](#xAxis.categories), minor ticks are not supported.
         *
         * @sample {highcharts} highcharts/yaxis/minorticks-true/ Enabled on
         *         linear Y axis
         *
         * @type      {boolean}
         * @default   false
         * @since     6.0.0
         * @apioption xAxis.minorTicks
         */
        /**
         * The number of minor ticks per major tick. Works for `linear`,
         * `logarithmic` and `datetime` axes.
         *
         * For radial axes in gauges, the default value is 10.
         *
         * @sample {highcharts} highcharts/yaxis/minortickspermajor/
         *         2 minor ticks per major tick on Y axis
         *
         * @since  11.0.0
         *
         * @type {number}
         */
        minorTicksPerMajor: 5,
        /**
         * The pixel width of the minor tick mark.
         *
         * @sample {highcharts} highcharts/yaxis/minortickwidth/
         *         3px width
         * @sample {highstock} stock/xaxis/minorticks/
         *         1px width
         *
         * @type      {number}
         * @default   0
         * @apioption xAxis.minorTickWidth
         */
        /**
         * Padding of the min value relative to the length of the axis. A
         * padding of 0.05 will make a 100px axis 5px longer. This is useful
         * when you don't want the lowest data value to appear on the edge
         * of the plot area. When the axis' `min` option is set or a min extreme
         * is set using `axis.setExtremes()`, the minPadding will be ignored.
         *
         * @productdesc {highstock}
         * For an [ordinal](#xAxis.ordinal) axis, `minPadding` and `maxPadding`
         * are ignored. Use [overscroll](#xAxis.overscroll) instead.
         *
         * @sample {highcharts} highcharts/yaxis/minpadding/
         *         Min padding of 0.2
         * @sample {highstock} stock/xaxis/minpadding-maxpadding/
         *         Greater min- and maxPadding
         * @sample {highmaps} maps/chart/plotbackgroundcolor-gradient/
         *         Add some padding
         *
         * @default    {highcharts} 0.01
         * @default    {highstock|highmaps} 0
         * @since      1.2.0
         * @product    highcharts highstock gantt
         */
        minPadding: 0.01,
        /**
         * The minimum range to display on this axis. The entire axis will not
         * be allowed to span over a smaller interval than this. For example,
         * for a datetime axis the main unit is milliseconds. If minRange is
         * set to 3600000, you can't zoom in more than to one hour.
         *
         * The default minRange for the x axis is five times the smallest
         * interval between any of the data points.
         *
         * On a logarithmic axis, the unit for the minimum range is the power.
         * So a minRange of 1 means that the axis can be zoomed to 10-100,
         * 100-1000, 1000-10000 etc.
         *
         * **Note**: The `minPadding`, `maxPadding`, `startOnTick` and
         * `endOnTick` settings also affect how the extremes of the axis
         * are computed.
         *
         * @sample {highcharts} highcharts/xaxis/minrange/
         *         Minimum range of 5
         * @sample {highstock} stock/xaxis/minrange/
         *         Max zoom of 6 months overrides user selections
         *
         * @type      {number}
         * @apioption xAxis.minRange
         */
        /**
         * The minimum tick interval allowed in axis values. For example on
         * zooming in on an axis with daily data, this can be used to prevent
         * the axis from showing hours. Defaults to the closest distance between
         * two points on the axis.
         *
         * @type      {number}
         * @since     2.3.0
         * @apioption xAxis.minTickInterval
         */
        /**
         * The distance in pixels from the plot area to the axis line.
         * A positive offset moves the axis with its line, labels and ticks
         * away from the plot area. This is typically used when two or more
         * axes are displayed on the same side of the plot. With multiple
         * axes the offset is dynamically adjusted to avoid collision, this
         * can be overridden by setting offset explicitly.
        *
        * For radial axes in gauges, the offset can be a percentage string, and
        * defaults to render the line and ticks on the inside of the pane and
        * plot bands (since v13). Labels are not affected by the offset in
        * radial axes.
         *
         * @sample {highcharts} highcharts/yaxis/offset/
         *         Y axis offset of 70
         * @sample {highcharts} highcharts/yaxis/offset-centered/
         *         Axes positioned in the center of the plot
         * @sample {highcharts} highcharts/yaxis/radial-offset
         *         Radial axis offset in gauge
         * @sample {highstock} stock/xaxis/offset/
         *         Y axis offset by 70 px
         *
         * @type {number}
         */
        offset: void 0,
        /**
         * Whether to display the axis on the opposite side of the normal. The
         * normal is on the left side for vertical axes and bottom for
         * horizontal, so the opposite sides will be right and top respectively.
         * This is typically used with dual or multiple axes.
         *
         * @sample {highcharts} highcharts/yaxis/opposite/
         *         Secondary Y axis opposite
         * @sample {highstock} stock/xaxis/opposite/
         *         Y axis on left side
         *
         * @default   {highcharts|highstock|highmaps} false
         * @default   {gantt} true
         * @type      {boolean}
         * @apioption xAxis.opposite
         */
        /**
         * In an ordinal axis, the points are equally spaced in the chart
         * regardless of the actual time or x distance between them. This means
         * that missing data periods (e.g. nights or weekends for a stock chart)
         * will not take up space in the chart.
         * Having `ordinal: false` will show any gaps created by the `gapSize`
         * setting proportionate to their duration.
         *
         * In stock charts the X axis is ordinal by default, unless
         * the boost module is used and at least one of the series' data length
         * exceeds the [boostThreshold](#series.line.boostThreshold).
         *
         * For an ordinal axis, `minPadding` and `maxPadding` are ignored. Use
         * [overscroll](#xAxis.overscroll) instead.
         *
         * @sample {highstock} stock/xaxis/ordinal-true/
         *         True by default
         * @sample {highstock} stock/xaxis/ordinal-false/
         *         False
         *
         * @see [overscroll](#xAxis.overscroll)
         *
         * @type      {boolean}
         * @default   true
         * @since     1.1
         * @product   highstock
         * @apioption xAxis.ordinal
         */
        /**
         * Additional range on the right side of the xAxis. Works similar to
         * `xAxis.maxPadding`, but the value is set in terms of axis values,
         * percentage or pixels.
         *
         * If it's a number, it is interpreted as axis values, which in a
         * datetime axis equals milliseconds.
         *
         * If it's a percentage string, is interpreted as percentages of axis
         * length. An overscroll of 50% will make a 100px axis 50px longer.
         *
         * If it's a pixel string, it is interpreted as a fixed pixel value, but
         * limited to 90% of the axis length.
         *
         * @sample {highstock} stock/xaxis/overscroll/ One minute overscroll
         *         with live data
         * @sample {highstock} stock/xaxis/overscroll-percent/ Overscroll set in
         *         percentage
         * @sample {highstock} stock/xaxis/overscroll-pixel/ Overscroll set in
         *         pixels
         *
         * @type      {number | string}
         * @default   0
         * @since     6.0.0
         * @product   highstock
         * @apioption xAxis.overscroll
         */
        /**
         * Refers to the index in the [panes](#panes) array. Used for circular
         * gauges and polar charts. When the option is not set then first pane
         * will be used.
         *
         * @sample highcharts/demo/gauge-vu-meter
         *         Two gauges with different center
         *
         * @type      {number}
         * @product   highcharts
         * @apioption xAxis.pane
         */
        /**
         * The zoomed range to display when only defining one or none of `min`
         * or `max`. For example, to show the latest month, a range of one month
         * can be set.
         *
         * @sample {highstock} stock/xaxis/range/
         *         Setting a zoomed range when the rangeSelector is disabled
         *
         * @type      {number}
         * @product   highstock
         * @apioption xAxis.range
         */
        /**
         * Whether to reverse the axis so that the highest number is closest
         * to the origin. If the chart is inverted, the x axis is reversed by
         * default.
         *
         * @sample {highcharts} highcharts/yaxis/reversed/
         *         Reversed Y axis
         * @sample {highstock} stock/xaxis/reversed/
         *         Reversed Y axis
         *
         * @type      {boolean}
         * @default   undefined
         * @apioption xAxis.reversed
         */
        reversed: void 0,
        /**
         * This option determines how stacks should be ordered within a group.
         * For example reversed xAxis also reverses stacks, so first series
         * comes last in a group. To keep order like for non-reversed xAxis
         * enable this option.
         *
         * @sample {highcharts} highcharts/xaxis/reversedstacks/
         *         Reversed stacks comparison
         * @sample {highstock} highcharts/xaxis/reversedstacks/
         *         Reversed stacks comparison
         *
         * @since     6.1.1
         * @product   highcharts highstock
         */
        reversedStacks: false,
        /**
         * An optional scrollbar to display on the X axis in response to
         * limiting the minimum and maximum of the axis values.
         *
         * In styled mode, all the presentational options for the scrollbar are
         * replaced by the classes `.highcharts-scrollbar-thumb`,
         * `.highcharts-scrollbar-arrow`, `.highcharts-scrollbar-button`,
         * `.highcharts-scrollbar-rifles` and `.highcharts-scrollbar-track`.
         *
         * @sample {highstock} stock/yaxis/heatmap-scrollbars/
         *         Heatmap with both scrollbars
         *
         * @extends   scrollbar
         * @since     4.2.6
         * @product   highstock
         * @apioption xAxis.scrollbar
         */
        /**
         * Whether to show the axis line and title when the axis has no data.
         *
         * @sample {highcharts} highcharts/yaxis/showempty/
         *         When clicking the legend to hide series, one axis preserves
         *         line and title, the other doesn't
         * @sample {highstock} highcharts/yaxis/showempty/
         *         When clicking the legend to hide series, one axis preserves
         *         line and title, the other doesn't
         *
         * @since     1.1
         */
        showEmpty: true,
        /**
         * Whether to show the first tick label.
         *
         * @sample {highcharts} highcharts/xaxis/showfirstlabel-false/
         *         Set to false on X axis
         * @sample {highstock} stock/xaxis/showfirstlabel/
         *         Labels below plot lines on Y axis
         */
        showFirstLabel: true,
        /**
         * Whether to show the last tick label. Defaults to `true` on cartesian
         * charts, and `false` on polar charts.
         *
         * @sample {highcharts} highcharts/xaxis/showlastlabel-true/
         *         Set to true on X axis
         * @sample {highstock} stock/xaxis/showfirstlabel/
         *         Labels below plot lines on Y axis
         *
         * @type    {boolean}
         * @default undefined
         * @product highcharts highstock gantt
         */
        showLastLabel: true,
        /**
         * A soft maximum for the axis. If the series data maximum is less than
         * this, the axis will stay at this maximum, but if the series data
         * maximum is higher, the axis will flex to show all data.
         *
         * @sample highcharts/yaxis/softmin-softmax/
         *         Soft min and max
         *
         * @type      {number}
         * @since     5.0.1
         * @product   highcharts highstock gantt
         * @apioption xAxis.softMax
         */
        /**
         * A soft minimum for the axis. If the series data minimum is greater
         * than this, the axis will stay at this minimum, but if the series
         * data minimum is lower, the axis will flex to show all data.
         *
         * @sample highcharts/yaxis/softmin-softmax/
         *         Soft min and max
         *
         * @type      {number}
         * @since     5.0.1
         * @product   highcharts highstock gantt
         * @apioption xAxis.softMin
         */
        /**
         * For datetime axes, this decides where to put the tick between weeks.
         *  0 = Sunday, 1 = Monday.
         *
         * @sample {highcharts} highcharts/xaxis/startofweek-monday/
         *         Monday by default
         * @sample {highcharts} highcharts/xaxis/startofweek-sunday/
         *         Sunday
         * @sample {highstock} stock/xaxis/startofweek-1
         *         Monday by default
         * @sample {highstock} stock/xaxis/startofweek-0
         *         Sunday
         *
         * @product highcharts highstock gantt
         */
        startOfWeek: 1,
        /**
         * Whether to force the axis to start on a tick. Use this option with
         * the `minPadding` option to control the axis start.
         *
         * @productdesc {highstock}
         * In Highcharts Stock, `startOnTick` is always `false` when
         * the navigator is enabled, to prevent jumpy scrolling.
         *
         * @sample {highcharts} highcharts/xaxis/startontick-false/
         *         False by default
         * @sample {highcharts} highcharts/xaxis/startontick-true/
         *         True
         *
         * @since 1.2.0
         */
        startOnTick: false,
        /**
         * The amount of ticks to draw on the axis. This opens up for aligning
         * the ticks of multiple charts or panes within a chart. This option
         * overrides the `tickPixelInterval` option.
         *
         * This option only has an effect on linear axes. Datetime, logarithmic
         * or category axes are not affected.
         *
         * @sample {highcharts} highcharts/yaxis/tickamount/
         *         8 ticks on Y axis
         * @sample {highstock} highcharts/yaxis/tickamount/
         *         8 ticks on Y axis
         *
         * @type      {number}
         * @since     4.1.0
         * @product   highcharts highstock gantt
         * @apioption xAxis.tickAmount
         */
        /**
         * The interval of the tick marks in axis units. When `undefined`, the
         * tick interval is computed to approximately follow the
         * [tickPixelInterval](#xAxis.tickPixelInterval) on linear and datetime
         * axes. On categorized axes, a `undefined` tickInterval will default to
         * 1, one category. Note that datetime axes are based on milliseconds,
         * so for example an interval of one day is expressed as
         * `24 * 3600 * 1000`.
         *
         * On logarithmic axes, the tickInterval is based on powers, so a
         * tickInterval of 1 means one tick on each of 0.1, 1, 10, 100 etc. A
         * tickInterval of 2 means a tick of 0.1, 10, 1000 etc. A tickInterval
         * of 0.2 puts a tick on 0.1, 0.2, 0.4, 0.6, 0.8, 1, 2, 4, 6, 8, 10, 20,
         * 40 etc.
         *
         *
         * If the tickInterval is too dense for labels to be drawn, Highcharts
         * may remove ticks.
         *
         * If the chart has multiple axes, the [alignTicks](#chart.alignTicks)
         * option may interfere with the `tickInterval` setting.
         *
         * @see [tickPixelInterval](#xAxis.tickPixelInterval)
         * @see [tickPositions](#xAxis.tickPositions)
         * @see [tickPositioner](#xAxis.tickPositioner)
         *
         * @sample {highcharts} highcharts/xaxis/tickinterval-5/
         *         Tick interval of 5 on a linear axis
         * @sample {highstock} stock/xaxis/tickinterval/
         *         Tick interval of 0.01 on Y axis
         *
         * @type      {number}
         * @apioption xAxis.tickInterval
         */
        /**
         * The pixel length of the main tick marks.
         *
         * @sample {highcharts} highcharts/xaxis/ticklength/
         *         20 px tick length on the X axis
         * @sample {highstock} stock/xaxis/ticks/
         *         Formatted ticks on X axis
         */
        tickLength: 10,
        /**
         * For categorized axes only. If `on` the tick mark is placed in the
         * center of the category, if `between` the tick mark is placed between
         * categories. The default is `between` if the `tickInterval` is 1, else
         * `on`. In order to render tick marks on a category axis it is necessary
         * to provide a [tickWidth](#xAxis.tickWidth).
         *
         * @sample {highcharts} highcharts/xaxis/tickmarkplacement-between/
         *         "between" by default
         * @sample {highcharts} highcharts/xaxis/tickmarkplacement-on/
         *         "on"
         *
         * @product    highcharts gantt
         * @validvalue ["on", "between"]
         */
        tickmarkPlacement: 'between',
        /**
         * If tickInterval is `null` this option sets the approximate pixel
         * interval of the tick marks. Not applicable to categorized axis.
         *
         * The tick interval is also influenced by the [minTickInterval](
         * #xAxis.minTickInterval) option, that, by default prevents ticks from
         * being denser than the data points.
         *
         * @see [tickInterval](#xAxis.tickInterval)
         * @see [tickPositioner](#xAxis.tickPositioner)
         * @see [tickPositions](#xAxis.tickPositions)
         *
         * @sample {highcharts} highcharts/xaxis/tickpixelinterval-50/
         *         50 px on X axis
         * @sample {highstock} stock/xaxis/tickpixelinterval/
         *         200 px on X axis
         */
        tickPixelInterval: 100,
        /**
         * The position of the major tick marks relative to the axis line.
         * Can be one of `inside` and `outside`.
         *
         * @sample {highcharts} highcharts/xaxis/tickposition-outside/
         *         "outside" by default
         * @sample {highcharts} highcharts/xaxis/tickposition-inside/
         *         "inside"
         * @sample {highstock} stock/xaxis/ticks/
         *         Formatted ticks on X axis
         *
         * @validvalue ["inside", "outside"]
         */
        tickPosition: 'outside',
        /**
         * A callback function returning array defining where the ticks are
         * laid out on the axis. This overrides the default behavior of
         * [tickPixelInterval](#xAxis.tickPixelInterval) and [tickInterval](
         * #xAxis.tickInterval). The automatic tick positions are accessible
         * through `this.tickPositions` and can be modified by the callback.
         * Since v12.6.0, the callback also receives `ctx` as the third
         * argument, so that arrow functions can access the same context as
         * regular functions using `this`.
         *
         * @see [tickPositions](#xAxis.tickPositions)
         *
         * @sample {highcharts} highcharts/xaxis/tickpositions-tickpositioner/
         *         Demo of tickPositions and tickPositioner
         * @sample {highstock} highcharts/xaxis/tickpositions-tickpositioner/
         *         Demo of tickPositions and tickPositioner
         *
         * @type      {Highcharts.AxisTickPositionerCallbackFunction}
         * @apioption xAxis.tickPositioner
         */
        /**
         * An array defining where the ticks are laid out on the axis. This
         * overrides the default behavior of [tickPixelInterval](
         * #xAxis.tickPixelInterval) and [tickInterval](#xAxis.tickInterval).
         *
         * Note: When working with date-time axes, be aware of time zone
         * handling. See the [documentation on time options](https://www.highcharts.com/docs/chart-concepts/axes#datetime)
         * for best practices.
         *
         * @see [tickPositioner](#xAxis.tickPositioner)
         *
         * @sample {highcharts} highcharts/xaxis/tickpositions-tickpositioner/
         *         Demo of tickPositions and tickPositioner
         * @sample {highstock} highcharts/xaxis/tickpositions-tickpositioner/
         *         Demo of tickPositions and tickPositioner
         *
         * @type      {Array<number>}
         * @apioption xAxis.tickPositions
         */
        /**
         * The pixel width of the major tick marks. Defaults to 0 on category
         * axes, otherwise 1.
         *
         * In styled mode, the stroke width is given in the `.highcharts-tick`
         * class, but in order for the element to be generated on category axes,
         * the option must be explicitly set to 1.
         *
         * @sample {highcharts} highcharts/xaxis/tickwidth/
         *         10 px width
         * @sample {highcharts} highcharts/css/axis-grid/
         *         Styled mode
         * @sample {highstock} stock/xaxis/ticks/
         *         Formatted ticks on X axis
         * @sample {highstock} highcharts/css/axis-grid/
         *         Styled mode
         *
         * @type      {undefined|number}
         * @default   {highstock} 1
         * @default   {highmaps} 0
         * @apioption xAxis.tickWidth
         */
        /**
         * The axis title, showing next to the axis line.
         *
         * @productdesc {highmaps}
         * In Highmaps, the axis is hidden by default, but adding an axis title
         * is still possible. X axis and Y axis titles will appear at the bottom
         * and left by default.
         */
        title: {
            /**
             * Alignment of the title relative to the axis values. Possible
             * values are "low", "middle" or "high".
             *
             * @sample {highcharts} highcharts/xaxis/title-align-low/
             *         "low"
             * @sample {highcharts} highcharts/xaxis/title-align-center/
             *         "middle" by default
             * @sample {highcharts} highcharts/xaxis/title-align-high/
             *         "high"
             * @sample {highcharts} highcharts/yaxis/title-offset/
             *         Place the Y axis title on top of the axis
             * @sample {highstock} stock/xaxis/title-align/
             *         Aligned to "high" value
             *
             * @type {Highcharts.AxisTitleAlignValue}
             */
            align: 'middle',
            /**
             * Deprecated. Set the `text` to `undefined` to disable the title.
             *
             * @deprecated 3.0.0
             * @type      {boolean}
             * @product   highcharts
             * @apioption xAxis.title.enabled
             */
            /**
             * The pixel distance between the axis labels or line and the title.
             * Defaults to 0 for horizontal axes, 10 for vertical
             *
             * @sample {highcharts} highcharts/xaxis/title-margin/
             *         Y axis title margin of 60
             *
             * @type      {number}
             * @apioption xAxis.title.margin
             */
            /**
             * The distance from the plot area to the axis line. A positive
             * offset moves the axis with its line, labels and ticks away from
             * the plot area. With multiple axes the offset is dynamically
             * adjusted to avoid collision, this can be overridden by setting
             * offset explicitly.
             *
             * For radial axes in gauges, the offset can be a percentage string,
             * and defaults to render the line and ticks on the inside of the
             * pane and plot bands (since v13). Labels are not affected by the
             * offset in radial axes.
             *
             * @sample highcharts/yaxis/offset/
             *         Axis offfset
             * @sample {highcharts} highcharts/yaxis/offset-centered/
             *         Axes positioned in the center of the plot
             * @sample {highstock} stock/xaxis/offset/
             *         Y axis offset in stock chart
             *
             * @type      {number|string}
             * @since     2.2.0
             * @apioption xAxis.title.offset
             */
            /**
             * Whether to reserve space for the title when laying out the axis.
             *
             * @type      {boolean}
             * @default   true
             * @since     5.0.11
             * @product   highcharts highstock gantt
             * @apioption xAxis.title.reserveSpace
             */
            /**
             * The rotation of the text in degrees. 0 is horizontal, 270 is
             * vertical reading from bottom to top. Defaults to 0 for horizontal
             * axes, 270 for left-side axes and 90 for right-side axes.
             *
             * @sample    {highcharts} highcharts/yaxis/title-offset/
             *            Horizontal
             *
             * @type      {number}
             * @default   undefined
             * @apioption xAxis.title.rotation
             */
            /**
             * The actual text of the axis title. It can contain basic HTML tags
             * like `b`, `i` and `span` with style.
             *
             * @sample {highcharts} highcharts/xaxis/title-text/
             *         Custom HTML
             * @sample {highstock} stock/xaxis/title-text/
             *         Titles for both axes
             *
             * @type      {string|null}
             * @apioption xAxis.title.text
             */
            /**
             * Alignment of the text, can be `"left"`, `"right"` or `"center"`.
             * Default alignment depends on the
             * [title.align](xAxis.title.align):
             *
             * Horizontal axes:
             * - for `align` = `"low"`, `textAlign` is set to `left`
             * - for `align` = `"middle"`, `textAlign` is set to `center`
             * - for `align` = `"high"`, `textAlign` is set to `right`
             *
             * Vertical axes:
             * - for `align` = `"low"` and `opposite` = `true`, `textAlign` is
             *   set to `right`
             * - for `align` = `"low"` and `opposite` = `false`, `textAlign` is
             *   set to `left`
             * - for `align` = `"middle"`, `textAlign` is set to `center`
             * - for `align` = `"high"` and `opposite` = `true` `textAlign` is
             *   set to `left`
             * - for `align` = `"high"` and `opposite` = `false` `textAlign` is
             *   set to `right`
             *
             * @type      {Highcharts.AlignValue}
             * @apioption xAxis.title.textAlign
             */
            /**
             * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
             * to render the axis title.
             *
             * @product   highcharts highstock gantt
             */
            useHTML: false,
            /**
             * Horizontal pixel offset of the title position.
             *
             * @since     4.1.6
             * @product   highcharts highstock gantt
             */
            x: 0,
            /**
             * Vertical pixel offset of the title position.
             *
             * @product   highcharts highstock gantt
             */
            y: 0,
            /**
             * CSS styles for the title. If the title text is longer than the
             * axis length, it will wrap to multiple lines by default. This can
             * be customized by setting the `lineClamp` property, by setting a
             * specific `width` or by setting `whiteSpace: 'nowrap'`.
             *
             * In styled mode, the stroke width is given in the
             * `.highcharts-axis-title` class.
             *
             * @sample {highcharts} highcharts/xaxis/title-style/
             *         Red
             * @sample {highcharts} highcharts/css/axis/
             *         Styled mode
             *
             * @type    {Highcharts.CSSObject}
             */
            style: {
                /**
                 * @type {Highcharts.ColorType}
                 */
                color: 'var(--highcharts-neutral-color-60)',
                /**
                 * @type {number|string}
                 */
                fontSize: '0.8em'
            }
        },
        /**
         * The type of axis. Can be one of `linear`, `logarithmic`, `datetime`
         * or `category`. In a datetime axis, the numbers are given in
         * milliseconds, and tick marks are placed on appropriate values like
         * full hours or days. In a category axis, the
         * [point names](#series.line.data.name) of the chart's series are used
         * for categories, if not a [categories](#xAxis.categories) array is
         * defined.
         *
         * @sample {highcharts} highcharts/xaxis/type-linear/
         *         Linear
         * @sample {highcharts} highcharts/yaxis/type-log/
         *         Logarithmic
         * @sample {highcharts} highcharts/yaxis/type-log-minorgrid/
         *         Logarithmic with minor grid lines
         * @sample {highcharts} highcharts/xaxis/type-log-both/
         *         Logarithmic on two axes
         * @sample {highcharts} highcharts/yaxis/type-log-negative/
         *         Logarithmic with extension to emulate negative values
         *
         * @type    {Highcharts.AxisTypeValue}
         * @default linear
         * @product highcharts gantt
         * @apioption xAxis.type
         */
        /**
         * If there are multiple axes on the same side of the chart, the pixel
         * margin between the axes. Defaults to 0 on vertical axes, 15 on
         * horizontal axes.
         *
         * @type      {number}
         * @since     7.0.3
         * @apioption xAxis.margin
         */
        /**
         * Applies only when the axis `type` is `category`. When `uniqueNames`
         * is true, points are placed on the X axis according to their names.
         * If the same point name is repeated in the same or another series,
         * the point is placed on the same X position as other points of the
         * same name. When `uniqueNames` is false, the points are laid out in
         * increasing X positions regardless of their names, and the X axis
         * category will take the name of the last point in each position.
         *
         * @sample {highcharts} highcharts/xaxis/uniquenames-true/
         *         True by default
         * @sample {highcharts} highcharts/xaxis/uniquenames-false/
         *         False
         *
         * @since     4.2.7
         * @product   highcharts gantt
         * @type      {boolean}
         * @default   true
         * @apioption xAxis.uniqueNames
         */
        /**
         * Datetime axis only. An array determining what time intervals the
         * ticks are allowed to fall on. Each array item is an array where the
         * first value is the time unit and the second value another array of
         * allowed multiples.
         *
         * Defaults to:
         * ```js
         * units: [[
         *     'millisecond', // unit name
         *     [1, 2, 5, 10, 20, 25, 50, 100, 200, 500] // allowed multiples
         * ], [
         *     'second',
         *     [1, 2, 5, 10, 15, 30]
         * ], [
         *     'minute',
         *     [1, 2, 5, 10, 15, 30]
         * ], [
         *     'hour',
         *     [1, 2, 3, 4, 6, 8, 12]
         * ], [
         *     'day',
         *     [1, 2]
         * ], [
         *     'week',
         *     [1, 2]
         * ], [
         *     'month',
         *     [1, 2, 3, 4, 6]
         * ], [
         *     'year',
         *     null
         * ]]
         * ```
         *
         * @sample {highcharts} highcharts/xaxis/units/
         *         Axis units demonstrated
         *
         * @type      {Array<Array<string,(Array<number>|null)>>}
         * @product   highcharts highstock gantt
         * @apioption xAxis.units
         */
        /**
         * Whether axis, including axis title, line, ticks and labels, should
         * be visible.
         *
         * @since     4.1.9
         * @product   highcharts highstock gantt
         */
        visible: true,
        /**
         * Color of the minor, secondary grid lines.
         *
         * In styled mode, the stroke width is given in the
         * `.highcharts-minor-grid-line` class.
         *
         * @sample {highcharts} highcharts/yaxis/minorgridlinecolor/
         *         Bright grey lines from Y axis
         * @sample {highcharts|highstock} highcharts/css/axis-grid/
         *         Styled mode
         * @sample {highstock} stock/xaxis/minorgridlinecolor/
         *         Bright grey lines from Y axis
         *
         * @type    {Highcharts.ColorType}
         */
        minorGridLineColor: 'var(--highcharts-neutral-color-5)',
        /**
         * Width of the minor, secondary grid lines.
         *
         * In styled mode, the stroke width is given in the
         * `.highcharts-grid-line` class.
         *
         * @sample {highcharts} highcharts/yaxis/minorgridlinewidth/
         *         2px lines from Y axis
         * @sample {highcharts|highstock} highcharts/css/axis-grid/
         *         Styled mode
         * @sample {highstock} stock/xaxis/minorgridlinewidth/
         *         2px lines from Y axis
         */
        minorGridLineWidth: 1,
        /**
         * Color for the minor tick marks.
         *
         * @sample {highcharts} highcharts/yaxis/minortickcolor/
         *         Black tick marks on Y axis
         * @sample {highstock} stock/xaxis/minorticks/
         *         Black tick marks on Y axis
         *
         * @type    {Highcharts.ColorType}
         */
        minorTickColor: 'var(--highcharts-neutral-color-40)',
        /**
         * The color of the line marking the axis itself.
         *
         * In styled mode, the line stroke is given in the
         * `.highcharts-axis-line` or `.highcharts-xaxis-line` class.
         *
         * @sample {highcharts} highcharts/yaxis/linecolor/
         *         A red line on Y axis
         * @sample {highcharts|highstock} highcharts/css/axis/
         *         Axes in styled mode
         * @sample {highstock} stock/xaxis/linecolor/
         *         A red line on X axis
         *
         * @type    {Highcharts.ColorType}
         */
        lineColor: 'var(--highcharts-neutral-color-80)',
        /**
         * The width of the line marking the axis itself.
         *
         * In styled mode, the stroke width is given in the
         * `.highcharts-axis-line` or `.highcharts-xaxis-line` class.
         *
         * @sample {highcharts} highcharts/yaxis/linecolor/
         *         A 1px line on Y axis
         * @sample {highcharts|highstock} highcharts/css/axis/
         *         Axes in styled mode
         * @sample {highstock} stock/xaxis/linewidth/
         *         A 2px line on X axis
         *
         * @default {highcharts|highstock} 1
         * @default {highmaps} 0
         */
        lineWidth: 1,
        /**
         * Color of the grid lines extending the ticks across the plot area.
         *
         * In styled mode, the stroke is given in the `.highcharts-grid-line`
         * class.
         *
         * @productdesc {highmaps}
         * In Highmaps, the grid lines are hidden by default.
         *
         * @sample {highcharts} highcharts/yaxis/gridlinecolor/
         *         Green lines
         * @sample {highcharts|highstock} highcharts/css/axis-grid/
         *         Styled mode
         * @sample {highstock} stock/xaxis/gridlinecolor/
         *         Green lines
         *
         * @type    {Highcharts.ColorType}
         */
        gridLineColor: 'var(--highcharts-neutral-color-10)',
        /**
         * The width of the grid lines extending the ticks across the plot area.
         * Defaults to 1 on the Y axis and 0 on the X axis, except for 3d
         * charts and gauges.
         *
         * In gauges, the grid lines are limited to the pane's `size` and
         * `innerSize`.
         *
         * In styled mode, the stroke width is given in the
         * `.highcharts-grid-line` class.
         *
         * @sample {highcharts} highcharts/yaxis/gridlinewidth/
         *         2px lines
         * @sample {highcharts|highstock} highcharts/css/axis-grid/
         *         Styled mode
         * @sample {highcharts} highcharts/yaxis/radial-gridline
         *         Grid lines on gauge
         * @sample {highstock} stock/xaxis/gridlinewidth/
         *         2px lines
         *
         * @type      {number}
         * @apioption xAxis.gridLineWidth
         */
        gridLineWidth: void 0,
        /**
         * The height as the vertical axis. If it's a number, it is
         * interpreted as pixels.
         *
         * Since Highcharts 2: If it's a percentage string, it is interpreted
         * as percentages of the total plot height.
         *
         * @sample {highcharts} highcharts/xaxis/axis-position-properties
         *         Different axis position properties
         *
         * @type      {number|string}
         * @product   highcharts highstock
         * @apioption xAxis.height
         */
        /**
         * The width as the horizontal axis. If it's a number, it is interpreted
         * as pixels.
         *
         * Since Highcharts v5.0.13: If it's a percentage string, it is
         * interpreted as percentages of the total plot width.
         *
         * @sample {highcharts} highcharts/xaxis/axis-position-properties
         *         Different axis position properties
         *
         * @type      {number|string}
         * @product   highcharts highstock
         * @apioption xAxis.width
         */
        /**
         * Color for the main tick marks.
         *
         * In styled mode, the stroke is given in the `.highcharts-tick`
         * class.
         *
         * @sample {highcharts} highcharts/xaxis/tickcolor/
         *         Red ticks on X axis
         * @sample {highcharts|highstock} highcharts/css/axis-grid/
         *         Styled mode
         * @sample {highstock} stock/xaxis/ticks/
         *         Formatted ticks on X axis
         *
         * @type    {Highcharts.ColorType}
         */
        tickColor: 'var(--highcharts-neutral-color-80)'
        // `tickWidth: 1`
    };
    /**
     * The Z axis or depth axis for 3D plots.
     *
     * See the [Axis class](/class-reference/Highcharts.Axis) for programmatic
     * access to the axis.
     *
     * @sample {highcharts} highcharts/3d/scatter-zaxis-categories/
     *         Z-Axis with Categories
     * @sample {highcharts} highcharts/3d/scatter-zaxis-grid/
     *         Z-Axis with styling
     *
     * @type      {*|Array<*>}
     * @extends   xAxis
     * @since     5.0.0
     * @product   highcharts
     * @excluding breaks, crosshair, height, left, lineColor, lineWidth,
     *            nameToX, showEmpty, top, width
     * @apioption zAxis
     */
    /**
     * The Y axis or value axis. Normally this is the vertical axis,
     * though if the chart is inverted this is the horizontal axis.
     * In case of multiple axes, the yAxis node is an array of
     * configuration objects.
     *
     * See [the Axis object](/class-reference/Highcharts.Axis) for programmatic
     * access to the axis.
     *
     * @type         {*|Array<*>}
     * @extends      xAxis
     * @excluding    currentDateIndicator,ordinal,overscroll
     * @optionparent yAxis
     */
    AxisDefaults.yAxis = {
        /**
         * The type of axis. Can be one of `linear`, `logarithmic`, `datetime`,
         * `category` or `treegrid`. Defaults to `treegrid` for Gantt charts,
         * `linear` for other chart types.
         *
         * In a datetime axis, the numbers are given in milliseconds, and tick
         * marks are placed on appropriate values, like full hours or days. In a
         * category or treegrid axis, the [point names](#series.line.data.name)
         * of the chart's series are used for categories, if a
         * [categories](#xAxis.categories) array is not defined.
         *
         * @sample {highcharts} highcharts/yaxis/type-log-minorgrid/
         *         Logarithmic with minor grid lines
         * @sample {highcharts} highcharts/yaxis/type-log-negative/
         *         Logarithmic with extension to emulate negative values
         * @sample {gantt} gantt/treegrid-axis/demo
         *         Treegrid axis
         *
         * @type      {Highcharts.AxisTypeValue}
         * @default   {highcharts} linear
         * @default   {gantt} treegrid
         * @product   highcharts gantt
         * @apioption yAxis.type
         */
        /**
         * The height of the Y axis. If it's a number, it is interpreted as
         * pixels.
         *
         * Since Highcharts 2: If it's a percentage string, it is interpreted as
         * percentages of the total plot height.
         *
         * @see [yAxis.top](#yAxis.top)
         *
         * @sample {highstock} stock/demo/candlestick-and-volume/
         *         Percentage height panes
         *
         * @type      {number|string}
         * @product   highcharts highstock
         * @apioption yAxis.height
         */
        /**
         * Solid gauge only. Unless [stops](#yAxis.stops) are set, the color
         * to represent the maximum value of the Y axis.
         *
         * @sample {highcharts} highcharts/yaxis/mincolor-maxcolor/
         *         Min and max colors
         *
         * @type      {Highcharts.ColorType}
         * @default   #003399
         * @since     4.0
         * @product   highcharts
         * @apioption yAxis.maxColor
         */
        /**
         * Solid gauge only. Unless [stops](#yAxis.stops) are set, the color
         * to represent the minimum value of the Y axis.
         *
         * @sample {highcharts} highcharts/yaxis/mincolor-maxcolor/
         *         Min and max color
         *
         * @type      {Highcharts.ColorType}
         * @default   #e6ebf5
         * @since     4.0
         * @product   highcharts
         * @apioption yAxis.minColor
         */
        /**
         * Whether to reverse the axis so that the highest number is closest
         * to the origin.
         *
         * @sample {highcharts} highcharts/yaxis/reversed/
         *         Reversed Y axis
         * @sample {highstock} stock/xaxis/reversed/
         *         Reversed Y axis
         *
         * @type      {boolean}
         * @default   {highcharts} false
         * @default   {highstock} false
         * @default   {highmaps} true
         * @default   {gantt} true
         * @apioption yAxis.reversed
         */
        /**
         * If `true`, the first series in a stack will be drawn on top in a
         * positive, non-reversed Y axis. If `false`, the first series is in
         * the base of the stack.
         *
         * @sample {highcharts} highcharts/yaxis/reversedstacks-false/
         *         Non-reversed stacks
         * @sample {highstock} highcharts/yaxis/reversedstacks-false/
         *         Non-reversed stacks
         *
         * @type      {boolean}
         * @default   true
         * @since     3.0.10
         * @product   highcharts highstock
         * @apioption yAxis.reversedStacks
         */
        reversedStacks: true,
        /**
         * Solid gauge series only. Color stops for the solid gauge. Use this
         * in cases where a linear gradient between a `minColor` and `maxColor`
         * is not sufficient. The stops is an array of tuples, where the first
         * item is a float between 0 and 1 assigning the relative position in
         * the gradient, and the second item is the color.
         *
         * For solid gauges, the Y axis also inherits the concept of
         * [data classes](https://api.highcharts.com/highmaps#colorAxis.dataClasses)
         * from the Highmaps color axis.
         *
         * @sample {highcharts} highcharts/demo/gauge-solid/
         *         Gauge with stops
         *
         * @see [minColor](#yAxis.minColor)
         * @see [maxColor](#yAxis.maxColor)
         *
         * @type      {Array<Array<number,Highcharts.ColorType>>}
         * @since     4.0
         * @product   highcharts
         * @apioption yAxis.stops
         */
        /**
         * The pixel width of the major tick marks.
         *
         * @sample {highcharts} highcharts/xaxis/tickwidth/ 10 px width
         * @sample {highstock} stock/xaxis/ticks/ Formatted ticks on X axis
         *
         * @type      {number}
         * @default   0
         * @product   highcharts highstock gantt
         * @apioption yAxis.tickWidth
         */
        /**
         * Whether to force the axis to end on a tick. Use this option with
         * the `maxPadding` option to control the axis end.
         *
         * This option is always disabled, when panning type is
         * either `y` or `xy`.
         *
         * @see [type](#chart.panning.type)
         *
         *
         * @sample {highcharts} highcharts/yaxis/endontick/
         *         True by default
         * @sample {highcharts} highcharts/yaxis/endontick-false/
         *         False
         * @sample {highstock} stock/demo/basic-line/
         *         True by default
         * @sample {highstock} stock/xaxis/endontick/
         *         False for Y axis
         *
         * @since 1.2.0
         */
        endOnTick: true,
        /**
         * Padding of the max value relative to the length of the axis. A
         * padding of 0.05 will make a 100px axis 5px longer. This is useful
         * when you don't want the highest data value to appear on the edge
         * of the plot area. When the axis' `max` option is set or a max extreme
         * is set using `axis.setExtremes()`, the maxPadding will be ignored.
         *
         * Also the `softThreshold` option takes precedence over `maxPadding`,
         * so if the data is tangent to the threshold, `maxPadding` may not
         * apply unless `softThreshold` is set to false.
         *
         * @sample {highcharts} highcharts/yaxis/maxpadding-02/
         *         Max padding of 0.2
         * @sample {highstock} stock/xaxis/minpadding-maxpadding/
         *         Greater min- and maxPadding
         *
         * @since   1.2.0
         * @product highcharts highstock gantt
         */
        maxPadding: 0.05,
        /**
         * Padding of the min value relative to the length of the axis. A
         * padding of 0.05 will make a 100px axis 5px longer. This is useful
         * when you don't want the lowest data value to appear on the edge
         * of the plot area. When the axis' `min` option is set or a max extreme
         * is set using `axis.setExtremes()`, the maxPadding will be ignored.
         *
         * Also the `softThreshold` option takes precedence over `minPadding`,
         * so if the data is tangent to the threshold, `minPadding` may not
         * apply unless `softThreshold` is set to false.
         *
         * @sample {highcharts} highcharts/yaxis/minpadding/
         *         Min padding of 0.2
         * @sample {highstock} stock/xaxis/minpadding-maxpadding/
         *         Greater min- and maxPadding
         *
         * @since   1.2.0
         * @product highcharts highstock gantt
         */
        minPadding: 0.05,
        /**
         * @productdesc {highstock}
         * In Highcharts Stock 1.x, the Y axis was placed
         * on the left side by default.
         *
         * @sample {highcharts} highcharts/yaxis/opposite/
         *         Secondary Y axis opposite
         * @sample {highstock} stock/xaxis/opposite/
         *         Y axis on left side
         *
         * @type      {boolean}
         * @default   {highstock} true
         * @default   {highcharts} false
         * @product   highstock highcharts gantt
         * @apioption yAxis.opposite
         */
        /**
         * @see [tickInterval](#xAxis.tickInterval)
         * @see [tickPositioner](#xAxis.tickPositioner)
         * @see [tickPositions](#xAxis.tickPositions)
         */
        tickPixelInterval: 72,
        /**
         * Whether to show the last tick label.
         *
         * @productdesc {highcharts|gantt}
         * Defaults to `true` on cartesian charts, and `false` on polar charts.
         *
         * @productdesc {highstock}
         * Defaults to `true` for categorized yAxis and `false` for other types
         * of yAxis.
         *
         * @default undefined
         */
        showLastLabel: true,
        /**
         * @extends xAxis.labels
         */
        labels: {
            /**
             * The label's pixel distance from the perimeter of the plot area.
             * On cartesian charts, this is overridden if the `labels.y` setting
             * is set.
             *
             * On polar charts, if it's a percentage string, it is interpreted
             * the same as [series.radius](#plotOptions.gauge.radius), so the
             * label can be aligned under the gauge's shape.
             *
             * @sample {highcharts} highcharts/yaxis/labels-distance/
             *         Polar chart, labels centered under the arc
             *
             * @type      {number|string}
             * @product   highcharts
             * @apioption yAxis.labels.distance
             */
            /**
             * The y position offset of all labels relative to the tick
             * positions on the axis. For polar and radial axis consider the use
             * of the [distance](#yAxis.labels.distance) option.
             *
             * @sample {highcharts} highcharts/xaxis/labels-x/
             *         Y axis labels placed on grid lines
             *
             * @type      {number}
             * @default   {highcharts} 3
             * @default   {highstock} -2
             * @default   {highmaps} 3
             * @apioption yAxis.labels.y
             */
            /**
             * What part of the string the given position is anchored to. Can
             * be one of `"left"`, `"center"` or `"right"`. The exact position
             * also depends on the `labels.x` setting.
             *
             * Angular gauges and solid gauges defaults to `"center"`.
             * Solid gauges with two labels have additional option `"auto"`
             * for automatic horizontal and vertical alignment.
             *
             * @sample {highcharts} highcharts/yaxis/labels-align-left/
             *         Left
             * @sample {highcharts} highcharts/series-solidgauge/labels-auto-aligned/
             *         Solid gauge labels auto aligned
             *
             * @type       {Highcharts.AlignValue}
             * @default    {highstock} right
             * @apioption  yAxis.labels.align
             */
            /**
             * The x position offset of all labels relative to the tick
             * positions on the axis. Defaults to -15 for left axis, 15 for
             * right axis.
             *
             * @sample {highcharts} highcharts/xaxis/labels-x/
             *         Y axis labels placed on grid lines
             *
             * @type {number}
             */
            x: void 0
        },
        /**
         * @sample {highcharts} highcharts/yaxis/max-200/
         *         Y axis max of 200
         * @sample {highcharts} highcharts/yaxis/max-logarithmic/
         *         Y axis max on logarithmic axis
         * @sample {highstock} stock/yaxis/min-max/
         *         Fixed min and max on Y axis
         *
         * @apioption yAxis.max
         */
        /**
         * @sample {highcharts} highcharts/yaxis/min-startontick-false/
         *         -50 with startOnTick to false
         * @sample {highcharts} highcharts/yaxis/min-startontick-true/
         *         -50 with startOnTick true by default
         * @sample {highstock} stock/yaxis/min-max/
         *         Fixed min and max on Y axis
         *
         * @apioption yAxis.min
         */
        /**
         * An optional scrollbar to display on the Y axis in response to
         * limiting the minimum an maximum of the axis values.
         *
         * In styled mode, all the presentational options for the scrollbar
         * are replaced by the classes `.highcharts-scrollbar-thumb`,
         * `.highcharts-scrollbar-arrow`, `.highcharts-scrollbar-button`,
         * `.highcharts-scrollbar-rifles` and `.highcharts-scrollbar-track`.
         *
         * @sample {highstock} stock/yaxis/scrollbar/
         *         Scrollbar on the Y axis
         *
         * @extends   scrollbar
         * @since     4.2.6
         * @product   highstock
         * @excluding height
         * @apioption yAxis.scrollbar
         */
        /**
         * Enable the scrollbar on the Y axis.
         *
         * @sample {highstock} stock/yaxis/scrollbar/
         *         Enabled on Y axis
         *
         * @type      {boolean}
         * @default   false
         * @since     4.2.6
         * @product   highstock
         * @apioption yAxis.scrollbar.enabled
         */
        /**
         * Pixel margin between the scrollbar and the axis elements.
         *
         * @type      {number}
         * @default   10
         * @since     4.2.6
         * @product   highstock
         * @apioption yAxis.scrollbar.margin
         */
        /* eslint-disable @highcharts/highcharts/doclet-apioption-last */
        /**
         * Defines the position of the scrollbar. By default, it is positioned
         * on the opposite of the main axis (right side of the chart).
         * However, in the case of RTL languages could be set to `false`
         * which positions the scrollbar on the left.
         *
         * Works only for vertical axes.
         * This means yAxis in a non-inverted chart and xAxis in the inverted.
         *
         * @sample stock/yaxis/scrollbar-opposite/
         *         A scrollbar not on the opposite side
         *
         * @type      {boolean}
         * @default   true
         * @since 9.3.0
         *
         * @apioption yAxis.scrollbar.opposite
         * @apioption xAxis.scrollbar.opposite
         *
         */
        /* eslint-enable @highcharts/highcharts/doclet-apioption-last */
        /**
         * Whether to show the scrollbar when it is fully zoomed out at max
         * range. Setting it to `false` on the Y axis makes the scrollbar stay
         * hidden until the user zooms in, like common in browsers.
         *
         * @type      {boolean}
         * @default   true
         * @since     4.2.6
         * @product   highstock
         * @apioption yAxis.scrollbar.showFull
         */
        /**
         * The width of a vertical scrollbar or height of a horizontal
         * scrollbar. Defaults to 20 on touch devices.
         *
         * @type      {number}
         * @default   14
         * @since     4.2.6
         * @product   highstock
         * @apioption yAxis.scrollbar.size
         */
        /**
         * Z index of the scrollbar elements.
         *
         * @type      {number}
         * @default   3
         * @since     4.2.6
         * @product   highstock
         * @apioption yAxis.scrollbar.zIndex
         */
        /**
         * A soft maximum for the axis. If the series data maximum is less
         * than this, the axis will stay at this maximum, but if the series
         * data maximum is higher, the axis will flex to show all data.
         *
         * **Note**: The [series.softThreshold](
         * #plotOptions.series.softThreshold) option takes precedence over this
         * option.
         *
         * @sample highcharts/yaxis/softmin-softmax/
         *         Soft min and max
         *
         * @type      {number}
         * @since     5.0.1
         * @product   highcharts highstock gantt
         * @apioption yAxis.softMax
         */
        /**
         * A soft minimum for the axis. If the series data minimum is greater
         * than this, the axis will stay at this minimum, but if the series
         * data minimum is lower, the axis will flex to show all data.
         *
         * **Note**: The [series.softThreshold](
         * #plotOptions.series.softThreshold) option takes precedence over this
         * option.
         *
         * @sample highcharts/yaxis/softmin-softmax/
         *         Soft min and max
         *
         * @type      {number}
         * @since     5.0.1
         * @product   highcharts highstock gantt
         * @apioption yAxis.softMin
         */
        /**
         * Whether to force the axis to start on a tick. Use this option with
         * the `maxPadding` option to control the axis start.
         *
         * This option is always disabled, when panning type is
         * either `y` or `xy`.
         *
         * @see [type](#chart.panning.type)
         *
         * @sample {highcharts} highcharts/xaxis/startontick-false/
         *         False by default
         * @sample {highcharts} highcharts/xaxis/startontick-true/
         *         True
         * @sample {highstock} stock/xaxis/endontick/
         *         False for Y axis
         *
         * @since   1.2.0
         * @product highcharts highstock gantt
         */
        startOnTick: true,
        title: {
        /**
         * The pixel distance between the axis labels and the title.
         * Positive values are outside the axis line, negative are inside.
         *
         * @sample {highcharts} highcharts/xaxis/title-margin/
         *         Y axis title margin of 60
         *
         * @type      {number}
         * @default   40
         * @apioption yAxis.title.margin
         */
        /**
         * The actual text of the axis title. Horizontal texts can contain
         * HTML, but rotated texts are painted using vector techniques and
         * must be clean text. The Y axis title is disabled by setting the
         * `text` option to `undefined`. The default value is overridden by
         * the `lang.yAxisTitle` language option.
         *
         * @sample {highcharts} highcharts/xaxis/title-text/
         *         Custom HTML
         *
         * @type    {string|undefined}
         * @default {highcharts} Values
         * @default {highstock} undefined
         * @product highcharts highstock gantt
         * @apioption yAxis.title.text
         */
        },
        /**
         * The top position of the Y axis. If it's a number, it is interpreted
         * as pixel position relative to the chart.
         *
         * Since Highcharts 2: If it's a percentage string, it is interpreted as
         * percentages of the plot height, offset from plot area top.
         *
         * @see [yAxis.height](#yAxis.height)
         *
         * @sample {highstock} stock/demo/candlestick-and-volume/
         *         Percentage height panes
         *
         * @type      {number|string}
         * @product   highcharts highstock
         * @apioption yAxis.top
         */
        /**
         * The stack labels show the total value for each bar in a stacked
         * column or bar chart. The label will be placed on top of positive
         * columns and below negative columns. In case of an inverted column
         * chart or a bar chart the label is placed to the right of positive
         * bars and to the left of negative bars.
         *
         * @product highcharts
         */
        stackLabels: {
            /**
             * Defines the horizontal alignment of the stack total label. Can be
             * one of `"left"`, `"center"` or `"right"`. The default value is
             * calculated at runtime and depends on orientation and whether the
             * stack is positive or negative.
             *
             * @sample {highcharts} highcharts/yaxis/stacklabels-align-left/
             *         Aligned to the left
             * @sample {highcharts} highcharts/yaxis/stacklabels-align-center/
             *         Aligned in center
             * @sample {highcharts} highcharts/yaxis/stacklabels-align-right/
             *         Aligned to the right
             *
             * @type      {Highcharts.AlignValue}
             * @since     2.1.5
             * @product   highcharts
             * @apioption yAxis.stackLabels.align
             */
            /**
             * A format string for the data label. Available variables are the
             * same as for `formatter`.
             *
             * @type      {string}
             * @default   {total}
             * @since     3.0.2
             * @product   highcharts highstock
             * @apioption yAxis.stackLabels.format
             */
            /**
             * Rotation of the labels in degrees.
             *
             * @sample {highcharts} highcharts/yaxis/stacklabels-rotation/
             *         Labels rotated 45°
             *
             * @type      {number}
             * @default   0
             * @since     2.1.5
             * @product   highcharts
             * @apioption yAxis.stackLabels.rotation
             */
            /**
             * The text alignment for the label. While `align` determines where
             * the texts anchor point is placed with regards to the stack,
             * `textAlign` determines how the text is aligned against its anchor
             * point. Possible values are `"left"`, `"center"` and `"right"`.
             * The default value is calculated at runtime and depends on
             * orientation and whether the stack is positive or negative.
             *
             * @sample {highcharts} highcharts/yaxis/stacklabels-textalign-left/
             *         Label in center position but text-aligned left
             *
             * @type      {Highcharts.AlignValue}
             * @since     2.1.5
             * @product   highcharts
             * @apioption yAxis.stackLabels.textAlign
             */
            /**
             * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
             * to render the labels.
             *
             * @type      {boolean}
             * @default   false
             * @since     3.0
             * @product   highcharts highstock
             * @apioption yAxis.stackLabels.useHTML
             */
            /**
             * Defines the vertical alignment of the stack total label. Can be
             * one of `"top"`, `"middle"` or `"bottom"`. The default value is
             * calculated at runtime and depends on orientation and whether the
             * stack is positive or negative.
             *
             * @sample {highcharts} highcharts/yaxis/stacklabels-verticalalign-top/
             *         Vertically aligned top
             * @sample {highcharts} highcharts/yaxis/stacklabels-verticalalign-middle/
             *         Vertically aligned middle
             * @sample {highcharts} highcharts/yaxis/stacklabels-verticalalign-bottom/
             *         Vertically aligned bottom
             *
             * @type      {Highcharts.VerticalAlignValue}
             * @since     2.1.5
             * @product   highcharts
             * @apioption yAxis.stackLabels.verticalAlign
             */
            /**
             * The x position offset of the label relative to the left of the
             * stacked bar. The default value is calculated at runtime and
             * depends on orientation and whether the stack is positive or
             * negative.
             *
             * @sample {highcharts} highcharts/yaxis/stacklabels-x/
             *         Stack total labels with x offset
             *
             * @type      {number}
             * @since     2.1.5
             * @product   highcharts
             * @apioption yAxis.stackLabels.x
             */
            /**
             * The y position offset of the label relative to the tick position
             * on the axis. The default value is calculated at runtime and
             * depends on orientation and whether the stack is positive or
             * negative.
             *
             * @sample {highcharts} highcharts/yaxis/stacklabels-y/
             *         Stack total labels with y offset
             *
             * @type      {number}
             * @since     2.1.5
             * @product   highcharts
             * @apioption yAxis.stackLabels.y
             */
            /**
             * Enable or disable the initial animation when a series is
             * displayed for the `stackLabels`. The animation can also be set as
             * a configuration object. Please note that this option only
             * applies to the initial animation.
             * For other animations, see [chart.animation](#chart.animation)
             * and the animation parameter under the API methods.
             * The following properties are supported:
             *
             * - `defer`: The animation delay time in milliseconds.
             *
             * @sample {highcharts} highcharts/plotoptions/animation-defer/
             *          Animation defer settings
             * @type {boolean|Partial<Highcharts.AnimationOptionsObject>}
             * @since 8.2.0
             * @apioption yAxis.stackLabels.animation
             */
            animation: {},
            /**
             * The animation delay time in milliseconds.
             * Set to `0` renders stackLabel immediately.
             * As `undefined` inherits defer time from the [series.animation.defer](#plotOptions.series.animation.defer).
             *
             * @type      {number}
             * @since 8.2.0
             * @apioption yAxis.stackLabels.animation.defer
             */
            /**
             * Allow the stack labels to overlap.
             *
             * @sample {highcharts} highcharts/yaxis/stacklabels-allowoverlap-false/
             *         Default false
             *
             * @since   5.0.13
             * @product highcharts
             */
            allowOverlap: false,
            /**
             * The background color or gradient for the stack label.
             *
             * @sample {highcharts} highcharts/yaxis/stacklabels-box/
             *          Stack labels box options
             * @type      {Highcharts.ColorType}
             * @since 8.1.0
             * @apioption yAxis.stackLabels.backgroundColor
             */
            /**
             * The border color for the stack label. Defaults to `undefined`.
             *
             * @sample {highcharts} highcharts/yaxis/stacklabels-box/
             *          Stack labels box options
             * @type      {Highcharts.ColorType}
             * @since 8.1.0
             * @apioption yAxis.stackLabels.borderColor
             */
            /**
             * The border radius in pixels for the stack label.
             *
             * @sample {highcharts} highcharts/yaxis/stacklabels-box/
             *          Stack labels box options
             * @type      {number}
             * @default   0
             * @since 8.1.0
             * @apioption yAxis.stackLabels.borderRadius
             */
            /**
             * The border width in pixels for the stack label.
             *
             * @sample {highcharts} highcharts/yaxis/stacklabels-box/
             *          Stack labels box options
             * @type      {number}
             * @default   0
             * @since 8.1.0
             * @apioption yAxis.stackLabels.borderWidth
             */
            /**
             * Enable or disable the stack total labels.
             *
             * @sample {highcharts} highcharts/yaxis/stacklabels-enabled/
             *         Enabled stack total labels
             * @sample {highcharts} highcharts/yaxis/stacklabels-enabled-waterfall/
             *         Enabled stack labels in waterfall chart
             *
             * @since   2.1.5
             * @product highcharts
             */
            enabled: false,
            /**
             * Whether to hide stack labels that are outside the plot area.
             * By default, the stack label is moved
             * inside the plot area according to the
             * [overflow](/highcharts/#yAxis/stackLabels/overflow)
             * option.
             *
             * @type  {boolean}
             * @since 7.1.3
             */
            crop: true,
            /**
             * How to handle stack total labels that flow outside the plot area.
             * The default is set to `"justify"`,
             * which aligns them inside the plot area.
             * For columns and bars, this means it will be moved inside the bar.
             * To display stack labels outside the plot area,
             * set `crop` to `false` and `overflow` to `"allow"`.
             *
             * @sample highcharts/yaxis/stacklabels-overflow/
             *         Stack labels flows outside the plot area.
             *
             * @type  {Highcharts.DataLabelsOverflowValue}
             * @since 7.1.3
             */
            overflow: 'justify',
            /**
             * Callback JavaScript function to format the label. The value is
             * given by `this.total`. Since v12.6.0, the callback also receives
             * `ctx` as the first argument, so that arrow functions can access
             * the same context as regular functions using `this`.
             *
             * @sample {highcharts} highcharts/yaxis/stacklabels-formatter/
             *         Added units to stack total value
             *
             * @type    {Highcharts.FormatterCallbackFunction<Highcharts.StackItemObject>}
             * @since   2.1.5
             * @product highcharts
             */
            formatter: function () {
                return this.axis.chart.numberFormatter(this.total || 0, -1);
            },
            /**
             * CSS styles for the label.
             *
             * In styled mode, the styles are set in the
             * `.highcharts-stack-label` class.
             *
             * @sample {highcharts} highcharts/yaxis/stacklabels-style/
             *         Red stack total labels
             *
             * @type    {Highcharts.CSSObject}
             * @since   2.1.5
             * @product highcharts
             */
            style: {
                /** @type {Highcharts.ColorType} */
                color: 'var(--highcharts-neutral-color-100)',
                /**
                 * @type {number|string}
                 */
                fontSize: '0.7em',
                fontWeight: 'bold',
                textOutline: '1px contrast'
            }
        },
        gridLineWidth: 1,
        lineWidth: 0
    };
})(AxisDefaults || (AxisDefaults = {}));
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Axis_AxisDefaults = (AxisDefaults);

;// ./code/es-modules/Core/Foundation.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


/* *
 *
 *  Class Namespace
 *
 * */
/** @internal */
var Foundation;
(function (Foundation) {
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Register event options. If an event handler is set on the options, it
     * should be subject to Chart.update, Axis.update and Series.update. This is
     * contrary to general handlers that are set directly using addEvent either
     * on the class or on the instance. #6538, #6943, #10861.
     * @internal
     */
    function registerEventOptions(component, options) {
        // A lookup over those events that are added by _options_ (not
        // programmatically). These are updated through .update()
        component.eventOptions = component.eventOptions || {};
        // Register event listeners
        objectEach(options.events, function (event, eventType) {
            // If event does not exist, or is changed by the .update()
            // function
            if (component.eventOptions[eventType] !== event) {
                // Remove existing if set by option
                if (component.eventOptions[eventType]) {
                    removeEvent(component, eventType, component.eventOptions[eventType]);
                    delete component.eventOptions[eventType];
                }
                if (isFunction(event)) {
                    component.eventOptions[eventType] = event;
                    addEvent(component, eventType, event, {
                        order: 0 // #14080 fire those events as firsts
                    });
                }
            }
        });
    }
    Foundation.registerEventOptions = registerEventOptions;
})(Foundation || (Foundation = {}));
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
/* harmony default export */ const Core_Foundation = (Foundation);

;// ./code/es-modules/Core/Axis/Tick.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */



const { deg2rad: Tick_deg2rad } = Core_Globals;

/* *
 *
 *  Class
 *
 * */
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
 * The Tick class.
 *
 * @class
 * @name Highcharts.Tick
 *
 * @param {Highcharts.Axis} axis
 * The axis of the tick.
 *
 * @param {number} pos
 * The position of the tick on the axis in terms of axis values.
 *
 * @param {string} [type]
 * The type of tick, either 'minor' or an empty string
 *
 * @param {boolean} [noLabel=false]
 * Whether to disable the label or not. Defaults to false.
 *
 * @param {Object} [parameters]
 * Optional parameters for the tick.
 */
class Tick {
    /* *
     *
     *  Constructors
     *
     * */
    constructor(axis, pos, type, noLabel, parameters) {
        /** @internal */
        this.isNew = true;
        /** @internal */
        this.isNewLabel = true;
        /**
         * The related axis of the tick.
         * @name Highcharts.Tick#axis
         * @type {Highcharts.Axis}
         */
        this.axis = axis;
        /**
         * The logical position of the tick on the axis in terms of axis values.
         * @name Highcharts.Tick#pos
         * @type {number}
         */
        this.pos = pos;
        /**
         * The tick type, which can be `"minor"`, or an empty string.
         * @name Highcharts.Tick#type
         * @type {string}
         */
        this.type = type || '';
        this.parameters = parameters || {};
        /**
         * The mark offset of the tick on the axis. Usually `undefined`, numeric
         * for grid axes.
         * @name Highcharts.Tick#tickmarkOffset
         * @type {number|undefined}
         */
        this.tickmarkOffset = this.parameters.tickmarkOffset;
        this.options = this.parameters.options;
        fireEvent(this, 'init');
        if (!type && !noLabel) {
            this.addLabel();
        }
    }
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Write the tick label.
     *
     * @internal
     * @function Highcharts.Tick#addLabel
     */
    addLabel() {
        const tick = this, axis = tick.axis, options = axis.options, chart = axis.chart, categories = axis.categories, log = axis.logarithmic, names = axis.names, pos = tick.pos, labelOptions = pick(tick.options?.labels, options.labels), tickPositions = axis.tickPositions, isFirst = pos === tickPositions[0], isLast = pos === tickPositions[tickPositions.length - 1], tickPositionInfo = tickPositions.info, boundary = tickPositionInfo?.boundaryTicks[pos], DTLFormats = options.dateTimeLabelFormats;
        let label = tick.label, dateTimeLabelFormat, dateTimeLabelFormats, i;
        // The context value
        let value = this.parameters.category || (categories ?
            pick(categories[pos], names[pos], pos) :
            pos);
        if (log && isNumber(value)) {
            value = correctFloat(log.lin2log(value));
        }
        // Set the datetime label format. If a boundary is set for this
        // position, use that. If not, use the main format from base ticks.
        if (axis.dateTime && DTLFormats) {
            if (tickPositionInfo) {
                const boundariesMap = {
                    millisecond: 'hour',
                    second: 'hour',
                    minute: 'hour',
                    hour: 'day',
                    day: 'month',
                    week: 'month',
                    month: 'year',
                    year: 'year'
                };
                const unitName = tickPositionInfo.unitName, boundaryKey = boundariesMap[unitName], format = !options.grid?.enabled &&
                    boundary &&
                    boundaryKey &&
                    DTLFormats[boundaryKey]?.boundary ||
                    DTLFormats[unitName];
                dateTimeLabelFormats = chart.time.resolveDTLFormat(format);
                dateTimeLabelFormat = dateTimeLabelFormats.main;
            }
            else if (isNumber(value)) { // #1441
                dateTimeLabelFormat = axis.dateTime.getXDateFormat(value, DTLFormats || {});
            }
        }
        // Set properties for access in render method
        /**
         * True if the tick is the first one on the axis.
         * @name Highcharts.Tick#isFirst
         * @readonly
         * @type {boolean|undefined}
         */
        tick.isFirst = isFirst;
        /**
         * True if the tick is the last one on the axis.
         * @name Highcharts.Tick#isLast
         * @readonly
         * @type {boolean|undefined}
         */
        tick.isLast = isLast;
        /**
         * Boundary time unit for the label (e.g `day`, `month`, `year`), used
         * for date/time formatting.
         * @name Highcharts.Tick#boundary
         * @type {string|undefined}
         */
        tick.boundary = boundary;
        // Get the string
        const ctx = {
            axis,
            chart,
            dateTimeLabelFormat: dateTimeLabelFormat,
            boundary,
            isFirst,
            isLast,
            pos,
            tick: tick,
            tickPositionInfo,
            value
        };
        // Fire an event that allows modifying the context for use in
        // `labels.format` and `labels.formatter`.
        fireEvent(this, 'labelFormat', ctx);
        // Label formatting. When `labels.format` is given, we first run the
        // defaultFormatter and append the result to the context as `text`.
        // Handy for adding prefix or suffix while keeping default number
        // formatting.
        const labelFormatter = (ctx) => {
            if (labelOptions.formatter) {
                return labelOptions.formatter.call(ctx, ctx);
            }
            if (labelOptions.format) {
                ctx.text = axis.defaultLabelFormatter.call(ctx);
                return Core_Templating.format(labelOptions.format, ctx, chart);
            }
            return axis.defaultLabelFormatter.call(ctx);
        };
        const text = labelFormatter.call(ctx, ctx);
        // Set up conditional formatting based on the format list if existing.
        const list = dateTimeLabelFormats?.list;
        if (list) {
            tick.shortenLabel = function () {
                for (i = 0; i < list.length; i++) {
                    extend(ctx, { dateTimeLabelFormat: list[i] });
                    label.attr({
                        text: labelFormatter.call(ctx, ctx)
                    });
                    if (label.getBBox().width <
                        axis.getSlotWidth(tick) - 2 *
                            (labelOptions.padding || 0)) {
                        return;
                    }
                }
                label.attr({
                    text: ''
                });
            };
        }
        else {
            // #15692
            tick.shortenLabel = void 0;
        }
        // First call
        if (!label) {
            /**
             * The rendered text label of the tick.
             * @name Highcharts.Tick#label
             * @type {Highcharts.SVGElement|undefined}
             */
            tick.label = label = tick.createLabel(text, labelOptions);
            // Base value to detect change for new calls to getBBox
            tick.rotation = 0;
            // Update
        }
        else if (label.textStr !== text) {
            // When resetting text, also reset the width if dynamically set
            // (#8809)
            if (label.textWidth &&
                !labelOptions.style.width &&
                !label.styles.width) {
                label.css({ width: void 0 });
            }
            label.attr({ text });
            label.textPxLength = label.getBBox().width;
        }
    }
    /**
     * Render and return the label of the tick.
     *
     * @internal
     * @function Highcharts.Tick#createLabel
     */
    createLabel(str, labelOptions, xy) {
        const axis = this.axis, { renderer, styledMode } = axis.chart, whiteSpace = labelOptions.style.whiteSpace, label = defined(str) && labelOptions.enabled ?
            renderer
                .text(str, xy?.x, xy?.y, labelOptions.useHTML)
                .add(axis.labelGroup) :
            void 0;
        // Un-rotated length
        if (label) {
            if (!styledMode) {
                label.css(merge(labelOptions.style));
            }
            label.textPxLength = label.getBBox().width;
            // Apply the white-space setting after we read the full text width
            if (!styledMode && whiteSpace) {
                label.css({ whiteSpace });
            }
        }
        return label;
    }
    /**
     * Destructor for the tick prototype
     *
     * @internal
     * @function Highcharts.Tick#destroy
     */
    destroy() {
        destroyObjectProperties(this, this.axis);
    }
    /**
     * Gets the x and y positions for ticks in terms of pixels.
     *
     * @internal
     * @function Highcharts.Tick#getPosition
     *
     * @param {boolean} horiz
     * Whether the tick is on an horizontal axis or not.
     *
     * @param {number} tickPos
     * Position of the tick.
     *
     * @param {number} tickmarkOffset
     * Tickmark offset for all ticks.
     *
     * @param {boolean} [old]
     * Whether the axis has changed or not.
     *
     * @return {Highcharts.PositionObject}
     * The tick position.
     *
     * @emits Highcharts.Tick#event:afterGetPosition
     */
    getPosition(horiz, tickPos, tickmarkOffset, old) {
        const axis = this.axis, chart = axis.chart, cHeight = (old && chart.oldChartHeight) || chart.chartHeight, pos = {
            x: horiz ?
                correctFloat(axis.translate(tickPos + tickmarkOffset, void 0, void 0, old) +
                    axis.transB) :
                (axis.left +
                    axis.offset +
                    (axis.opposite ?
                        (((old && chart.oldChartWidth) ||
                            chart.chartWidth) -
                            axis.right -
                            axis.left) :
                        0)),
            y: horiz ?
                (cHeight -
                    axis.bottom +
                    axis.offset -
                    (axis.opposite ? axis.height : 0)) :
                correctFloat(cHeight -
                    axis.translate(tickPos + tickmarkOffset, void 0, void 0, old) -
                    axis.transB)
        };
        // Chrome workaround for #10516
        pos.y = clamp(pos.y, -1e9, 1e9);
        fireEvent(this, 'afterGetPosition', { pos: pos });
        return pos;
    }
    /**
     * Get the x, y position of the tick label
     * @internal
     */
    getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step) {
        const axis = this.axis, transA = axis.transA, reversed = ( // #7911
        axis.isLinked && axis.linkedParent ?
            axis.linkedParent.reversed :
            axis.reversed), staggerLines = axis.staggerLines, rotCorr = axis.tickRotCorr || { x: 0, y: 0 }, 
        // Adjust for label alignment if we use reserveSpace: true (#5286)
        labelOffsetCorrection = (!horiz && !axis.reserveSpaceDefault ?
            -axis.labelOffset * (axis.labelAlign === 'center' ? 0.5 : 1) :
            0), distance = labelOptions.distance, pos = {};
        let yOffset, line;
        if (axis.side === 0) {
            yOffset = label.rotation ? -distance : -label.getBBox().height;
        }
        else if (axis.side === 2) {
            yOffset = rotCorr.y + distance;
        }
        else {
            // #3140, #3140
            yOffset = Math.cos(label.rotation * Tick_deg2rad) *
                (rotCorr.y - label.getBBox(false, 0).height / 2);
        }
        if (defined(labelOptions.y)) {
            yOffset = axis.side === 0 && axis.horiz ?
                labelOptions.y + yOffset :
                labelOptions.y;
        }
        x = x +
            pick(labelOptions.x, [0, 1, 0, -1][axis.side] * distance) +
            labelOffsetCorrection +
            rotCorr.x -
            (tickmarkOffset && horiz ?
                tickmarkOffset * transA * (reversed ? -1 : 1) :
                0);
        y = y + yOffset - (tickmarkOffset && !horiz ?
            tickmarkOffset * transA * (reversed ? 1 : -1) : 0);
        // Correct for staggered labels
        if (staggerLines) {
            line = (index / (step || 1) % staggerLines);
            if (axis.opposite) {
                line = staggerLines - line - 1;
            }
            y += line * (axis.labelOffset / staggerLines);
        }
        pos.x = x;
        pos.y = Math.round(y);
        fireEvent(this, 'afterGetLabelPosition', { pos: pos, tickmarkOffset: tickmarkOffset, index: index });
        return pos;
    }
    /**
     * Get the offset height or width of the label
     *
     * @internal
     * @function Highcharts.Tick#getLabelSize
     */
    getLabelSize() {
        return this.label ?
            this.label.getBBox()[this.axis.horiz ? 'height' : 'width'] :
            0;
    }
    /**
     * Extendible method to return the path of the marker
     * @internal
     */
    getMarkPath(x, y, tickLength, tickWidth, horiz = false, renderer) {
        return renderer.crispLine([[
                'M',
                x,
                y
            ], [
                'L',
                x + (horiz ? 0 : -tickLength),
                y + (horiz ? tickLength : 0)
            ]], tickWidth);
    }
    /**
     * Handle the label overflow by adjusting the labels to the left and right
     * edge, or hide them if they collide into the neighbor label.
     *
     * @internal
     * @function Highcharts.Tick#handleOverflow
     */
    handleOverflow(xy) {
        const tick = this, axis = this.axis, labelOptions = axis.options.labels, pxPos = xy.x, chartWidth = axis.chart.chartWidth, spacing = axis.chart.spacing, leftBound = pick(axis.labelLeft, Math.min(axis.pos, spacing[3])), rightBound = pick(axis.labelRight, Math.max(!axis.isRadial ? axis.pos + axis.len : 0, chartWidth - spacing[1])), label = this.label, rotation = this.rotation, factor = getAlignFactor(axis.labelAlign || label.attr('align')), labelWidth = label.getBBox().width, slotWidth = axis.getSlotWidth(tick), xCorrection = factor, css = {};
        let modifiedSlotWidth = slotWidth, goRight = 1, leftPos, rightPos, textWidth;
        // Check if the label overshoots the chart spacing box. If it does, move
        // it. If it now overshoots the slotWidth, add ellipsis.
        if (!rotation && labelOptions.overflow === 'justify') {
            leftPos = pxPos - factor * labelWidth;
            rightPos = pxPos + (1 - factor) * labelWidth;
            if (leftPos < leftBound) {
                modifiedSlotWidth =
                    xy.x + modifiedSlotWidth * (1 - factor) - leftBound;
            }
            else if (rightPos > rightBound) {
                modifiedSlotWidth =
                    rightBound - xy.x + modifiedSlotWidth * factor;
                goRight = -1;
            }
            modifiedSlotWidth = Math.min(slotWidth, modifiedSlotWidth); // #4177
            if (modifiedSlotWidth < slotWidth && axis.labelAlign === 'center') {
                xy.x += (goRight *
                    (slotWidth -
                        modifiedSlotWidth -
                        xCorrection * (slotWidth - Math.min(labelWidth, modifiedSlotWidth))));
            }
            // If the label width exceeds the available space, set a text width
            // to be picked up below. Also, if a width has been set before, we
            // need to set a new one because the reported labelWidth will be
            // limited by the box (#3938).
            if (labelWidth > modifiedSlotWidth ||
                (axis.autoRotation && label?.styles?.width)) {
                textWidth = modifiedSlotWidth;
            }
            // Add ellipsis to prevent rotated labels to be clipped against the edge
            // of the chart
        }
        else if (rotation < 0 &&
            pxPos - factor * labelWidth < leftBound) {
            textWidth = Math.round(pxPos / Math.cos(rotation * Tick_deg2rad) - leftBound);
        }
        else if (rotation > 0 &&
            pxPos + factor * labelWidth > rightBound) {
            textWidth = Math.round((chartWidth - pxPos) /
                Math.cos(rotation * Tick_deg2rad));
        }
        if (textWidth && label) {
            if (tick.shortenLabel) {
                tick.shortenLabel();
            }
            else {
                label.css(extend(css, {
                    width: Math.floor(textWidth) + 'px',
                    lineClamp: axis.isRadial ? 0 : 1
                }));
            }
        }
    }
    /**
     * Put everything in place
     *
     * @internal
     * @param {number} index
     *
     * @param {boolean} [old]
     * Use old coordinates to prepare an animation into new position
     *
     * @param {number} [opacity]
     */
    render(index, old, opacity) {
        const tick = this, axis = tick.axis, horiz = axis.horiz, pos = tick.pos, tickmarkOffset = pick(tick.tickmarkOffset, axis.tickmarkOffset), xy = tick.getPosition(horiz, pos, tickmarkOffset, old), x = xy.x, y = xy.y, axisStart = axis.pos, axisEnd = axisStart + axis.len, pxPos = horiz ? x : y;
        const labelOpacity = pick(opacity, tick.label?.newOpacity, // #15528
        1);
        // Anything that is not between `axis.pos` and `axis.pos + axis.length`
        // should not be visible (#20166). The `correctFloat` is for reversed
        // axes in Safari.
        if (!axis.chart.polar &&
            (correctFloat(pxPos) < axisStart || pxPos > axisEnd)) {
            opacity = 0;
        }
        opacity ?? (opacity = 1);
        this.isActive = true;
        // Create the grid line
        this.renderGridLine(old, opacity);
        // Create the tick mark
        this.renderMark(xy, opacity);
        // The label is created on init - now move it into place
        this.renderLabel(xy, old, labelOpacity, index);
        tick.isNew = false;
        fireEvent(this, 'afterRender');
    }
    /**
     * Renders the gridLine.
     *
     * @internal
     * @function Highcharts.Tick#renderGridLine
     * @param {boolean} old  Whether or not the tick is old
     * @param {number} opacity  The opacity of the grid line
     */
    renderGridLine(old, opacity) {
        const tick = this, axis = tick.axis, options = axis.options, attribs = {}, pos = tick.pos, type = tick.type, tickmarkOffset = pick(tick.tickmarkOffset, axis.tickmarkOffset), renderer = axis.chart.renderer;
        let gridLine = tick.gridLine, gridLinePath, gridLineWidth = options.gridLineWidth, gridLineColor = options.gridLineColor, dashStyle = options.gridLineDashStyle;
        if (tick.type === 'minor') {
            gridLineWidth = options.minorGridLineWidth;
            gridLineColor = options.minorGridLineColor;
            dashStyle = options.minorGridLineDashStyle;
        }
        if (!gridLine) {
            if (!axis.chart.styledMode) {
                attribs.stroke = gridLineColor;
                attribs['stroke-width'] = gridLineWidth || 0;
                attribs.dashstyle = dashStyle;
            }
            if (!type) {
                attribs.zIndex = 1;
            }
            if (old) {
                opacity = 0;
            }
            /**
             * The rendered grid line of the tick.
             * @name Highcharts.Tick#gridLine
             * @type {Highcharts.SVGElement|undefined}
             */
            tick.gridLine = gridLine = renderer.path()
                .attr(attribs)
                .addClass('highcharts-' + (type ? type + '-' : '') + 'grid-line')
                .add(axis.gridGroup);
        }
        if (gridLine) {
            gridLinePath = axis.getPlotLinePath({
                value: pos + tickmarkOffset,
                lineWidth: gridLine.strokeWidth(),
                force: 'pass',
                old: old,
                acrossPanes: false // #18025
            });
            // If the parameter 'old' is set, the current call will be followed
            // by another call, therefore do not do any animations this time
            if (gridLinePath) {
                gridLine[old || tick.isNew ? 'attr' : 'animate']({
                    d: gridLinePath,
                    opacity: opacity
                });
            }
        }
    }
    /**
     * Renders the tick mark.
     *
     * @internal
     * @function Highcharts.Tick#renderMark
     * @param {Highcharts.PositionObject} xy  The position vector of the mark
     * @param {number} opacity  The opacity of the mark
     */
    renderMark(xy, opacity) {
        const tick = this, axis = tick.axis, options = axis.options, renderer = axis.chart.renderer, type = tick.type, tickSize = axis.tickSize(type ? type + 'Tick' : 'tick'), x = xy.x, y = xy.y, tickWidth = pick(options[type !== 'minor' ? 'tickWidth' : 'minorTickWidth'], !type && axis.isXAxis ? 1 : 0), // X axis defaults to 1
        tickColor = options[type !== 'minor' ? 'tickColor' : 'minorTickColor'];
        let mark = tick.mark;
        const isNewMark = !mark;
        if (tickSize) {
            // Negate the length
            if (axis.opposite) {
                tickSize[0] = -tickSize[0];
            }
            // First time, create it
            if (!mark) {
                /**
                 * The rendered mark of the tick.
                 * @name Highcharts.Tick#mark
                 * @type {Highcharts.SVGElement|undefined}
                 */
                tick.mark = mark = renderer.path()
                    .addClass('highcharts-' + (type ? type + '-' : '') + 'tick')
                    .add(axis.axisGroup);
                if (!axis.chart.styledMode) {
                    mark.attr({
                        stroke: tickColor,
                        'stroke-width': tickWidth
                    });
                }
            }
            mark[isNewMark ? 'attr' : 'animate']({
                d: tick.getMarkPath(x, y, tickSize[0], mark.strokeWidth(), axis.horiz, renderer),
                opacity: opacity
            });
        }
    }
    /**
     * Renders the tick label.
     * Note: The label should already be created in init(), so it should only
     * have to be moved into place.
     *
     * @internal
     * @function Highcharts.Tick#renderLabel
     * @param {Highcharts.PositionObject} xy  The position vector of the label
     * @param {boolean} old  Whether or not the tick is old
     * @param {number} opacity  The opacity of the label
     * @param {number} index  The index of the tick
     */
    renderLabel(xy, old, opacity, index) {
        const tick = this, axis = tick.axis, horiz = axis.horiz, options = axis.options, label = tick.label, labelOptions = options.labels, step = labelOptions.step, tickmarkOffset = pick(tick.tickmarkOffset, axis.tickmarkOffset), x = xy.x, y = xy.y;
        let show = true;
        if (label && isNumber(x)) {
            label.xy = xy = tick.getLabelPosition(x, y, label, horiz, labelOptions, tickmarkOffset, index, step);
            // Apply show first and show last. If the tick is both first and
            // last, it is a single centered tick, in which case we show the
            // label anyway (#2100).
            if ((tick.isFirst &&
                !tick.isLast &&
                !options.showFirstLabel) ||
                (tick.isLast &&
                    !tick.isFirst &&
                    !options.showLastLabel)) {
                show = false;
                // Handle label overflow and show or hide accordingly
            }
            else if (horiz && !old && opacity !== 0) {
                tick.handleOverflow(xy);
            }
            // Apply step
            if (step && index % step) {
                // Show those indices dividable by step
                show = false;
            }
            // Set the new position, and show or hide
            if (show && isNumber(xy.y)) {
                xy.opacity = opacity;
                label[tick.isNewLabel ? 'attr' : 'animate'](xy).show(true);
                tick.isNewLabel = false;
            }
            else {
                label.hide(); // #1338, #15863
                tick.isNewLabel = true;
            }
        }
    }
}
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Axis_Tick = (Tick);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * Optional parameters for the tick.
 * @internal
 * @interface Highcharts.TickParametersObject
 */ /**
* Set category for the tick.
* @name Highcharts.TickParametersObject#category
* @type {string|undefined}
*/ /**
* @name Highcharts.TickParametersObject#options
* @type {Highcharts.Dictionary<any>|undefined}
*/ /**
* Set tickmarkOffset for the tick.
* @name Highcharts.TickParametersObject#tickmarkOffset
* @type {number|undefined}
*/
/**
 * Additional time tick information.
 *
 * @interface Highcharts.TimeTicksInfoObject
 * @extends Highcharts.TimeNormalizedObject
 */ /**
* @name Highcharts.TimeTicksInfoObject#boundaryTicks
* @type {Array<string>}
*/ /**
* @name Highcharts.TimeTicksInfoObject#totalRange
* @type {number}
*/
(''); // Keeps doclets above in JS file

;// ./code/es-modules/Core/Axis/Axis.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { animObject: Axis_animObject } = AnimationUtilities;

const { xAxis, yAxis } = Axis_AxisDefaults;

const { parse: Axis_color } = Color_Color;

const { defaultOptions: Axis_defaultOptions } = Defaults;

const { registerEventOptions } = Core_Foundation;

const { deg2rad: Axis_deg2rad } = Core_Globals;



const getNormalizedTickInterval = (axis, tickInterval) => normalizeTickInterval(tickInterval, void 0, void 0, pick(axis.options.allowDecimals, 
// If the tick interval is greater than 0.5, avoid decimals, as
// linear axes are often used to render discrete values (#3363). If
// a tick amount is set, allow decimals by default, as it increases
// the chances for a good fit.
tickInterval < 0.5 || axis.tickAmount !== void 0), !!axis.tickAmount);
extend(Axis_defaultOptions, { xAxis, yAxis: merge(xAxis, yAxis) });
/* *
 *
 *  Class
 *
 * */
/**
 * Create a new axis object. Called internally when instantiating a new chart or
 * adding axes by {@link Highcharts.Chart#addAxis}.
 *
 * A chart can have from 0 axes (pie chart) to multiples. In a normal, single
 * series cartesian chart, there is one X axis and one Y axis.
 *
 * The X axis or axes are referenced by {@link Highcharts.Chart.xAxis}, which is
 * an array of Axis objects. If there is only one axis, it can be referenced
 * through `chart.xAxis[0]`, and multiple axes have increasing indices. The same
 * pattern goes for Y axes.
 *
 * If you need to get the axes from a series object, use the `series.xAxis` and
 * `series.yAxis` properties. These are not arrays, as one series can only be
 * associated to one X and one Y axis.
 *
 * A third way to reference the axis programmatically is by `id`. Add an `id` in
 * the axis configuration options, and get the axis by
 * {@link Highcharts.Chart#get}.
 *
 * Configuration options for the axes are given in options.xAxis and
 * options.yAxis.
 *
 * @class
 * @name Highcharts.Axis
 *
 * @param {Highcharts.Chart} chart
 * The Chart instance to apply the axis on.
 *
 * @param {Highcharts.AxisOptions} userOptions
 * Axis options
 */
class Axis {
    /* *
     *
     *  Constructors
     *
     * */
    constructor(chart, userOptions, coll) {
        /**
         * Whether this axis is allowed to be clipped to the plot area.
         * @internal
         */
        this.clippable = true;
        this.init(chart, userOptions, coll);
    }
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Overridable function to initialize the axis.
     *
     * @see {@link Axis}
     *
     * @function Highcharts.Axis#init
     *
     * @param {Highcharts.Chart} chart
     * The Chart instance to apply the axis on.
     *
     * @param {AxisOptions} userOptions
     * Axis options.
     *
     * @emits Highcharts.Axis#event:afterInit
     * @emits Highcharts.Axis#event:init
     */
    init(chart, userOptions, coll = this.coll) {
        const isXAxis = coll === 'xAxis', axis = this, horiz = axis.isZAxis || (chart.inverted ? !isXAxis : isXAxis);
        /**
         * The Chart that the axis belongs to.
         *
         * @name Highcharts.Axis#chart
         * @type {Highcharts.Chart}
         */
        axis.chart = chart;
        /**
         * Whether the axis is horizontal.
         *
         * @name Highcharts.Axis#horiz
         * @type {boolean|undefined}
         */
        axis.horiz = horiz;
        /**
         * Whether the axis is the x-axis.
         *
         * @name Highcharts.Axis#isXAxis
         * @type {boolean|undefined}
         */
        axis.isXAxis = isXAxis;
        /**
         * The collection where the axis belongs, for example `xAxis`, `yAxis`
         * or `colorAxis`. Corresponds to properties on Chart, for example
         * {@link Chart.xAxis}.
         *
         * @name Highcharts.Axis#coll
         * @type {string}
         */
        axis.coll = coll;
        fireEvent(this, 'init', { userOptions: userOptions });
        // Needed in setOptions
        axis.opposite = pick(userOptions.opposite, axis.opposite);
        /**
         * The side on which the axis is rendered. 0 is top, 1 is right, 2
         * is bottom and 3 is left.
         *
         * @name Highcharts.Axis#side
         * @type {number}
         */
        axis.side = pick(userOptions.side, axis.side, (horiz ?
            (axis.opposite ? 0 : 2) : // Top : bottom
            (axis.opposite ? 1 : 3)) // Right : left
        );
        /**
         * Current options for the axis after merge of defaults and user's
         * options.
         *
         * @name Highcharts.Axis#options
         * @type {Highcharts.AxisOptions}
         */
        axis.setOptions(userOptions);
        const options = axis.options, labelsOptions = options.labels;
        // Set the type and fire an event
        axis.type ?? (axis.type = options.type || 'linear');
        axis.uniqueNames ?? (axis.uniqueNames = options.uniqueNames ?? true);
        fireEvent(axis, 'afterSetType');
        /**
         * User's options for this axis without defaults.
         *
         * @name Highcharts.Axis#userOptions
         * @type {Highcharts.AxisOptions}
         */
        axis.userOptions = userOptions;
        axis.minPixelPadding = 0;
        /**
         * Whether the axis is reversed. Based on the `axis.reversed`,
         * option, but inverted charts have reversed xAxis by default.
         *
         * @name Highcharts.Axis#reversed
         * @type {boolean}
         */
        axis.reversed = pick(options.reversed, axis.reversed);
        axis.visible = options.visible;
        axis.zoomEnabled = options.zoomEnabled;
        // Initial categories
        axis.hasNames = this.type === 'category' || options.categories === true;
        /**
         * If categories are present for the axis, names are used instead of
         * numbers for that axis.
         *
         * Since Highcharts 3.0, categories can also be extracted by giving each
         * point a name and setting axis type to `category`. However, if you
         * have multiple series, best practice remains defining the `categories`
         * array.
         *
         * @see [xAxis.categories](/highcharts/xAxis.categories)
         *
         * @name Highcharts.Axis#categories
         * @type {Array<string>}
         * @readonly
         */
        axis.categories = (isArray(options.categories) && options.categories) ||
            (axis.hasNames ? [] : void 0);
        // Axis names and its map for quick access. Backwards mapping is much
        // faster than array searching (#7725). Preserve on update (#3830)
        axis.names || (axis.names = []);
        axis.namesMap || (axis.namesMap = {});
        // Placeholder for plotLines and plotBands groups
        axis.plotLinesAndBandsGroups = {};
        // Shorthand types
        axis.positiveValuesOnly = !!axis.logarithmic;
        // Flag, if axis is linked to another axis
        axis.isLinked = defined(options.linkedTo);
        /**
         * List of major ticks mapped by position on axis.
         *
         * @see {@link Highcharts.Tick}
         *
         * @name Highcharts.Axis#ticks
         * @type {Highcharts.Dictionary<Highcharts.Tick>}
         */
        axis.ticks = {};
        axis.labelEdge = [];
        /**
         * List of minor ticks mapped by position on the axis.
         *
         * @see {@link Highcharts.Tick}
         *
         * @name Highcharts.Axis#minorTicks
         * @type {Highcharts.Dictionary<Highcharts.Tick>}
         */
        axis.minorTicks = {};
        // List of plotLines/Bands
        axis.plotLinesAndBands = [];
        // Alternate bands
        axis.alternateBands = {};
        /**
         * The length of the axis in terms of pixels.
         *
         * @name Highcharts.Axis#len
         * @type {number}
         */
        axis.len ?? (axis.len = 0);
        axis.minRange = axis.userMinRange = options.minRange || options.maxZoom;
        axis.range = options.range;
        /**
         * The maximum value of the axis. In a logarithmic axis, this is the
         * logarithm of the real value, and the real value can be obtained from
         * {@link Axis#getExtremes}.
         *
         * @name Highcharts.Axis#max
         * @type {number|undefined}
         */
        axis.max = void 0;
        /**
         * The minimum value of the axis. In a logarithmic axis, this is the
         * logarithm of the real value, and the real value can be obtained from
         * {@link Axis#getExtremes}.
         *
         * @name Highcharts.Axis#min
         * @type {number|undefined}
         */
        axis.min = void 0;
        /**
         * The processed crosshair options.
         *
         * @name Highcharts.Axis#crosshair
         * @type {boolean|Highcharts.AxisCrosshairOptions}
         */
        const crosshair = pick(options.crosshair, splat(chart.options.tooltip.crosshairs)[isXAxis ? 0 : 1]);
        axis.crosshair = crosshair === true ? {} : crosshair;
        // Register. Don't add it again on Axis.update().
        if (chart.axes.indexOf(axis) === -1) { //
            if (isXAxis) { // #2713
                chart.axes.splice(chart.xAxis.length, 0, axis);
            }
            else {
                chart.axes.push(axis);
            }
            insertItem(this, chart[this.coll]);
        }
        chart.orderItems(axis.coll);
        /**
         * All series associated to the axis.
         *
         * @name Highcharts.Axis#series
         * @type {Array<Highcharts.Series>}
         */
        axis.series = axis.series || []; // Populated by Series
        // Reversed axis
        if (chart.inverted &&
            !axis.isZAxis &&
            isXAxis &&
            !defined(axis.reversed)) {
            axis.reversed = true;
        }
        axis.labelRotation = isNumber(labelsOptions.rotation) ?
            labelsOptions.rotation :
            void 0;
        // Register event listeners
        registerEventOptions(axis, options);
        fireEvent(this, 'afterInit');
    }
    /**
     * Merge and set options.
     *
     * @internal
     * @function Highcharts.Axis#setOptions
     *
     * @param {Highcharts.AxisOptions} userOptions
     * Axis options.
     *
     * @emits Highcharts.Axis#event:afterSetOptions
     */
    setOptions(userOptions) {
        const sideSpecific = this.horiz ?
            // Top and bottom axis defaults
            {
                labels: {
                    autoRotation: [-45],
                    padding: 3
                },
                margin: 15
            } :
            // Left and right axis, title rotated 90 or 270 degrees
            // respectively
            {
                labels: {
                    padding: 1
                },
                title: {
                    rotation: 90 * this.side
                }
            };
        this.options = merge(sideSpecific, 
        // Merge in the default title for y-axis, which changes with
        // language settings
        this.coll === 'yAxis' ? {
            title: {
                text: this.chart.options.lang.yAxisTitle
            }
        } : {}, Axis_defaultOptions[this.coll], userOptions);
        fireEvent(this, 'afterSetOptions', { userOptions });
    }
    /**
     * The default label formatter. The context is a special config object for
     * the label. In apps, use the
     * [labels.formatter](https://api.highcharts.com/highcharts/xAxis.labels.formatter)
     * instead, except when a modification is needed.
     *
     * @function Highcharts.Axis#defaultLabelFormatter
     *
     * @param {Highcharts.AxisLabelsFormatterContextObject} this
     * Formatter context of axis label.
     *
     * @return {string}
     * The formatted label content.
     */
    defaultLabelFormatter() {
        const axis = this.axis, chart = this.chart, { numberFormatter } = chart, value = isNumber(this.value) ? this.value : NaN, time = axis.chart.time, categories = axis.categories, dateTimeLabelFormat = this.dateTimeLabelFormat, lang = Axis_defaultOptions.lang, numericSymbols = lang.numericSymbols, numSymMagnitude = lang.numericSymbolMagnitude || 1000, 
        // Make sure the same symbol is added for all labels on a linear
        // axis
        numericSymbolDetector = axis.logarithmic ?
            Math.abs(value) :
            axis.tickInterval;
        let i = numericSymbols?.length, multi, ret;
        if (categories) {
            ret = `${this.value}`;
        }
        else if (dateTimeLabelFormat) { // Datetime axis
            ret = time.dateFormat(dateTimeLabelFormat, value, true);
        }
        else if (i &&
            numericSymbols &&
            numericSymbolDetector >= 1000) {
            // Decide whether we should add a numeric symbol like k (thousands)
            // or M (millions). If we are to enable this in tooltip or other
            // places as well, we can move this logic to the numberFormatter and
            // enable it by a parameter.
            while (i-- && typeof ret === 'undefined') {
                multi = Math.pow(numSymMagnitude, i + 1);
                if (
                // Only accept a numeric symbol when the distance is more
                // than a full unit. So for example if the symbol is k, we
                // don't accept numbers like 0.5k.
                numericSymbolDetector >= multi &&
                    // Accept one decimal before the symbol. Accepts 0.5k but
                    // not 0.25k. How does this work with the previous?
                    (value * 10) % multi === 0 &&
                    numericSymbols[i] !== null &&
                    value !== 0) { // #5480
                    ret = numberFormatter(value / multi, -1, void 0, void 0, chart) + numericSymbols[i];
                }
            }
        }
        ret ?? (ret = numberFormatter(value, -1, void 0, 
        // Add thousands separators when 10 000 or more
        Math.abs(value) < 10000 ? '' : void 0, chart));
        return ret;
    }
    /**
     * Get the minimum and maximum for the series of each axis. The function
     * analyzes the axis series and updates `this.dataMin` and `this.dataMax`.
     *
     * @internal
     * @function Highcharts.Axis#getSeriesExtremes
     *
     * @emits Highcharts.Axis#event:afterGetSeriesExtremes
     * @emits Highcharts.Axis#event:getSeriesExtremes
     */
    getSeriesExtremes() {
        const axis = this;
        let xExtremes;
        fireEvent(this, 'getSeriesExtremes', null, function () {
            axis.hasVisibleSeries = false;
            // Reset properties in case we're redrawing (#3353)
            axis.dataMin = axis.dataMax = axis.threshold = void 0;
            axis.softThreshold = !axis.isXAxis;
            // Loop through this axis' series
            axis.series.forEach((series) => {
                if (series.reserveSpace()) {
                    const seriesOptions = series.options;
                    let xData, threshold = seriesOptions.threshold, seriesDataMin, seriesDataMax;
                    axis.hasVisibleSeries = true;
                    // Validate threshold in logarithmic axes
                    if (axis.positiveValuesOnly && (threshold || 0) <= 0) {
                        threshold = void 0;
                    }
                    // Get dataMin and dataMax for X axes
                    if (axis.isXAxis) {
                        xData = series.getColumn('x');
                        if (xData.length) {
                            xData = axis.logarithmic ?
                                xData.filter((x) => x > 0) :
                                xData;
                            xExtremes = series.getXExtremes(xData);
                            // If xData contains values which is not numbers,
                            // then filter them out. To prevent performance hit,
                            // we only do this after we have already found
                            // seriesDataMin because in most cases all data is
                            // valid. #5234.
                            seriesDataMin = xExtremes.min;
                            seriesDataMax = xExtremes.max;
                            if (!isNumber(seriesDataMin) &&
                                // #5010:
                                !(seriesDataMin instanceof Date)) {
                                xData = xData.filter(isNumber);
                                xExtremes = series.getXExtremes(xData);
                                // Do it again with valid data
                                seriesDataMin = xExtremes.min;
                                seriesDataMax = xExtremes.max;
                            }
                            if (xData.length) {
                                axis.dataMin = Math.min(pick(axis.dataMin, seriesDataMin), seriesDataMin);
                                axis.dataMax = Math.max(pick(axis.dataMax, seriesDataMax), seriesDataMax);
                            }
                        }
                        // Get dataMin and dataMax for Y axes, as well as handle
                        // stacking and processed data
                    }
                    else {
                        // Get this particular series extremes
                        const dataExtremes = series.applyExtremes();
                        // Get the dataMin and dataMax so far. If percentage is
                        // used, the min and max are always 0 and 100. If
                        // seriesDataMin and seriesDataMax is null, then series
                        // doesn't have active y data, we continue with nulls
                        if (isNumber(dataExtremes.dataMin)) {
                            seriesDataMin = dataExtremes.dataMin;
                            axis.dataMin = Math.min(pick(axis.dataMin, seriesDataMin), seriesDataMin);
                        }
                        if (isNumber(dataExtremes.dataMax)) {
                            seriesDataMax = dataExtremes.dataMax;
                            axis.dataMax = Math.max(pick(axis.dataMax, seriesDataMax), seriesDataMax);
                        }
                        // Adjust to threshold
                        if (defined(threshold)) {
                            axis.threshold = threshold;
                        }
                        // If any series has a hard threshold, it takes
                        // precedence
                        if (!seriesOptions.softThreshold ||
                            axis.positiveValuesOnly) {
                            axis.softThreshold = false;
                        }
                    }
                }
            });
        });
        fireEvent(this, 'afterGetSeriesExtremes');
    }
    /**
     * Translate from axis value to pixel position on the chart, or back. Use
     * the `toPixels` and `toValue` functions in applications.
     *
     * @internal
     * @function Highcharts.Axis#translate
     */
    translate(val, backwards, cvsCoord, old, handleLog, pointPlacement) {
        const axis = (this.linkedParent || this), // #1417
        localMin = (old && axis.old ? axis.old.min : axis.min);
        if (!isNumber(localMin)) {
            return NaN;
        }
        const minPixelPadding = axis.minPixelPadding, doPostTranslate = (axis.isOrdinal ||
            axis.brokenAxis?.hasBreaks ||
            (axis.logarithmic && handleLog)) && !!axis.lin2val;
        let sign = 1, cvsOffset = 0, localA = old && axis.old ? axis.old.transA : axis.transA, returnValue = 0;
        if (!localA) {
            localA = axis.transA;
        }
        // In vertical axes, the canvas coordinates start from 0 at the top like
        // in SVG.
        if (cvsCoord) {
            sign *= -1; // Canvas coordinates inverts the value
            cvsOffset = axis.len;
        }
        // Handle reversed axis
        if (axis.reversed) {
            sign *= -1;
            cvsOffset -= sign * (axis.sector || axis.len);
        }
        // From pixels to value
        if (backwards) { // Reverse translation
            val = val * sign + cvsOffset;
            val -= minPixelPadding;
            // From chart pixel to value:
            returnValue = val / localA + localMin;
            if (doPostTranslate) { // Log, ordinal and broken axis
                returnValue = axis.lin2val(returnValue);
            }
            // From value to pixels
        }
        else {
            if (doPostTranslate) { // Log, ordinal and broken axis
                val = axis.val2lin(val);
            }
            const value = sign * (val - localMin) * localA;
            returnValue = value +
                cvsOffset +
                (sign * minPixelPadding) +
                (isNumber(pointPlacement) ? localA * pointPlacement : 0);
            if (!axis.isRadial) {
                returnValue = correctFloat(returnValue);
            }
        }
        return returnValue;
    }
    /**
     * Translate a value in terms of axis units into pixels within the chart.
     *
     * @function Highcharts.Axis#toPixels
     *
     * @param {number|string} value
     * A value in terms of axis units. For datetime axes, a timestamp or
     * date/time string is accepted.
     *
     * @param {boolean} [paneCoordinates=false]
     * Whether to return the pixel coordinate relative to the chart or just the
     * axis/pane itself.
     *
     * @return {number}
     * Pixel position of the value on the chart or axis.
     */
    toPixels(value, paneCoordinates) {
        return this.translate(this.chart?.time.parse(value) ?? NaN, false, !this.horiz, void 0, true) + (paneCoordinates ? 0 : this.pos);
    }
    /**
     * Translate a pixel position along the axis to a value in terms of axis
     * units.
     *
     * @function Highcharts.Axis#toValue
     *
     * @param {number} pixel
     * The pixel value coordinate.
     *
     * @param {boolean} [paneCoordinates=false]
     * Whether the input pixel is relative to the chart or just the axis/pane
     * itself.
     *
     * @return {number}
     * The axis value.
     */
    toValue(pixel, paneCoordinates) {
        return this.translate(pixel - (paneCoordinates ? 0 : this.pos), true, !this.horiz, void 0, true);
    }
    /**
     * Create the path for a plot line that goes from the given value on
     * this axis, across the plot to the opposite side. Also used internally for
     * grid lines and crosshairs.
     *
     * @function Highcharts.Axis#getPlotLinePath
     *
     * @param {Highcharts.AxisPlotLinePathOptionsObject} options
     * Options for the path.
     *
     * @return {Highcharts.SVGPathArray|null}
     * The SVG path definition for the plot line.
     */
    getPlotLinePath(options) {
        const axis = this, chart = axis.chart, axisLeft = axis.left, axisTop = axis.top, old = options.old, value = options.value, lineWidth = options.lineWidth, cHeight = (old && chart.oldChartHeight) || chart.chartHeight, cWidth = (old && chart.oldChartWidth) || chart.chartWidth, transB = axis.transB;
        let translatedValue = options.translatedValue, force = options.force, x1, y1, x2, y2, skip;
        // eslint-disable-next-line valid-jsdoc
        /**
         * Check if x is between a and b. If not, either move to a/b
         * or skip, depending on the force parameter.
         * @internal
         */
        function between(x, a, b) {
            if (force !== 'pass' && (x < a || x > b)) {
                if (force) {
                    x = clamp(x, a, b);
                }
                else {
                    skip = true;
                }
            }
            return x;
        }
        const evt = {
            value,
            lineWidth,
            old,
            force,
            acrossPanes: options.acrossPanes,
            translatedValue
        };
        fireEvent(this, 'getPlotLinePath', evt, function (e) {
            translatedValue = pick(translatedValue, axis.translate(value, void 0, void 0, old));
            // Keep the translated value within sane bounds, and avoid Infinity
            // to fail the isNumber test (#7709).
            translatedValue = clamp(translatedValue, -1e9, 1e9);
            x1 = x2 = translatedValue + transB;
            y1 = y2 = cHeight - translatedValue - transB;
            if (!isNumber(translatedValue)) { // No min or max
                skip = true;
                force = false; // #7175, don't force it when path is invalid
            }
            else if (axis.horiz) {
                y1 = axisTop;
                y2 = cHeight - axis.bottom + (axis.options.isInternal ?
                    0 :
                    (chart.scrollablePixelsY || 0)); // #20354, scrollablePixelsY shouldn't be used for navigator
                x1 = x2 = between(x1, axisLeft, axisLeft + axis.width);
            }
            else {
                x1 = axisLeft;
                x2 = cWidth - axis.right + (chart.scrollablePixelsX || 0);
                y1 = y2 = between(y1, axisTop, axisTop + axis.height);
            }
            e.path = skip && !force ?
                void 0 :
                chart.renderer.crispLine([['M', x1, y1], ['L', x2, y2]], lineWidth || 1);
        });
        return evt.path;
    }
    /**
     * Internal function to get the tick positions of a linear axis to round
     * values like whole tens or every five.
     *
     * @function Highcharts.Axis#getLinearTickPositions
     *
     * @param {number} tickInterval
     * The normalized tick interval.
     *
     * @param {number} min
     * Axis minimum.
     *
     * @param {number} max
     * Axis maximum.
     *
     * @return {Array<number>}
     * An array of axis values where ticks should be placed.
     */
    getLinearTickPositions(tickInterval, min, max) {
        const roundedMin = correctFloat(Math.floor(min / tickInterval) * tickInterval), roundedMax = correctFloat(Math.ceil(max / tickInterval) * tickInterval), tickPositions = [];
        let pos, lastPos, precision;
        // When the precision is higher than what we filter out in
        // correctFloat, skip it (#6183).
        if (correctFloat(roundedMin + tickInterval) === roundedMin) {
            precision = 20;
        }
        // For single points, add a tick regardless of the relative position
        // (#2662, #6274)
        if (this.single) {
            return [min];
        }
        // Populate the intermediate values
        pos = roundedMin;
        while (pos <= roundedMax) {
            // Place the tick on the rounded value
            tickPositions.push(pos);
            // Always add the raw tickInterval, not the corrected one.
            pos = correctFloat(pos + tickInterval, precision);
            // If the interval is not big enough in the current min - max range
            // to actually increase the loop variable, we need to break out to
            // prevent endless loop. Issue #619
            if (pos === lastPos) {
                break;
            }
            // Record the last value
            lastPos = pos;
        }
        return tickPositions;
    }
    /**
     * Resolve the new minorTicks/minorTickInterval options into the legacy
     * loosely typed minorTickInterval option.
     *
     * @function Highcharts.Axis#getMinorTickInterval
     *
     * @return {number|"auto"|null}
     * Legacy option
     */
    getMinorTickInterval() {
        const { minorTicks, minorTickInterval } = this.options;
        if (minorTicks === true) {
            return pick(minorTickInterval, 'auto');
        }
        if (minorTicks === false) {
            return;
        }
        return minorTickInterval;
    }
    /**
     * Internal function to return the minor tick positions. For logarithmic
     * axes, the same logic as for major ticks is reused.
     *
     * @function Highcharts.Axis#getMinorTickPositions
     *
     * @return {Array<number>}
     * An array of axis values where ticks should be placed.
     */
    getMinorTickPositions() {
        const axis = this, options = axis.options, tickPositions = axis.tickPositions, minorTickInterval = axis.minorTickInterval, pointRangePadding = axis.pointRangePadding || 0, min = (axis.min || 0) - pointRangePadding, // #1498
        max = (axis.max || 0) + pointRangePadding, // #1498
        range = axis.brokenAxis?.hasBreaks ?
            axis.brokenAxis.unitLength : max - min;
        let minorTickPositions = [], pos;
        // If minor ticks get too dense, they are hard to read, and may cause
        // long running script. So we don't draw them.
        if (range && range / minorTickInterval < axis.len / 3) { // #3875
            const logarithmic = axis.logarithmic;
            if (logarithmic) {
                // For each interval in the major ticks, compute the minor ticks
                // separately.
                this.paddedTicks.forEach(function (_pos, i, paddedTicks) {
                    if (i) {
                        minorTickPositions.push.apply(minorTickPositions, logarithmic.getLogTickPositions(minorTickInterval, paddedTicks[i - 1], paddedTicks[i], true));
                    }
                });
            }
            else if (axis.dateTime &&
                this.getMinorTickInterval() === 'auto') { // #1314
                minorTickPositions = minorTickPositions.concat(axis.getTimeTicks(axis.dateTime.normalizeTimeTickInterval(minorTickInterval), min, max, options.startOfWeek));
            }
            else {
                for (pos = min + (tickPositions[0] - min) % minorTickInterval; pos <= max; pos += minorTickInterval) {
                    // Very, very, tight grid lines (#5771)
                    if (pos === minorTickPositions[0]) {
                        break;
                    }
                    minorTickPositions.push(pos);
                }
            }
        }
        if (minorTickPositions.length !== 0) {
            axis.trimTicks(minorTickPositions); // #3652 #3743 #1498 #6330
        }
        return minorTickPositions;
    }
    /**
     * Adjust the min and max for the minimum range. Keep in mind that the
     * series data is not yet processed, so we don't have information on data
     * cropping and grouping, or updated `axis.pointRange` or
     * `series.pointRange`. The data can't be processed until we have finally
     * established min and max.
     *
     * @internal
     * @function Highcharts.Axis#adjustForMinRange
     */
    adjustForMinRange() {
        const axis = this, options = axis.options, logarithmic = axis.logarithmic, time = axis.chart.time;
        let { max, min, minRange } = axis, zoomOffset, spaceAvailable, closestDataRange, minArgs, maxArgs;
        // Set the automatic minimum range based on the closest point distance
        if (axis.isXAxis &&
            typeof minRange === 'undefined' &&
            !logarithmic) {
            if (defined(options.min) ||
                defined(options.max) ||
                defined(options.floor) ||
                defined(options.ceiling)) {
                // Setting it to null, as opposed to undefined, signals we don't
                // run this block again as per the condition above.
                minRange = null;
            }
            else {
                // Find the closest distance between raw data points, as opposed
                // to closestPointRange that applies to processed points
                // (cropped and grouped)
                closestDataRange = getClosestDistance(axis.series.map((s) => {
                    const xData = s.getColumn('x');
                    // If xIncrement, we only need to measure the two first
                    // points to get the distance. Saves processing time.
                    return s.xIncrement ? xData.slice(0, 2) : xData;
                })) || 0;
                minRange = Math.min(closestDataRange * 5, axis.dataMax - axis.dataMin);
            }
        }
        // If minRange is exceeded, adjust
        if (isNumber(max) &&
            isNumber(min) &&
            isNumber(minRange) &&
            max - min < minRange) {
            spaceAvailable =
                axis.dataMax - axis.dataMin >=
                    minRange;
            zoomOffset = (minRange - max + min) / 2;
            // If min and max options have been set, don't go beyond it
            minArgs = [
                min - zoomOffset,
                time.parse(options.min) ?? (min - zoomOffset)
            ];
            // If space is available, stay within the data range
            if (spaceAvailable) {
                minArgs[2] = logarithmic ?
                    logarithmic.log2lin(axis.dataMin) :
                    axis.dataMin;
            }
            min = arrayMax(minArgs);
            maxArgs = [
                min + minRange,
                time.parse(options.max) ?? (min + minRange)
            ];
            // If space is available, stay within the data range
            if (spaceAvailable) {
                maxArgs[2] = logarithmic ?
                    logarithmic.log2lin(axis.dataMax) :
                    axis.dataMax;
            }
            max = arrayMin(maxArgs);
            // Now if the max is adjusted, adjust the min back
            if (max - min < minRange) {
                minArgs[0] = max - minRange;
                minArgs[1] = time.parse(options.min) ?? (max - minRange);
                min = arrayMax(minArgs);
            }
        }
        // Record modified extremes
        axis.minRange = minRange;
        axis.min = min;
        axis.max = max;
    }
    /**
     * Find the closestPointRange across all series, including the single data
     * series.
     *
     * @internal
     * @function Highcharts.Axis#getClosest
     */
    getClosest() {
        let closestSingleDistance, closestDistance;
        if (this.categories) {
            closestDistance = 1;
        }
        else {
            const singleXs = [];
            this.series.forEach(function (series) {
                const seriesClosest = series.closestPointRange, xData = series.getColumn('x');
                if (xData.length === 1) {
                    singleXs.push(xData[0]);
                }
                else if (series.sorted &&
                    defined(seriesClosest) &&
                    series.reserveSpace()) {
                    closestDistance = defined(closestDistance) ?
                        Math.min(closestDistance, seriesClosest) :
                        seriesClosest;
                }
            });
            if (singleXs.length) {
                singleXs.sort((a, b) => a - b);
                closestSingleDistance = getClosestDistance([singleXs]);
            }
        }
        if (closestSingleDistance && closestDistance) {
            return Math.min(closestSingleDistance, closestDistance);
        }
        return closestSingleDistance || closestDistance;
    }
    /**
     * When a point name is given and no x, search for the name in the existing
     * categories, or if categories aren't provided, search names or create a
     * new category (#2522).
     *
     * @internal
     * @function Highcharts.Axis#nameToX
     *
     * @param {Highcharts.Point} point
     * The point to inspect.
     *
     * @return {number}
     * The X value that the point is given.
     */
    nameToX(point, nameX) {
        const explicitCategories = isArray(this.options.categories), { name, series } = point, names = explicitCategories ? this.categories : this.names, namesMap = this.namesMap;
        let x;
        series.requireSorting = false;
        if (!defined(nameX)) {
            nameX = this.uniqueNames && names ?
                (explicitCategories ?
                    names.indexOf(name) :
                    (namesMap[name] ?? -1)) :
                series.autoIncrement();
        }
        if (nameX === -1) { // Not found in current categories
            if (!explicitCategories && names) {
                x = names.length;
            }
        }
        else if (isNumber(nameX)) {
            x = nameX;
        }
        // Write the last point's name to the names array
        if (typeof x !== 'undefined') {
            this.names[x] = point.name;
            namesMap[point.name] = x;
        }
        else if (isNumber(point.x)) {
            x = point.x; // #17438
        }
        else {
            x = series.autoIncrement();
        }
        return x;
    }
    /**
     * When changes have been done to series data, update the axis.names.
     *
     * @internal
     * @function Highcharts.Axis#updateNames
     */
    updateNames() {
        const axis = this, { names, namesMap } = this, i = names.length;
        if (i > 0) {
            Object.keys(namesMap).forEach(function (key) {
                delete namesMap[key];
            });
            names.length = 0;
            this.minRange = this.userMinRange; // Reset
            (this.series || []).forEach((series) => {
                // Reset incrementer (#5928)
                series.xIncrement = null;
                delete series.xColumn;
                // When adding a series, points are not yet generated
                if (!series.points || series.isDirtyData) {
                    // When we're updating the series with data that is longer
                    // than it was, and cropThreshold is passed, we need to make
                    // sure that the axis.max is increased _before_ running the
                    // premature processData. Otherwise this early iteration of
                    // processData will crop the points to axis.max, and the
                    // names array will be too short (#5857).
                    axis.max = Math.max(axis.max || 0, series.dataTable.rowCount - 1);
                    series.processData();
                    series.generatePoints();
                }
                else {
                    const xData = series.getColumn('x');
                    series.points?.forEach((point) => {
                        point.x = xData[point.index];
                    });
                }
            });
        }
    }
    /**
     * Update translation information.
     *
     * @internal
     * @function Highcharts.Axis#setAxisTranslation
     *
     * @emits Highcharts.Axis#event:afterSetAxisTranslation
     */
    setAxisTranslation() {
        const axis = this, range = axis.max - axis.min, linkedParent = axis.linkedParent, hasCategories = !!axis.categories, isXAxis = axis.isXAxis;
        let pointRange = axis.axisPointRange || 0, closestPointRange, minPointOffset = 0, pointRangePadding = 0, ordinalCorrection, transA = axis.transA;
        // Adjust translation for padding. Y axis with categories need to go
        // through the same (#1784).
        if (isXAxis || hasCategories || pointRange) {
            // Get the closest points
            closestPointRange = axis.getClosest();
            if (linkedParent) {
                minPointOffset = linkedParent.minPointOffset;
                pointRangePadding = linkedParent.pointRangePadding;
            }
            else {
                axis.series.forEach(function (series) {
                    const seriesPointRange = hasCategories ?
                        1 :
                        (isXAxis ?
                            pick(series.options.pointRange, closestPointRange, 0) :
                            (axis.axisPointRange || 0)), // #2806
                    pointPlacement = series.options.pointPlacement;
                    pointRange = Math.max(pointRange, seriesPointRange);
                    if (!axis.single || hasCategories) {
                        // TODO: series should internally set x- and y-
                        // pointPlacement to simplify this logic.
                        const isPointPlacementAxis = series.is('xrange') ?
                            !isXAxis :
                            isXAxis;
                        // The `minPointOffset` is the value padding to the left
                        // of the axis in order to make room for points with a
                        // pointRange, typically columns, or line/scatter points
                        // on a category axis. When the `pointPlacement` option
                        // is 'between' or 'on', this padding does not apply.
                        minPointOffset = Math.max(minPointOffset, isPointPlacementAxis && isString(pointPlacement) ?
                            0 :
                            seriesPointRange / 2);
                        // Determine the total padding needed to the length of
                        // the axis to make room for the pointRange. If the
                        // series' pointPlacement is 'on', no padding is added.
                        pointRangePadding = Math.max(pointRangePadding, isPointPlacementAxis && pointPlacement === 'on' ?
                            0 :
                            seriesPointRange);
                    }
                });
            }
            // Record minPointOffset and pointRangePadding
            ordinalCorrection = (axis.ordinal?.slope && closestPointRange) ?
                axis.ordinal.slope / closestPointRange :
                1; // #988, #1853
            axis.minPointOffset = minPointOffset =
                minPointOffset * ordinalCorrection;
            axis.pointRangePadding =
                pointRangePadding = pointRangePadding * ordinalCorrection;
            // The `pointRange` is the width reserved for each point, like in a
            // column chart
            axis.pointRange = Math.min(pointRange, axis.single && hasCategories ? 1 : range);
            // The `closestPointRange` is the closest distance between points.
            // In columns it is mostly equal to pointRange, but in lines
            // pointRange is 0 while closestPointRange is some other value
            if (isXAxis) {
                axis.closestPointRange = closestPointRange;
            }
        }
        // Secondary values
        axis.translationSlope = axis.transA = transA =
            axis.staticScale ||
                axis.len / ((range + pointRangePadding) || 1);
        // Translation addend
        axis.transB = axis.horiz ? axis.left : axis.bottom;
        axis.minPixelPadding = transA * minPointOffset;
        fireEvent(this, 'afterSetAxisTranslation');
    }
    /**
     * @internal
     * @function Highcharts.Axis#minFromRange
     */
    minFromRange() {
        const { max, min } = this;
        return isNumber(max) && isNumber(min) && max - min || void 0;
    }
    /**
     * Set the tick positions to round values and optionally extend the extremes
     * to the nearest tick.
     *
     * @internal
     * @function Highcharts.Axis#setTickInterval
     *
     * @param {boolean} secondPass
     * TO-DO: parameter description
     *
     * @emits Highcharts.Axis#event:foundExtremes
     */
    setTickInterval(secondPass) {
        const axis = this, { categories, chart, dataMax, dataMin, dateTime, isXAxis, logarithmic, options, softThreshold } = axis, time = chart.time, threshold = isNumber(axis.threshold) ? axis.threshold : void 0, minRange = axis.minRange || 0, { ceiling, floor, linkedTo, softMax, softMin } = options, linkedParent = isNumber(linkedTo) && chart[axis.coll]?.[linkedTo], tickPixelIntervalOption = options.tickPixelInterval;
        let maxPadding = options.maxPadding, minPadding = options.minPadding, range = 0, linkedParentExtremes, 
        // Only non-negative tickInterval is valid, #12961
        tickIntervalOption = isNumber(options.tickInterval) && options.tickInterval >= 0 ?
            options.tickInterval : void 0, thresholdMin, thresholdMax, hardMin, hardMax;
        if (!dateTime && !categories && !linkedParent) {
            this.getTickAmount();
        }
        // Min or max set either by zooming/setExtremes or initial options
        hardMin = pick(axis.userMin, time.parse(options.min));
        hardMax = pick(axis.userMax, time.parse(options.max));
        // Linked axis gets the extremes from the parent axis
        if (linkedParent) {
            axis.linkedParent = linkedParent;
            linkedParentExtremes = linkedParent.getExtremes();
            axis.min = pick(linkedParentExtremes.min, linkedParentExtremes.dataMin);
            axis.max = pick(linkedParentExtremes.max, linkedParentExtremes.dataMax);
            if (this.type !== linkedParent.type) {
                // Can't link axes of different type
                error(11, true, chart);
            }
            // Initial min and max from the extreme data values
        }
        else {
            // Adjust to hard threshold
            if (softThreshold &&
                defined(threshold) &&
                isNumber(dataMax) &&
                isNumber(dataMin)) {
                if (dataMin >= threshold) {
                    thresholdMin = threshold;
                    minPadding = 0;
                }
                else if (dataMax <= threshold) {
                    thresholdMax = threshold;
                    maxPadding = 0;
                }
            }
            axis.min = pick(hardMin, thresholdMin, dataMin);
            axis.max = pick(hardMax, thresholdMax, dataMax);
        }
        if (isNumber(axis.max) && isNumber(axis.min)) {
            if (logarithmic) {
                if (axis.positiveValuesOnly &&
                    !secondPass &&
                    Math.min(axis.min, pick(dataMin, axis.min)) <= 0) { // #978
                    // Can't plot negative values on log axis
                    error(10, true, chart);
                }
                // The correctFloat cures #934, float errors on full tens. But
                // it was too aggressive for #4360 because of conversion back to
                // lin, therefore use precision 15.
                axis.min = correctFloat(logarithmic.log2lin(axis.min), 16);
                axis.max = correctFloat(logarithmic.log2lin(axis.max), 16);
            }
            // Handle zoomed range
            if (axis.range && isNumber(dataMin)) {
                // #618, #6773:
                axis.userMin = axis.min = hardMin = Math.max(dataMin, axis.minFromRange() || 0);
                axis.userMax = hardMax = axis.max;
                axis.range = void 0; // Don't use it when running setExtremes
            }
        }
        // Hook for Highcharts Stock Scroller and bubble axis padding
        fireEvent(axis, 'foundExtremes');
        // Adjust min and max for the minimum range
        axis.adjustForMinRange();
        if (isNumber(axis.min) && isNumber(axis.max)) {
            // Handle options for floor, ceiling, softMin and softMax (#6359)
            if (!isNumber(axis.userMin) &&
                isNumber(softMin) &&
                softMin < axis.min) {
                axis.min = hardMin = softMin; // #6894
            }
            if (!isNumber(axis.userMax) &&
                isNumber(softMax) &&
                softMax > axis.max) {
                axis.max = hardMax = softMax; // #6894
            }
            // Pad the values to get clear of the chart's edges. To avoid
            // tickInterval taking the padding into account, we do this after
            // computing tick interval (#1337).
            if (!categories &&
                !axis.axisPointRange &&
                !axis.stacking?.usePercentage &&
                !linkedParent) {
                range = axis.max - axis.min;
                if (range) {
                    if (!defined(hardMin) && minPadding) {
                        axis.min -= range * minPadding;
                    }
                    if (!defined(hardMax) && maxPadding) {
                        axis.max += range * maxPadding;
                    }
                }
            }
            if (!isNumber(axis.userMin) && isNumber(floor)) {
                axis.min = Math.max(axis.min, floor);
            }
            if (!isNumber(axis.userMax) && isNumber(ceiling)) {
                axis.max = Math.min(axis.max, ceiling);
            }
            // When the threshold is soft, adjust the extreme value only if the
            // data extreme and the padded extreme land on either side of the
            // threshold. For example, a series of [0, 1, 2, 3] would make the
            // yAxis add a tick for -1 because of the default `minPadding` and
            // `startOnTick` options. This is prevented by the `softThreshold`
            // option.
            if (softThreshold &&
                isNumber(dataMin) &&
                isNumber(dataMax)) {
                const numThreshold = threshold || 0;
                if (!defined(hardMin) &&
                    axis.min < numThreshold &&
                    dataMin >= numThreshold) {
                    axis.min = options.minRange ?
                        Math.min(numThreshold, axis.max - minRange) :
                        numThreshold;
                }
                else if (!defined(hardMax) &&
                    axis.max > numThreshold &&
                    dataMax <= numThreshold) {
                    axis.max = options.minRange ?
                        Math.max(numThreshold, axis.min + minRange) :
                        numThreshold;
                }
            }
            // If min is bigger than highest, or if max less than lowest value,
            // the chart should not render points. (#14417)
            if (!chart.polar && axis.min > axis.max) {
                if (defined(options.min)) {
                    axis.max = axis.min;
                }
                else if (defined(options.max)) {
                    axis.min = axis.max;
                }
            }
            range = axis.max - axis.min;
        }
        // Get tickInterval
        if (axis.min === axis.max ||
            !isNumber(axis.min) ||
            !isNumber(axis.max)) {
            axis.tickInterval = 1;
        }
        else if (linkedParent &&
            !tickIntervalOption &&
            tickPixelIntervalOption === linkedParent.options.tickPixelInterval) {
            axis.tickInterval = tickIntervalOption = linkedParent.tickInterval;
        }
        else {
            axis.tickInterval = pick(tickIntervalOption, this.tickAmount ?
                range / Math.max(this.tickAmount - 1, 1) :
                void 0, 
            // For categorized axis, 1 is default, for linear axis use
            // tickPix
            categories ?
                1 :
                // Don't let it be more than the data range
                range * tickPixelIntervalOption /
                    Math.max(axis.len, tickPixelIntervalOption));
        }
        // Now we're finished detecting min and max, crop and group series data.
        // This is in turn needed in order to find tick positions in ordinal
        // axes.
        if (isXAxis && !secondPass) {
            const hasExtremesChanged = axis.min !== axis.old?.min ||
                axis.max !== axis.old?.max;
            // First process all series assigned to that axis.
            axis.series.forEach(function (series) {
                // Allows filtering out points outside the plot area.
                series.forceCrop = series.forceCropping?.();
                series.processData(hasExtremesChanged);
            });
            // Then apply grouping if needed. The hasExtremesChanged helps to
            // decide if the data grouping should be skipped in the further
            // calculations #16319.
            fireEvent(this, 'postProcessData', { hasExtremesChanged });
        }
        // Set the translation factor used in translate function
        axis.setAxisTranslation();
        // Hook for ordinal axes and radial axes
        fireEvent(this, 'initialAxisTranslation');
        // In column-like charts, don't cramp in more ticks than there are
        // points (#1943, #4184)
        if (axis.pointRange && !tickIntervalOption) {
            axis.tickInterval = Math.max(axis.pointRange, axis.tickInterval);
        }
        // Before normalizing the tick interval, handle minimum tick interval.
        // This applies only if tickInterval is not defined.
        const minTickInterval = pick(options.minTickInterval, 
        // In datetime axes, don't go below the data interval, except when
        // there are scatter-like series involved (#13369).
        dateTime &&
            !axis.series.some((s) => !s.sorted) ?
            axis.closestPointRange : 0);
        if (!tickIntervalOption &&
            minTickInterval &&
            axis.tickInterval < minTickInterval) {
            axis.tickInterval = minTickInterval;
        }
        // For linear axes, normalize the interval
        if (!dateTime && !logarithmic && !tickIntervalOption) {
            axis.tickInterval = getNormalizedTickInterval(axis, axis.tickInterval);
        }
        // Prevent ticks from getting so close that we can't draw the labels
        if (!this.tickAmount) {
            axis.tickInterval = axis.unsquish();
        }
        this.setTickPositions();
    }
    /**
     * Now we have computed the normalized tickInterval, get the tick positions.
     *
     * @internal
     * @function Highcharts.Axis#setTickPositions
     *
     * @emits Highcharts.Axis#event:afterSetTickPositions
     */
    setTickPositions() {
        const axis = this, options = this.options, tickPositionsOption = options.tickPositions, tickPositioner = options.tickPositioner, minorTickIntervalOption = this.getMinorTickInterval(), allowEndOnTick = !this.isPanning, startOnTick = allowEndOnTick && options.startOnTick, endOnTick = allowEndOnTick && options.endOnTick;
        let tickPositions = [], tickPositionerResult;
        // Set the tickmarkOffset
        this.tickmarkOffset = (this.categories &&
            options.tickmarkPlacement === 'between' &&
            this.tickInterval === 1) ? 0.5 : 0; // #3202
        // When there is only one point, or all points have the same value on
        // this axis, then min and max are equal and tickPositions.length is 0
        // or 1. In this case, add some padding in order to center the point,
        // but leave it with one tick. #1337.
        this.single =
            this.min === this.max &&
                defined(this.min) &&
                !this.tickAmount &&
                (
                // Data is on integer (#6563)
                this.min % 1 === 0 ||
                    // Between integers and decimals are not allowed (#6274)
                    options.allowDecimals !== false);
        /**
         * Contains the current positions that are laid out on the axis. The
         * positions are numbers in terms of axis values. In a category axis
         * they are integers, in a datetime axis they are also integers, but
         * designating milliseconds.
         *
         * This property is read only - for modifying the tick positions, use
         * the `tickPositioner` callback or [axis.tickPositions(
         * https://api.highcharts.com/highcharts/xAxis.tickPositions) option
         * instead.
         *
         * @name Highcharts.Axis#tickPositions
         * @type {Highcharts.AxisTickPositionsArray|undefined}
         */
        if (tickPositionsOption) {
            // Find the tick positions. Work on a copy (#1565)
            tickPositions = tickPositionsOption.slice();
        }
        else if (isNumber(this.min) && isNumber(this.max)) {
            // Too many ticks (#6405). Create a friendly warning and provide two
            // ticks so at least we can show the data series.
            if (!axis.ordinal?.positions &&
                ((this.max - this.min) /
                    this.tickInterval >
                    Math.max(2 * this.len, 200))) {
                tickPositions = [this.min, this.max];
                error(19, false, this.chart);
            }
            else if (axis.dateTime) {
                tickPositions = axis.getTimeTicks(axis.dateTime.normalizeTimeTickInterval(this.tickInterval, options.units), this.min, this.max, options.startOfWeek, axis.ordinal?.positions, this.closestPointRange, true);
            }
            else if (axis.logarithmic) {
                tickPositions = axis.logarithmic.getLogTickPositions(this.tickInterval, this.min, this.max);
            }
            else {
                const startingTickInterval = this.tickInterval;
                let adjustedTickInterval = startingTickInterval;
                while (adjustedTickInterval <= startingTickInterval * 2) {
                    tickPositions = this.getLinearTickPositions(this.tickInterval, this.min, this.max);
                    // If there are more tick positions than the set tickAmount,
                    // increase the tickInterval and continue until it fits.
                    // (#17100)
                    if (this.tickAmount &&
                        tickPositions.length > this.tickAmount) {
                        this.tickInterval = getNormalizedTickInterval(this, adjustedTickInterval *= 1.1);
                    }
                    else {
                        break;
                    }
                }
            }
            // Too dense ticks, keep only the first and last (#4477)
            if (tickPositions.length > this.len) {
                tickPositions = [
                    tickPositions[0],
                    tickPositions[tickPositions.length - 1]
                ];
                // Reduce doubled value (#7339)
                if (tickPositions[0] === tickPositions[1]) {
                    tickPositions.length = 1;
                }
            }
            // Run the tick positioner callback, that allows modifying auto tick
            // positions.
            if (tickPositioner) {
                // Make it available to the positioner
                this.tickPositions = tickPositions;
                tickPositionerResult = tickPositioner.apply(axis, [this.min, this.max, axis]);
                if (tickPositionerResult) {
                    tickPositions = tickPositionerResult;
                }
            }
        }
        // Ensure the old ticks are removed and new ones added (#17393)
        if (!this.isDirty &&
            tickPositions.length !==
                this.tickPositions?.length) {
            this.isDirty = true;
        }
        this.tickPositions = tickPositions;
        // Get minorTickInterval
        this.minorTickInterval =
            minorTickIntervalOption === 'auto' && this.tickInterval ?
                this.tickInterval / options.minorTicksPerMajor :
                minorTickIntervalOption;
        // Reset min/max or remove extremes based on start/end on tick
        this.paddedTicks = tickPositions.slice(0); // Used for logarithmic minor
        this.trimTicks(tickPositions, startOnTick, endOnTick);
        if (!this.isLinked && isNumber(this.min) && isNumber(this.max)) {
            // Subtract half a unit (#2619, #2846, #2515, #3390), but not in
            // case of multiple ticks (#6897)
            if (this.single &&
                tickPositions.length < 2 &&
                !this.categories &&
                !this.series.some((s) => (s.is('heatmap') && s.options.pointPlacement === 'between'))) {
                this.min -= 0.5;
                this.max += 0.5;
            }
            if (!tickPositionsOption && !tickPositionerResult) {
                this.adjustTickAmount();
            }
        }
        fireEvent(this, 'afterSetTickPositions');
    }
    /**
     * Handle startOnTick and endOnTick by either adapting to padding min/max or
     * rounded min/max. Also handle single data points.
     *
     * @internal
     * @function Highcharts.Axis#trimTicks
     *
     * @param {Array<number>} tickPositions
     * TO-DO: parameter description
     *
     * @param {boolean} [startOnTick]
     * TO-DO: parameter description
     *
     * @param {boolean} [endOnTick]
     * TO-DO: parameter description
     */
    trimTicks(tickPositions, startOnTick, endOnTick) {
        const roundedMin = tickPositions[0], roundedMax = tickPositions[tickPositions.length - 1], minPointOffset = (!this.isOrdinal && this.minPointOffset) || 0; // (#12716)
        fireEvent(this, 'trimTicks');
        if (!this.isLinked ||
            // Linked non-grid axes should trim ticks, #21743.
            // Grid axis has custom handling of ticks.
            !this.grid) {
            if (startOnTick && isNumber(roundedMin)) { // #6502
                this.min = roundedMin;
            }
            else if (tickPositions.length) {
                while (this.min - minPointOffset > tickPositions[0]) {
                    tickPositions.shift();
                }
            }
            if (endOnTick && isNumber(roundedMax)) { // #6502
                this.max = roundedMax;
            }
            else {
                while (this.max + minPointOffset <
                    tickPositions[tickPositions.length - 1]) {
                    tickPositions.pop();
                }
            }
            // If no tick are left, set one tick in the middle (#3195)
            if (tickPositions.length === 0 &&
                defined(roundedMin) &&
                !this.options.tickPositions) {
                tickPositions.push((roundedMax + roundedMin) / 2);
            }
        }
    }
    /**
     * Check if there are multiple axes in the same pane.
     *
     * @internal
     * @function Highcharts.Axis#alignToOthers
     *
     * @return {boolean|undefined}
     * True if there are other axes.
     */
    alignToOthers() {
        const axis = this, chart = axis.chart, alignedAxes = [this], options = axis.options, chartOptions = chart.options.chart, alignThresholds = (this.coll === 'yAxis' &&
            chartOptions.alignThresholds), thresholdAlignments = [];
        let hasOther;
        axis.thresholdAlignment = void 0;
        if ((
        // Only if alignTicks or alignThresholds is true
        (chartOptions.alignTicks !== false && options.alignTicks) ||
            alignThresholds) &&
            // Disabled when startOnTick or endOnTick are false (#7604)
            options.startOnTick !== false &&
            options.endOnTick !== false &&
            // Don't try to align ticks on a log axis, they are not evenly
            // spaced (#6021)
            !axis.logarithmic) {
            // Get a key identifying which pane the axis belongs to
            const getKey = (axis) => {
                const { horiz, options } = axis;
                return [
                    horiz ? options.left : options.top,
                    options.width,
                    options.height,
                    options.pane
                ].join(',');
            };
            const thisKey = getKey(this);
            chart[this.coll].forEach(function (otherAxis) {
                const { series } = otherAxis;
                if (
                // #4442
                series.length &&
                    series.some((s) => s.visible) &&
                    otherAxis !== axis &&
                    getKey(otherAxis) === thisKey) {
                    hasOther = true; // #4201
                    alignedAxes.push(otherAxis);
                }
            });
        }
        if (hasOther && alignThresholds) {
            // Handle alignThresholds. The `thresholdAlignments` array keeps
            // records of where each axis in the group wants its threshold, from
            // 0 which is on `axis.min`, to 1 which is on `axis.max`.
            alignedAxes.forEach((otherAxis) => {
                const threshAlign = otherAxis.getThresholdAlignment(axis);
                if (isNumber(threshAlign)) {
                    thresholdAlignments.push(threshAlign);
                }
            });
            // For each of the axes in the group, record the average
            // `thresholdAlignment`.
            const thresholdAlignment = thresholdAlignments.length > 1 ?
                thresholdAlignments.reduce((sum, n) => (sum += n), 0) / thresholdAlignments.length :
                void 0;
            alignedAxes.forEach((axis) => {
                axis.thresholdAlignment = thresholdAlignment;
            });
        }
        return hasOther;
    }
    /**
     * Where the axis wants its threshold, from 0 which is on `axis.min`, to 1 which
     * is on `axis.max`.
     *
     * @internal
     * @function Highcharts.Axis#getThresholdAlignment
     */
    getThresholdAlignment(callerAxis) {
        if (!isNumber(this.dataMin) ||
            (this !== callerAxis &&
                this.series.some((s) => 
                // The xAxis.isDirty check is for setExtremes (#23677)
                s.isDirty || s.isDirtyData || s.xAxis?.isDirty))) {
            this.getSeriesExtremes();
        }
        if (isNumber(this.threshold)) {
            let thresholdAlignment = clamp(((this.threshold - (this.dataMin || 0)) /
                ((this.dataMax || 0) - (this.dataMin || 0))), 0, 1);
            if (this.options.reversed) {
                thresholdAlignment = 1 - thresholdAlignment;
            }
            return thresholdAlignment;
        }
    }
    /**
     * Find the max ticks of either the x and y axis collection, and record it
     * in `this.tickAmount`.
     *
     * @internal
     * @function Highcharts.Axis#getTickAmount
     */
    getTickAmount() {
        const axis = this, options = this.options, tickPixelInterval = options.tickPixelInterval;
        let tickAmount = options.tickAmount;
        if (!defined(options.tickInterval) &&
            !tickAmount &&
            this.len < tickPixelInterval &&
            !this.isRadial &&
            !axis.logarithmic &&
            options.startOnTick &&
            options.endOnTick) {
            tickAmount = 2;
        }
        if (!tickAmount && this.alignToOthers()) {
            // Add 1 because 4 tick intervals require 5 ticks (including first
            // and last)
            tickAmount = Math.ceil(this.len / tickPixelInterval) + 1;
        }
        // For tick amounts of 2 and 3, compute five ticks and remove the
        // intermediate ones. This prevents the axis from adding ticks that are
        // too far away from the data extremes.
        if (tickAmount < 4) {
            this.finalTickAmt = tickAmount;
            tickAmount = 5;
        }
        this.tickAmount = tickAmount;
    }
    /**
     * When using multiple axes, adjust the number of ticks to match the highest
     * number of ticks in that group.
     *
     * @internal
     * @function Highcharts.Axis#adjustTickAmount
     */
    adjustTickAmount() {
        const axis = this, { finalTickAmt, max, min, options, tickPositions, tickAmount, thresholdAlignment } = axis, currentTickAmount = tickPositions?.length, threshold = pick(axis.threshold, axis.softThreshold ? 0 : null);
        let len, i, tickInterval = axis.tickInterval, thresholdTickIndex;
        const 
        // Extend the tickPositions by appending a position
        append = () => tickPositions.push(correctFloat(tickPositions[tickPositions.length - 1] +
            tickInterval)), 
        // Extend the tickPositions by prepending a position
        prepend = () => tickPositions.unshift(correctFloat(tickPositions[0] - tickInterval));
        // If `thresholdAlignment` is a number, it means the `alignThresholds`
        // option is true. The `thresholdAlignment` is a scalar value between 0
        // and 1 for where the threshold should be relative to `axis.min` and
        // `axis.max`. Now that we know the tick amount, convert this to the
        // tick index. Unless `thresholdAlignment` is exactly 0 or 1, avoid the
        // first or last tick because that would lead to series being clipped.
        if (isNumber(thresholdAlignment)) {
            thresholdTickIndex = thresholdAlignment === 0 ? 0 :
                thresholdAlignment === 1 ? tickAmount - 1 :
                    // Get the closest integer between 1 and
                    // `tickAmount - 2` (#23787)
                    Math.round(clamp(thresholdAlignment * (tickAmount - 1), 1, tickAmount - 2));
            if (options.reversed) {
                thresholdTickIndex = tickAmount - 1 - thresholdTickIndex;
            }
        }
        if (axis.hasData() && isNumber(min) && isNumber(max)) { // #14769
            // Adjust extremes and translation to the modified tick positions
            const adjustExtremes = () => {
                axis.transA *= (currentTickAmount - 1) / (tickAmount - 1);
                // Do not crop when ticks are not extremes (#9841)
                axis.min = options.startOnTick ?
                    tickPositions[0] :
                    Math.min(min, tickPositions[0]);
                axis.max = options.endOnTick ?
                    tickPositions[tickPositions.length - 1] :
                    Math.max(max, tickPositions[tickPositions.length - 1]);
            };
            // When the axis is subject to the alignThresholds option. Use
            // axis.threshold because the local threshold includes the
            // `softThreshold`.
            if (isNumber(thresholdTickIndex) && isNumber(axis.threshold)) {
                // Throw away the previously computed tickPositions and start
                // from scratch with only the threshold itself, then add ticks
                // below the threshold first, then fill up above the threshold.
                // If we are not able to fill up to axis.max, double the
                // tickInterval and run again.
                while (tickPositions[thresholdTickIndex] !== threshold ||
                    tickPositions.length !== tickAmount ||
                    tickPositions[0] > min ||
                    tickPositions[tickPositions.length - 1] < max) {
                    tickPositions.length = 0;
                    tickPositions.push(axis.threshold);
                    while (tickPositions.length < tickAmount) {
                        if (
                        // Start by prepending positions until the threshold
                        // is at the required index...
                        tickPositions[thresholdTickIndex] === void 0 ||
                            tickPositions[thresholdTickIndex] > axis.threshold) {
                            prepend();
                        }
                        else {
                            // ... then append positions until we have the
                            // required length
                            append();
                        }
                    }
                    // Safety vent
                    if (tickInterval > axis.tickInterval * 8) {
                        break;
                    }
                    tickInterval *= 2;
                }
                adjustExtremes();
            }
            else if (currentTickAmount < tickAmount) {
                while (tickPositions.length < tickAmount) {
                    // Extend evenly for both sides unless we're on the
                    // threshold (#3965)
                    if (tickPositions.length % 2 || min === threshold) {
                        append();
                    }
                    else {
                        prepend();
                    }
                }
                adjustExtremes();
            }
            // The finalTickAmt property is set in getTickAmount
            if (defined(finalTickAmt)) {
                i = len = tickPositions.length;
                while (i--) {
                    if (
                    // Remove every other tick
                    (finalTickAmt === 3 && i % 2 === 1) ||
                        // Remove all but first and last
                        (finalTickAmt <= 2 && i > 0 && i < len - 1)) {
                        tickPositions.splice(i, 1);
                    }
                }
                axis.finalTickAmt = void 0;
            }
        }
    }
    /**
     * Set the scale based on data min and max, user set min and max or options.
     *
     * @internal
     * @function Highcharts.Axis#setScale
     *
     * @emits Highcharts.Axis#event:afterSetScale
     */
    setScale() {
        const axis = this, { coll, stacking } = axis;
        let isDirtyData = false, isXAxisDirty = false;
        axis.series.forEach((series) => {
            isDirtyData = isDirtyData || series.isDirtyData || series.isDirty;
            // When x axis is dirty, we need new data extremes for y as
            // well:
            isXAxisDirty = (isXAxisDirty ||
                series.xAxis?.isDirty ||
                false);
        });
        // Set the new axisLength
        axis.setAxisSize();
        const isDirtyAxisLength = axis.len !== axis.old?.len;
        // Do we really need to go through all this?
        if (isDirtyAxisLength ||
            isDirtyData ||
            isXAxisDirty ||
            axis.isLinked ||
            axis.forceRedraw ||
            axis.userMin !== axis.old?.userMin ||
            axis.userMax !== axis.old?.userMax ||
            axis.alignToOthers()) {
            if (stacking && coll === 'yAxis') {
                stacking.buildStacks();
            }
            axis.forceRedraw = false;
            // #18066 delete minRange property to ensure that it will be
            // calculated again after dirty data in series
            if (!axis.userMinRange) {
                axis.minRange = void 0;
            }
            // Get data extremes if needed
            axis.getSeriesExtremes();
            // Get fixed positions based on tickInterval
            axis.setTickInterval();
            if (stacking && coll === 'xAxis') {
                stacking.buildStacks();
            }
            // Mark as dirty if it is not already set to dirty and extremes have
            // changed. #595.
            if (!axis.isDirty) {
                axis.isDirty =
                    isDirtyAxisLength ||
                        axis.min !== axis.old?.min ||
                        axis.max !== axis.old?.max;
            }
        }
        else if (stacking) {
            stacking.cleanStacks();
        }
        // Recalculate all extremes object when the data has changed. It is
        // required when vertical panning is enabled.
        if (isDirtyData) {
            delete axis.allExtremes;
        }
        fireEvent(this, 'afterSetScale');
    }
    /**
     * Set the minimum and maximum of the axes after render time. If the
     * `startOnTick` and `endOnTick` options are true, the minimum and maximum
     * values are rounded off to the nearest tick. To prevent this, these
     * options can be set to false before calling setExtremes. Also, setExtremes
     * will not allow a range lower than the `minRange` option, which by default
     * is the range of five points.
     *
     * @sample highcharts/members/axis-setextremes/
     *         Set extremes from a button
     * @sample highcharts/members/axis-setextremes-datetime/
     *         Set extremes on a datetime axis
     * @sample highcharts/members/axis-setextremes-off-ticks/
     *         Set extremes off ticks
     * @sample stock/members/axis-setextremes/
     *         Set extremes in Highcharts Stock
     *
     * @function Highcharts.Axis#setExtremes
     *
     * @param {number|string} [min]
     * The new minimum value. For datetime axes, date strings are accepted.
     *
     * @param {number|string} [max]
     * The new maximum value. For datetime axes, date strings are accepted.
     *
     * @param {boolean} [redraw=true]
     * Whether to redraw the chart or wait for an explicit call to
     * {@link Highcharts.Chart#redraw}
     *
     * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true]
     * Enable or modify animations.
     *
     * @param {*} [eventArguments]
     * Arguments to be accessed in event handler.
     *
     * @emits Highcharts.Axis#event:setExtremes
     */
    setExtremes(min, max, redraw = true, animation, eventArguments) {
        const chart = this.chart;
        this.series.forEach((s) => {
            delete s.kdTree;
        });
        min = chart.time.parse(min);
        max = chart.time.parse(max);
        // Extend the arguments with min and max
        eventArguments = extend(eventArguments, { min, max });
        // Fire the event
        fireEvent(this, 'setExtremes', eventArguments, (e) => {
            this.userMin = e.min;
            this.userMax = e.max;
            this.eventArgs = e;
            if (redraw) {
                chart.redraw(animation);
            }
        });
    }
    /**
     * Update the axis metrics.
     *
     * @internal
     * @function Highcharts.Axis#setAxisSize
     */
    setAxisSize() {
        const chart = this.chart, options = this.options, 
        // [top, right, bottom, left]
        offsets = options.offsets || [0, 0, 0, 0], horiz = this.horiz, 
        // Check for percentage based input values. Rounding fixes problems
        // with column overflow and plot line filtering (#4898, #4899)
        width = this.width = Math.round(relativeLength(pick(options.width, chart.plotWidth - offsets[3] + offsets[1]), chart.plotWidth)), height = this.height = Math.round(relativeLength(pick(options.height, chart.plotHeight - offsets[0] + offsets[2]), chart.plotHeight)), top = this.top = Math.round(relativeLength(pick(options.top, chart.plotTop + offsets[0]), chart.plotHeight, chart.plotTop)), left = this.left = Math.round(relativeLength(pick(options.left, chart.plotLeft + offsets[3]), chart.plotWidth, chart.plotLeft));
        // Expose basic values to use in Series object and navigator
        this.bottom = chart.chartHeight - height - top;
        this.right = chart.chartWidth - width - left;
        // Direction agnostic properties
        this.len = Math.max(horiz ? width : height, 0); // Math.max fixes #905
        /**
         * The position of the axis in terms of pixels, compared to the chart
         * edge. In a horizontal axis it is the same as `chart.plotLeft` unless
         * the axis is explicitly positioned, and in a default vertical axis it
         * is the same as `chart.plotTop`.
         *
         * @name Highcharts.Axis#pos
         * @type {number}
         */
        this.pos = horiz ? left : top; // Distance from SVG origin
    }
    /**
     * Get the current extremes for the axis.
     *
     * @sample highcharts/members/axis-getextremes/
     *         Report extremes by click on a button
     *
     * @function Highcharts.Axis#getExtremes
     *
     * @return {Highcharts.ExtremesObject}
     * An object containing extremes information.
     */
    getExtremes() {
        const axis = this, log = axis.logarithmic;
        return {
            min: log ?
                correctFloat(log.lin2log(axis.min)) :
                axis.min,
            max: log ?
                correctFloat(log.lin2log(axis.max)) :
                axis.max,
            dataMin: axis.dataMin,
            dataMax: axis.dataMax,
            userMin: axis.userMin,
            userMax: axis.userMax
        };
    }
    /**
     * Get the zero plane either based on zero or on the min or max value.
     * Used in bar and area plots.
     *
     * @function Highcharts.Axis#getThreshold
     *
     * @param {number} threshold
     * The threshold in axis values.
     *
     * @return {number}
     * The translated threshold position in terms of pixels, and corrected to
     * stay within the axis bounds.
     */
    getThreshold(threshold) {
        const axis = this, log = axis.logarithmic, realMin = log ? log.lin2log(axis.min) : axis.min, realMax = log ? log.lin2log(axis.max) : axis.max;
        if (threshold === null || threshold === -Infinity) {
            threshold = realMin;
        }
        else if (threshold === Infinity) {
            threshold = realMax;
        }
        else if (realMin > threshold) {
            threshold = realMin;
        }
        else if (realMax < threshold) {
            threshold = realMax;
        }
        return axis.translate(threshold, 0, 1, 0, 1);
    }
    /**
     * Compute auto alignment for the axis label based on which side the axis is
     * on and the given rotation for the label.
     *
     * @internal
     * @function Highcharts.Axis#autoLabelAlign
     *
     * @param {number} rotation
     * The rotation in degrees as set by either the `rotation` or `autoRotation`
     * options.
     *
     * @return {Highcharts.AlignValue}
     * Can be `"center"`, `"left"` or `"right"`.
     */
    autoLabelAlign(rotation) {
        const angle = ((rotation - this.side * 90) % 360 + 360) % 360, evt = { align: 'center' };
        fireEvent(this, 'autoLabelAlign', evt, function (e) {
            if (angle > 15 && angle < 165) {
                e.align = 'right';
            }
            else if (angle > 195 && angle < 345) {
                e.align = 'left';
            }
        });
        return evt.align;
    }
    /**
     * Get the tick length and width for the axis based on axis options.
     *
     * @internal
     * @function Highcharts.Axis#tickSize
     *
     * @param {string} [prefix]
     * 'tick' or 'minorTick'
     *
     * @return {Array<number,number>|undefined}
     * An array of tickLength and tickWidth
     */
    tickSize(prefix) {
        const options = this.options, tickWidth = pick(options[prefix === 'tick' ? 'tickWidth' : 'minorTickWidth'], 
        // Default to 1 on linear and datetime X axes
        prefix === 'tick' && this.isXAxis && !this.categories ? 1 : 0);
        let tickLength = options[prefix === 'tick' ? 'tickLength' : 'minorTickLength'], tickSize;
        if (tickWidth && tickLength) {
            // Negate the length
            if (options[prefix + 'Position'] === 'inside') {
                tickLength = -tickLength;
            }
            tickSize = [tickLength, tickWidth];
        }
        const e = { tickSize, prefix };
        fireEvent(this, 'afterTickSize', e);
        return e.tickSize;
    }
    /**
     * Return the size of the labels.
     *
     * @internal
     * @function Highcharts.Axis#labelMetrics
     */
    labelMetrics() {
        const renderer = this.chart.renderer, ticks = this.ticks, tick = ticks[Object.keys(ticks)[0]] || {};
        return this.chart.renderer.fontMetrics(tick.label ||
            renderer.box);
    }
    /**
     * Prevent the ticks from getting so close we can't draw the labels. On a
     * horizontal axis, this is handled by rotating the labels, removing ticks
     * and adding ellipsis. On a vertical axis remove ticks and add ellipsis.
     *
     * @internal
     * @function Highcharts.Axis#unsquish
     */
    unsquish() {
        const labelOptions = this.options.labels, padding = labelOptions.padding || 0, horiz = this.horiz, tickInterval = this.tickInterval, slotSize = this.len / (((this.categories ? 1 : 0) +
            this.max -
            this.min) /
            tickInterval), rotationOption = labelOptions.rotation, 
        // We don't know the actual rendered line height at this point, but
        // it defaults to 0.8em
        lineHeight = correctFloat(this.labelMetrics().h * 0.8), range = Math.max(this.max - this.min, 0), 
        // Return the multiple of tickInterval that is needed to avoid
        // collision
        getStep = function (spaceNeeded) {
            let step = (spaceNeeded + 2 * padding) / (slotSize || 1);
            step = step > 1 ? Math.ceil(step) : 1;
            // Guard for very small or negative angles (#9835)
            if (step * tickInterval > range &&
                spaceNeeded !== Infinity &&
                slotSize !== Infinity &&
                range) {
                step = Math.ceil(range / tickInterval);
            }
            return correctFloat(step * tickInterval);
        };
        let newTickInterval = tickInterval, rotation, bestScore = Number.MAX_VALUE, autoRotation;
        if (horiz) {
            if (!labelOptions.staggerLines) {
                if (isNumber(rotationOption)) {
                    autoRotation = [rotationOption];
                }
                else if (slotSize < labelOptions.autoRotationLimit) {
                    autoRotation = labelOptions.autoRotation;
                }
            }
            if (autoRotation) {
                let step, score;
                // Loop over the given autoRotation options, and determine which
                // gives the best score. The best score is that with the lowest
                // number of steps and a rotation closest to horizontal.
                for (const rot of autoRotation) {
                    if (rot === rotationOption ||
                        (rot && rot >= -90 && rot <= 90)) { // #3891
                        step = getStep(Math.abs(lineHeight / Math.sin(Axis_deg2rad * rot)));
                        score = step + Math.abs(rot / 360);
                        if (score < bestScore) {
                            bestScore = score;
                            rotation = rot;
                            newTickInterval = step;
                        }
                    }
                }
            }
        }
        else { // #4411
            newTickInterval = getStep(lineHeight * 0.75);
        }
        this.autoRotation = autoRotation;
        this.labelRotation = pick(rotation, isNumber(rotationOption) ? rotationOption : 0);
        return labelOptions.step ? tickInterval : newTickInterval;
    }
    /**
     * Get the general slot width for labels/categories on this axis. This may
     * change between the pre-render (from Axis.getOffset) and the final tick
     * rendering and placement.
     *
     * @internal
     * @function Highcharts.Axis#getSlotWidth
     *
     * @param {Highcharts.Tick} [tick] Optionally, calculate the slot width
     * basing on tick label. It is used in highcharts-3d module, where the slots
     * has different widths depending on perspective angles.
     *
     * @return {number}
     * The pixel width allocated to each axis label.
     */
    getSlotWidth(tick) {
        // #5086, #1580, #1931
        const chart = this.chart, horiz = this.horiz, labelOptions = this.options.labels, slotCount = Math.max(this.tickPositions.length - (this.categories ? 0 : 1), 1), marginLeft = chart.margin[3];
        // Used by grid axis
        if (tick && isNumber(tick.slotWidth)) { // #13221, can be 0
            return tick.slotWidth;
        }
        if (horiz && labelOptions.step < 2 && !this.isRadial) {
            if (labelOptions.rotation) { // #4415
                return 0;
            }
            return ((this.staggerLines || 1) * this.len) / slotCount;
        }
        if (!horiz) {
            // #7028
            const cssWidth = labelOptions.style.width;
            if (cssWidth !== void 0) {
                return parseInt(String(cssWidth), 10);
            }
            // Skip marginLeft for opposite axis to avoid label cutoff, #22821
            if (!this.opposite && marginLeft) {
                return marginLeft - chart.spacing[3];
            }
        }
        // Last resort, a fraction of the available size
        return chart.chartWidth * 0.33;
    }
    /**
     * Render the axis labels and determine whether ellipsis or rotation need to
     * be applied.
     *
     * @internal
     * @function Highcharts.Axis#renderUnsquish
     */
    renderUnsquish() {
        const chart = this.chart, renderer = chart.renderer, tickPositions = this.tickPositions, ticks = this.ticks, labelOptions = this.options.labels, labelStyleOptions = labelOptions.style, horiz = this.horiz, slotWidth = this.getSlotWidth(), innerWidth = Math.max(1, Math.round(slotWidth - (horiz ?
            2 * (labelOptions.padding || 0) :
            labelOptions.distance || 0 // #21172
        ))), attr = {}, labelMetrics = this.labelMetrics(), lineClampOption = labelStyleOptions.lineClamp;
        let commonWidth, lineClamp = lineClampOption ?? (Math.floor(this.len / (tickPositions.length * labelMetrics.h)) || 1), maxLabelLength = 0;
        // Set rotation option unless it is "auto", like in gauges
        if (!isString(labelOptions.rotation)) {
            // #4443
            attr.rotation = labelOptions.rotation || 0;
        }
        // Get the longest label length
        tickPositions.forEach(function (tickPosition) {
            maxLabelLength = Math.max(maxLabelLength, ticks[tickPosition].label?.textPxLength || 0);
        });
        this.maxLabelLength = maxLabelLength;
        // Handle auto rotation on horizontal axis
        if (this.autoRotation) {
            // Apply rotation only if the label is too wide for the slot, and
            // the label is wider than its height.
            if (maxLabelLength > innerWidth &&
                maxLabelLength > labelMetrics.h) {
                attr.rotation = this.labelRotation;
            }
            else {
                this.labelRotation = 0;
            }
            // Handle word-wrap or ellipsis on vertical axis
        }
        else if (slotWidth) {
            // For word-wrap or ellipsis
            commonWidth = innerWidth;
        }
        // Add ellipsis if the label length is significantly longer than ideal
        if (attr.rotation) {
            commonWidth = (maxLabelLength > chart.chartHeight * 0.5 ?
                chart.chartHeight * 0.33 :
                maxLabelLength);
            if (!lineClampOption) {
                lineClamp = 1;
            }
        }
        // Set the explicit or automatic label alignment
        this.labelAlign = labelOptions.align ||
            this.autoLabelAlign(this.labelRotation || 0);
        if (this.labelAlign) {
            attr.align = this.labelAlign;
        }
        // Apply general and specific CSS
        tickPositions.forEach(function (pos) {
            const tick = ticks[pos], label = tick?.label, widthOption = labelStyleOptions.width, css = {};
            if (label) {
                // This needs to go before the CSS in old IE (#4502)
                label.attr(attr);
                if (tick.shortenLabel) {
                    tick.shortenLabel();
                }
                else if (commonWidth &&
                    !widthOption &&
                    // Setting width in this case messes with the bounding box
                    // (#7975)
                    labelStyleOptions.whiteSpace !== 'nowrap' &&
                    (
                    // Speed optimizing, #7656
                    commonWidth < (label.textPxLength || 0) ||
                        // Resetting CSS, #4928
                        label.element.tagName === 'DIV')) {
                    label.css(extend(css, {
                        width: `${commonWidth}px`,
                        lineClamp
                    }));
                    // Reset previously shortened label (#8210, #22961)
                }
                else {
                    const wasSquished = label.styles.width ||
                        label.textWidth || label.styles.lineClamp;
                    if (wasSquished && !css.width && !widthOption) {
                        label.css({
                            width: 'auto',
                            lineClamp: 0
                        });
                    }
                }
                tick.rotation = attr.rotation;
            }
        }, this);
        // Note: Why is this not part of getLabelPosition?
        this.tickRotCorr = renderer.rotCorr(labelMetrics.b, this.labelRotation || 0, this.side !== 0);
    }
    /**
     * Return true if the axis has associated data.
     *
     * @function Highcharts.Axis#hasData
     *
     * @return {boolean}
     * True if the axis has associated visible series and those series have
     * either valid data points or explicit `min` and `max` settings.
     */
    hasData() {
        return this.series.some(function (s) {
            return s.hasData();
        }) ||
            (this.options.showEmpty &&
                defined(this.min) &&
                defined(this.max));
    }
    /**
     * Adds the title defined in axis.options.title.
     *
     * @function Highcharts.Axis#addTitle
     *
     * @param {boolean} [display]
     * Whether or not to display the title.
     */
    addTitle(display) {
        const axis = this, renderer = axis.chart.renderer, horiz = axis.horiz, opposite = axis.opposite, options = axis.options, axisTitleOptions = options.title, styledMode = axis.chart.styledMode;
        let textAlign;
        if (!axis.axisTitle) {
            textAlign = axisTitleOptions.textAlign;
            if (!textAlign) {
                textAlign = (horiz ? {
                    low: 'left',
                    middle: 'center',
                    high: 'right'
                } : {
                    low: opposite ? 'right' : 'left',
                    middle: 'center',
                    high: opposite ? 'left' : 'right'
                })[axisTitleOptions.align];
            }
            axis.axisTitle = renderer
                .text(axisTitleOptions.text || '', 0, 0, axisTitleOptions.useHTML)
                .attr({
                zIndex: 7,
                rotation: axisTitleOptions.rotation || 0,
                align: textAlign
            })
                .addClass('highcharts-axis-title');
            // #7814, don't mutate style option
            if (!styledMode) {
                axis.axisTitle.css(merge(axisTitleOptions.style));
            }
            axis.axisTitle.add(axis.axisGroup);
            axis.axisTitle.isNew = true;
        }
        // Max width defaults to the length of the axis
        if (!styledMode &&
            !axisTitleOptions.style.width &&
            !axis.isRadial) {
            axis.axisTitle.css({
                width: axis.len + 'px'
            });
        }
        // Hide or show the title depending on whether showEmpty is set
        axis.axisTitle[display ? 'show' : 'hide'](display);
    }
    /**
     * Generates a tick for initial positioning.
     *
     * @internal
     * @function Highcharts.Axis#generateTick
     */
    generateTick(pos) {
        const axis = this, ticks = axis.ticks;
        if (!ticks[pos]) {
            ticks[pos] = new Axis_Tick(axis, pos);
        }
        else {
            ticks[pos].addLabel(); // Update labels depending on tick interval
        }
    }
    /**
     * Create the axisGroup and gridGroup elements on first iteration.
     *
     * @internal
     * @function Highcharts.Axis#getOffset
     *
     * @emits Highcharts.Axis#event:afterGetOffset
     */
    createGroups() {
        const { axisParent, // Used in color axis
        chart, coll, options } = this, renderer = chart.renderer;
        const createGroup = (name, suffix, zIndex) => renderer.g(name)
            .attr({ zIndex })
            .addClass(`highcharts-${coll.toLowerCase()}${suffix} ` +
            (this.isRadial ? `highcharts-radial-axis${suffix} ` : '') +
            (options.className || ''))
            .add(axisParent);
        if (!this.axisGroup) {
            this.gridGroup = createGroup('grid', '-grid', options.gridZIndex).clip(this.clippable ? chart.plotClipInner : void 0);
            this.axisGroup = createGroup('axis', '', options.zIndex);
            this.labelGroup = createGroup('axis-labels', '-labels', options.labels.zIndex);
        }
    }
    /**
     * Shuffle existing category ticks, like in bar race chart
     *
     * @internal
     */
    shuffleTicks() {
        const ticks = this.ticks, oldNames = this.old?.names;
        if (this.type === 'category' && oldNames) {
            oldNames.forEach((name, oldPos) => {
                const pos = this.namesMap[name];
                if (defined(pos) && oldPos !== pos) {
                    // Move tick instance
                    if (ticks[oldPos]) {
                        ticks[oldPos].pos = pos;
                        this.isDirty = true; // Mark for redraw
                    }
                    // Check if the existing tick in the new position has a
                    // new place to go
                    if (ticks[pos] &&
                        this.names.indexOf(oldNames[pos]) === -1) {
                        // Mark for destruction below
                        ticks[pos].pos = NaN;
                    }
                }
            });
            // Remap ticks to new positions
            const values = Object.values(ticks);
            Object.keys(ticks).forEach((key) => {
                delete ticks[key];
            });
            values.forEach((tick) => {
                if (!isNaN(tick.pos)) {
                    ticks[tick.pos] = tick;
                }
                else {
                    tick.destroy();
                }
            });
        }
    }
    /**
     * Render the tick labels to a preliminary position to get their sizes
     *
     * @internal
     * @function Highcharts.Axis#getOffset
     *
     * @emits Highcharts.Axis#event:afterGetOffset
     */
    getOffset() {
        const axis = this, { chart, horiz, options, side, ticks, tickPositions, coll } = axis, hasData = axis.hasData(), axisTitleOptions = options.title, labelOptions = options.labels, hasCrossing = isNumber(options.crossing), axisOffset = chart.axisOffset, clipOffset = chart.clipOffset, directionFactor = [-1, 1, 1, -1][side];
        let tickRotCorr = axis.tickRotCorr || { x: 0, y: 0 }, absTickRotCorrX = 0, showAxis, titleOffset = 0, titleOffsetOption, titleMargin = 0, labelOffset = 0, // Reset
        labelOffsetPadded, lineHeightCorrection, reserveSpaceDefault;
        // For reuse in Axis.render
        axis.showAxis = showAxis = hasData || options.showEmpty;
        // Set/reset staggerLines
        axis.staggerLines = (axis.horiz && labelOptions.staggerLines) || void 0;
        axis.createGroups();
        if (hasData || axis.isLinked) {
            // Shuffle existing category ticks
            axis.shuffleTicks();
            // Generate new ticks
            tickPositions.forEach(axis.generateTick.bind(axis));
            axis.renderUnsquish();
            tickRotCorr = axis.tickRotCorr;
            absTickRotCorrX = Math.abs(tickRotCorr.x);
            // Left side must be align: right and right side must
            // have align: left for labels
            reserveSpaceDefault = axis.reserveSpaceDefault = (side === 0 ||
                side === 2 ||
                ({ 1: 'left', 3: 'right' })[side] === axis.labelAlign);
            if (labelOptions.reserveSpace ??
                (hasCrossing ? false : null) ??
                (axis.labelAlign === 'center' ? true : null) ??
                reserveSpaceDefault) {
                tickPositions.forEach(function (pos) {
                    // Get the highest offset
                    labelOffset = Math.max(ticks[pos].getLabelSize(), labelOffset);
                });
            }
            if (axis.staggerLines) {
                labelOffset *= axis.staggerLines;
            }
            if (!horiz &&
                isNumber(axis.labelRotation) &&
                reserveSpaceDefault) {
                labelOffset -= absTickRotCorrX;
            }
            axis.labelOffset = labelOffset * (axis.opposite ? -1 : 1);
        }
        else { // Doesn't have data
            objectEach(ticks, function (tick, n) {
                tick.destroy();
                delete ticks[n];
            });
        }
        if (axisTitleOptions?.text &&
            axisTitleOptions.enabled !== false) {
            axis.addTitle(showAxis);
            if (showAxis &&
                !hasCrossing &&
                axisTitleOptions.reserveSpace !== false) {
                axis.titleOffset = titleOffset =
                    axis.axisTitle.getBBox()[horiz ? 'height' : 'width'];
                titleOffsetOption = axisTitleOptions.offset;
                titleMargin = defined(titleOffsetOption) ?
                    0 :
                    pick(axisTitleOptions.margin, horiz ? 5 : 10);
            }
        }
        // Render the axis line
        axis.renderLine();
        // Handle automatic or user set offset
        axis.offset = directionFactor * (isNumber(options.offset) ?
            options.offset :
            axisOffset[side] ?
                axisOffset[side] + (options.margin || 0) :
                0);
        if (side === 0) {
            lineHeightCorrection = -axis.labelMetrics().h;
        }
        else if (side === 2) {
            lineHeightCorrection = tickRotCorr.y;
        }
        else {
            lineHeightCorrection = 0;
        }
        // Find the padded label offset
        labelOffsetPadded = Math.abs(labelOffset) + titleMargin;
        if (labelOffset) {
            labelOffsetPadded -= lineHeightCorrection;
            labelOffsetPadded += directionFactor * (horiz ?
                (labelOptions.y ??
                    (tickRotCorr.y +
                        directionFactor * labelOptions.distance)) :
                (labelOptions.x ?? (reserveSpaceDefault ?
                    directionFactor * (labelOptions.distance - absTickRotCorrX) :
                    tickRotCorr.x +
                        directionFactor * labelOptions.distance)));
            if (!horiz &&
                !reserveSpaceDefault &&
                axis.labelAlign === 'center' &&
                isNumber(axis.labelRotation)) {
                labelOffsetPadded += absTickRotCorrX;
            }
        }
        axis.axisTitleMargin = titleOffsetOption ?? labelOffsetPadded;
        axis.maxLabelDimensions = axis.getMaxLabelDimensions?.(ticks, tickPositions);
        // Due to GridAxis.tickSize, tickSize should be calculated after ticks
        // has rendered.
        if (coll !== 'colorAxis' && clipOffset) {
            const tickSize = this.tickSize('tick');
            axisOffset[side] = Math.max(axisOffset[side], (axis.axisTitleMargin || 0) + titleOffset +
                directionFactor * axis.offset, labelOffsetPadded, // #3027
            tickPositions?.length && tickSize ?
                tickSize[0] + directionFactor * axis.offset :
                0 // #4866
            );
            // Decide the clipping needed to keep the graph inside
            // the plot area and axis lines
            const clip = !axis.axisLine || options.offset ?
                0 :
                // #4308, #4371
                axis.axisLine.strokeWidth() / 2;
            clipOffset[side] = Math.max(clipOffset[side], clip);
        }
        fireEvent(this, 'afterGetOffset');
    }
    /**
     * Internal function to get the path for the axis line. Extended for polar
     * charts.
     *
     * @function Highcharts.Axis#getLinePath
     *
     * @param {number} lineWidth
     * The line width in pixels.
     *
     * @return {Highcharts.SVGPathArray}
     * The SVG path definition in array form.
     */
    getLinePath(lineWidth) {
        const chart = this.chart, opposite = this.opposite, offset = this.offset, horiz = this.horiz, lineLeft = this.left + (opposite ? this.width : 0) + offset, lineTop = chart.chartHeight - this.bottom -
            (opposite ? this.height : 0) + offset;
        if (opposite) {
            lineWidth *= -1; // Crispify the other way - #1480, #1687
        }
        return chart.renderer
            .crispLine([
            [
                'M',
                horiz ?
                    this.left :
                    lineLeft,
                horiz ?
                    lineTop :
                    this.top
            ],
            [
                'L',
                horiz ?
                    chart.chartWidth - this.right :
                    lineLeft,
                horiz ?
                    lineTop :
                    chart.chartHeight - this.bottom
            ]
        ], lineWidth);
    }
    /**
     * Render the axis line. Called internally when rendering and redrawing the
     * axis.
     *
     * @function Highcharts.Axis#renderLine
     */
    renderLine() {
        const { chart, offset = 0, options } = this;
        this.axisLine || (this.axisLine = chart.renderer.path()
            .addClass('highcharts-axis-line')
            .attr(chart.styledMode ? {} : {
            stroke: options.lineColor,
            'stroke-width': options.lineWidth,
            zIndex: 7
        })
            .clip(this.clippable && offset <= 0 ?
            chart.plotClipOuter :
            void 0)
            .add(this.axisGroup));
    }
    /**
     * Position the axis title.
     *
     * @internal
     * @function Highcharts.Axis#getTitlePosition
     *
     * @return {Highcharts.PositionObject}
     * X and Y positions for the title.
     */
    getTitlePosition(axisTitle) {
        // Compute anchor points for each of the title align options
        const horiz = this.horiz, axisLeft = this.left, axisTop = this.top, axisLength = this.len, axisTitleOptions = this.options.title, margin = horiz ? axisLeft : axisTop, opposite = this.opposite, offset = this.offset, xOption = axisTitleOptions.x, yOption = axisTitleOptions.y, fontMetrics = this.chart.renderer.fontMetrics(axisTitle), 
        // The part of a multiline text that is below the baseline of the
        // first line. Subtract 1 to preserve pixel-perfectness from the
        // old behavior (v5.0.12), where only one line was allowed.
        textHeightOvershoot = axisTitle ? Math.max(axisTitle.getBBox(false, 0).height - fontMetrics.h - 1, 0) : 0, 
        // The position in the length direction of the axis
        alongAxis = ({
            low: margin + (horiz ? 0 : axisLength),
            middle: margin + axisLength / 2,
            high: margin + (horiz ? axisLength : 0)
        })[axisTitleOptions.align], 
        // The position in the perpendicular direction of the axis
        offAxis = (horiz ? axisTop + this.height : axisLeft) +
            (horiz ? 1 : -1) * // Horizontal axis reverses the margin
                (opposite ? -1 : 1) * // So does opposite axes
                (this.axisTitleMargin || 0) +
            [
                -textHeightOvershoot, // Top
                textHeightOvershoot, // Right
                fontMetrics.f, // Bottom
                -textHeightOvershoot // Left
            ][this.side], titlePosition = {
            x: horiz ?
                alongAxis + xOption :
                offAxis + (opposite ? this.width : 0) + offset + xOption,
            y: horiz ?
                offAxis + yOption - (opposite ? this.height : 0) + offset :
                alongAxis + yOption
        };
        fireEvent(this, 'afterGetTitlePosition', { titlePosition: titlePosition });
        return titlePosition;
    }
    /**
     * Render a minor tick into the given position. If a minor tick already
     * exists in this position, move it.
     *
     * @function Highcharts.Axis#renderMinorTick
     *
     * @param {number} pos
     * The position in axis values.
     *
     * @param {boolean} slideIn
     * Whether the tick should animate in from last computed position
     */
    renderMinorTick(pos, slideIn) {
        const axis = this;
        const minorTicks = axis.minorTicks;
        if (!minorTicks[pos]) {
            minorTicks[pos] = new Axis_Tick(axis, pos, 'minor');
        }
        // Render new ticks in old position
        if (slideIn && minorTicks[pos].isNew) {
            minorTicks[pos].render(null, true);
        }
        minorTicks[pos].render(null, false, 1);
    }
    /**
     * Render a major tick into the given position. If a tick already exists
     * in this position, move it.
     *
     * @function Highcharts.Axis#renderTick
     *
     * @param {number} pos
     * The position in axis values.
     *
     * @param {number} i
     * The tick index.
     *
     * @param {boolean} slideIn
     * Whether the tick should animate in from last computed position
     */
    renderTick(pos, i, slideIn) {
        const axis = this, isLinked = axis.isLinked, ticks = axis.ticks;
        // Linked axes need an extra check to find out if
        if (!isLinked ||
            (pos >= axis.min && pos <= axis.max) ||
            axis.grid?.isColumn) {
            if (!ticks[pos]) {
                ticks[pos] = new Axis_Tick(axis, pos);
            }
            // NOTE this seems like overkill. Could be handled in tick.render by
            // setting old position in attr, then set new position in animate.
            // render new ticks in old position
            if (slideIn && ticks[pos].isNew) {
                // Start with negative opacity so that it is visible from
                // halfway into the animation
                ticks[pos].render(i, true, -1);
            }
            ticks[pos].render(i);
        }
    }
    /**
     * Render the axis.
     *
     * @internal
     * @function Highcharts.Axis#render
     *
     * @emits Highcharts.Axis#event:afterRender
     */
    render() {
        const axis = this, chart = axis.chart, log = axis.logarithmic, renderer = chart.renderer, options = axis.options, isLinked = axis.isLinked, tickPositions = axis.tickPositions, axisTitle = axis.axisTitle, ticks = axis.ticks, minorTicks = axis.minorTicks, alternateBands = axis.alternateBands, stackLabelOptions = options.stackLabels, alternateGridColor = options.alternateGridColor, crossing = options.crossing, tickmarkOffset = axis.tickmarkOffset, axisLine = axis.axisLine, showAxis = axis.showAxis, animation = Axis_animObject(renderer.globalAnimation);
        let from, to;
        // Reset
        axis.labelEdge.length = 0;
        axis.overlap = false;
        // Mark all elements inActive before we go over and mark the active ones
        [ticks, minorTicks, alternateBands].forEach(function (coll) {
            objectEach(coll, function (tick) {
                tick.isActive = false;
            });
        });
        // Crossing
        if (isNumber(crossing)) {
            const otherAxis = this.isXAxis ? chart.yAxis[0] : chart.xAxis[0], directionFactor = [1, -1, -1, 1][this.side];
            if (otherAxis) {
                let px = otherAxis.toPixels(crossing, true);
                if (axis.horiz) {
                    px = otherAxis.len - px;
                }
                axis.offset = directionFactor * px;
            }
        }
        // If the series has data draw the ticks. Else only the line and title
        if (axis.hasData() || isLinked) {
            const slideInTicks = axis.chart.hasRendered &&
                axis.old && isNumber(axis.old.min);
            // Minor ticks
            if (axis.minorTickInterval && !axis.categories) {
                axis.getMinorTickPositions().forEach(function (pos) {
                    axis.renderMinorTick(pos, slideInTicks);
                });
            }
            // Major ticks. Pull out the first item and render it last so that
            // we can get the position of the neighbor label. #808.
            if (tickPositions.length) { // #1300
                tickPositions.forEach(function (pos, i) {
                    axis.renderTick(pos, i, slideInTicks);
                });
                // In a categorized axis, the tick marks are displayed
                // between labels. So we need to add a tick mark and
                // grid line at the left edge of the X axis.
                if (tickmarkOffset && (axis.min === 0 || axis.single)) {
                    if (!ticks[-1]) {
                        ticks[-1] = new Axis_Tick(axis, -1, null, true);
                    }
                    ticks[-1].render(-1);
                }
            }
            // Alternate grid color
            if (alternateGridColor) {
                tickPositions.forEach(function (pos, i) {
                    to = typeof tickPositions[i + 1] !== 'undefined' ?
                        tickPositions[i + 1] + tickmarkOffset :
                        axis.max - tickmarkOffset;
                    if (i % 2 === 0 &&
                        pos < axis.max &&
                        to <= axis.max + (chart.polar ?
                            -tickmarkOffset :
                            tickmarkOffset)) { // #2248, #4660
                        if (!alternateBands[pos]) {
                            // Should be imported from PlotLineOrBand.js, but
                            // the dependency cycle with axis is a problem
                            alternateBands[pos] = new Core_Globals.PlotLineOrBand(axis, {});
                        }
                        from = pos + tickmarkOffset; // #949
                        alternateBands[pos].options = {
                            from: log ? log.lin2log(from) : from,
                            to: log ? log.lin2log(to) : to,
                            color: alternateGridColor,
                            className: 'highcharts-alternate-grid'
                        };
                        alternateBands[pos].render();
                        alternateBands[pos].isActive = true;
                    }
                });
            }
            // Custom plot lines and bands
            if (!axis._addedPlotLB) { // Only first time
                axis._addedPlotLB = true;
                (options.plotLines || [])
                    .concat(options.plotBands || [])
                    .forEach(function (plotLineOptions) {
                    axis
                        .addPlotBandOrLine(plotLineOptions);
                });
            }
        } // End if hasData
        // Remove inactive ticks
        [ticks, minorTicks, alternateBands].forEach(function (coll) {
            const forDestruction = [], delay = animation.duration, destroyInactiveItems = function () {
                let i = forDestruction.length;
                while (i--) {
                    // When resizing rapidly, the same items
                    // may be destroyed in different timeouts,
                    // or the may be reactivated
                    if (coll[forDestruction[i]] &&
                        !coll[forDestruction[i]].isActive) {
                        coll[forDestruction[i]].destroy();
                        delete coll[forDestruction[i]];
                    }
                }
            };
            objectEach(coll, function (tick, pos) {
                if (!tick.isActive) {
                    // Render to zero opacity
                    tick.render(pos, false, 0);
                    tick.isActive = false;
                    forDestruction.push(pos);
                }
            });
            // When the objects are finished fading out, destroy them
            syncTimeout(destroyInactiveItems, coll === alternateBands ||
                !chart.hasRendered ||
                !delay ?
                0 :
                delay);
        });
        // Set the axis line path
        if (axisLine) {
            axisLine[axisLine.isPlaced ? 'animate' : 'attr']({
                d: this.getLinePath(axisLine.strokeWidth())
            });
            axisLine.isPlaced = true;
            // Show or hide the line depending on options.showEmpty
            axisLine[showAxis ? 'show' : 'hide'](showAxis);
        }
        if (axisTitle && showAxis) {
            axisTitle[axisTitle.isNew ? 'attr' : 'animate'](axis.getTitlePosition(axisTitle));
            axisTitle.isNew = false;
        }
        // Stacked totals
        if (stackLabelOptions?.enabled && axis.stacking) {
            axis.stacking.renderStackTotals();
        }
        // First time, save the existing state
        if (!this.old) {
            this.saveOld();
        }
        axis.isDirty = false;
        fireEvent(this, 'afterRender');
    }
    /**
     * Redraw the axis to reflect changes in the data or axis extremes. Called
     * internally from Highcharts.Chart#redraw.
     *
     * @internal
     * @function Highcharts.Axis#redraw
     */
    redraw() {
        if (this.visible) {
            // Render the axis
            this.render();
            // Move plot lines and bands
            this.plotLinesAndBands.forEach(function (plotLine) {
                plotLine.render();
            });
        }
        // Mark associated series as dirty and ready for redraw
        this.series.forEach(function (series) {
            series.isDirty = true;
        });
    }
    /**
     * Record old scaling for updating/animation.
     *
     * @private
     */
    saveOld() {
        this.old = isNumber(this.min) ? {
            len: this.len,
            max: this.max,
            min: this.min,
            names: this.names.slice(),
            transA: this.transA,
            userMax: this.userMax,
            userMin: this.userMin
        } : void 0;
    }
    /**
     * Returns an array of axis properties, that should be untouched during
     * reinitialization.
     *
     * @internal
     * @function Highcharts.Axis#getKeepProps
     */
    getKeepProps() {
        return (this.keepProps || Axis.keepProps);
    }
    /**
     * Destroys an Axis instance. See {@link Axis#remove} for the API endpoint
     * to fully remove the axis.
     *
     * @internal
     * @function Highcharts.Axis#destroy
     *
     * @param {boolean} [keepEvents]
     * Whether to preserve events, used internally in Axis.update.
     */
    destroy(keepEvents) {
        const axis = this, plotLinesAndBands = axis.plotLinesAndBands, eventOptions = this.eventOptions;
        fireEvent(this, 'destroy', { keepEvents: keepEvents });
        // Remove the events
        if (!keepEvents) {
            removeEvent(axis);
        }
        // Destroy collections
        [axis.ticks, axis.minorTicks, axis.alternateBands].forEach(function (coll) {
            destroyObjectProperties(coll);
        });
        if (plotLinesAndBands) {
            let i = plotLinesAndBands.length;
            while (i--) { // #1975
                plotLinesAndBands[i].destroy();
            }
        }
        // Destroy elements
        [
            'axisLine', 'axisTitle', 'axisGroup',
            'gridGroup', 'labelGroup', 'cross', 'scrollbar'
        ].forEach(function (prop) {
            if (axis[prop]) {
                axis[prop] = axis[prop].destroy();
            }
        });
        // Destroy each generated group for plotLines and plotBands
        for (const plotGroup in axis.plotLinesAndBandsGroups) { // eslint-disable-line guard-for-in
            axis.plotLinesAndBandsGroups[plotGroup] =
                axis.plotLinesAndBandsGroups[plotGroup].destroy();
        }
        // Delete all properties and fall back to the prototype.
        objectEach(axis, function (_val, key) {
            if (axis.getKeepProps().indexOf(key) === -1) {
                delete axis[key];
            }
        });
        this.eventOptions = eventOptions;
    }
    /**
     * Internal function to draw a crosshair.
     *
     * @function Highcharts.Axis#drawCrosshair
     *
     * @param {Highcharts.PointerEventObject} [e]
     * The event arguments from the modified pointer event, extended with
     * `chartX` and `chartY`
     *
     * @param {Highcharts.Point} [point]
     * The Point object if the crosshair snaps to points.
     *
     * @emits Highcharts.Axis#event:afterDrawCrosshair
     * @emits Highcharts.Axis#event:drawCrosshair
     */
    drawCrosshair(e, point) {
        const options = this.crosshair, snap = options?.snap ?? true, chart = this.chart, graphic = this.cross;
        let path, pos, categorized, crossOptions;
        fireEvent(this, 'drawCrosshair', { e: e, point: point });
        // Use last available event when updating non-snapped crosshairs without
        // mouse interaction (#5287)
        if (!e) {
            e = this.cross?.e;
        }
        if (
        // Disabled in options
        !options ||
            // Snap
            ((defined(point) || !snap) === false)) {
            this.hideCrosshair();
        }
        else {
            clearTimeout(this.crossShowTimer);
            // Get the path
            if (!snap) {
                pos = e &&
                    (this.horiz ?
                        e.chartX - this.pos :
                        this.len - e.chartY + this.pos);
            }
            else if (defined(point)) {
                // #3834
                pos = pick(this.coll !== 'colorAxis' ?
                    point.crosshairPos : // 3D axis extension
                    null, this.isXAxis ?
                    point.plotX :
                    this.len - point.plotY);
            }
            if (defined(pos)) {
                crossOptions = {
                    // Value, only used on radial
                    value: point && (this.isXAxis ?
                        point.x :
                        pick(point.stackY, point.y)),
                    translatedValue: pos
                };
                if (chart.polar) {
                    // Additional information required for crosshairs in
                    // polar chart
                    extend(crossOptions, {
                        isCrosshair: true,
                        chartX: e?.chartX,
                        chartY: e?.chartY,
                        point: point
                    });
                }
                path = this.getPlotLinePath(crossOptions) ||
                    null; // #3189
            }
            if (!defined(path)) {
                this.hideCrosshair();
                return;
            }
            categorized = this.categories && !this.isRadial;
            this.crossShowTimer = syncTimeout(() => {
                // Get the *current* crosshair, in case it was created by
                // another call while this one was delayed.
                let cross = this.cross;
                // Draw the cross
                if (!cross) {
                    this.cross = cross = chart.renderer
                        .path()
                        .addClass('highcharts-crosshair highcharts-crosshair-' +
                        (categorized ? 'category ' : 'thin ') +
                        (options.className || ''))
                        .attr({
                        zIndex: pick(options.zIndex, 2)
                    })
                        .clip(options.clip === false ?
                        void 0 :
                        chart.plotClipInner)
                        .add();
                    // Presentational attributes
                    if (!chart.styledMode) {
                        cross.attr({
                            stroke: options.color ||
                                (categorized ?
                                    Axis_color(
                                    // eslint-disable-next-line max-len
                                    'var(--highcharts-highlight-color-20)').setOpacity(0.25).get() :
                                    'var(--highcharts-neutral-color-20)'),
                            'stroke-width': pick(options.width, 1)
                        }).css({
                            'pointer-events': 'none'
                        });
                        if (options.dashStyle) {
                            cross.attr({
                                dashstyle: options.dashStyle
                            });
                        }
                    }
                }
                cross
                    .show()
                    .animate({ d: path }, Axis_animObject(options?.animation));
                if (categorized && !options.width) {
                    cross.attr({
                        'stroke-width': this.transA
                    });
                }
                if (this.cross) {
                    this.cross.e = e;
                }
            }, 
            // Only use delay if the crosshair is currently hidden
            (!graphic || graphic.attr('visibility') === 'hidden') ?
                options.showDelay || 0 :
                0);
        }
        fireEvent(this, 'afterDrawCrosshair', { e: e, point: point });
    }
    /**
     * Hide the crosshair if visible.
     *
     * @function Highcharts.Axis#hideCrosshair
     */
    hideCrosshair() {
        clearTimeout(this.crossShowTimer);
        if (this.cross) {
            this.cross.hide();
        }
        fireEvent(this, 'afterHideCrosshair');
    }
    /**
     * Update an axis object with a new set of options. The options are merged
     * with the existing options, so only new or altered options need to be
     * specified.
     *
     * @sample highcharts/members/axis-update/
     *         Axis update demo
     *
     * @function Highcharts.Axis#update
     *
     * @param {Highcharts.AxisOptions} options
     * The new options that will be merged in with existing options on the axis.
     *
     * @param {boolean} [redraw=true]
     * Whether to redraw the chart after the axis is altered. If doing more
     * operations on the chart, it is a good idea to set redraw to false and
     * call {@link Chart#redraw} after.
     */
    update(options, redraw) {
        const chart = this.chart;
        options = merge(this.userOptions, options);
        this.destroy(true);
        this.init(chart, options);
        chart.isDirtyBox = true;
        if (pick(redraw, true)) {
            chart.redraw();
        }
    }
    /**
     * Remove the axis from the chart.
     *
     * @sample highcharts/members/chart-addaxis/
     *         Add and remove axes
     *
     * @function Highcharts.Axis#remove
     *
     * @param {boolean} [redraw=true]
     * Whether to redraw the chart following the remove.
     */
    remove(redraw) {
        const chart = this.chart, coll = this.coll, axisSeries = this.series;
        let i = axisSeries.length;
        // Remove associated series (#2687)
        while (i--) {
            if (axisSeries[i]) {
                axisSeries[i].remove(false);
            }
        }
        // Remove the axis
        erase(chart.axes, this);
        erase(chart[coll] || [], this);
        chart.orderItems(coll);
        this.destroy();
        chart.isDirtyBox = true;
        if (pick(redraw, true)) {
            chart.redraw();
        }
    }
    /**
     * Update the axis title by options after render time.
     *
     * @sample highcharts/members/axis-settitle/
     *         Set a new Y axis title
     *
     * @function Highcharts.Axis#setTitle
     *
     * @param {Highcharts.AxisTitleOptions} titleOptions
     * The additional title options.
     *
     * @param {boolean} [redraw=true]
     * Whether to redraw the chart after setting the title.
     */
    setTitle(titleOptions, redraw) {
        this.update({ title: titleOptions }, redraw);
    }
    /**
     * Set new axis categories and optionally redraw.
     *
     * @sample highcharts/members/axis-setcategories/
     *         Set categories by click on a button
     *
     * @function Highcharts.Axis#setCategories
     *
     * @param {Array<string>} categories
     * The new categories.
     *
     * @param {boolean} [redraw=true]
     * Whether to redraw the chart.
     */
    setCategories(categories, redraw) {
        this.update({ categories: categories }, redraw);
    }
}
/* *
 *
 *  Static Properties
 *
 * */
/**
 * Properties to survive after destroy, needed for Axis.update (#4317,
 * #5773, #5881).
 * @internal
 */
Axis.keepProps = [
    'coll',
    'extKey',
    'hcEvents',
    'len',
    'names',
    'series',
    'userMax',
    'userMin'
];
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Axis_Axis = (Axis);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * Options for the path on the Axis to be calculated.
 * @interface Highcharts.AxisPlotLinePathOptionsObject
 */ /**
* Axis value.
* @name Highcharts.AxisPlotLinePathOptionsObject#value
* @type {number|undefined}
*/ /**
* Line width used for calculation crisp line coordinates. Defaults to 1.
* @name Highcharts.AxisPlotLinePathOptionsObject#lineWidth
* @type {number|undefined}
*/ /**
* If `false`, the function will return null when it falls outside the axis
* bounds. If `true`, the function will return a path aligned to the plot area
* sides if it falls outside. If `pass`, it will return a path outside.
* @name Highcharts.AxisPlotLinePathOptionsObject#force
* @type {string|boolean|undefined}
*/ /**
* Used in Highcharts Stock. When `true`, plot paths
* (crosshair, plotLines, gridLines)
* will be rendered on all axes when defined on the first axis.
* @name Highcharts.AxisPlotLinePathOptionsObject#acrossPanes
* @type {boolean|undefined}
*/ /**
* Use old coordinates (for resizing and rescaling).
* If not set, defaults to `false`.
* @name Highcharts.AxisPlotLinePathOptionsObject#old
* @type {boolean|undefined}
*/ /**
* If given, return the plot line path of a pixel position on the axis.
* @name Highcharts.AxisPlotLinePathOptionsObject#translatedValue
* @type {number|undefined}
*/ /**
* Used in Polar axes. Reverse the positions for concatenation of polygonal
* plot bands
* @name Highcharts.AxisPlotLinePathOptionsObject#reverse
* @type {boolean|undefined}
*/
/**
 * Options for crosshairs on axes.
 *
 * @product highstock
 *
 * @typedef {Highcharts.XAxisCrosshairOptions|Highcharts.YAxisCrosshairOptions} Highcharts.AxisCrosshairOptions
 */
/**
 * @typedef {"navigator"|"pan"|"rangeSelectorButton"|"rangeSelectorInput"|"scrollbar"|"traverseUpButton"|"zoom"} Highcharts.AxisExtremesTriggerValue
 */
/**
 * @callback Highcharts.AxisEventCallbackFunction
 *
 * @param {Highcharts.Axis} this
 */
/**
 * @callback Highcharts.AxisLabelsFormatterCallbackFunction
 *
 * @param {Highcharts.AxisLabelsFormatterContextObject} this
 *
 * @param {Highcharts.AxisLabelsFormatterContextObject} ctx
 * Since v12.6.0, the formatter context passed as an extra argument for arrow
 * functions.
 *
 * @return {string}
 */
/**
 * @interface Highcharts.AxisLabelsFormatterContextObject
 */ /**
* The axis item of the label
* @name Highcharts.AxisLabelsFormatterContextObject#axis
* @type {Highcharts.Axis}
*/ /**
* The chart instance.
* @name Highcharts.AxisLabelsFormatterContextObject#chart
* @type {Highcharts.Chart}
*/ /**
* Default formatting of date/time labels.
* @name Highcharts.AxisLabelsFormatterContextObject#dateTimeLabelFormat
* @type {string|undefined}
*/ /**
* Whether the label belongs to the first tick on the axis.
* @name Highcharts.AxisLabelsFormatterContextObject#isFirst
* @type {boolean}
*/ /**
* Whether the label belongs to the last tick on the axis.
* @name Highcharts.AxisLabelsFormatterContextObject#isLast
* @type {boolean}
*/ /**
* The position on the axis in terms of axis values. For category axes, a
* zero-based index. For datetime axes, the JavaScript time in milliseconds
* since 1970.
* @name Highcharts.AxisLabelsFormatterContextObject#pos
* @type {number}
*/ /**
* The preformatted text as the result of the default formatting. For example
* dates will be formatted as strings, and numbers with language-specific comma
* separators, thousands separators and numeric symbols like `k` or `M`.
* @name Highcharts.AxisLabelsFormatterContextObject#text
* @type {string|undefined}
*/ /**
* The Tick instance.
* @name Highcharts.AxisLabelsFormatterContextObject#tick
* @type {Highcharts.Tick}
*/ /**
* This can be either a numeric value or a category string.
* @name Highcharts.AxisLabelsFormatterContextObject#value
* @type {number|string}
*/
/**
 * Options for axes.
 *
 * @typedef {Highcharts.XAxisOptions|Highcharts.YAxisOptions|Highcharts.ZAxisOptions} Highcharts.AxisOptions
 */
/**
 * @callback Highcharts.AxisPointBreakEventCallbackFunction
 *
 * @param {Highcharts.Axis} this
 *
 * @param {Highcharts.AxisPointBreakEventObject} evt
 */
/**
 * @interface Highcharts.AxisPointBreakEventObject
 */ /**
* @name Highcharts.AxisPointBreakEventObject#brk
* @type {Highcharts.Dictionary<number>}
*/ /**
* @name Highcharts.AxisPointBreakEventObject#point
* @type {Highcharts.Point}
*/ /**
* @name Highcharts.AxisPointBreakEventObject#preventDefault
* @type {Function}
*/ /**
* @name Highcharts.AxisPointBreakEventObject#target
* @type {Highcharts.SVGElement}
*/ /**
* @name Highcharts.AxisPointBreakEventObject#type
* @type {"pointBreak"|"pointInBreak"}
*/
/**
 * @callback Highcharts.AxisSetExtremesEventCallbackFunction
 *
 * @param {Highcharts.Axis} this
 *
 * @param {Highcharts.AxisSetExtremesEventObject} evt
 */
/**
 * @interface Highcharts.AxisSetExtremesEventObject
 * @extends Highcharts.ExtremesObject
 */ /**
* @name Highcharts.AxisSetExtremesEventObject#preventDefault
* @type {Function}
*/ /**
* @name Highcharts.AxisSetExtremesEventObject#target
* @type {Highcharts.SVGElement}
*/ /**
* @name Highcharts.AxisSetExtremesEventObject#trigger
* @type {Highcharts.AxisExtremesTriggerValue|string}
*/ /**
* @name Highcharts.AxisSetExtremesEventObject#type
* @type {"setExtremes"}
*/
/**
 * @callback Highcharts.AxisTickPositionerCallbackFunction
 *
 * @param {Highcharts.Axis} this
 *
 * @param {number} min
 * Current minimum value.
 *
 * @param {number} max
 * Current maximum value.
 *
 * @param {Highcharts.Axis} [ctx]
 * Since v12.6.0, the axis context passed as an extra argument for arrow
 * functions.
 *
 * @return {Highcharts.AxisTickPositionsArray|undefined}
 */
/**
 * @interface Highcharts.AxisTickPositionsArray
 * @augments Array<number>
 */
/**
 * @typedef {"high"|"low"|"middle"} Highcharts.AxisTitleAlignValue
 */
/**
 * @typedef {Highcharts.XAxisTitleOptions|Highcharts.YAxisTitleOptions|Highcharts.ZAxisTitleOptions} Highcharts.AxisTitleOptions
 */
/**
 * @typedef {"linear"|"logarithmic"|"datetime"|"category"|"treegrid"} Highcharts.AxisTypeValue
 */
/**
 * The returned object literal from the {@link Highcharts.Axis#getExtremes}
 * function.
 *
 * @interface Highcharts.ExtremesObject
 */ /**
* The maximum value of the axis' associated series.
* @name Highcharts.ExtremesObject#dataMax
* @type {number}
*/ /**
* The minimum value of the axis' associated series.
* @name Highcharts.ExtremesObject#dataMin
* @type {number}
*/ /**
* The maximum axis value, either automatic or set manually. If the `max` option
* is not set, `maxPadding` is 0 and `endOnTick` is false, this value will be
* the same as `dataMax`.
* @name Highcharts.ExtremesObject#max
* @type {number}
*/ /**
* The minimum axis value, either automatic or set manually. If the `min` option
* is not set, `minPadding` is 0 and `startOnTick` is false, this value will be
* the same as `dataMin`.
* @name Highcharts.ExtremesObject#min
* @type {number}
*/ /**
* The user defined maximum, either from the `max` option or from a zoom or
* `setExtremes` action.
* @name Highcharts.ExtremesObject#userMax
* @type {number|undefined}
*/ /**
* The user defined minimum, either from the `min` option or from a zoom or
* `setExtremes` action.
* @name Highcharts.ExtremesObject#userMin
* @type {number|undefined}
*/
/**
 * Formatter function for the text of a crosshair label.
 *
 * @callback Highcharts.XAxisCrosshairLabelFormatterCallbackFunction
 *
 * @param {Highcharts.Axis} this
 * Axis context
 *
 * @param {number} value
 * Y value of the data point
 *
 * @param {Highcharts.Axis} [ctx]
 * Since v12.6.0, the axis context passed as an extra argument for arrow
 * functions.
 *
 * @return {string}
 */
''; // Keeps doclets above in JS file

;// ./code/es-modules/Core/Axis/DateTimeAxis.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */



/* *
 *
 *  Composition
 *
 * */
var DateTimeAxis;
(function (DateTimeAxis) {
    /* *
     *
     *  Declarations
     *
     * */
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Extends axis class with date and time support.
     * @internal
     */
    function compose(AxisClass) {
        if (!AxisClass.keepProps.includes('dateTime')) {
            AxisClass.keepProps.push('dateTime');
            const axisProto = AxisClass.prototype;
            axisProto.getTimeTicks = getTimeTicks;
            addEvent(AxisClass, 'afterSetType', onAfterSetType);
        }
        return AxisClass;
    }
    DateTimeAxis.compose = compose;
    /**
     * Set the tick positions to a time unit that makes sense, for example
     * on the first of each month or on every Monday. Return an array with
     * the time positions. Used in datetime axes as well as for grouping
     * data on a datetime axis.
     *
     * @internal
     * @function Highcharts.Axis#getTimeTicks
     */
    function getTimeTicks() {
        return this.chart.time.getTimeTicks.apply(this.chart.time, arguments);
    }
    /** @internal */
    function onAfterSetType() {
        if (this.type !== 'datetime') {
            this.dateTime = void 0;
            return;
        }
        if (!this.dateTime) {
            this.dateTime = new Additions(this);
        }
    }
    /* *
     *
     *  Classes
     *
     * */
    /** @internal */
    class Additions {
        /* *
         *
         *  Constructors
         *
         * */
        constructor(axis) {
            this.axis = axis;
        }
        /* *
         *
         *  Functions
         *
         * */
        /**
         * Get a normalized tick interval for dates. Returns a configuration
         * object with unit range (interval), count and name. Used to prepare
         * data for `getTimeTicks`. Previously this logic was part of
         * getTimeTicks, but as `getTimeTicks` now runs of segments in stock
         * charts, the normalizing logic was extracted in order to prevent it
         * for running over again for each segment having the same interval.
         * #662, #697.
         * @internal
         */
        normalizeTimeTickInterval(tickInterval, unitsOption) {
            const units = (unitsOption || [[
                    // Unit name
                    'millisecond',
                    // Allowed multiples
                    [1, 2, 5, 10, 20, 25, 50, 100, 200, 500]
                ], [
                    'second',
                    [1, 2, 5, 10, 15, 30]
                ], [
                    'minute',
                    [1, 2, 5, 10, 15, 30]
                ], [
                    'hour',
                    [1, 2, 3, 4, 6, 8, 12]
                ], [
                    'day',
                    [1, 2]
                ], [
                    'week',
                    [1, 2]
                ], [
                    'month',
                    [1, 2, 3, 4, 6]
                ], [
                    'year',
                    null
                ]]);
            let unit = units[units.length - 1], // Default unit is years
            interval = timeUnits[unit[0]], multiples = unit[1], i, match;
            // Loop through the units to find the one that best fits the
            // tickInterval
            for (i = 0; i < units.length; i++) {
                unit = units[i];
                interval = timeUnits[unit[0]];
                multiples = unit[1];
                if (units[i + 1]) {
                    // `lessThan` is in the middle between the highest multiple
                    // and the next unit.
                    const lessThan = (interval *
                        multiples[multiples.length - 1] +
                        timeUnits[units[i + 1][0]]) / 2;
                    // Break and keep the current unit
                    if (tickInterval <= lessThan) {
                        match = lessThan / tickInterval;
                        break;
                    }
                }
            }
            // Prevent 2.5 years intervals, though 25, 250 etc. are allowed
            if (interval === timeUnits.year && tickInterval < 5 * interval) {
                multiples = [1, 2, 5];
            }
            // Get the count
            const count = normalizeTickInterval(tickInterval / interval, multiples, unit[0] === 'year' ? // #1913, #2360
                Math.max(getMagnitude(tickInterval / interval), 1) :
                1);
            return {
                unitRange: interval,
                count: count,
                unitName: unit[0],
                match
            };
        }
        /**
         * Get the best date format for a specific X value based on the closest
         * point range on the axis.
         *
         * @internal
         */
        getXDateFormat(x, dateTimeLabelFormats) {
            const { axis } = this, time = axis.chart.time;
            return axis.closestPointRange ?
                time.getDateFormat(axis.closestPointRange, x, axis.options.startOfWeek, dateTimeLabelFormats) ||
                    // #2546, 2581
                    time.resolveDTLFormat(dateTimeLabelFormats.year).main :
                time.resolveDTLFormat(dateTimeLabelFormats.day).main;
        }
    }
    DateTimeAxis.Additions = Additions;
})(DateTimeAxis || (DateTimeAxis = {}));
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Axis_DateTimeAxis = (DateTimeAxis);

;// ./code/es-modules/Core/Axis/LogarithmicAxis.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


/* *
 *
 *  Class
 *
 * */
/** @internal */
var LogarithmicAxis;
(function (LogarithmicAxis) {
    /* *
     *
     *  Declarations
     *
     * */
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Provides logarithmic support for axes.
     * @internal
     */
    function compose(AxisClass) {
        if (!AxisClass.keepProps.includes('logarithmic')) {
            AxisClass.keepProps.push('logarithmic');
            addEvent(AxisClass, 'afterSetType', onAfterSetType);
            addEvent(AxisClass, 'afterInit', onAfterInit);
        }
        return AxisClass;
    }
    LogarithmicAxis.compose = compose;
    /** @internal */
    function onAfterSetType() {
        if (this.type !== 'logarithmic') {
            this.logarithmic = void 0;
        }
        else {
            this.logarithmic ?? (this.logarithmic = new Additions(this));
        }
    }
    /** @internal */
    function onAfterInit() {
        const axis = this;
        const log = axis.logarithmic;
        // Extend logarithmic axis
        if (log) {
            axis.lin2val = function (num) {
                return log.lin2log(num);
            };
            axis.val2lin = function (num) {
                return log.log2lin(num);
            };
        }
    }
    /* *
     *
     *  Class
     *
     * */
    /**
     * Provides logarithmic support for axes.
     * @internal
     * @class
     */
    class Additions {
        /* *
        *
        *  Constructors
        *
        * */
        constructor(axis) {
            this.axis = axis;
        }
        /* *
        *
        *  Functions
        *
        * */
        /**
         * Set the tick positions of a logarithmic axis.
         */
        getLogTickPositions(interval, min, max, minor) {
            const log = this;
            const axis = log.axis;
            const axisLength = axis.len;
            const options = axis.options;
            // Since we use this method for both major and minor ticks,
            // use a local variable and return the result
            let positions = [];
            // Reset
            if (!minor) {
                log.minorAutoInterval = void 0;
            }
            // First case: All ticks fall on whole logarithms: 1, 10, 100 etc.
            if (interval >= 0.5) {
                interval = Math.round(interval);
                positions = axis.getLinearTickPositions(interval, min, max);
                // Second case: We need intermediary ticks. For example
                // 1, 2, 4, 6, 8, 10, 20, 40 etc.
            }
            else if (interval >= 0.08) {
                const roundedMin = Math.floor(min);
                let intermediate, i, j, len, pos, lastPos, break2;
                if (interval > 0.3) {
                    intermediate = [1, 2, 4];
                    // 0.2 equals five minor ticks per 1, 10, 100 etc
                }
                else if (interval > 0.15) {
                    intermediate = [1, 2, 4, 6, 8];
                }
                else { // 0.1 equals ten minor ticks per 1, 10, 100 etc
                    intermediate = [1, 2, 3, 4, 5, 6, 7, 8, 9];
                }
                for (i = roundedMin; i < max + 1 && !break2; i++) {
                    len = intermediate.length;
                    for (j = 0; j < len && !break2; j++) {
                        pos = log.log2lin(log.lin2log(i) * intermediate[j]);
                        // #1670, lastPos is #3113
                        if (pos > min &&
                            (!minor || lastPos <= max) &&
                            typeof lastPos !== 'undefined') {
                            positions.push(lastPos);
                        }
                        if (lastPos > max) {
                            break2 = true;
                        }
                        lastPos = pos;
                    }
                }
                // Third case: We are so deep in between whole logarithmic values,
                // that we might as well handle the tick positions like a linear
                // axis. For example 1.01, 1.02, 1.03, 1.04.
            }
            else {
                const realMin = log.lin2log(min), realMax = log.lin2log(max), tickIntervalOption = minor ?
                    axis.getMinorTickInterval() :
                    options.tickInterval, filteredTickIntervalOption = tickIntervalOption === 'auto' ?
                    null :
                    tickIntervalOption, tickPixelIntervalOption = options.tickPixelInterval / (minor ? 5 : 1), totalPixelLength = minor ?
                    axisLength / axis.tickPositions.length :
                    axisLength;
                interval = pick(filteredTickIntervalOption, log.minorAutoInterval, (realMax - realMin) *
                    tickPixelIntervalOption / (totalPixelLength || 1));
                interval = normalizeTickInterval(interval);
                positions = axis.getLinearTickPositions(interval, realMin, realMax).map(log.log2lin);
                if (!minor) {
                    log.minorAutoInterval = interval / 5;
                }
            }
            // Set the axis-level tickInterval variable
            if (!minor) {
                axis.tickInterval = interval;
            }
            return positions;
        }
        /**
         * Converts a linear value to a logarithmic value.
         *
         * @internal
         *
         * @param {number} num
         * The linear value.
         *
         * @return {number}
         * The logarithmic value.
         */
        lin2log(num) {
            return Math.pow(10, num);
        }
        /**
         * Converts a logarithmic value to a linear value.
         *
         * @internal
         *
         * @param {number} num
         * The logarithmic value.
         *
         * @return {number}
         * The linear value.
         */
        log2lin(num) {
            return Math.log(num) / Math.LN10;
        }
    }
    LogarithmicAxis.Additions = Additions;
})(LogarithmicAxis || (LogarithmicAxis = {}));
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
/* harmony default export */ const Axis_LogarithmicAxis = (LogarithmicAxis);

;// ./code/es-modules/Core/Axis/PlotLineOrBand/PlotLineOrBandAxis.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


/* *
 *
 *  Composition
 *
 * */
var PlotLineOrBandAxis;
(function (PlotLineOrBandAxis) {
    /* *
     *
     *  Declarations
     *
     * */
    /* *
     *
     *  Variables
     *
     * */
    let PlotLineOrBandClass;
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Add a plot band after render time.
     *
     * @sample highcharts/members/axis-addplotband/
     *         Toggle the plot band from a button
     *
     * @function Highcharts.Axis#addPlotBand
     *
     * @param {Highcharts.AxisPlotBandsOptions} options
     * A configuration object for the plot band, as defined in
     * [xAxis.plotBands](https://api.highcharts.com/highcharts/xAxis.plotBands).
     *
     * @return {Highcharts.PlotLineOrBand|undefined}
     * The added plot band, or `undefined` if the options are not valid.
     */
    function addPlotBand(options) {
        return this.addPlotBandOrLine(options, 'plotBands');
    }
    /**
     * Add a plot band or plot line after render time. Called from
     * addPlotBand and addPlotLine internally.
     *
     * @internal
     * @function Highcharts.Axis#addPlotBandOrLine
     * @param {Highcharts.AxisPlotBandsOptions|Highcharts.AxisPlotLinesOptions} options
     * The plotBand or plotLine configuration object.
     */
    function addPlotBandOrLine(options, coll) {
        const userOptions = this.userOptions;
        let obj = new PlotLineOrBandClass(this, options);
        if (this.visible) {
            obj = obj.render();
        }
        if (obj) { // #2189
            if (!this._addedPlotLB) {
                this._addedPlotLB = true;
                (userOptions.plotLines || [])
                    .concat(userOptions.plotBands || [])
                    .forEach((plotLineOptions) => {
                    this.addPlotBandOrLine(plotLineOptions);
                });
            }
            // Add it to the user options for exporting and Axis.update
            if (coll) {
                // Workaround Microsoft/TypeScript issue #32693
                const updatedOptions = (userOptions[coll] || []);
                updatedOptions.push(options);
                userOptions[coll] = updatedOptions;
            }
            this.plotLinesAndBands.push(obj);
        }
        return obj;
    }
    /**
     * Add a plot line after render time.
     *
     * @sample highcharts/members/axis-addplotline/
     *         Toggle the plot line from a button
     *
     * @function Highcharts.Axis#addPlotLine
     *
     * @param {Highcharts.AxisPlotLinesOptions} options
     * A configuration object for the plot line, as defined in
     * [xAxis.plotLines](https://api.highcharts.com/highcharts/xAxis.plotLines).
     *
     * @return {Highcharts.PlotLineOrBand|undefined}
     * The added plot line, or `undefined` if the options are not valid.
     */
    function addPlotLine(options) {
        return this.addPlotBandOrLine(options, 'plotLines');
    }
    /** @internal */
    function compose(PlotLineOrBandType, AxisClass) {
        const axisProto = AxisClass.prototype;
        if (!axisProto.addPlotBand) {
            PlotLineOrBandClass = PlotLineOrBandType;
            extend(axisProto, {
                addPlotBand,
                addPlotLine,
                addPlotBandOrLine,
                getPlotBandPath,
                removePlotBand,
                removePlotLine,
                removePlotBandOrLine
            });
        }
        return AxisClass;
    }
    PlotLineOrBandAxis.compose = compose;
    /**
     * Internal function to create the SVG path definition for a plot band.
     *
     * @function Highcharts.Axis#getPlotBandPath
     *
     * @param {number} from
     * The axis value to start from.
     *
     * @param {number} to
     * The axis value to end on.
     *
     * @param {Highcharts.AxisPlotBandsOptions|Highcharts.AxisPlotLinesOptions} options
     * The plotBand or plotLine configuration object.
     *
     * @return {Highcharts.SVGPathArray}
     * The SVG path definition in array form.
     */
    function getPlotBandPath(from, to, options) {
        options = options || this.options;
        const toPath = this.getPlotLinePath({
            value: to,
            force: true,
            acrossPanes: options.acrossPanes
        }), result = [], horiz = this.horiz, outside = !isNumber(this.min) ||
            !isNumber(this.max) ||
            (from < this.min && to < this.min) ||
            (from > this.max && to > this.max), path = this.getPlotLinePath({
            value: from,
            force: true,
            acrossPanes: options.acrossPanes
        });
        let i, 
        // #4964 check if chart is inverted or plot band is on yAxis
        plus = 1, isFlat;
        if (path && toPath) {
            // Flat paths don't need labels (#3836)
            if (outside) {
                isFlat = path.toString() === toPath.toString();
                plus = 0;
            }
            // Go over each subpath - for panes in Highcharts Stock
            for (i = 0; i < path.length; i += 2) {
                const pathStart = path[i], pathEnd = path[i + 1], toPathStart = toPath[i], toPathEnd = toPath[i + 1];
                // Type checking all affected path segments. Consider
                // something smarter.
                if ((pathStart[0] === 'M' || pathStart[0] === 'L') &&
                    (pathEnd[0] === 'M' || pathEnd[0] === 'L') &&
                    (toPathStart[0] === 'M' || toPathStart[0] === 'L') &&
                    (toPathEnd[0] === 'M' || toPathEnd[0] === 'L')) {
                    // Add 1 pixel when coordinates are the same
                    if (horiz && toPathStart[1] === pathStart[1]) {
                        toPathStart[1] += plus;
                        toPathEnd[1] += plus;
                    }
                    else if (!horiz && toPathStart[2] === pathStart[2]) {
                        toPathStart[2] += plus;
                        toPathEnd[2] += plus;
                    }
                    result.push(['M', pathStart[1], pathStart[2]], ['L', pathEnd[1], pathEnd[2]], ['L', toPathEnd[1], toPathEnd[2]], ['L', toPathStart[1], toPathStart[2]], ['Z']);
                }
                result.isFlat = isFlat;
            }
        }
        return result;
    }
    /**
     * Remove a plot band by its id.
     *
     * @sample highcharts/members/axis-removeplotband/
     *         Remove plot band by id
     * @sample highcharts/members/axis-addplotband/
     *         Toggle the plot band from a button
     *
     * @function Highcharts.Axis#removePlotBand
     *
     * @param {string} id
     *        The plot band's `id` as given in the original configuration
     *        object or in the `addPlotBand` option.
     */
    function removePlotBand(id) {
        this.removePlotBandOrLine(id);
    }
    /**
     * Remove a plot band or plot line from the chart by id. Called
     * internally from `removePlotBand` and `removePlotLine`.
     * @internal
     * @function Highcharts.Axis#removePlotBandOrLine
     */
    function removePlotBandOrLine(id) {
        const plotLinesAndBands = this.plotLinesAndBands, options = this.options, userOptions = this.userOptions;
        if (plotLinesAndBands) { // #15639
            let i = plotLinesAndBands.length;
            while (i--) {
                if (plotLinesAndBands[i].id === id) {
                    plotLinesAndBands[i].destroy();
                }
            }
            ([
                options.plotLines || [],
                userOptions.plotLines || [],
                options.plotBands || [],
                userOptions.plotBands || []
            ]).forEach(function (arr) {
                i = arr.length;
                while (i--) {
                    if (arr[i]?.id === id) {
                        erase(arr, arr[i]);
                    }
                }
            });
        }
    }
    /**
     * Remove a plot line by its id.
     *
     * @sample highcharts/xaxis/plotlines-id/
     *         Remove plot line by id
     * @sample highcharts/members/axis-addplotline/
     *         Toggle the plot line from a button
     *
     * @function Highcharts.Axis#removePlotLine
     *
     * @param {string} id
     *        The plot line's `id` as given in the original configuration
     *        object or in the `addPlotLine` option.
     */
    function removePlotLine(id) {
        this.removePlotBandOrLine(id);
    }
})(PlotLineOrBandAxis || (PlotLineOrBandAxis = {}));
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const PlotLineOrBand_PlotLineOrBandAxis = (PlotLineOrBandAxis);

;// ./code/es-modules/Core/Axis/PlotLineOrBand/PlotLineOrBand.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */



/* *
 *
 *  Class
 *
 * */
/**
 * The object wrapper for plot lines and plot bands
 *
 * @class
 * @name Highcharts.PlotLineOrBand
 *
 * @param {Highcharts.Axis} axis
 * Related axis.
 *
 * @param {Highcharts.AxisPlotLinesOptions|Highcharts.AxisPlotBandsOptions} [options]
 * Options to use.
 */
class PlotLineOrBand {
    /* *
     *
     *  Static Functions
     *
     * */
    /**
     * Composes `PlotLineOrBand` with `Axis` and `Chart`.
     *
     * @param {Highcharts.Chart} ChartClass
     * Chart class to compose.
     *
     * @param {Highcharts.Axis} AxisClass
     * Axis class to compose.
     *
     * @internal
     */
    static compose(ChartClass, AxisClass) {
        addEvent(ChartClass, 'afterInit', function () {
            this.labelCollectors.push(() => {
                const labels = [];
                for (const axis of this.axes) {
                    for (const { label, options } of axis.plotLinesAndBands) {
                        if (label && !options?.label?.allowOverlap) {
                            labels.push(label);
                        }
                    }
                }
                return labels;
            });
        });
        return PlotLineOrBand_PlotLineOrBandAxis.compose(PlotLineOrBand, AxisClass);
    }
    /* *
     *
     *  Constructor
     *
     * */
    constructor(axis, options) {
        /**
         * Related axis.
         *
         * @name Highcharts.PlotLineOrBand#axis
         * @type {Highcharts.Axis}
         */
        this.axis = axis;
        /**
         * Options of the plot line or band.
         *
         * @name Highcharts.PlotLineOrBand#options
         * @type {AxisPlotBandsOptions|AxisPlotLinesOptions}
         */
        this.options = options;
        this.id = options.id;
    }
    /* *
     *
     *  Functions
     *
     * */
    /* eslint-disable no-invalid-this, valid-jsdoc */
    /**
     * Render the plot line or plot band. If it is already existing,
     * move it.
     * @internal
     * @function Highcharts.PlotLineOrBand#render
     */
    render() {
        fireEvent(this, 'render');
        const { axis, options } = this, { chart, horiz, logarithmic } = axis, { color, events, zIndex = 0 } = options, { renderer, time } = axis.chart, groupAttribs = {}, 
        // These properties only exist on either band or line
        to = time.parse(options.to), from = time.parse(options.from), value = time.parse(options.value), borderWidth = options.borderWidth;
        let optionsLabel = options.label, { label, svgElem } = this, path = [], group;
        const isBand = defined(from) && defined(to), isLine = defined(value), isNew = !svgElem, attribs = {
            'class': 'highcharts-plot-' + (isBand ? 'band ' : 'line ') +
                (options.className || '')
        };
        let groupName = isBand ? 'bands' : 'lines';
        // Set the presentational attributes
        if (!chart.styledMode) {
            if (isLine) {
                attribs.stroke = color || 'var(--highcharts-neutral-color-40)';
                attribs['stroke-width'] = pick(options.width, 1);
                if (options.dashStyle) {
                    attribs.dashstyle = options.dashStyle;
                }
            }
            else if (isBand) { // Plot band
                attribs.fill = color || 'var(--highcharts-highlight-color-10)';
                if (borderWidth) {
                    attribs.stroke = options.borderColor;
                    attribs['stroke-width'] = borderWidth;
                }
            }
        }
        // Grouping and zIndex
        groupAttribs.zIndex = zIndex;
        groupName += '-' + zIndex;
        group = axis.plotLinesAndBandsGroups[groupName];
        if (!group) {
            axis.plotLinesAndBandsGroups[groupName] = group =
                renderer.g('plot-' + groupName)
                    .clip(chart.plotClipOuter)
                    .attr(groupAttribs).add();
        }
        // Create the path
        if (!svgElem) {
            /**
             * SVG element of the plot line or band.
             *
             * @name Highcharts.PlotLineOrBand#svgElem
             * @type {Highcharts.SVGElement}
             */
            this.svgElem = svgElem = renderer
                .path()
                .attr(attribs)
                .add(group);
        }
        // Set the path or return
        if (defined(value)) { // Plot line
            path = axis.getPlotLinePath({
                value: logarithmic?.log2lin(value) ?? value,
                lineWidth: svgElem.strokeWidth(),
                acrossPanes: options.acrossPanes
            });
        }
        else if (defined(from) && defined(to)) { // Plot band
            path = axis.getPlotBandPath(logarithmic?.log2lin(from) ?? from, logarithmic?.log2lin(to) ?? to, options);
        }
        else {
            return;
        }
        // Common for lines and bands. Add events only if they were not added
        // before.
        if (!this.eventsAdded && events) {
            objectEach(events, (_event, eventType) => {
                svgElem?.on(eventType, (e) => {
                    events[eventType].apply(this, [e, this]);
                });
            });
            this.eventsAdded = true;
        }
        if ((isNew || !svgElem.d) && path?.length) {
            svgElem.attr({ d: path });
        }
        else if (svgElem) {
            if (path) {
                svgElem.show();
                svgElem.animate({ d: path });
            }
            else if (svgElem.d) {
                svgElem.hide();
                if (label) {
                    this.label = label = label.destroy();
                }
            }
        }
        // The plot band/line label
        if (optionsLabel &&
            (defined(optionsLabel.text) || defined(optionsLabel.formatter)) &&
            path?.length &&
            axis.width > 0 &&
            axis.height > 0 &&
            !path.isFlat) {
            // Apply defaults
            optionsLabel = merge({
                align: horiz && isBand ? 'center' : void 0,
                x: horiz ? !isBand && 4 : 10,
                verticalAlign: !horiz && isBand ? 'middle' : void 0,
                y: horiz ? isBand ? 16 : 10 : isBand ? 6 : -4,
                rotation: horiz && !isBand ? 90 : 0,
                ...(isBand ? { inside: true } : {})
            }, optionsLabel);
            this.renderLabel(optionsLabel, path, isBand, zIndex);
            // Move out of sight
        }
        else if (label) {
            label.hide();
        }
        // Chainable
        return this;
    }
    /**
     * Render and align label for plot line or band.
     * @internal
     * @function Highcharts.PlotLineOrBand#renderLabel
     */
    renderLabel(optionsLabel, path, isBand, zIndex) {
        const plotLine = this, axis = plotLine.axis, renderer = axis.chart.renderer, inside = optionsLabel.inside;
        let label = plotLine.label;
        // Add the SVG element
        if (!label) {
            /**
             * SVG element of the label.
             *
             * @name Highcharts.PlotLineOrBand#label
             * @type {Highcharts.SVGElement}
             */
            plotLine.label = label = renderer
                .text(this.getLabelText(optionsLabel), 0, 0, optionsLabel.useHTML)
                .attr({
                align: optionsLabel.textAlign || optionsLabel.align,
                rotation: optionsLabel.rotation,
                'class': 'highcharts-plot-' + (isBand ? 'band' : 'line') +
                    '-label ' + (optionsLabel.className || ''),
                zIndex
            });
            if (!axis.chart.styledMode) {
                label.css(merge({
                    // To allow theming, and in lack of a general place to set
                    // default options for plot lines and bands, default to the
                    // title color. If we expose the palette, we should use that
                    // instead.
                    color: axis.chart.options.title?.style?.color,
                    fontSize: '0.8em',
                    textOverflow: (isBand && !inside) ? '' : 'ellipsis'
                }, optionsLabel.style));
            }
            label.add();
        }
        // Get the bounding box and align the label
        // #3000 changed to better handle choice between plotband or plotline
        const xBounds = path.xBounds ||
            [path[0][1], path[1][1], (isBand ? path[2][1] : path[0][1])], yBounds = path.yBounds ||
            [path[0][2], path[1][2], (isBand ? path[2][2] : path[0][2])], x = arrayMin(xBounds), y = arrayMin(yBounds), bBoxWidth = arrayMax(xBounds) - x;
        label.align(optionsLabel, false, {
            x,
            y,
            width: bBoxWidth,
            height: arrayMax(yBounds) - y
        });
        label.alignAttr.y -= renderer.fontMetrics(label).b;
        if (!label.alignValue ||
            label.alignValue === 'left' ||
            defined(inside)) {
            label.css({
                width: (optionsLabel.style?.width || ((!isBand ||
                    !inside) ? (label.rotation === 90 ?
                    axis.height - (label.alignAttr.y -
                        axis.top) : (optionsLabel.clip ?
                    (axis.width + axis.left) :
                    axis.chart.chartWidth) - label.alignAttr.x) :
                    bBoxWidth)) + 'px'
            });
        }
        label.show(true);
    }
    /**
     * Get label's text content.
     * @internal
     * @function Highcharts.PlotLineOrBand#getLabelText
     */
    getLabelText(optionsLabel) {
        return defined(optionsLabel.formatter) ?
            optionsLabel.formatter.call(this, this) :
            optionsLabel.text;
    }
    /**
     * Remove the plot line or band.
     *
     * @function Highcharts.PlotLineOrBand#destroy
     */
    destroy() {
        // Remove it from the lookup
        erase(this.axis.plotLinesAndBands, this);
        delete this.axis;
        destroyObjectProperties(this);
    }
}
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const PlotLineOrBand_PlotLineOrBand = (PlotLineOrBand);
/* *
 *
 *  API Options
 *
 * */
/**
 * Options for plot bands on axes.
 *
 * @typedef {Highcharts.XAxisPlotBandsOptions|Highcharts.YAxisPlotBandsOptions|Highcharts.ZAxisPlotBandsOptions} Highcharts.AxisPlotBandsOptions
 */
/**
 * Options for plot band labels on axes.
 *
 * @typedef {Highcharts.XAxisPlotBandsLabelOptions|Highcharts.YAxisPlotBandsLabelOptions|Highcharts.ZAxisPlotBandsLabelOptions} Highcharts.AxisPlotBandsLabelOptions
 */
/**
 * Options for plot lines on axes.
 *
 * @typedef {Highcharts.XAxisPlotLinesOptions|Highcharts.YAxisPlotLinesOptions|Highcharts.ZAxisPlotLinesOptions} Highcharts.AxisPlotLinesOptions
 */
/**
 * Options for plot line labels on axes.
 *
 * @typedef {Highcharts.XAxisPlotLinesLabelOptions|Highcharts.YAxisPlotLinesLabelOptions|Highcharts.ZAxisPlotLinesLabelOptions} Highcharts.AxisPlotLinesLabelOptions
 */
('');
/* *
 *
 *  API Options
 *
 * */
/**
 * An array of colored bands stretching across the plot area marking an
 * interval on the axis.
 *
 * In styled mode, the plot bands are styled by the `.highcharts-plot-band`
 * class in addition to the `className` option.
 *
 * @productdesc {highcharts}
 * In a gauge, a plot band on the Y axis (value axis) will stretch along the
 * perimeter of the gauge.
 *
 * @type      {Array<*>}
 * @product   highcharts highstock gantt
 * @apioption xAxis.plotBands
 */
/**
 * Flag to decide if plotBand should be rendered across all panes.
 *
 * @since     7.1.2
 * @product   highstock
 * @type      {boolean}
 * @default   true
 * @apioption xAxis.plotBands.acrossPanes
 */
/**
 * Border color for the plot band. Also requires `borderWidth` to be set.
 *
 * @type      {Highcharts.ColorString}
 * @apioption xAxis.plotBands.borderColor
 */
/**
 * Border radius for the plot band. Applies only to gauges. Can be a pixel
 * value or a percentage, for example `50%`.
 *
 * @type      {number|string|Highcharts.BorderRadiusOptionsObject}
 * @since 11.4.2
 * @sample    {highcharts} highcharts/xaxis/plotbands-gauge-borderradius
 *            Angular gauge with rounded plot bands
 * @apioption xAxis.plotBands.borderRadius
 */
/**
 * Border width for the plot band. Also requires `borderColor` to be set.
 *
 * @type      {number}
 * @default   0
 * @apioption xAxis.plotBands.borderWidth
 */
/**
 * A custom class name, in addition to the default `highcharts-plot-band`,
 * to apply to each individual band.
 *
 * @type      {string}
 * @since     5.0.0
 * @apioption xAxis.plotBands.className
 */
/**
 * The color of the plot band.
 *
 * @sample {highcharts} highcharts/xaxis/plotbands-color/
 *         Color band
 * @sample {highstock} stock/xaxis/plotbands/
 *         Plot band on Y axis
 *
 * @type      {Highcharts.ColorType}
 * @default   var(--highcharts-highlight-color-10)
 * @apioption xAxis.plotBands.color
 */
/**
 * An object defining mouse events for the plot band. Supported properties
 * are `click`, `mouseover`, `mouseout`, `mousemove`.
 *
 * @sample {highcharts} highcharts/xaxis/plotbands-events/
 *         Mouse events demonstrated
 *
 * @since     1.2
 * @apioption xAxis.plotBands.events
 */
/**
 * Click event on a plot band.
 *
 * @type      {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>}
 * @apioption xAxis.plotBands.events.click
 */
/**
 * Mouse move event on a plot band.
 *
 * @type      {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>}
 * @apioption xAxis.plotBands.events.mousemove
 */
/**
 * Mouse out event on the corner of a plot band.
 *
 * @type      {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>}
 * @apioption xAxis.plotBands.events.mouseout
 */
/**
 * Mouse over event on a plot band.
 *
 * @type      {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>}
 * @apioption xAxis.plotBands.events.mouseover
 */
/**
 * The start position of the plot band in axis units.
 *
 * On datetime axes, the value can be given as a timestamp or a date string.
 *
 * @sample {highcharts} highcharts/xaxis/plotbands-color/
 *         Datetime axis
 * @sample {highcharts} highcharts/xaxis/plotbands-from/
 *         Categorized axis
 * @sample {highstock} stock/xaxis/plotbands/
 *         Plot band on Y axis
 *
 * @type      {number|string}
 * @apioption xAxis.plotBands.from
 */
/**
 * An id used for identifying the plot band in Axis.removePlotBand.
 *
 * @sample {highcharts} highcharts/xaxis/plotbands-id/
 *         Remove plot band by id
 * @sample {highstock} highcharts/xaxis/plotbands-id/
 *         Remove plot band by id
 *
 * @type      {string}
 * @apioption xAxis.plotBands.id
 */
/**
 * The end position of the plot band in axis units.
 *
 * On datetime axes, the value can be given as a timestamp or a date string.
 *
 * @sample {highcharts} highcharts/xaxis/plotbands-color/
 *         Datetime axis
 * @sample {highcharts} highcharts/xaxis/plotbands-from/
 *         Categorized axis
 * @sample {highstock} stock/xaxis/plotbands/
 *         Plot band on Y axis
 *
 * @type      {number|string}
 * @apioption xAxis.plotBands.to
 */
/**
 * The z index of the plot band within the chart, relative to other
 * elements. Using the same z index as another element may give
 * unpredictable results, as the last rendered element will be on top.
 * Values from 0 to 20 make sense.
 *
 * @sample {highcharts} highcharts/xaxis/plotbands-color/
 *         Behind plot lines by default
 * @sample {highcharts} highcharts/xaxis/plotbands-zindex/
 *         Above plot lines
 * @sample {highcharts} highcharts/xaxis/plotbands-zindex-above-series/
 *         Above plot lines and series
 *
 * @type      {number}
 * @since     1.2
 * @apioption xAxis.plotBands.zIndex
 */
/**
 * Text labels for the plot bands
 *
 * @product   highcharts highstock gantt
 * @apioption xAxis.plotBands.label
 */
/**
 * Horizontal alignment of the label. Can be one of "left", "center" or
 * "right".
 *
 * @sample {highcharts} highcharts/xaxis/plotbands-label-align/
 *         Aligned to the right
 * @sample {highstock} stock/xaxis/plotbands-label/
 *         Plot band with labels
 *
 * @type      {Highcharts.AlignValue}
 * @default   center
 * @since     2.1
 * @apioption xAxis.plotBands.label.align
 */
/**
 * Whether or not the label can be hidden if it overlaps with another label.
 *
 * @sample {highcharts} highcharts/xaxis/plotbands-label-allowoverlap/
 *         A Plotband label overlapping another
 *
 * @type      {boolean}
 * @default   undefined
 * @since     11.4.8
 * @apioption xAxis.plotBands.label.allowOverlap
 */
/**
 * Wether or not the text of the label can exceed the width of the label.
 *
 * @type      {boolean}
 * @product   highcharts highstock gantt
 * @sample {highcharts} highcharts/xaxis/plotbands-label-textwidth/
 *         Displaying text with text-wrapping/ellipsis, or the full text.
 *
 * @default   true
 * @since     11.4.6
 * @apioption xAxis.plotBands.label.inside
 */
/**
 * Rotation of the text label in degrees .
 *
 * @sample {highcharts} highcharts/xaxis/plotbands-label-rotation/
 *         Vertical text
 *
 * @type      {number}
 * @default   0
 * @since     2.1
 * @apioption xAxis.plotBands.label.rotation
 */
/**
 * CSS styles for the text label.
 *
 * In styled mode, the labels are styled by the
 * `.highcharts-plot-band-label` class.
 *
 * @sample {highcharts} highcharts/xaxis/plotbands-label-style/
 *         Blue and bold label
 *
 * @type      {Highcharts.CSSObject}
 * @since     2.1
 * @apioption xAxis.plotBands.label.style
 */
/**
 * The string text itself. A subset of HTML is supported.
 *
 * @type      {string}
 * @since     2.1
 * @apioption xAxis.plotBands.label.text
 */
/**
 * The text alignment for the label. While `align` determines where the
 * texts anchor point is placed within the plot band, `textAlign` determines
 * how the text is aligned against its anchor point. Possible values are
 * "left", "center" and "right". Defaults to the same as the `align` option.
 *
 * @sample {highcharts} highcharts/xaxis/plotbands-label-rotation/
 *         Vertical text in center position but text-aligned left
 *
 * @type       {Highcharts.AlignValue}
 * @since      2.1
 * @apioption  xAxis.plotBands.label.textAlign
 */
/**
 * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
 * to render the labels.
 *
 * @type      {boolean}
 * @default   false
 * @since     3.0.3
 * @apioption xAxis.plotBands.label.useHTML
 */
/**
 * Vertical alignment of the label relative to the plot band. Can be one of
 * "top", "middle" or "bottom".
 *
 * @sample {highcharts} highcharts/xaxis/plotbands-label-verticalalign/
 *         Vertically centered label
 * @sample {highstock} stock/xaxis/plotbands-label/
 *         Plot band with labels
 *
 * @type       {Highcharts.VerticalAlignValue}
 * @default    top
 * @since      2.1
 * @apioption  xAxis.plotBands.label.verticalAlign
 */
/**
 * Horizontal position relative the alignment. Default varies by
 * orientation.
 *
 * @sample {highcharts} highcharts/xaxis/plotbands-label-align/
 *         Aligned 10px from the right edge
 * @sample {highstock} stock/xaxis/plotbands-label/
 *         Plot band with labels
 *
 * @type      {number}
 * @since     2.1
 * @apioption xAxis.plotBands.label.x
 */
/**
 * Vertical position of the text baseline relative to the alignment. Default
 * varies by orientation.
 *
 * @sample {highcharts} highcharts/xaxis/plotbands-label-y/
 *         Label on x axis
 * @sample {highstock} stock/xaxis/plotbands-label/
 *         Plot band with labels
 *
 * @type      {number}
 * @since     2.1
 * @apioption xAxis.plotBands.label.y
 */
/**
 * An array of lines stretching across the plot area, marking a specific
 * value on one of the axes.
 *
 * In styled mode, the plot lines are styled by the
 * `.highcharts-plot-line` class in addition to the `className` option.
 *
 * @type      {Array<*>}
 * @product   highcharts highstock gantt
 * @sample {highcharts} highcharts/xaxis/plotlines-color/
 *         Basic plot line
 * @sample {highcharts} highcharts/series-solidgauge/labels-auto-aligned/
 *         Solid gauge plot line
 * @apioption xAxis.plotLines
 */
/**
 * Flag to decide if plotLine should be rendered across all panes.
 *
 * @sample {highstock} stock/xaxis/plotlines-acrosspanes/
 *         Plot lines on different panes
 *
 * @since     7.1.2
 * @product   highstock
 * @type      {boolean}
 * @default   true
 * @apioption xAxis.plotLines.acrossPanes
 */
/**
 * A custom class name, in addition to the default `highcharts-plot-line`,
 * to apply to each individual line.
 *
 * @type      {string}
 * @since     5.0.0
 * @apioption xAxis.plotLines.className
 */
/**
 * The color of the line.
 *
 * @sample {highcharts} highcharts/xaxis/plotlines-color/
 *         A red line from X axis
 * @sample {highstock} stock/xaxis/plotlines/
 *         Plot line on Y axis
 *
 * @type      {Highcharts.ColorString}
 * @default   var(--highcharts-neutral-color-40)
 * @apioption xAxis.plotLines.color
 */
/**
 * The dashing or dot style for the plot line. For possible values see
 * [this overview](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/plotoptions/series-dashstyle-all/).
 *
 * @sample {highcharts} highcharts/xaxis/plotlines-dashstyle/
 *         Dash and dot pattern
 * @sample {highstock} stock/xaxis/plotlines/
 *         Plot line on Y axis
 *
 * @type      {Highcharts.DashStyleValue}
 * @default   Solid
 * @since     1.2
 * @apioption xAxis.plotLines.dashStyle
 */
/**
 * An object defining mouse events for the plot line. Supported
 * properties are `click`, `mouseover`, `mouseout`, `mousemove`.
 *
 * @sample {highcharts} highcharts/xaxis/plotlines-events/
 *         Mouse events demonstrated
 *
 * @since     1.2
 * @apioption xAxis.plotLines.events
 */
/**
 * Click event on a plot band.
 *
 * @type      {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>}
 * @apioption xAxis.plotLines.events.click
 */
/**
 * Mouse move event on a plot band.
 *
 * @type      {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>}
 * @apioption xAxis.plotLines.events.mousemove
 */
/**
 * Mouse out event on the corner of a plot band.
 *
 * @type      {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>}
 * @apioption xAxis.plotLines.events.mouseout
 */
/**
 * Mouse over event on a plot band.
 *
 * @type      {Highcharts.EventCallbackFunction<Highcharts.PlotLineOrBand>}
 * @apioption xAxis.plotLines.events.mouseover
 */
/**
 * An id used for identifying the plot line in Axis.removePlotLine.
 *
 * @sample {highcharts} highcharts/xaxis/plotlines-id/
 *         Remove plot line by id
 *
 * @type      {string}
 * @apioption xAxis.plotLines.id
 */
/**
 * The position of the line in axis units.
 *
 * On datetime axes, the value can be given as a timestamp or a date string.
 *
 * @sample {highcharts} highcharts/xaxis/plotlines-color/
 *         Between two categories on X axis
 * @sample {highstock} stock/xaxis/plotlines/
 *         Plot line on Y axis
 *
 * @type      {number|string}
 * @apioption xAxis.plotLines.value
 */
/**
 * The width or thickness of the plot line.
 *
 * @sample {highcharts} highcharts/xaxis/plotlines-color/
 *         2px wide line from X axis
 * @sample {highstock} stock/xaxis/plotlines/
 *         Plot line on Y axis
 *
 * @type      {number}
 * @default   2
 * @apioption xAxis.plotLines.width
 */
/**
 * The z index of the plot line within the chart.
 *
 * @sample {highcharts} highcharts/xaxis/plotlines-zindex-behind/
 *         Behind plot lines by default
 * @sample {highcharts} highcharts/xaxis/plotlines-zindex-above/
 *         Above plot lines
 * @sample {highcharts} highcharts/xaxis/plotlines-zindex-above-all/
 *         Above plot lines and series
 *
 * @type      {number}
 * @since     1.2
 * @apioption xAxis.plotLines.zIndex
 */
/**
 * Text labels for the plot lines
 *
 * @apioption xAxis.plotLines.label
 */
/**
 * Horizontal alignment of the label. Can be one of "left", "center" or
 * "right".
 *
 * @sample {highcharts} highcharts/xaxis/plotlines-label-align-right/
 *         Aligned to the right
 * @sample {highstock} stock/xaxis/plotlines/
 *         Plot line on Y axis
 *
 * @type       {Highcharts.AlignValue}
 * @default    left
 * @since      2.1
 * @apioption  xAxis.plotLines.label.align
 */
/**
 * Whether or not the label can be hidden if it overlaps with another label.
 *
 * @type      {boolean}
 * @default   undefined
 * @since     11.4.8
 * @apioption xAxis.plotBands.label.allowOverlap
 */
/**
 * Whether to hide labels that are outside the plot area.
 *
 * @type      {boolean}
 * @default   false
 * @since 10.3.3
 * @apioption xAxis.plotLines.labels.clip
 */
/**
 * Callback JavaScript function to format the label. Useful properties like
 * the value of plot line or the range of plot band (`from` & `to`
 * properties) can be found in `this.options` object. Since v12.6.0, the
 * callback also receives `ctx` as the first argument, so that arrow functions
 * can access the same context as regular functions using `this`.
 *
 * @sample {highcharts} highcharts/xaxis/plotlines-plotbands-label-formatter
 *         Label formatters for plot line and plot band.
 * @type      {Highcharts.FormatterCallbackFunction<Highcharts.PlotLineOrBand>}
 * @apioption xAxis.plotLines.label.formatter
 */
/**
 * Rotation of the text label in degrees. Defaults to 0 for horizontal plot
 * lines and 90 for vertical lines.
 *
 * @sample {highcharts} highcharts/xaxis/plotlines-label-verticalalign-middle/
 *         Slanted text
 *
 * @type      {number}
 * @since     2.1
 * @apioption xAxis.plotLines.label.rotation
 */
/**
 * CSS styles for the text label.
 *
 * In styled mode, the labels are styled by the
 * `.highcharts-plot-line-label` class.
 *
 * @sample {highcharts} highcharts/xaxis/plotlines-label-style/
 *         Blue and bold label
 *
 * @type      {Highcharts.CSSObject}
 * @since     2.1
 * @apioption xAxis.plotLines.label.style
 */
/**
 * The text itself. A subset of HTML is supported.
 *
 * @type      {string}
 * @since     2.1
 * @apioption xAxis.plotLines.label.text
 */
/**
 * The text alignment for the label. While `align` determines where the
 * texts anchor point is placed within the plot band, `textAlign` determines
 * how the text is aligned against its anchor point. Possible values are
 * "left", "center" and "right". Defaults to the same as the `align` option.
 *
 * @sample {highcharts} highcharts/xaxis/plotlines-label-textalign/
 *         Text label in bottom position
 *
 * @type      {Highcharts.AlignValue}
 * @since     2.1
 * @apioption xAxis.plotLines.label.textAlign
 */
/**
 * Whether to [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
 * to render the labels.
 *
 * @type      {boolean}
 * @default   false
 * @since     3.0.3
 * @apioption xAxis.plotLines.label.useHTML
 */
/**
 * Vertical alignment of the label relative to the plot line. Can be
 * one of "top", "middle" or "bottom".
 *
 * @sample {highcharts} highcharts/xaxis/plotlines-label-verticalalign-middle/
 *         Vertically centered label
 *
 * @type       {Highcharts.VerticalAlignValue}
 * @default    {highcharts} top
 * @default    {highstock} top
 * @since      2.1
 * @apioption  xAxis.plotLines.label.verticalAlign
 */
/**
 * Horizontal position relative the alignment. Default varies by
 * orientation.
 *
 * @sample {highcharts} highcharts/xaxis/plotlines-label-align-right/
 *         Aligned 10px from the right edge
 * @sample {highstock} stock/xaxis/plotlines/
 *         Plot line on Y axis
 *
 * @type      {number}
 * @since     2.1
 * @apioption xAxis.plotLines.label.x
 */
/**
 * Vertical position of the text baseline relative to the alignment. Default
 * varies by orientation.
 *
 * @sample {highcharts} highcharts/xaxis/plotlines-label-y/
 *         Label below the plot line
 * @sample {highstock} stock/xaxis/plotlines/
 *         Plot line on Y axis
 *
 * @type      {number}
 * @since     2.1
 * @apioption xAxis.plotLines.label.y
 */
/**
 * @type      {Array<*>}
 * @extends   xAxis.plotBands
 * @apioption yAxis.plotBands
 */
/**
 * In a gauge chart, this option determines the inner radius of the
 * plot band that stretches along the perimeter. It can be given as
 * a percentage string, like `"100%"`, or as a pixel number, like `100`.
 * By default, the inner radius is controlled by the [thickness](
 * #yAxis.plotBands.thickness) option.
 *
 * @sample {highcharts} highcharts/xaxis/plotbands-gauge
 *         Gauge plot band
 *
 * @type      {number|string}
 * @since     2.3
 * @product   highcharts
 * @apioption yAxis.plotBands.innerRadius
 */
/**
 * In a gauge chart, this option determines the outer radius of the
 * plot band that stretches along the perimeter. It can be given as
 * a percentage string, like `"100%"`, or as a pixel number, like `100`.
 *
 * @sample {highcharts} highcharts/xaxis/plotbands-gauge
 *         Gauge plot band
 *
 * @type      {number|string}
 * @default   100%
 * @since     2.3
 * @product   highcharts
 * @apioption yAxis.plotBands.outerRadius
 */
/**
 * In a gauge chart, this option sets the width of the plot band stretching
 * along the perimeter. It can be given as a percentage string, like `'10%'`, or
 * as a pixel number, like `10`. If `undefined`, the plot band fills out the
 * range between `pane.size` and `pane.innerSize`.
 *
 * @sample {highcharts} highcharts/xaxis/plotbands-gauge Gauge plot band
 *
 * @type      {number|string}
 * @since     2.3
 * @product   highcharts
 * @apioption yAxis.plotBands.thickness
 */
/**
 * @type      {Array<*>}
 * @extends   xAxis.plotLines
 * @apioption yAxis.plotLines
 */
(''); // Keeps doclets above in JS file

;// ./code/es-modules/Core/Tooltip.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { animObject: Tooltip_animObject } = AnimationUtilities;

const { format: Tooltip_format } = Core_Templating;

const { composed: Tooltip_composed, dateFormats, doc: Tooltip_doc, isSafari } = Core_Globals;

const { distribute } = Renderer_RendererUtilities;


/**
 * Clear all timeouts for showing and hiding the tooltip.
 *
 * @internal
 */
const clearTimeouts = (tooltip) => {
    clearTimeout(tooltip.hideTimer);
    clearTimeout(tooltip.showTimer);
};
/* *
 *
 *  Class
 *
 * */
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
 * Tooltip of a chart.
 *
 * @class
 * @name Highcharts.Tooltip
 *
 * @param {Highcharts.Chart} chart
 * The chart instance.
 *
 * @param {Highcharts.TooltipOptions} options
 * Tooltip options.
 *
 * @param {Highcharts.Pointer} pointer
 * The pointer instance.
 */
class Tooltip {
    /* *
     *
     *  Constructors
     *
     * */
    constructor(chart, options, pointer) {
        /* *
         *
         *  Properties
         *
         * */
        this.allowShared = true;
        /**
         * List of crosshairs.
         *
         * @internal
         * @readonly
         * @name Highcharts.Tooltip#crosshairs
         * @type {Array<null>}
         */
        this.crosshairs = [];
        this.distance = 0;
        /**
         * Tooltips are initially hidden.
         *
         * @internal
         * @readonly
         * @name Highcharts.Tooltip#isHidden
         * @type {boolean}
         */
        this.isHidden = true;
        this.isSticky = false;
        /**
         * Used tooltip options.
         *
         * @readonly
         * @name Highcharts.Tooltip#options
         * @type {Highcharts.TooltipOptions}
         */
        this.options = {};
        /**
         * Whether to allow the tooltip to render outside the chart's SVG
         * element box. By default (false), the tooltip is rendered within the
         * chart's SVG element, which results in the tooltip being aligned
         * inside the chart area.
         *
         * @readonly
         * @name Highcharts.Tooltip#outside
         * @type {boolean}
         *
         * @todo
         * Split tooltip does not support outside in the first iteration. Should
         * not be too complicated to implement.
         */
        this.outside = false;
        this.chart = chart;
        this.init(chart, options);
        this.pointer = pointer;
    }
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Build the body (lines) of the tooltip by iterating over the items and
     * returning one entry for each item, abstracting this functionality allows
     * to easily overwrite and extend it.
     *
     * @internal
     * @function Highcharts.Tooltip#bodyFormatter
     */
    bodyFormatter(points) {
        return points.map((point) => {
            const tooltipOptions = point.series.tooltipOptions, formatPrefix = point.formatPrefix || 'point';
            return (tooltipOptions[formatPrefix + 'Formatter'] ||
                point.tooltipFormatter).call(point, tooltipOptions[formatPrefix + 'Format'] || '', point);
        });
    }
    /**
     * Destroy the single tooltips in a split tooltip.
     * If the tooltip is active then it is not destroyed, unless forced to.
     *
     * @internal
     * @function Highcharts.Tooltip#cleanSplit
     *
     * @param {boolean} [force]
     * Force destroy all tooltips.
     */
    cleanSplit(force) {
        this.chart.series.forEach(function (series) {
            const tt = series?.tt;
            if (tt) {
                if (!tt.isActive || force) {
                    series.tt = tt.destroy();
                }
                else {
                    tt.isActive = false;
                }
            }
        });
    }
    /**
     * In case no user defined formatter is given, this will be used. Note that
     * the context here is an object holding point, series, x, y etc.
     *
     * @function Highcharts.Tooltip#defaultFormatter
     *
     * @param {Highcharts.Tooltip} tooltip The tooltip instance.
     *
     * @return {string|Array<string>}
     * Returns a string (single tooltip and shared)
     * or an array of strings (split tooltip)
     */
    defaultFormatter(tooltip) {
        const hoverPoints = this.points || splat(this);
        let s;
        // Build the header
        s = [tooltip.headerFooterFormatter(hoverPoints[0])];
        // Build the values
        s = s.concat(tooltip.bodyFormatter(hoverPoints));
        // Footer
        s.push(tooltip.headerFooterFormatter(hoverPoints[0], true));
        return s;
    }
    /**
     * Removes and destroys the tooltip and its elements.
     *
     * @function Highcharts.Tooltip#destroy
     */
    destroy() {
        // Destroy and clear local variables
        if (this.label) {
            this.label = this.label.destroy();
        }
        if (this.split) {
            this.cleanSplit(true);
            if (this.tt) {
                this.tt = this.tt.destroy();
            }
        }
        if (this.renderer) {
            this.renderer = this.renderer.destroy();
            discardElement(this.container);
        }
        internalClearTimeout(this.hideTimer);
        clearTimeouts(this);
    }
    /**
     * Extendable method to get the anchor position of the tooltip
     * from a point or set of points
     *
     * @internal
     * @function Highcharts.Tooltip#getAnchor
     */
    getAnchor(points, mouseEvent) {
        const { chart, pointer } = this, inverted = chart.inverted, plotTop = chart.plotTop, plotLeft = chart.plotLeft;
        let ret;
        points = splat(points);
        // If reversedStacks are false the tooltip position should be taken from
        // the last point (#17948)
        if (points[0].series?.yAxis &&
            !points[0].series.yAxis.options.reversedStacks) {
            points = points.slice().reverse();
        }
        // When tooltip follows mouse, relate the position to the mouse
        if (this.followPointer && mouseEvent) {
            if (typeof mouseEvent.chartX === 'undefined') {
                mouseEvent = pointer.normalize(mouseEvent);
            }
            ret = [
                mouseEvent.chartX - plotLeft,
                mouseEvent.chartY - plotTop
            ];
            // Some series types use a specifically calculated tooltip position for
            // each point
        }
        else if (points[0].tooltipPos) {
            ret = points[0].tooltipPos;
            // Calculate the average position and adjust for axis positions
        }
        else {
            let chartX = 0, chartY = 0;
            points.forEach(function (point) {
                const pos = point.pos(true);
                if (pos) {
                    chartX += pos[0];
                    chartY += pos[1];
                }
            });
            chartX /= points.length;
            chartY /= points.length;
            // When shared, place the tooltip next to the mouse (#424)
            if (this.shared && points.length > 1 && mouseEvent) {
                if (inverted) {
                    chartX = mouseEvent.chartX;
                }
                else {
                    chartY = mouseEvent.chartY;
                }
            }
            // Use the average position for multiple points
            ret = [chartX - plotLeft, chartY - plotTop];
        }
        const params = { point: points[0], ret };
        fireEvent(this, 'getAnchor', params);
        return params.ret.map(Math.round);
    }
    /**
     * Get the CSS class names for the tooltip's label. Styles the label
     * by `colorIndex` or user-defined CSS.
     *
     * @function Highcharts.Tooltip#getClassName
     *
     * @return {string}
     *         The class names.
     */
    getClassName(point, isSplit, isHeader) {
        const options = this.options, series = point.series, seriesOptions = series.options;
        return [
            options.className,
            'highcharts-label',
            isHeader && 'highcharts-tooltip-header',
            isSplit ? 'highcharts-tooltip-box' : 'highcharts-tooltip',
            !isHeader && 'highcharts-color-' + pick(point.colorIndex, series.colorIndex),
            seriesOptions?.className
        ].filter(isString).join(' ');
    }
    /**
     * Creates the Tooltip label element if it does not exist, then returns it.
     *
     * @function Highcharts.Tooltip#getLabel
     *
     * @return {Highcharts.SVGElement}
     * Tooltip label
     */
    getLabel({ anchorX, anchorY } = { anchorX: 0, anchorY: 0 }) {
        const tooltip = this, styledMode = this.chart.styledMode, options = this.options, doSplit = this.split && this.allowShared;
        let container = this.container, renderer = this.chart.renderer;
        // If changing from a split tooltip to a non-split tooltip, we must
        // destroy it in order to get the SVG right. #13868.
        if (this.label) {
            const wasSplit = !this.label.hasClass('highcharts-label');
            if ((!doSplit && wasSplit) || (doSplit && !wasSplit)) {
                this.destroy();
            }
        }
        if (!this.label) {
            if (this.outside) {
                const chart = this.chart, chartStyle = chart.options.chart.style;
                /**
                 * Reference to the tooltip's container, when
                 * [Highcharts.Tooltip#outside] is set to true, otherwise
                 * it's undefined.
                 *
                 * @name Highcharts.Tooltip#container
                 * @type {Highcharts.HTMLDOMElement|undefined}
                 */
                this.container = container = Core_Globals.doc.createElement('div');
                container.className = ('highcharts-container ' +
                    'highcharts-tooltip-container ' +
                    (chart.renderTo.className.match(/(highcharts[a-zA-Z0-9-]+)\s?/gm) || [].join(' ')));
                // For picking up the specific palette
                container.dataset['highchartsChart'] = chart.index.toString();
                // We need to set pointerEvents = 'none' as otherwise it makes
                // the area under the tooltip non-hoverable even after the
                // tooltip disappears, #19035.
                css(container, {
                    position: 'absolute',
                    top: '1px',
                    pointerEvents: 'none',
                    zIndex: Math.max(options.style.zIndex || 0, (chartStyle?.zIndex || 0) + 3)
                });
                /**
                 * Reference to the tooltip's renderer, when
                 * [Highcharts.Tooltip#outside] is set to true, otherwise
                 * it's undefined.
                 *
                 * @name Highcharts.Tooltip#renderer
                 * @type {Highcharts.SVGRenderer|undefined}
                 */
                this.renderer = renderer = new SVG_SVGRenderer(container, 0, 0, chartStyle, void 0, void 0, renderer.styledMode);
            }
            // Create the label
            if (doSplit) {
                this.label = renderer.g('tooltip');
            }
            else {
                this.label = renderer
                    .label('', anchorX, anchorY, options.shape || 'callout', void 0, void 0, options.useHTML, void 0, 'tooltip')
                    .attr({
                    padding: options.padding,
                    r: options.borderRadius
                });
                if (!styledMode) {
                    this.label
                        .attr({
                        fill: options.backgroundColor,
                        'stroke-width': options.borderWidth ??
                            +!options.fixed
                    })
                        // #2301, #2657
                        .css(options.style)
                        .css({
                        pointerEvents: (options.style.pointerEvents ||
                            (this.shouldStickOnContact() ? 'auto' : 'none'))
                    });
                }
            }
            // Split tooltip use updateTooltipContainer to position the tooltip
            // container.
            if (tooltip.outside) {
                const label = this.label;
                [label.xSetter, label.ySetter].forEach((setter, i) => {
                    label[i ? 'ySetter' : 'xSetter'] = (value) => {
                        setter.call(label, tooltip.distance);
                        label[i ? 'y' : 'x'] = value;
                        if (container) {
                            container.style[i ? 'top' : 'left'] = `${value}px`;
                        }
                    };
                });
            }
            this.label
                .attr({ zIndex: 8 })
                .shadow(options.shadow ?? !options.fixed)
                .add();
        }
        if (container && !container.parentElement) {
            Core_Globals.doc.body.appendChild(container);
        }
        return this.label;
    }
    /**
     * Get the total area available area to place the tooltip
     *
     * @internal
     */
    getPlayingField() {
        const { body, documentElement } = Tooltip_doc, { chart, distance, outside } = this;
        return {
            width: outside ?
                // Subtract distance to prevent scrollbars
                Math.max(body.scrollWidth, documentElement.scrollWidth, body.offsetWidth, documentElement.offsetWidth, documentElement.clientWidth) - (2 * distance) - 2 :
                chart.chartWidth,
            height: outside ?
                Math.max(body.scrollHeight, documentElement.scrollHeight, body.offsetHeight, documentElement.offsetHeight, documentElement.clientHeight) :
                chart.chartHeight
        };
    }
    /**
     * Place the tooltip in a chart without spilling over and not covering the
     * point itself.
     *
     * @function Highcharts.Tooltip#getPosition
     *
     * @param {number} boxWidth
     *        Width of the tooltip box.
     *
     * @param {number} boxHeight
     *        Height of the tooltip box.
     *
     * @param {Highcharts.Point} point
     *        Tooltip related point.
     *
     * @return {Highcharts.PositionObject}
     *         Recommended position of the tooltip.
     */
    getPosition(boxWidth, boxHeight, point) {
        const { distance, chart, outside, pointer } = this, { inverted, plotLeft, plotTop, polar } = chart, { plotX = 0, plotY = 0 } = point, ret = {}, 
        // Don't use h if chart isn't inverted (#7242) ???
        h = (inverted && point.h) || 0, // #4117 ?
        { height: outerHeight, width: outerWidth } = this.getPlayingField(), chartPosition = pointer.getChartPosition(), scaleX = (val) => (val * chartPosition.scaleX), scaleY = (val) => (val * chartPosition.scaleY), 
        // Build parameter arrays for firstDimension()/secondDimension()
        buildDimensionArray = (dim) => {
            const isX = dim === 'x';
            return [
                dim, // Dimension - x or y
                isX ? outerWidth : outerHeight,
                isX ? boxWidth : boxHeight
            ].concat(outside ? [
                // If we are using tooltip.outside, we need to scale the
                // position to match scaling of the container in case there
                // is a transform/zoom on the container. #11329
                isX ? scaleX(boxWidth) : scaleY(boxHeight),
                isX ? chartPosition.left - distance +
                    scaleX(plotX + plotLeft) :
                    chartPosition.top - distance +
                        scaleY(plotY + plotTop),
                0,
                isX ? outerWidth : outerHeight
            ] : [
                // Not outside, no scaling is needed
                isX ? boxWidth : boxHeight,
                isX ? plotX + plotLeft : plotY + plotTop,
                isX ? plotLeft : plotTop,
                isX ? plotLeft + chart.plotWidth :
                    plotTop + chart.plotHeight
            ]);
        };
        let first = buildDimensionArray('y'), second = buildDimensionArray('x'), swapped;
        // Handle negative points or reversed axis (#13780)
        let flipped = !!point.negative;
        if (!polar &&
            chart.hoverSeries?.yAxis?.reversed) {
            flipped = !flipped;
        }
        // The far side is right or bottom
        const preferFarSide = !this.followPointer &&
            pick(point.ttBelow, polar ? false : !inverted === flipped), // #4984
        /*
         * Handle the preferred dimension. When the preferred dimension is
         * tooltip on top or bottom of the point, it will look for space
         * there.
         *
         * @internal
         */
        firstDimension = function (dim, outerSize, innerSize, scaledInnerSize, // #11329
        point, min, max) {
            const scaledDist = outside ?
                (dim === 'y' ? scaleY(distance) : scaleX(distance)) :
                distance, scaleDiff = (innerSize - scaledInnerSize) / 2, roomLeft = scaledInnerSize < point - distance, roomRight = point + distance + scaledInnerSize < outerSize, alignedLeft = point - scaledDist - innerSize + scaleDiff, alignedRight = point + scaledDist - scaleDiff;
            if (preferFarSide && roomRight) {
                ret[dim] = alignedRight;
            }
            else if (!preferFarSide && roomLeft) {
                ret[dim] = alignedLeft;
            }
            else if (roomLeft) {
                ret[dim] = Math.min(max - scaledInnerSize, alignedLeft - h < 0 ? alignedLeft : alignedLeft - h);
            }
            else if (roomRight) {
                ret[dim] = Math.max(min, alignedRight + h + innerSize > outerSize ?
                    alignedRight :
                    alignedRight + h);
            }
            else {
                ret[dim] = 0;
                return false;
            }
        }, 
        /*
         * Handle the secondary dimension. If the preferred dimension is
         * tooltip on top or bottom of the point, the second dimension is to
         * align the tooltip above the point, trying to align center but
         * allowing left or right align within the chart box.
         *
         * @internal
         */
        secondDimension = function (dim, outerSize, innerSize, scaledInnerSize, // #11329
        point) {
            // Too close to the edge, return false and swap dimensions
            if (point < distance || point > outerSize - distance) {
                return false;
            }
            // Align left/top
            if (point < innerSize / 2) {
                ret[dim] = 1;
                // Align right/bottom
            }
            else if (point > outerSize - scaledInnerSize / 2) {
                ret[dim] = outerSize - scaledInnerSize - 2;
                // Align center
            }
            else {
                ret[dim] = point - innerSize / 2;
            }
        }, 
        /*
         * Swap the dimensions
         */
        swap = function (count) {
            [first, second] = [second, first];
            swapped = count;
        }, run = () => {
            if (firstDimension.apply(0, first) !== false) {
                if (secondDimension.apply(0, second) === false &&
                    !swapped) {
                    swap(true);
                    run();
                }
            }
            else if (!swapped) {
                swap(true);
                run();
            }
            else {
                ret.x = ret.y = 0;
            }
        };
        // Under these conditions, prefer the tooltip on the side of the point
        if ((inverted && !polar) || this.len > 1) {
            swap();
        }
        run();
        if (outside) {
            ret.x -= chartPosition.left;
            ret.y -= chartPosition.top;
        }
        return ret;
    }
    /**
     * Place the tooltip when `position.fixed` is true. This is called both for
     * single tooltips, and for partial tooltips when `split`.
     *
     * @internal
     */
    getFixedPosition(boxWidth, boxHeight, point) {
        const series = point.series, { chart, options, split } = this, position = options.position, relativeToOption = position.relativeTo, noPane = options.shared || series?.yAxis?.isRadial &&
            (relativeToOption === 'pane' || !relativeToOption), relativeTo = noPane ? 'plotBox' : relativeToOption, bounds = relativeTo === 'chart' ?
            chart.renderer :
            chart[relativeTo] ||
                chart.getClipBox(series, true);
        return {
            x: bounds.x + (bounds.width - boxWidth) *
                getAlignFactor(position.align) +
                position.x,
            y: bounds.y + (bounds.height - boxHeight) *
                getAlignFactor(position.verticalAlign) +
                (!split && position.y || 0)
        };
    }
    /**
     * Hides the tooltip with a fade out animation.
     *
     * @function Highcharts.Tooltip#hide
     *
     * @param {number} [delay]
     *        The fade out in milliseconds. If no value is provided the value
     *        of the tooltip.hideDelay option is used. A value of 0 disables
     *        the fade out animation.
     */
    hide(delay) {
        const tooltip = this;
        // Disallow duplicate timers (#1728, #1766)
        clearTimeouts(this);
        delay = pick(delay, this.options.hideDelay);
        if (!this.isHidden) {
            this.hideTimer = syncTimeout(function () {
                const label = tooltip.getLabel();
                // If there is a delay, fade out with the default duration. If
                // the hideDelay is 0, we assume no animation is wanted, so we
                // pass 0 duration. #12994.
                tooltip.getLabel().animate({
                    opacity: 0
                }, {
                    duration: delay ? 150 : delay,
                    complete: () => {
                        // #3088, assuming we're only using this for tooltips
                        label.hide();
                        // Clear the container for outside tooltip (#18490)
                        if (tooltip.container) {
                            tooltip.container.remove();
                        }
                    }
                });
                tooltip.isHidden = true;
            }, delay);
        }
    }
    /**
     * Initialize tooltip.
     *
     * @internal
     * @function Highcharts.Tooltip#init
     *
     * @param {Highcharts.Chart} chart
     *        The chart instance.
     *
     * @param {Highcharts.TooltipOptions} options
     *        Tooltip options.
     */
    init(chart, options) {
        /**
         * Chart of the tooltip.
         *
         * @readonly
         * @name Highcharts.Tooltip#chart
         * @type {Highcharts.Chart}
         */
        this.chart = chart;
        /**
         * Used tooltip options.
         *
         * @readonly
         * @name Highcharts.Tooltip#options
         * @type {Highcharts.TooltipOptions}
         */
        this.options = options;
        /**
         * List of crosshairs.
         *
         * @internal
         * @readonly
         * @name Highcharts.Tooltip#crosshairs
         * @type {Array<null>}
         */
        this.crosshairs = [];
        /**
         * Tooltips are initially hidden.
         *
         * @internal
         * @readonly
         * @name Highcharts.Tooltip#isHidden
         * @type {boolean}
         */
        this.isHidden = true;
        /**
         * True, if the tooltip is split into one label per series, with the
         * header close to the axis.
         *
         * @readonly
         * @name Highcharts.Tooltip#split
         * @type {boolean|undefined}
         */
        this.split = options.split && !chart.inverted && !chart.polar;
        /**
         * When the tooltip is shared, the entire plot area will capture mouse
         * movement or touch events.
         *
         * @readonly
         * @name Highcharts.Tooltip#shared
         * @type {boolean|undefined}
         */
        this.shared = options.shared || this.split;
        /**
         * Whether to allow the tooltip to render outside the chart's SVG
         * element box. By default (false), the tooltip is rendered within the
         * chart's SVG element, which results in the tooltip being aligned
         * inside the chart area.
         *
         * @readonly
         * @name Highcharts.Tooltip#outside
         * @type {boolean}
         *
         * @todo
         * Split tooltip does not support outside in the first iteration. Should
         * not be too complicated to implement.
         */
        this.outside = pick(options.outside, Boolean(chart.scrollablePixelsX || chart.scrollablePixelsY));
    }
    shouldStickOnContact(pointerEvent) {
        return !!(!this.followPointer &&
            this.options.stickOnContact &&
            (!pointerEvent || this.pointer.inClass(pointerEvent.target, 'highcharts-tooltip')));
    }
    /**
     * Moves the tooltip with a soft animation to a new position.
     *
     * @internal
     * @function Highcharts.Tooltip#move
     *
     * @param {number} x
     *
     * @param {number} y
     *
     * @param {number} anchorX
     *
     * @param {number} anchorY
     */
    move(x, y, anchorX, anchorY) {
        const { followPointer, options } = this, animation = Tooltip_animObject(!followPointer &&
            !this.isHidden &&
            !options.fixed &&
            options.animation), skipAnchor = followPointer || (this.len || 0) > 1, attr = { x, y };
        if (!skipAnchor) {
            attr.anchorX = anchorX;
            attr.anchorY = anchorY;
        }
        else {
            // Clear anchor with NaN to prevent animation (#22295)
            attr.anchorX = attr.anchorY = NaN;
        }
        animation.step = () => this.drawTracker();
        this.getLabel().animate(attr, animation);
    }
    /**
     * Refresh the tooltip's text and position.
     *
     * @function Highcharts.Tooltip#refresh
     *
     * @param {Highcharts.Point|Array<Highcharts.Point>} pointOrPoints
     *        Either a point or an array of points.
     *
     * @param {Highcharts.PointerEventObject} [mouseEvent]
     *        Mouse event, that is responsible for the refresh and should be
     *        used for the tooltip update.
     */
    refresh(pointOrPoints, mouseEvent) {
        const tooltip = this, { chart, options, pointer, shared } = this, points = splat(pointOrPoints), point = points[0], formatString = options.format, formatter = options.formatter || tooltip.defaultFormatter, styledMode = chart.styledMode;
        let wasShared = tooltip.allowShared;
        if (!options.enabled || !point.series) { // #16820
            return;
        }
        clearTimeouts(this);
        // A switch saying if this specific tooltip configuration allows shared
        // or split modes
        tooltip.allowShared = !(!isArray(pointOrPoints) &&
            pointOrPoints.series &&
            pointOrPoints.series.noSharedTooltip);
        wasShared = wasShared && !tooltip.allowShared;
        // Get the reference point coordinates (pie charts use tooltipPos)
        tooltip.followPointer = (!tooltip.split && point.series.tooltipOptions.followPointer);
        const anchor = tooltip.getAnchor(pointOrPoints, mouseEvent), x = anchor[0], y = anchor[1];
        // Shared tooltip, array is sent over
        if (shared && tooltip.allowShared) {
            pointer.applyInactiveState(points);
            // Now set hover state for the chosen ones:
            points.forEach((item) => item.setState('hover'));
            point.points = points;
        }
        this.len = points.length; // #6128
        const text = isString(formatString) ?
            Tooltip_format(formatString, point, chart) :
            formatter.call(point, tooltip, point);
        // Reset the preliminary circular references
        point.points = void 0;
        // Register the current series
        const currentSeries = point.series;
        this.distance = pick(currentSeries.tooltipOptions.distance, 16);
        // Update the inner HTML
        if (text === false) {
            this.hide();
        }
        else {
            this.showTimer = syncTimeout(() => {
                // Update text
                if (tooltip.split && tooltip.allowShared) { // #13868
                    tooltip.renderSplit(text, points);
                }
                else {
                    let checkX = x;
                    let checkY = y;
                    if (mouseEvent && pointer.isDirectTouch) {
                        checkX = mouseEvent.chartX - chart.plotLeft;
                        checkY = mouseEvent.chartY - chart.plotTop;
                    }
                    // #11493, #13095
                    if (chart.polar ||
                        currentSeries.options.clip === false ||
                        points.some((p) => // #16004
                         pointer.isDirectTouch || // ##17929
                            p.series.shouldShowTooltip(checkX, checkY))) {
                        const label = tooltip.getLabel(wasShared && tooltip.tt || {});
                        // Prevent the tooltip from flowing over the chart box
                        // (#6659)
                        if (!options.style.width || styledMode) {
                            label.css({
                                // Subtract padding on both sides so the box
                                // stays within the available space (#24104)
                                width: (this.outside ?
                                    this.getPlayingField() :
                                    chart.spacingBox).width - 2 * options.padding + 'px'
                            });
                        }
                        label.attr({
                            // Add class before the label BBox calculation
                            // (#21035)
                            'class': tooltip.getClassName(point),
                            text: isArray(text) ?
                                text.join('') :
                                text
                        });
                        // When the length of the label has increased,
                        // immediately update the x position to prevent
                        // tooltip from flowing outside the viewport
                        // during animation (#21371)
                        if (this.outside) {
                            label.attr({
                                x: clamp(label.x || 0, 0, this.getPlayingField().width -
                                    (label.width || 0) -
                                    1)
                            });
                        }
                        if (!styledMode) {
                            label.attr({
                                stroke: (options.borderColor ||
                                    point.color ||
                                    currentSeries.color ||
                                    'var(--highcharts-neutral-color-60)')
                            });
                        }
                        tooltip.updatePosition({
                            plotX: x,
                            plotY: y,
                            negative: point.negative,
                            ttBelow: point.ttBelow,
                            series: currentSeries,
                            h: anchor[2] || 0
                        });
                    }
                    else {
                        tooltip.hide();
                        return;
                    }
                }
                // Show it
                if (tooltip.isHidden && tooltip.label) {
                    tooltip.label.attr({
                        opacity: 1
                    }).show();
                }
                tooltip.isHidden = false;
            }, tooltip.isHidden ? options.showDelay || 0 : 0);
        }
        fireEvent(this, 'refresh');
    }
    /**
     * Render the split tooltip. Loops over each point's text and adds
     * a label next to the point, then uses the distribute function to
     * find best non-overlapping positions.
     *
     * @internal
     * @function Highcharts.Tooltip#renderSplit
     *
     * @param {string|Array<(boolean|string)>} labels
     *
     * @param {Array<Highcharts.Point>} points
     */
    renderSplit(labels, points) {
        const tooltip = this;
        const { chart, chart: { chartWidth, chartHeight, plotHeight, plotLeft, plotTop, scrollablePixelsY = 0, scrollablePixelsX, styledMode }, distance, options, options: { fixed, position, positioner }, pointer } = tooltip;
        const { scrollLeft = 0, scrollTop = 0 } = chart.scrollablePlotArea?.scrollingContainer || {};
        // The area which the tooltip should be limited to. Limit to scrollable
        // plot area if enabled, otherwise limit to the chart container. If
        // outside is true it should be the whole viewport
        const bounds = (tooltip.outside &&
            typeof scrollablePixelsX !== 'number') ?
            Tooltip_doc.documentElement.getBoundingClientRect() : {
            left: scrollLeft,
            right: scrollLeft + chartWidth,
            top: scrollTop,
            bottom: scrollTop + chartHeight
        };
        const tooltipLabel = tooltip.getLabel();
        const ren = this.renderer || chart.renderer;
        const headerTop = Boolean(chart.xAxis[0]?.opposite);
        const { left: chartLeft, top: chartTop } = pointer.getChartPosition();
        const hasFixedPosition = positioner || fixed;
        let distributionBoxTop = plotTop + scrollTop;
        let headerHeight = 0;
        let adjustedPlotHeight = plotHeight - scrollablePixelsY;
        /**
         * Calculates the anchor position for the partial tooltip
         *
         * @internal
         * @param {Highcharts.Point} point The point related to the tooltip
         * @return {Object} Returns an object with anchorX and anchorY
         */
        function getAnchor(point) {
            const { isHeader, plotX = 0, plotY = 0, series } = point;
            let anchorX;
            let anchorY;
            if (isHeader) {
                // Set anchorX to plotX
                anchorX = Math.max(plotLeft + plotX, plotLeft);
                // Set anchorY to center of visible plot area.
                anchorY = plotTop + plotHeight / 2;
            }
            else {
                const { xAxis, yAxis } = series;
                // Set anchorX to plotX. Limit to within xAxis.
                anchorX = xAxis.pos + clamp(plotX, -distance, xAxis.len + distance);
                // Set anchorY, limit to the scrollable plot area
                if (series.shouldShowTooltip(0, yAxis.pos - plotTop + plotY, {
                    ignoreX: true
                })) {
                    anchorY = yAxis.pos + plotY;
                }
            }
            // Limit values to plot area
            anchorX = clamp(anchorX, bounds.left - distance, bounds.right + distance);
            return { anchorX, anchorY };
        }
        /**
         * Calculate the position of the partial tooltip
         * @internal
         */
        const defaultPositioner = function (boxWidth, boxHeight, point, ctx, anchor = [0, 0], alignedLeft = true) {
            let x, y;
            if (point.isHeader) {
                y = headerTop ? 0 : adjustedPlotHeight;
                x = clamp(anchor[0] - (boxWidth / 2), bounds.left, bounds.right - boxWidth - (tooltip.outside ? chartLeft : 0));
            }
            else if (fixed && point) {
                const pos = tooltip.getFixedPosition(boxWidth, boxHeight, point);
                x = pos.x;
                y = pos.y - distributionBoxTop;
            }
            else {
                y = anchor[1] - distributionBoxTop;
                x = alignedLeft ?
                    anchor[0] - boxWidth - distance :
                    anchor[0] + distance;
                x = clamp(x, alignedLeft ? x : bounds.left, bounds.right);
            }
            // NOTE: y is relative to distributionBoxTop
            return { x, y };
        };
        /**
         * Updates the attributes and styling of the partial tooltip. Creates a
         * new partial tooltip if it does not exists.
         *
         * @internal
         * @param {Highcharts.SVGElement|undefined} partialTooltip
         *  The partial tooltip to update
         * @param {Highcharts.Point} point
         *  The point related to the partial tooltip
         * @param {boolean|string} str The text for the partial tooltip
         * @return {Highcharts.SVGElement} Returns the updated partial tooltip
         */
        function updatePartialTooltip(partialTooltip, point, str) {
            let tt = partialTooltip;
            const { isHeader, series } = point, ttOptions = series.tooltipOptions || options, specificOptions = isHeader ?
                merge(ttOptions, ttOptions.header) :
                ttOptions;
            if (!tt) {
                const attribs = {
                    padding: ttOptions.padding,
                    r: ttOptions.borderRadius
                };
                if (!styledMode) {
                    attribs.fill = specificOptions.backgroundColor;
                    attribs['stroke-width'] = specificOptions.borderWidth ??
                        +!ttOptions.fixed;
                }
                tt = ren
                    .label('', 0, 0, specificOptions.shape || (fixed && !isHeader ? 'rect' : 'callout'), void 0, void 0, ttOptions.useHTML)
                    .addClass(tooltip.getClassName(point, true, isHeader))
                    .attr(attribs)
                    .add(tooltipLabel);
            }
            tt.isActive = true;
            // Apply styles before text to ensure correct font metrics on
            // first render. (#24293)
            if (!styledMode) {
                tt.css(specificOptions.style);
            }
            tt.attr({
                text: str
            });
            if (!styledMode) {
                tt.attr({
                    stroke: (specificOptions.borderColor ||
                        point.color ||
                        series.color ||
                        'var(--highcharts-neutral-color-80)')
                });
            }
            return tt;
        }
        // Graceful degradation for legacy formatters
        if (isString(labels)) {
            labels = [false, labels];
        }
        // Create the individual labels for header and points, ignore footer
        let boxes = labels.slice(0, points.length + 1).reduce(function (boxes, str, i) {
            if (str !== false && str !== '') {
                const point = (points[i - 1] ||
                    {
                        // Item 0 is the header. Instead of this, we could also
                        // use the crosshair label
                        isHeader: true,
                        plotX: points[0].plotX,
                        plotY: plotHeight,
                        series: {}
                    });
                const isHeader = point.isHeader;
                // Store the tooltip label reference on the series
                const owner = isHeader ? tooltip : point.series;
                const tt = owner.tt = updatePartialTooltip(owner.tt, point, str.toString());
                // Get X position now, so we can move all to the other side in
                // case of overflow
                const bBox = tt.getBBox();
                const boxWidth = bBox.width + tt.strokeWidth();
                if (isHeader) {
                    headerHeight = bBox.height + options.header.distance;
                    adjustedPlotHeight += headerHeight;
                    if (headerTop) {
                        distributionBoxTop -= headerHeight;
                    }
                }
                const { anchorX, anchorY } = getAnchor(point);
                if (typeof anchorY === 'number') {
                    const size = bBox.height + 1, boxPosition = (positioner || defaultPositioner).call(tooltip, boxWidth, size, point, tooltip, [anchorX, anchorY]);
                    boxes.push({
                        // 0-align to the top, 1-align to the bottom
                        align: hasFixedPosition ? 0 : void 0,
                        anchorX,
                        anchorY,
                        boxWidth,
                        point,
                        rank: pick(boxPosition.rank, isHeader ? 1 : 0),
                        size,
                        target: boxPosition.y,
                        tt,
                        x: boxPosition.x
                    });
                }
                else {
                    // Hide tooltips which anchorY is outside the visible plot
                    // area
                    tt.isActive = false;
                }
            }
            return boxes;
        }, []);
        // Realign the tooltips towards the right if there is not enough space
        // to the left and there is space to the right
        if (!hasFixedPosition && boxes.some((box) => {
            // Always realign if the beginning of a label is outside bounds
            const { outside } = tooltip;
            const boxStart = (outside ? chartLeft : 0) + box.anchorX;
            if (boxStart < bounds.left &&
                boxStart + box.boxWidth < bounds.right) {
                return true;
            }
            // Otherwise, check if there is more space available to the right
            return boxStart < (chartLeft - bounds.left) + box.boxWidth &&
                bounds.right - boxStart > boxStart;
        })) {
            boxes = boxes.map((box) => {
                const { x, y } = defaultPositioner.call(this, box.boxWidth, box.size, box.point, void 0, [box.anchorX, box.anchorY], false);
                return extend(box, {
                    target: y,
                    x
                });
            });
        }
        // Clean previous run (for missing points)
        tooltip.cleanSplit();
        // Distribute and put in place
        distribute(boxes, adjustedPlotHeight);
        const boxExtremes = {
            left: chartLeft,
            right: chartLeft
        };
        // Get the extremes from series tooltips
        boxes.forEach(function (box) {
            const { x, boxWidth, isHeader } = box;
            if (!isHeader) {
                if (tooltip.outside && chartLeft + x < boxExtremes.left) {
                    boxExtremes.left = chartLeft + x;
                }
                if (tooltip.outside &&
                    boxExtremes.left + boxWidth > boxExtremes.right) {
                    boxExtremes.right = chartLeft + x;
                }
            }
        });
        boxes.forEach(function (box) {
            const { x, anchorX, anchorY, pos, point: { isHeader } } = box;
            const attributes = {
                visibility: typeof pos === 'undefined' ? 'hidden' : 'inherit',
                x,
                /* NOTE: y should equal pos to be consistent with !split
                 * tooltip, but is currently relative to plotTop. Is left as is
                 * to avoid breaking change. Remove distributionBoxTop to make
                 * it consistent.
                 */
                y: (pos || 0) + distributionBoxTop + (fixed && position.y || 0),
                anchorX,
                anchorY
            };
            // Handle left-aligned tooltips overflowing the chart area
            if (tooltip.outside && x < anchorX) {
                const offset = chartLeft - boxExtremes.left;
                // Skip this if there is no overflow
                if (offset > 0) {
                    attributes.x = isHeader ?
                        (boxExtremes.right - boxExtremes.left) / 2 :
                        x + offset;
                    attributes.anchorX = anchorX + offset;
                }
            }
            // Put the label in place
            box.tt.attr(attributes);
        });
        /* If we have a separate tooltip container, then update the necessary
         * container properties.
         * Test that tooltip has its own container and renderer before executing
         * the operation.
         */
        const { container, outside, renderer } = tooltip;
        if (outside && container && renderer) {
            // Set container size to fit the bounds
            const { width, height, x, y } = tooltipLabel.getBBox();
            renderer.setSize(width + x, height + y, false);
            // Position the tooltip container to the chart container
            container.style.left = boxExtremes.left + 'px';
            container.style.top = chartTop + 'px';
        }
        // Workaround for #18927, artefacts left by the shadows of split
        // tooltips in Safari v16 (2023). Check again with later versions if we
        // can remove this.
        if (isSafari) {
            tooltipLabel.attr({
                // Force a redraw of the whole group by chaining the opacity
                // slightly
                opacity: tooltipLabel.opacity === 1 ? 0.999 : 1
            });
        }
    }
    /**
     * If the `stickOnContact` option is active, this will add a tracker shape.
     *
     * @internal
     * @function Highcharts.Tooltip#drawTracker
     */
    drawTracker() {
        const tooltip = this;
        if (!this.shouldStickOnContact()) {
            if (tooltip.tracker) {
                tooltip.tracker = tooltip.tracker.destroy();
            }
            return;
        }
        const chart = tooltip.chart;
        const label = tooltip.label;
        const points = tooltip.shared ? chart.hoverPoints : chart.hoverPoint;
        if (!label || !points) {
            return;
        }
        const box = {
            x: 0,
            y: 0,
            width: 0,
            height: 0
        };
        // Combine anchor and tooltip
        const anchorPos = this.getAnchor(points);
        const labelBBox = label.getBBox();
        anchorPos[0] += chart.plotLeft - (label.translateX || 0);
        anchorPos[1] += chart.plotTop - (label.translateY || 0);
        // When the mouse pointer is between the anchor point and the label,
        // the label should stick.
        box.x = Math.min(0, anchorPos[0]);
        box.y = Math.min(0, anchorPos[1]);
        box.width = (anchorPos[0] < 0 ?
            Math.max(Math.abs(anchorPos[0]), labelBBox.width - anchorPos[0]) :
            Math.max(Math.abs(anchorPos[0]), labelBBox.width));
        box.height = (anchorPos[1] < 0 ?
            Math.max(Math.abs(anchorPos[1]), labelBBox.height - Math.abs(anchorPos[1])) :
            Math.max(Math.abs(anchorPos[1]), labelBBox.height));
        if (tooltip.tracker) {
            tooltip.tracker.attr(box);
        }
        else {
            tooltip.tracker = label.renderer
                .rect(box)
                .addClass('highcharts-tracker')
                .add(label);
            // For a rapid move going outside of the elements keeping the
            // tooltip visible, cancel the hide (#23512).
            addEvent(tooltip.tracker.element, 'mouseenter', () => clearTimeouts(tooltip));
            if (!chart.styledMode) {
                tooltip.tracker.attr({
                    fill: 'rgba(0,0,0,0)'
                });
            }
        }
    }
    /** @internal */
    styledModeFormat(formatString) {
        return formatString
            .replace('style="font-size: 0.8em"', 'class="highcharts-header"')
            .replace(/style="color:{(point|series)\.color}"/g, 'class="highcharts-color-{$1.colorIndex} ' +
            '{series.options.className} ' +
            '{point.options.className}"');
    }
    /**
     * Format the footer/header of the tooltip
     * #3397: abstraction to enable formatting of footer and header
     *
     * @internal
     * @function Highcharts.Tooltip#headerFooterFormatter
     */
    headerFooterFormatter(point, isFooter) {
        const series = point.series, tooltipOptions = series.tooltipOptions, xAxis = series.xAxis, dateTime = xAxis?.dateTime, e = {
            isFooter,
            point
        };
        let xDateFormat = tooltipOptions.xDateFormat || '', formatString = tooltipOptions[isFooter ? 'footerFormat' : 'headerFormat'];
        fireEvent(this, 'headerFormatter', e, function (e) {
            // Guess the best date format based on the closest point distance
            // (#568, #3418)
            if (dateTime && !xDateFormat && isNumber(point.key)) {
                xDateFormat = dateTime.getXDateFormat(point.key, tooltipOptions.dateTimeLabelFormats);
            }
            // Insert the footer date format if any
            if (dateTime && xDateFormat) {
                if (isObject(xDateFormat)) {
                    const format = xDateFormat;
                    dateFormats[0] = (timestamp) => series.chart.time.dateFormat(format, timestamp);
                    xDateFormat = '%0';
                }
                (point.tooltipDateKeys || ['key']).forEach((key) => {
                    formatString = formatString.replace(new RegExp('point\\.' + key + '([ \\)}])'), `(point.${key}:${xDateFormat})$1`);
                });
            }
            // Replace default header style with class name
            if (series.chart.styledMode) {
                formatString = this.styledModeFormat(formatString);
            }
            e.text = Tooltip_format(formatString, point, this.chart);
        });
        return e.text || '';
    }
    /**
     * Updates the tooltip with the provided tooltip options.
     *
     * @function Highcharts.Tooltip#update
     *
     * @param {Highcharts.TooltipOptions} options
     *        The tooltip options to update.
     */
    update(options) {
        this.destroy();
        this.init(this.chart, merge(true, this.options, options));
    }
    /**
     * Find the new position and perform the move
     *
     * @internal
     * @function Highcharts.Tooltip#updatePosition
     *
     * @param {Highcharts.Point} point The point object.
     */
    updatePosition(point) {
        const { chart, container, distance, options, pointer, renderer } = this, label = this.getLabel(), { height = 0, width = 0 } = label, { fixed, positioner } = options, 
        // Needed for outside: true (#11688)
        { left, top, scaleX, scaleY } = pointer.getChartPosition(), pos = (positioner ||
            (fixed && this.getFixedPosition) ||
            this.getPosition).call(this, width, height, point, this), doc = Core_Globals.doc;
        let anchorX = (point.plotX || 0) + chart.plotLeft, anchorY = (point.plotY || 0) + chart.plotTop, pad;
        // Set the renderer size dynamically to prevent document size to change.
        // Renderer only exists when tooltip is outside.
        if (renderer && container) {
            pos.x += left;
            pos.y += top;
            // Scroll offset is only needed for custom/fixed positions.
            // Default getPosition already returns coordinates in the tooltip's
            // expected coordinate space.
            if (positioner || fixed) {
                const { scrollLeft = 0, scrollTop = 0 } = chart
                    .scrollablePlotArea?.scrollingContainer || {};
                pos.x += scrollLeft;
                pos.y += scrollTop;
            }
            // Pad it by the border width and distance. Add 2 to make room for
            // the default shadow (#19314).
            pad = (options.borderWidth ?? +!fixed) + 2 * distance + 2;
            renderer.setSize(
            // Clamp width to keep tooltip in viewport (#21698)
            // and subtract one since tooltip container has 'left: 1px;'
            clamp(width + pad, 0, doc.documentElement.clientWidth) - 1, height + pad, false);
            // Anchor and tooltip container need scaling if chart container has
            // scale transform/css zoom. #11329.
            if (scaleX !== 1 || scaleY !== 1) {
                css(container, {
                    transform: `scale(${scaleX}, ${scaleY})`
                });
                anchorX *= scaleX;
                anchorY *= scaleY;
            }
            anchorX += left - pos.x;
            anchorY += top - pos.y;
        }
        // Do the move
        this.move(Math.round(pos.x), Math.round(pos.y || 0), // Can be undefined (#3977)
        anchorX, anchorY);
    }
}
/* *
 *
 *  Class namespace
 *
 * */
(function (Tooltip) {
    /* *
     *
     *  Declarations
     *
     * */
    /* *
     *
     *  Functions
     *
     * */
    /** @internal */
    function compose(PointerClass) {
        if (pushUnique(Tooltip_composed, 'Core.Tooltip')) {
            addEvent(PointerClass, 'afterInit', function () {
                const chart = this.chart;
                if (chart.options.tooltip) {
                    /**
                     * Tooltip object for points of series.
                     *
                     * @name Highcharts.Chart#tooltip
                     * @type {Highcharts.Tooltip}
                     */
                    chart.tooltip = new Tooltip(chart, chart.options.tooltip, this);
                }
            });
        }
    }
    Tooltip.compose = compose;
})(Tooltip || (Tooltip = {}));
/* *
 *
 *  Default export
 *
 * */
/* harmony default export */ const Core_Tooltip = (Tooltip);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * Callback function to format the text of the tooltip from scratch.
 *
 * In case of single or shared tooltips, a string should be returned. In case
 * of split tooltips, it should return an array where the first item is the
 * header, and subsequent items are mapped to the points. Return `false` to
 * disable tooltip for a specific point on series.
 *
 * @callback Highcharts.TooltipFormatterCallbackFunction
 *
 * @param {Highcharts.Point} this
 * The formatter's context is the hovered `Point` instance. In case of shared or
 * split tooltips, all points are available in `this.points`.
 *
 * @param {Highcharts.Tooltip} tooltip
 * The tooltip instance
 *
 * @param {Highcharts.Point} [ctx]
 * Since v12.6.0, the point context passed as an extra argument for arrow
 * functions.
 *
 * @return {false|string|Array<(string|null|undefined)>|null|undefined}
 * Formatted text or false
 */
/**
 * A callback function to place the tooltip in a specific position.
 *
 * @callback Highcharts.TooltipPositionerCallbackFunction
 *
 * @param {Highcharts.Tooltip} this
 * Tooltip context of the callback.
 *
 * @param {number} labelWidth
 * Width of the tooltip.
 *
 * @param {number} labelHeight
 * Height of the tooltip.
 *
 * @param {Highcharts.TooltipPositionerPointObject} point
 * Point information for positioning a tooltip.
 *
 * @param {Highcharts.Tooltip} [ctx]
 * Since v12.6.0, the tooltip context passed as an extra argument for arrow
 * functions.
 *
 * @return {Highcharts.PositionObject}
 * New position for the tooltip.
 */
/**
 * Point information for positioning a tooltip.
 *
 * @interface Highcharts.TooltipPositionerPointObject
 * @extends Highcharts.Point
 */ /**
* If `tooltip.split` option is enabled and positioner is called for each of the
* boxes separately, this property indicates the call on the xAxis header, which
* is not a point itself.
* @name Highcharts.TooltipPositionerPointObject#isHeader
* @type {boolean}
*/ /**
* The reference point relative to the plot area. Add chart.plotLeft to get the
* full coordinates.
* @name Highcharts.TooltipPositionerPointObject#plotX
* @type {number}
*/ /**
* The reference point relative to the plot area. Add chart.plotTop to get the
* full coordinates.
* @name Highcharts.TooltipPositionerPointObject#plotY
* @type {number}
*/
/**
 * @typedef {"callout"|"circle"|"rect"} Highcharts.TooltipShapeValue
 */
''; // Keeps doclets above in JS file

;// ./code/es-modules/Core/Series/Point.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */



const { animObject: Point_animObject } = AnimationUtilities;

const { defaultOptions: Point_defaultOptions } = Defaults;

const { format: Point_format } = Core_Templating;


/* eslint-disable no-invalid-this, valid-jsdoc */
/* *
 *
 *  Class
 *
 * */
/**
 * The Point object. The point objects are generated from the `series.data`
 * configuration objects or raw numbers. They can be accessed from the
 * `Series.points` array. Other ways to instantiate points are through {@link
 * Highcharts.Series#addPoint} or {@link Highcharts.Series#setData}.
 *
 * @class
 * @name Highcharts.Point
 *
 * @param {Highcharts.Series} series
 *        The series object containing this point.
 *
 * @param {Highcharts.PointOptionsType} options
 *        The data in either number, array or object format.
 *
 * @param {number} [x]
 *        Optionally, the X value of the point.
 *
 * @emits Highcharts.Point#event:afterInit
 */
class Point {
    /* *
     *
     *  Constructor
     *
     * */
    constructor(series, options, x) {
        /** @internal */
        this.formatPrefix = 'point';
        /**
         * For certain series types, like pie charts, where individual points can
         * be shown or hidden.
         *
         * @name Highcharts.Point#visible
         * @type {boolean}
         * @default true
         */
        this.visible = true;
        // For tooltip and data label formatting
        this.point = this;
        this.series = series;
        this.applyOptions(options, x);
        // Add a unique ID to the point if none is assigned
        this.id ?? (this.id = uniqueKey());
        this.resolveColor();
        this.dataLabelOnNull ?? (this.dataLabelOnNull = series.options.nullInteraction);
        series.chart.pointCount++;
        // Set point properties for convenient access in tooltip and data labels
        this.category = series.xAxis?.categories?.[this.x] ?? this.x;
        this.key = this.name ?? this.category;
        fireEvent(this, 'afterInit');
    }
    /* *
     *
     *  API JSDoc doclet copies for uninitialized properties
     *
     * */
    /**
     * For categorized axes this property holds the category name for the
     * point. For other axes it holds the X value.
     *
     * @name Highcharts.Point#category
     * @type {number|string}
     */
    /**
     * The point's current color.
     *
     * @name Highcharts.Point#color
     * @type {Highcharts.ColorType|undefined}
     */
    /**
     * The point's current color index, used in styled mode instead of
     * `color`. The color index is inserted in class names used for styling.
     *
     * @name Highcharts.Point#colorIndex
     * @type {number|undefined}
     */
    /**
     * SVG graphic representing the point in the chart. In some cases it may be
     * a hidden graphic to improve accessibility.
     *
     * Typically this is a simple shape, like a `rect` for column charts or
     * `path` for line markers, but for some complex series types like boxplot
     * or 3D charts, the graphic may be a `g` element containing other shapes.
     * The graphic is generated the first time {@link Series#drawPoints} runs,
     * and updated and moved on subsequent runs.
     *
     * @see Highcharts.Point#graphics
     *
     * @name Highcharts.Point#graphic
     * @type {Highcharts.SVGElement|undefined}
     */
    /**
     * Array for multiple SVG graphics representing the point in the
     * chart. Only used in cases where the point can not be represented
     * by a single graphic.
     *
     * @see Highcharts.Point#graphic
     *
     * @name Highcharts.Point#graphics
     * @type {Array<Highcharts.SVGElement>|undefined}
     */
    /**
     * The point's name if it is defined, or its category in case of a category,
     * otherwise the x value. Convenient for tooltip and data label formatting.
     *
     * @name Highcharts.Point#key
     * @type {number|string}
     */
    /**
     * The name of the point. The name can be given as the first position of the
     * point configuration array, or as a `name` property in the configuration:
     *
     * @example
     * // Array config
     * data: [
     *     ['John', 1],
     *     ['Jane', 2]
     * ]
     *
     * // Object config
     * data: [{
     *     name: 'John',
     *     y: 1
     * }, {
     *     name: 'Jane',
     *     y: 2
     * }]
     *
     * @name Highcharts.Point#name
     * @type {string}
     */
    /**
     * The point's options as applied in the initial configuration, or
     * extended through `Point.update`.
     *
     * In TypeScript you have to extend `PointOptionsObject` via an
     * additional interface to allow custom data options:
     *
     * ```
     * declare interface PointOptionsObject {
     *     customProperty: string;
     * }
     * ```
     *
     * @name Highcharts.Point#options
     * @type {Highcharts.PointOptionsObject}
     */
    /**
     * The percentage for points in a stacked series, pies or gauges.
     *
     * @name Highcharts.Point#percentage
     * @type {number|undefined}
     */
    /**
     * Array of all hovered points when using shared tooltips.
     *
     * @name Highcharts.Point#points
     * @type {Array<Highcharts.Point>|undefined}
     */
    /**
     * Whether the point is selected or not.
     *
     * @see Point#select
     * @see Chart#getSelectedPoints
     *
     * @name Highcharts.Point#selected
     * @type {boolean}
     */
    /**
     * The series object associated with the point.
     *
     * @name Highcharts.Point#series
     * @type {Highcharts.Series}
     */
    /**
     * The attributes of the rendered SVG shape like in `column` or `pie`
     * series.
     *
     * @readonly
     * @name Highcharts.Point#shapeArgs
     * @type {Readonly<Highcharts.SVGAttributes>|undefined}
     */
    /**
     * Defines the tooltip's position for a data point in a chart. It is an
     * array of numbers representing the coordinates for the tooltip's
     * placement, allowing for precise control over its location.
     *
     * @readonly
     * @name Highcharts.Point#tooltipPos
     * @type {Readonly<Array<number>>|undefined}
     */
    /**
     * The total of values in either a stack for stacked series, or a pie in a
     * pie series.
     *
     * @name Highcharts.Point#total
     * @type {number|undefined}
     */
    /**
     * The x value of the point.
     *
     * @name Highcharts.Point#x
     * @type {number}
     */
    /**
     * The y value of the point.
     *
     * @name Highcharts.Point#y
     * @type {number|undefined}
     */
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Apply the options containing the x and y data and possible some extra
     * properties. Called on point init or from point.update.
     *
     * @internal
     * @function Highcharts.Point#applyOptions
     *
     * @param {Highcharts.PointOptionsType} options
     *        The point options as defined in series.data.
     *
     * @param {number} [x]
     *        Optionally, the x value.
     *
     * @param {boolean} [isMock]
     *        If true, the point is not a real instance, but a mock object
     *        created for handling data. Avoid some logic.
     *
     * @return {Highcharts.Point}
     *         The Point instance.
     */
    applyOptions(options, x, isMock) {
        const point = this, series = point.series, pointValKey = series.options.pointValKey || series.pointValKey;
        options = Point.prototype.optionsToObject.call(this, options);
        // Copy options directly to point
        extend(point, options);
        // If we're updating, extend or merge existing options
        if (point.options) {
            // If we're not allowed to mutate, merge new data into existing and
            // return a copy, because `point.options` is a reference to
            // `options.data[i]`
            point.options = series.chart.options.chart.allowMutatingData ?
                extend(point.options, options) :
                merge(point.options, options);
        }
        else {
            point.options = options;
        }
        // For higher dimension series types. For instance, for ranges, point.y
        // is mapped to point.low.
        if (pointValKey) {
            point.y = Point.prototype.getNestedProperty.call(point, pointValKey);
        }
        if (isNumber(x)) {
            point.x = x;
        }
        if (!isMock) {
            // The point is initially selected by options (#5777)
            if (point.selected) {
                point.state = 'select';
            }
            // Since options are copied into the Point instance, some accidental
            // options must be shielded (#5681)
            if (options.group) {
                delete point.group;
            }
            if (options.dataLabels) {
                delete point.dataLabels;
            }
            point.isNull = point.isValid && !point.isValid();
            // #9233, #10874
            point.formatPrefix = point.isNull ? 'null' : 'point';
        }
        return point;
    }
    /**
     * Get the origin position for entrance animation of new points. Modifies
     * the given x and y based on the chart orientation and shape dimensions.
     * Adds an opacity of 0 to make the point fade in.
     *
     * To disable entrance animation, return an empty object.
     *
     * @param {Highcharts.PositionObject} position
     *      The initial x and y position in terms of plot area coordinates.
     * @param {Highcharts.SVGAttributes} shape
     *      The shape arguments, containing width and height and more.
     * @return {Highcharts.SVGAttributes}
     *      The modified attributes with x and y adjusted for the shape.
     *
     * @internal
     * @function Highcharts.Point#getOrigin
     */
    getOrigin({ x = 0, y = 0 }, shape = {}) {
        const { graphic, series } = this;
        // The chart is inverted, but the marker group is not, like a scatter
        // series in an inverted chart.
        if (series.chart.inverted &&
            graphic?.parentGroup &&
            !graphic?.parentGroup?.rotation) {
            const pos = this.pos(false, x, y);
            if (pos) {
                x = pos[0];
                y = pos[1];
            }
        }
        x -= (shape.width || 0) / 2;
        y -= (shape.height || 0) / 2;
        const attribs = { x };
        // To avoid having to deal with stacking, column height etc, we only set
        // y for non-column series. Range series (having this.plotHigh) also
        // have their own logic, with two markers per point.
        if (!series.is('column') && !this.plotHigh) {
            attribs.y = y;
        }
        return attribs;
    }
    /**
     * Destroy a point to clear memory. Its reference still stays in
     * `series.data`.
     *
     * @internal
     * @function Highcharts.Point#destroy
     */
    destroy() {
        if (!this.condemned) {
            const point = this, series = point.series, chart = series.chart, hoverPoints = chart.hoverPoints, globalAnimation = point.series.chart.renderer.globalAnimation, { duration } = Point_animObject(globalAnimation);
            /**
             * Allow to call after animation.
             * @internal
             */
            const destroyPoint = () => {
                // Remove all events and elements
                if (point.graphic ||
                    point.graphics ||
                    point.dataLabel ||
                    point.dataLabels) {
                    removeEvent(point);
                    point.destroyElements();
                }
                for (const prop in point) { // eslint-disable-line guard-for-in
                    delete point[prop];
                }
            };
            if (point.legendItem) {
                // Pies have legend items
                chart.legend.destroyItem(point);
            }
            if (hoverPoints) {
                point.setState();
                erase(hoverPoints, point);
                if (!hoverPoints.length) {
                    chart.hoverPoints = void 0;
                }
            }
            if (point === chart.hoverPoint) {
                point.onMouseOut();
            }
            // Remove properties after animation
            if (duration && series.condemnedPoints) {
                series.condemnedPoints.push(this);
                this.graphic?.addClass('highcharts-point-condemned');
                setTimeout(destroyPoint, duration);
            }
            else {
                destroyPoint();
            }
            chart.pointCount--;
        }
        this.condemned = true;
    }
    /**
     * Destroy SVG elements associated with the point.
     *
     * @internal
     * @function Highcharts.Point#destroyElements
     *
     * @param {Highcharts.Dictionary<number>} [kinds]
     * Kinds of elements to destroy
     */
    destroyElements(kinds = { graphic: 1, dataLabel: 1 }) {
        const point = this, props = [];
        let prop, i;
        if (kinds.graphic) {
            props.push('graphic', 'connector');
        }
        if (kinds.dataLabel) {
            props.push('dataLabel', 'dataLabelPath', 'dataLabelUpper');
        }
        i = props.length;
        while (i--) {
            prop = props[i];
            if (point[prop]) {
                point[prop] = point[prop].destroy();
            }
        }
        ['graphic', 'dataLabel'].forEach((prop) => {
            const plural = `${prop}s`;
            if (kinds[prop] && point[plural]) {
                point[plural].forEach((item) => {
                    if (item?.element) {
                        item.destroy();
                    }
                });
                delete point[plural];
            }
        });
    }
    /**
     * Fire an event on the Point object.
     *
     * @internal
     * @function Highcharts.Point#firePointEvent
     *
     * @param {string} eventType
     *        Type of the event.
     *
     * @param {Highcharts.Dictionary<any>|Event} [eventArgs]
     *        Additional event arguments.
     *
     * @param {Highcharts.EventCallbackFunction<Highcharts.Point>|Function} [defaultFunction]
     *        Default event handler.
     *
     * @emits Highcharts.Point#event:*
     */
    firePointEvent(eventType, eventArgs, defaultFunction) {
        const point = this, series = this.series, seriesOptions = series.options;
        // Load event handlers on demand to save time on mouseover/out
        point.manageEvent(eventType);
        // Add default handler if in selection mode
        if (eventType === 'click' && seriesOptions.allowPointSelect) {
            defaultFunction = function (event) {
                // Control key is for Windows, meta (= Cmd key) for Mac, Shift
                // for Opera.
                if (!point.condemned && point.select) { // #2911, #19075
                    point.select(null, event.ctrlKey || event.metaKey || event.shiftKey);
                }
            };
        }
        fireEvent(point, eventType, eventArgs, defaultFunction);
    }
    /**
     * Get the CSS class names for individual points. Used internally where the
     * returned value is set on every point.
     *
     * @function Highcharts.Point#getClassName
     *
     * @return {string}
     *         The class names.
     */
    getClassName() {
        const point = this;
        return 'highcharts-point' +
            (point.selected ? ' highcharts-point-select' : '') +
            (point.negative && point.series.options.negativeColor !== false ?
                ' highcharts-negative' : '') +
            (point.isNull ? ' highcharts-null-point' : '') +
            (typeof point.colorIndex !== 'undefined' ?
                ' highcharts-color-' + point.colorIndex : '') +
            (point.options.className ? ' ' + point.options.className : '') +
            (point.zone?.className ?
                ' ' + point.zone.className.replace('highcharts-negative', '') :
                '');
    }
    /**
     * Returns the value of the point property for a given value.
     * @internal
     */
    getNestedProperty(key) {
        if (!key) {
            return;
        }
        if (key.indexOf('custom.') === 0) {
            return getNestedProperty(key, this.options);
        }
        return this[key];
    }
    /**
     * In a series with `zones`, return the zone that the point belongs to.
     *
     * @function Highcharts.Point#getZone
     *
     * @return {Highcharts.SeriesZonesOptionsObject}
     *         The zone item.
     */
    getZone() {
        const series = this.series, zones = series.zones, zoneAxis = series.zoneAxis || 'y';
        let zone, i = 0;
        zone = zones[i];
        while (this[zoneAxis] >= zone.value) {
            zone = zones[++i];
        }
        // For resetting or reusing the point (#8100)
        if (!this.nonZonedColor) {
            this.nonZonedColor = this.color;
        }
        if (zone?.color && !this.options.color) {
            this.color = zone.color;
        }
        else {
            this.color = this.nonZonedColor;
        }
        return zone;
    }
    /**
     * Utility to check if point has new shape type. Used in column series and
     * all others that are based on column series.
     * @internal
     */
    hasNewShapeType() {
        const point = this;
        const oldShapeType = point.graphic &&
            (point.graphic.symbolName || point.graphic.element.nodeName);
        return oldShapeType !== this.shapeType;
    }
    /**
     * Determine if point is valid.
     *
     * @internal
     * @function Highcharts.Point#isValid
     */
    isValid() {
        return ((isNumber(this.x) ||
            this.x instanceof Date) &&
            isNumber(this.y));
    }
    /**
     * Transform number or array configs into objects. Also called for object
     * configs. Used internally to unify the different configuration formats for
     * points. For example, a simple number `10` in a line series will be
     * transformed to `{ y: 10 }`, and an array config like `[1, 10]` in a
     * scatter series will be transformed to `{ x: 1, y: 10 }`.
     *
     * @function Highcharts.Point#optionsToObject
     *
     * @param {Highcharts.PointOptionsType} options
     * Series data options.
     *
     * @return {Highcharts.Dictionary<*>}
     * Transformed point options.
     */
    optionsToObject(options) {
        const series = this.series, keys = series.options.keys, pointArrayMap = keys || series.pointArrayMap || ['y'], valueCount = pointArrayMap.length;
        let ret = {}, firstItemType, i = 0, j = 0;
        if (isNumber(options) || options === null) {
            ret[pointArrayMap[0]] = options;
        }
        else if (isArray(options)) {
            // With leading x value
            if (!keys && options.length > valueCount) {
                firstItemType = typeof options[0];
                if (firstItemType === 'string') {
                    if (series.xAxis?.dateTime) {
                        ret.x = series.chart.time.parse(options[0]);
                    }
                    else {
                        ret[series.tupleKey || 'name'] = options[0];
                    }
                }
                else if (firstItemType === 'number') {
                    ret.x = options[0];
                }
                i++;
            }
            while (j < valueCount) {
                // Skip undefined positions for keys
                if (!keys || typeof options[i] !== 'undefined') {
                    ret[pointArrayMap[j]] = options[i];
                }
                i++;
                j++;
            }
        }
        else if (typeof options === 'object') {
            ret = options;
            // This is the fastest way to detect if there are individual point
            // dataLabels that need to be considered in drawDataLabels. These
            // can only occur in object configs.
            if (options.dataLabels) {
                // Override the prototype function to always return true,
                // regardless of whether data labels are enabled series-wide
                series.hasDataLabels = () => true;
            }
            // Same approach as above for markers
            if (options.marker) {
                series._hasPointMarkers = true;
            }
        }
        // Handle nested keys, e.g. ['color.pattern.image'], but only if we're
        // using keys or `dataTable`
        if (keys || !series.options.data) {
            Object.keys(ret).forEach((key) => {
                if (key.indexOf('.') > 0) {
                    Point.prototype.setNestedProperty(ret, ret[key], key);
                    // Delete the literal nested key
                    delete ret[key];
                }
            });
        }
        return ret;
    }
    /**
     * Get the pixel position of the point relative to the plot area.
     *
     * @function Highcharts.Point#pos
     *
     * @sample highcharts/point/position
     *         Get point's position in pixels.
     *
     * @param {boolean} chartCoordinates
     * If true, the returned position is relative to the full chart area.
     * If false, it is relative to the plot area determined by the axes.
     *
     * @param {number|undefined} plotX
     * A custom plot x position to be computed. Used internally for getting the
     * starting point of an animation.
     *
     * @param {number|undefined} plotY
     * A custom plot y position to be computed. Used internally for getting the
     * starting point of an animation, and for some series types that have
     * multiple `y` positions, like area range (low and high values).
     *
     * @return {Array<number>|undefined}
     * Coordinates of the point if the point exists.
     */
    pos(chartCoordinates, plotX = this.plotX, plotY = this.plotY) {
        const { series } = this, { chart, xAxis, yAxis } = series || {};
        let posX = 0, posY = 0;
        if (chart && isNumber(plotX) && isNumber(plotY)) {
            if (chartCoordinates) {
                posX = xAxis ? xAxis.pos : chart.plotLeft;
                posY = yAxis ? yAxis.pos : chart.plotTop;
            }
            return chart.inverted && xAxis && yAxis ?
                [yAxis.len - plotY + posY, xAxis.len - plotX + posX] :
                [plotX + posX, plotY + posY];
        }
    }
    /**
     * Resolve the color of a point.
     *
     * @internal
     * @function Highcharts.Point#resolveColor
     */
    resolveColor() {
        const { options, series } = this, chart = series.chart, optionsChart = chart.options.chart, styledMode = chart.styledMode;
        let color, colorCount = optionsChart.colorCount, colorIndex;
        // Remove points nonZonedColor for later recalculation
        delete this.nonZonedColor;
        if (series.options.colorByPoint) {
            if (!styledMode) {
                const colors = series.options.colors ||
                    chart.options.colors;
                color = colors?.[series.colorCounter];
                colorCount = colors?.length;
            }
            colorIndex = series.colorCounter;
            series.colorCounter++;
            // Loop back to zero
            if (series.colorCounter === colorCount) {
                series.colorCounter = 0;
            }
        }
        else {
            if (!styledMode) {
                color = series.color;
            }
            colorIndex = series.colorIndex;
        }
        this.colorIndex = options.colorIndex ?? colorIndex;
        this.color = options.color ?? color;
    }
    /**
     * Set a value in an object, on the property defined by key. The key
     * supports nested properties using dot notation. The function modifies the
     * input object and does not make a copy.
     *
     * @function Highcharts.Point#setNestedProperty<T>
     *
     * @param {T} object
     *        The object to set the value on.
     *
     * @param {*} value
     *        The value to set.
     *
     * @param {string} key
     *        Key to the property to set.
     *
     * @return {T}
     *         The modified object.
     */
    setNestedProperty(object, value, key) {
        const nestedKeys = key.split('.');
        nestedKeys.reduce(function (result, key, i, arr) {
            const isLastKey = arr.length - 1 === i;
            result[key] = (isLastKey ?
                value :
                isObject(result[key], true) ?
                    result[key] :
                    {});
            return result[key];
        }, object);
        return object;
    }
    /** @internal */
    shouldDraw() {
        return !this.isNull;
    }
    /**
     * Extendable method for formatting each point's tooltip line.
     *
     * @function Highcharts.Point#tooltipFormatter
     *
     * @param {string} pointFormat
     *        The point format.
     *
     * @return {string}
     *         A string to be concatenated in to the common tooltip text.
     */
    tooltipFormatter(pointFormat) {
        // Insert options for valueDecimals, valuePrefix, and valueSuffix
        const { chart, pointArrayMap = ['y'], tooltipOptions } = this.series, { valueDecimals = '', valuePrefix = '', valueSuffix = '' } = tooltipOptions;
        // Replace default point style with class name
        if (chart.styledMode) {
            pointFormat = chart.tooltip?.styledModeFormat(pointFormat) ||
                pointFormat;
        }
        // Loop over the point array map and replace unformatted values with
        // sprintf formatting markup
        pointArrayMap.forEach((key) => {
            key = '{point.' + key; // Without the closing bracket
            if (valuePrefix || valueSuffix) {
                pointFormat = pointFormat.replace(RegExp(key + '}', 'g'), valuePrefix + key + '}' + valueSuffix);
            }
            pointFormat = pointFormat.replace(RegExp(key + '}', 'g'), key + ':,.' + valueDecimals + 'f}');
        });
        return Point_format(pointFormat, this, chart);
    }
    /**
     * Update point with new options (typically x/y data) and optionally redraw
     * the series.
     *
     * @sample highcharts/members/point-update-column/
     *         Update column value
     * @sample highcharts/members/point-update-pie/
     *         Update pie slice
     * @sample maps/members/point-update/
     *         Update map area value in Highmaps
     *
     * @function Highcharts.Point#update
     *
     * @param {Highcharts.PointOptionsType} options
     *        The point options. Point options are handled as described under
     *        the `series.type.data` item for each series type. For example
     *        for a line series, if options is a single number, the point will
     *        be given that number as the marin y value. If it is an array, it
     *        will be interpreted as x and y values respectively. If it is an
     *        object, advanced options are applied.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart after the point is updated. If doing
     *        more operations on the chart, it is best practice to set
     *        `redraw` to false and call `chart.redraw()` after.
     *
     * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=true]
     *        Whether to apply animation, and optionally animation
     *        configuration.
     *
     * @emits Highcharts.Point#event:update
     */
    update(options, redraw = true, animation, runEvent) {
        const point = this, series = point.series, graphic = point.graphic, chart = series.chart, seriesOptions = series.options, dataOptions = seriesOptions.data;
        /**
         * Perform the actual update of the point.
         *
         * @internal
         */
        function update() {
            point.applyOptions(options);
            // Update visuals, #4146
            // Handle mock graphic elements for a11y, #12718
            const hasMockGraphic = graphic && point.hasMockGraphic, index = point.index;
            const shouldDestroyGraphic = point.y === null ?
                !hasMockGraphic :
                hasMockGraphic;
            if (graphic && shouldDestroyGraphic) {
                point.graphic = graphic.destroy();
                delete point.hasMockGraphic;
            }
            if (isObject(options, true)) {
                // Destroy so we can get new elements
                if (graphic?.element) {
                    // "null" is also a valid symbol
                    if (options?.marker &&
                        typeof options.marker.symbol !== 'undefined') {
                        point.graphic = graphic.destroy();
                    }
                }
                if (options?.dataLabels && point.dataLabel) {
                    point.dataLabel = point.dataLabel.destroy(); // #2468
                }
            }
            const pointOptions = point.optionsToObject(options);
            if (!series.hasProcessedDataTable) {
                // Record changes in the data table (#24451)
                series.dataTable.setRow(pointOptions, index);
                // Record the options to options.data. If the old or the new
                // config is an object, use point options, otherwise use raw
                // options (#4701, #4916).
                if (dataOptions) {
                    dataOptions[index] = (isObject(dataOptions[index], true) ||
                        isObject(options, true)) ?
                        point.options :
                        options ?? dataOptions[index];
                }
            }
            // Redraw
            series.isDirty = series.isDirtyData = true;
            if ('x' in pointOptions) {
                if (isString(pointOptions.x)) {
                    // Clear cache and force recalc in getColumn
                    series.xColumnIsNumbers = void 0;
                }
                point.x = series.getX(pointOptions.x);
                point.isNull = point.isValid && !point.isValid();
                if (series.xColumn) {
                    series.xColumn[index] = point.x;
                }
            }
            if (!series.fixedBox && series.hasCartesianSeries) { // #1906, #2320
                chart.isDirtyBox = true;
            }
            if (seriesOptions.legendType === 'point') { // #1831, #1885
                chart.isDirtyLegend = true;
            }
            if (redraw) {
                chart.redraw(animation);
            }
        }
        // Fire the event with a default handler of doing the update
        if (runEvent === false) { // When called from setData
            update();
        }
        else {
            point.firePointEvent('update', { options: options }, update);
        }
    }
    /**
     * Remove a point and optionally redraw the series and if necessary the axes
     *
     * @sample highcharts/plotoptions/series-point-events-remove/
     *         Remove point and confirm
     * @sample highcharts/members/point-remove/
     *         Remove pie slice
     * @sample maps/members/point-remove/
     *         Remove selected points in Highmaps
     *
     * @function Highcharts.Point#remove
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart or wait for an explicit call. When
     *        doing more operations on the chart, for example running
     *        `point.remove()` in a loop, it is best practice to set `redraw`
     *        to false and call `chart.redraw()` after.
     *
     * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation=false]
     *        Whether to apply animation, and optionally animation
     *        configuration.
     */
    remove(redraw, animation) {
        this.series.removePoint(this.series.data.indexOf(this), redraw, animation);
    }
    /**
     * Toggle the selection status of a point.
     *
     * @see Highcharts.Chart#getSelectedPoints
     *
     * @sample highcharts/members/point-select/
     *         Select a point from a button
     * @sample highcharts/members/point-select-lasso/
     *         Lasso selection
     * @sample highcharts/chart/events-selection-points/
     *         Rectangle selection
     * @sample maps/series/data-id/
     *         Select a point in Highmaps
     *
     * @function Highcharts.Point#select
     *
     * @param {boolean} [selected]
     * When `true`, the point is selected. When `false`, the point is
     * unselected. When `null` or `undefined`, the selection state is toggled.
     *
     * @param {boolean} [accumulate=false]
     * When `true`, the selection is added to other selected points.
     * When `false`, other selected points are deselected. Internally in
     * Highcharts, when
     * [allowPointSelect](https://api.highcharts.com/highcharts/plotOptions.series.allowPointSelect)
     * is `true`, selected points are accumulated on Control, Shift or Cmd
     * clicking the point.
     *
     * @emits Highcharts.Point#event:select
     * @emits Highcharts.Point#event:unselect
     */
    select(selected, accumulate) {
        const point = this, series = point.series, chart = series.chart;
        selected = pick(selected, !point.selected);
        this.selectedStaging = selected;
        // Fire the event with the default handler
        point.firePointEvent(selected ? 'select' : 'unselect', { accumulate: accumulate }, function () {
            point.selected = point.options.selected = selected;
            if (series.options.data) {
                series.options.data[series.data.indexOf(point)] =
                    point.options;
            }
            point.setState(selected && 'select');
            // Unselect all other points unless Ctrl or Cmd + click
            if (!accumulate) {
                chart.getSelectedPoints().forEach(function (loopPoint) {
                    const loopSeries = loopPoint.series, loopSeriesOptions = loopSeries.options;
                    if (loopPoint.selected && loopPoint !== point) {
                        loopPoint.selected = loopPoint.options.selected =
                            false;
                        if (loopSeriesOptions.data) {
                            loopSeriesOptions.data[loopSeries.data.indexOf(loopPoint)] = loopPoint.options;
                        }
                        // Programmatically selecting a point should restore
                        // normal state, but when click happened on other
                        // point, set inactive state to match other points
                        loopPoint.setState(chart.hoverPoints &&
                            loopSeriesOptions.inactiveOtherPoints ?
                            'inactive' : '');
                        loopPoint.firePointEvent('unselect');
                    }
                });
            }
        });
        delete this.selectedStaging;
    }
    /**
     * Runs on mouse over the point. Called internally from mouse and touch
     * events.
     *
     * @function Highcharts.Point#onMouseOver
     *
     * @param {Highcharts.PointerEventObject} [e]
     *        The event arguments.
     */
    onMouseOver(e) {
        const point = this, series = point.series, { inverted, pointer } = series.chart;
        if (pointer) {
            e = e ?
                pointer.normalize(e) :
                // In cases where onMouseOver is called directly without an
                // event
                pointer.getChartCoordinatesFromPoint(point, inverted);
            pointer.runPointActions(e, point);
        }
    }
    /**
     * Runs on mouse out from the point. Called internally from mouse and touch
     * events.
     *
     * @function Highcharts.Point#onMouseOut
     * @emits Highcharts.Point#event:mouseOut
     */
    onMouseOut() {
        const point = this;
        if (!point.series) {
            return;
        }
        const chart = point.series.chart;
        point.firePointEvent('mouseOut');
        if (!point.series.options.inactiveOtherPoints) {
            (chart.hoverPoints || []).forEach(function (p) {
                p.setState();
            });
        }
        chart.hoverPoints = chart.hoverPoint = null;
    }
    /**
     * Manage specific event from the series' and point's options. Only do it on
     * demand, to save processing time on hovering.
     *
     * @internal
     * @function Highcharts.Point#importEvents
     */
    manageEvent(eventType) {
        const point = this, options = merge(point.series.options.point, point.options), userEvent = options.events?.[eventType];
        if (isFunction(userEvent) &&
            (!point.hcEvents?.[eventType] ||
                // Some HC modules, like marker-clusters, draggable-points etc.
                // use events in their logic, so we need to be sure, that
                // callback function is different
                point.hcEvents?.[eventType]?.map((el) => el.fn)
                    .indexOf(userEvent) === -1)) {
            // While updating the existing callback event the old one should be
            // removed
            point.importedUserEvent?.();
            point.importedUserEvent = addEvent(point, eventType, userEvent);
            if (point.hcEvents) {
                point.hcEvents[eventType].userEvent = true;
            }
        }
        else if (point.importedUserEvent &&
            !userEvent &&
            point.hcEvents?.[eventType] &&
            point.hcEvents?.[eventType].userEvent) {
            removeEvent(point, eventType);
            delete point.hcEvents[eventType];
            if (!Object.keys(point.hcEvents)) {
                delete point.importedUserEvent;
            }
        }
    }
    /**
     * Set the point's state.
     *
     * @function Highcharts.Point#setState
     *
     * @param {Highcharts.PointStateValue|""} [state]
     *        The new state, can be one of `'hover'`, `'select'`, `'inactive'`,
     *        or `''` (an empty string), `'normal'` or `undefined` to set to
     *        normal state.
     * @param {boolean} [move]
     *        State for animation.
     *
     * @emits Highcharts.Point#event:afterSetState
     */
    setState(state, move) {
        const point = this, series = point.series, previousState = point.state, stateOptions = (series.options.states[state || 'normal'] ||
            {}), markerOptions = (Point_defaultOptions.plotOptions[series.type].marker &&
            series.options.marker), normalDisabled = (markerOptions && markerOptions.enabled === false), markerStateOptions = markerOptions?.states?.[state || 'normal'] ||
            {}, stateDisabled = markerStateOptions.enabled === false, pointMarker = point.marker || {}, chart = series.chart, hasMarkers = (markerOptions && series.markerAttribs);
        let halo = series.halo, markerAttribs, pointAttribs, pointAttribsAnimation, stateMarkerGraphic = series.stateMarkerGraphic, newSymbol;
        state = state || ''; // Empty string
        if (
        // Already has this state
        (state === point.state && !move) ||
            // Selected points don't respond to hover
            (point.selected && state !== 'select') ||
            // Series' state options is disabled
            (stateOptions.enabled === false) ||
            // General point marker's state options is disabled
            (state && (stateDisabled ||
                (normalDisabled &&
                    markerStateOptions.enabled === false))) ||
            // Individual point marker's state options is disabled
            (state &&
                pointMarker.states &&
                pointMarker.states[state] &&
                pointMarker.states[state].enabled === false) // #1610
        ) {
            return;
        }
        point.state = state;
        if (hasMarkers) {
            markerAttribs = series.markerAttribs(point, state);
        }
        // Apply hover styles to the existing point
        // Prevent from mocked null points (#14966)
        if (point.graphic && !point.hasMockGraphic) {
            if (previousState) {
                point.graphic.removeClass('highcharts-point-' + previousState);
            }
            if (state) {
                point.graphic.addClass('highcharts-point-' + state);
            }
            if (!chart.styledMode) {
                pointAttribs = series.pointAttribs(point, state);
                pointAttribsAnimation = pick(chart.options.chart.animation, stateOptions.animation);
                const opacity = pointAttribs.opacity;
                // Some inactive points (e.g. slices in pie) should apply
                // opacity also for their labels
                if (series.options.inactiveOtherPoints && isNumber(opacity)) {
                    (point.dataLabels || []).forEach(function (label) {
                        if (label &&
                            !label.hasClass('highcharts-data-label-hidden')) {
                            label.animate({ opacity }, pointAttribsAnimation);
                            if (label.connector) {
                                label.connector.animate({ opacity }, pointAttribsAnimation);
                            }
                        }
                    });
                }
                point.graphic.animate(pointAttribs, pointAttribsAnimation);
            }
            if (markerAttribs) {
                point.graphic.animate(markerAttribs, pick(
                // Turn off globally:
                chart.options.chart.animation, markerStateOptions.animation, markerOptions.animation));
            }
            // Zooming in from a range with no markers to a range with markers
            if (stateMarkerGraphic) {
                stateMarkerGraphic.hide();
            }
        }
        else {
            // If a graphic is not applied to each point in the normal state,
            // create a shared graphic for the hover state
            if (state && markerStateOptions) {
                newSymbol = pointMarker.symbol || series.symbol;
                // If the point has another symbol than the previous one, throw
                // away the state marker graphic and force a new one (#1459)
                if (stateMarkerGraphic &&
                    stateMarkerGraphic.currentSymbol !== newSymbol) {
                    stateMarkerGraphic = stateMarkerGraphic.destroy();
                }
                // Add a new state marker graphic
                if (markerAttribs) {
                    if (!stateMarkerGraphic) {
                        if (newSymbol) {
                            series.stateMarkerGraphic = stateMarkerGraphic =
                                chart.renderer
                                    .symbol(newSymbol, markerAttribs.x, markerAttribs.y, markerAttribs.width, markerAttribs.height, merge(markerOptions, markerStateOptions))
                                    .add(series.markerGroup);
                            stateMarkerGraphic.currentSymbol = newSymbol;
                        }
                        // Move the existing graphic
                    }
                    else {
                        stateMarkerGraphic[move ? 'animate' : 'attr']({
                            x: markerAttribs.x,
                            y: markerAttribs.y
                        });
                    }
                }
                if (!chart.styledMode && stateMarkerGraphic &&
                    point.state !== 'inactive') {
                    stateMarkerGraphic.attr(series.pointAttribs(point, state));
                }
            }
            if (stateMarkerGraphic) {
                stateMarkerGraphic[state && point.isInside ? 'show' : 'hide'](); // #2450
                stateMarkerGraphic.element.point = point; // #4310
                stateMarkerGraphic.addClass(point.getClassName(), true);
            }
        }
        // Show me your halo
        const haloOptions = stateOptions.halo;
        const markerGraphic = (point.graphic || stateMarkerGraphic);
        const markerVisibility = markerGraphic?.visibility || 'inherit';
        if (haloOptions?.size &&
            markerGraphic &&
            markerVisibility !== 'hidden' &&
            !point.isCluster) {
            if (!halo) {
                series.halo = halo = chart.renderer.path()
                    // #5818, #5903, #6705
                    .add(markerGraphic.parentGroup);
            }
            halo.show()[move ? 'animate' : 'attr']({
                d: point.haloPath(haloOptions.size)
            });
            halo.attr({
                'class': 'highcharts-halo highcharts-color-' +
                    pick(point.colorIndex, series.colorIndex) +
                    (point.className ? ' ' + point.className : ''),
                'visibility': markerVisibility,
                'zIndex': -1 // #4929, #8276
            });
            halo.point = point; // #6055
            if (!chart.styledMode) {
                halo.attr(extend({
                    'fill': point.color || series.color,
                    'fill-opacity': haloOptions.opacity
                }, HTML_AST.filterUserAttributes(haloOptions.attributes || {})));
            }
        }
        else if (halo?.point?.haloPath &&
            !halo.point.destroyed) {
            // Animate back to 0 on the current halo point (#6055)
            halo.animate({ d: halo.point.haloPath(0) }, null, 
            // Hide after unhovering. The `complete` callback runs in the
            // halo's context (#7681).
            halo.hide);
        }
        fireEvent(point, 'afterSetState', { state });
    }
    /**
     * Get the path definition for the halo, which is usually a shadow-like
     * circle around the currently hovered point.
     *
     * @function Highcharts.Point#haloPath
     *
     * @param {number} size
     *        The radius of the circular halo.
     *
     * @return {Highcharts.SVGPathArray}
     *         The path definition.
     */
    haloPath(size) {
        const pos = this.pos();
        return pos ? this.series.chart.renderer.symbols.circle(crisp(pos[0], 1) - size, pos[1] - size, size * 2, size * 2) : [];
    }
}
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Series_Point = (Point);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * Function callback when a series point is clicked. Return false to cancel the
 * action.
 *
 * @callback Highcharts.PointClickCallbackFunction
 *
 * @param {Highcharts.Point} this
 *        The point where the event occurred.
 *
 * @param {Highcharts.PointClickEventObject} event
 *        Event arguments.
 *
 * @param {Highcharts.Point} [ctx]
 *        Since v12.6.0, the point context passed as an extra argument for
 *        arrow functions.
 */
/**
 * Common information for a click event on a series point.
 *
 * @interface Highcharts.PointClickEventObject
 * @extends Highcharts.PointerEventObject
 */ /**
* Clicked point.
* @name Highcharts.PointClickEventObject#point
* @type {Highcharts.Point}
*/
/**
 * Gets fired when the mouse leaves the area close to the point.
 *
 * @callback Highcharts.PointMouseOutCallbackFunction
 *
 * @param {Highcharts.Point} this
 *        Point where the event occurred.
 *
 * @param {global.PointerEvent} event
 *        Event that occurred.
 *
 * @param {Highcharts.Point} [ctx]
 *        Since v12.6.0, the point context passed as an extra argument for
 *        arrow functions.
 */
/**
 * Gets fired when the mouse enters the area close to the point.
 *
 * @callback Highcharts.PointMouseOverCallbackFunction
 *
 * @param {Highcharts.Point} this
 *        Point where the event occurred.
 *
 * @param {global.Event} event
 *        Event that occurred.
 *
 * @param {Highcharts.Point} [ctx]
 *        Since v12.6.0, the point context passed as an extra argument for
 *        arrow functions.
 */
/**
 * The generic point options for all series.
 *
 * In TypeScript you have to extend `PointOptionsObject` with an additional
 * declaration to allow custom data options:
 *
 * ```
 * declare interface PointOptionsObject {
 *     customProperty: string;
 * }
 * ```
 *
 * @interface Highcharts.PointOptionsObject
 */
/**
 * Possible option types for a data point. Use `null` to indicate a gap.
 *
 * @typedef {number|string|Highcharts.PointOptionsObject|Array<(number|string|null)>|null} Highcharts.PointOptionsType
 */
/**
 * Gets fired when the point is removed using the `.remove()` method.
 *
 * @callback Highcharts.PointRemoveCallbackFunction
 *
 * @param {Highcharts.Point} this
 *        Point where the event occurred.
 *
 * @param {global.Event} event
 *        Event that occurred.
 *
 * @param {Highcharts.Point} [ctx]
 *        Since v12.6.0, the point context passed as an extra argument for
 *        arrow functions.
 */
/**
 * Possible key values for the point state options.
 *
 * @typedef {"hover"|"inactive"|"normal"|"select"} Highcharts.PointStateValue
 */
/**
 * Gets fired when the point is updated programmatically through the `.update()`
 * method.
 *
 * @callback Highcharts.PointUpdateCallbackFunction
 *
 * @param {Highcharts.Point} this
 *        Point where the event occurred.
 *
 * @param {Highcharts.PointUpdateEventObject} event
 *        Event that occurred.
 *
 * @param {Highcharts.Point} [ctx]
 *        Since v12.6.0, the point context passed as an extra argument for
 *        arrow functions.
 */
/**
 * Information about the update event.
 *
 * @interface Highcharts.PointUpdateEventObject
 * @extends global.Event
 */ /**
* Options data of the update event.
* @name Highcharts.PointUpdateEventObject#options
* @type {Highcharts.PointOptionsType}
*/
/**
 * @interface Highcharts.PointEventsOptionsObject
 */ /**
* Fires when the point is selected either programmatically or following a click
* on the point. One parameter, `event`, is passed to the function. Returning
* `false` cancels the operation.
* @name Highcharts.PointEventsOptionsObject#select
* @type {Highcharts.PointSelectCallbackFunction|undefined}
*/ /**
* Fires when the point is unselected either programmatically or following a
* click on the point. One parameter, `event`, is passed to the function.
* Returning `false` cancels the operation.
* @name Highcharts.PointEventsOptionsObject#unselect
* @type {Highcharts.PointUnselectCallbackFunction|undefined}
*/
/**
 * Information about the select/unselect event.
 *
 * @interface Highcharts.PointInteractionEventObject
 * @extends global.Event
 */ /**
* @name Highcharts.PointInteractionEventObject#accumulate
* @type {boolean}
*/
/**
 * Gets fired when the point is selected either programmatically or following a
 * click on the point.
 *
 * @callback Highcharts.PointSelectCallbackFunction
 *
 * @param {Highcharts.Point} this
 *        Point where the event occurred.
 *
 * @param {Highcharts.PointInteractionEventObject} event
 *        Event that occurred.
 *
 * @param {Highcharts.Point} [ctx]
 *        Since v12.6.0, the point context passed as an extra argument for
 *        arrow functions.
 */
/**
 * Fires when the point is unselected either programmatically or following a
 * click on the point.
 *
 * @callback Highcharts.PointUnselectCallbackFunction
 *
 * @param {Highcharts.Point} this
 *        Point where the event occurred.
 *
 * @param {Highcharts.PointInteractionEventObject} event
 *        Event that occurred.
 *
 * @param {Highcharts.Point} [ctx]
 *        Since v12.6.0, the point context passed as an extra argument for
 *        arrow functions.
 */
''; // Keeps doclets above in JS file.

;// ./code/es-modules/Core/Pointer.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { charts: Pointer_charts, composed: Pointer_composed, isTouchDevice: Pointer_isTouchDevice } = Core_Globals;

/**
 *
 *  Functions
 *
 */
/**
 * Check whether the configured action key allows the interaction.
 *
 * @internal
 * @param {Event} e
 *        A mouse event.
 * @param {string | undefined} key
 *        Zoom or pan key.
 * @return {boolean}
 *         True if the key is undefined
 *         or if the action key is pressed. False otherwise.
 */
const checkActionKey = (e, key) => !defined(key) || e[`${key}Key`];
/* *
 *
 *  Class
 *
 * */
/**
 * The mouse and touch tracker object. Each {@link Chart} item has one
 * associated Pointer item that can be accessed from the  {@link Chart.pointer}
 * property.
 *
 * @class
 * @name Highcharts.Pointer
 *
 * @param {Highcharts.Chart} chart
 * The chart instance.
 *
 * @param {Highcharts.Options} options
 * The root options object. The pointer uses options from the chart and tooltip
 * structures.
 */
class Pointer {
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Set inactive state to all series that are not currently hovered,
     * or, if `inactiveOtherPoints` is set to true, set inactive state to
     * all points within that series.
     *
     * @internal
     * @function Highcharts.Pointer#applyInactiveState
     *
     * @param {Array<Highcharts.Point>} points
     * Currently hovered points
     */
    applyInactiveState(points = []) {
        const activeSeries = [];
        // Get all active series from the hovered points
        points.forEach((item) => {
            const series = item.series;
            // Include itself
            activeSeries.push(series);
            // Include parent series
            if (series.linkedParent) {
                activeSeries.push(series.linkedParent);
            }
            // Include all child series
            if (series.linkedSeries) {
                activeSeries.push.apply(activeSeries, series.linkedSeries);
            }
            // Include navigator series
            if (series.navigatorSeries) {
                activeSeries.push(series.navigatorSeries);
            }
            // Include boosed series when they share markerGroup
            if (series.boosted && series.markerGroup) {
                activeSeries.push.apply(activeSeries, this.chart.series.filter((otherSeries) => otherSeries.markerGroup === series.markerGroup));
            }
        });
        for (const series of this.chart.series) {
            const seriesOptions = series.options;
            if (seriesOptions.states?.inactive?.enabled === false) {
                continue;
            }
            if (activeSeries.indexOf(series) === -1) {
                // Inactive series
                series.setState('inactive', true);
            }
            else if (seriesOptions.inactiveOtherPoints) {
                // Active series, but other points should be inactivated
                series.setAllPointsToState('inactive');
            }
        }
    }
    /**
     * Destroys the Pointer object and disconnects DOM events.
     *
     * @function Highcharts.Pointer#destroy
     */
    destroy() {
        const pointer = this;
        this.eventsToUnbind.forEach((unbind) => unbind());
        this.eventsToUnbind = [];
        if (!Core_Globals.chartCount) {
            Pointer.unbindDocumentMouseUp.forEach((el) => el.unbind());
            Pointer.unbindDocumentMouseUp.length = 0;
            if (Pointer.unbindDocumentTouchEnd) {
                Pointer.unbindDocumentTouchEnd = (Pointer.unbindDocumentTouchEnd());
            }
        }
        objectEach(pointer, function (_val, prop) {
            pointer[prop] = void 0;
        });
    }
    /**
     * Calculate attrs for selection marker.
     * @internal
     * @function Highcharts.Pointer#getSelectionMarkerAttrs
     * @emits getSelectionMarkerAttrs
     */
    getSelectionMarkerAttrs(chartX, chartY) {
        const e = {
            args: { chartX, chartY },
            attrs: {},
            shapeType: 'rect'
        };
        fireEvent(this, 'getSelectionMarkerAttrs', e, (e) => {
            const { chart, zoomHor, zoomVert } = this, { mouseDownX = 0, mouseDownY = 0 } = chart, attrs = e.attrs;
            let size;
            attrs.x = chart.plotLeft;
            attrs.y = chart.plotTop;
            attrs.width = zoomHor ? 1 : chart.plotWidth;
            attrs.height = zoomVert ? 1 : chart.plotHeight;
            // Adjust the width of the selection marker. Firefox needs at
            // least one pixel width or height in order to return a bounding
            // box.
            if (zoomHor) {
                size = chartX - mouseDownX;
                attrs.width = Math.max(1, Math.abs(size));
                attrs.x = (size > 0 ? 0 : size) + mouseDownX;
            }
            // Adjust the height of the selection marker
            if (zoomVert) {
                size = chartY - mouseDownY;
                attrs.height = Math.max(1, Math.abs(size));
                attrs.y = (size > 0 ? 0 : size) + mouseDownY;
            }
        });
        return e;
    }
    /**
     * Perform a drag operation in response to a mousemove event while the mouse
     * is down.
     * @internal
     * @function Highcharts.Pointer#drag
     */
    drag(e) {
        const { chart } = this, { mouseDownX = 0, mouseDownY = 0 } = chart, chartOptions = chart.options, { panning, panKey, selectionMarkerFill } = chartOptions.chart, plotLeft = chart.plotLeft, plotTop = chart.plotTop, plotWidth = chart.plotWidth, plotHeight = chart.plotHeight, panningEnabled = isObject(panning) ?
            panning.enabled :
            panning, panKeyPressed = panKey && e[`${panKey}Key`];
        let chartX = e.chartX, chartY = e.chartY, clickedInside, selectionMarker = this.selectionMarker;
        // If the device supports both touch and mouse (like IE11), and we are
        // touch-dragging inside the plot area, don't handle the mouse event.
        // #4339.
        if (selectionMarker && selectionMarker.touch) {
            return;
        }
        // If the mouse is outside the plot area, adjust to coordinates
        // inside to prevent the selection marker from going outside
        if (chartX < plotLeft) {
            chartX = plotLeft;
        }
        else if (chartX > plotLeft + plotWidth) {
            chartX = plotLeft + plotWidth;
        }
        if (chartY < plotTop) {
            chartY = plotTop;
        }
        else if (chartY > plotTop + plotHeight) {
            chartY = plotTop + plotHeight;
        }
        // Determine if the mouse has moved more than 10px
        this.hasDragged = Math.sqrt(Math.pow(mouseDownX - chartX, 2) +
            Math.pow(mouseDownY - chartY, 2));
        if (this.hasDragged > 10) {
            clickedInside = chart.isInsidePlot(mouseDownX - plotLeft, mouseDownY - plotTop, {
                visiblePlotOnly: true
            });
            const { shapeType, attrs } = this.getSelectionMarkerAttrs(chartX, chartY);
            // Make a selection
            if (this.hasZoom &&
                clickedInside &&
                !panKeyPressed) {
                if (!selectionMarker) {
                    this.selectionMarker = selectionMarker =
                        chart.renderer[shapeType]();
                    selectionMarker
                        .attr({
                        'class': 'highcharts-selection-marker',
                        zIndex: 7
                    })
                        .add();
                    if (!chart.styledMode) {
                        selectionMarker.attr({
                            fill: selectionMarkerFill
                        });
                    }
                }
            }
            if (selectionMarker) {
                selectionMarker.attr(attrs);
            }
            // Panning
            if (clickedInside && !selectionMarker && panningEnabled &&
                (checkActionKey(e, panKey))) {
                chart.pan(e, panning);
            }
        }
    }
    /**
     * Start a drag operation.
     * @internal
     * @function Highcharts.Pointer#dragStart
     */
    dragStart(e) {
        const chart = this.chart;
        // Record the start position
        chart.mouseIsDown = e.type;
        chart.cancelClick = false;
        chart.mouseDownX = e.chartX;
        chart.mouseDownY = e.chartY;
    }
    /**
     * Get selection box to calculate extremes
     * @internal
     * @function Highcharts.Pointer#getSelectionBox
     * @emits getSelectionBox
     */
    getSelectionBox(marker) {
        const e = {
            args: { marker },
            result: marker.getBBox()
        };
        fireEvent(this, 'getSelectionBox', e);
        return e.result;
    }
    /**
     * On mouse up or touch end across the entire document, drop the selection.
     * @internal
     * @function Highcharts.Pointer#drop
     */
    drop(e) {
        const { chart, selectionMarker } = this;
        // During a mouse, touch or mousewheel pan, the `startOnTick` and
        // `endOnTick` options are ignored. Otherwise the zooming or panning
        // would be jumpy, or even not performed because the end ticks would
        // block it. After the touch has ended, we undo this and render again.
        let redraw;
        for (const axis of chart.axes) {
            if (axis.isPanning) {
                axis.isPanning = false;
                if (axis.options.startOnTick ||
                    axis.options.endOnTick ||
                    axis.series.some((s) => s.boosted)) {
                    axis.forceRedraw = true;
                    axis.setExtremes(axis.userMin, axis.userMax, false);
                    redraw = true;
                }
            }
        }
        if (redraw) {
            chart.redraw();
        }
        if (selectionMarker && e) {
            // A selection has been made
            if (this.hasDragged) {
                const from = this.getSelectionBox(selectionMarker);
                chart.transform({
                    axes: chart.axes.filter((a) => a.zoomEnabled &&
                        ((a.coll === 'xAxis' && this.zoomX) ||
                            (a.coll === 'yAxis' && this.zoomY))),
                    selection: {
                        originalEvent: e, // #4890
                        xAxis: [],
                        yAxis: [],
                        ...from
                    },
                    from
                });
            }
            if (isNumber(chart.index)) {
                this.selectionMarker = selectionMarker.destroy();
            }
        }
        // Reset all. Check isNumber because it may be destroyed on mouse up
        // (#877)
        if (chart && isNumber(chart.index)) {
            css(chart.container, { cursor: chart._cursor });
            chart.cancelClick = this.hasDragged > 10; // #370
            chart.mouseIsDown = false;
            this.hasDragged = 0;
            this.pinchDown = [];
            this.hasPinchMoved = false;
        }
    }
    /**
     * Finds the closest point to a set of coordinates, using the k-d-tree
     * algorithm.
     *
     * @function Highcharts.Pointer#findNearestKDPoint
     *
     * @param {Array<Highcharts.Series>} series
     * All the series to search in.
     *
     * @param {boolean|undefined} shared
     * Whether it is a shared tooltip or not.
     *
     * @param {Highcharts.PointerEventObject} e
     * The pointer event object, containing chart coordinates of the pointer.
     *
     * @return {Highcharts.Point|undefined}
     * The point closest to given coordinates.
     */
    findNearestKDPoint(series, shared, e) {
        let closest;
        /** @internal */
        function sort(p1, p2) {
            const isCloserX = p1.distX - p2.distX, isCloser = p1.dist - p2.dist, isAbove = (p2.series.group?.zIndex -
                p1.series.group?.zIndex);
            let result;
            // We have two points which are not in the same place on xAxis
            // and shared tooltip:
            if (isCloserX !== 0 && shared) { // #5721
                result = isCloserX;
                // Points are not exactly in the same place on x/yAxis:
            }
            else if (isCloser !== 0) {
                result = isCloser;
                // The same xAxis and yAxis position, sort by z-index:
            }
            else if (isAbove !== 0) {
                result = isAbove;
                // The same zIndex, sort by array index:
            }
            else {
                result =
                    p1.series.index > p2.series.index ?
                        -1 :
                        1;
            }
            return result;
        }
        series.forEach(function (s) {
            const noSharedTooltip = s.noSharedTooltip && shared, compareX = (!noSharedTooltip &&
                s.options.findNearestPointBy.indexOf('y') < 0), point = s.searchPoint(e, compareX);
            if ( // Check that we actually found a point on the series.
            isObject(point, true) && point.series &&
                // Use the new point if it is closer.
                (!isObject(closest, true) ||
                    (sort(closest, point) > 0))) {
                closest = point;
            }
        });
        return closest;
    }
    /** @internal */
    getChartCoordinatesFromPoint(point, inverted) {
        const { xAxis, yAxis } = point.series, shapeArgs = point.shapeArgs;
        if (xAxis && yAxis) {
            let x = point.clientX ?? point.plotX ?? 0, y = point.plotY || 0;
            if (point.isNode &&
                shapeArgs &&
                isNumber(shapeArgs.x) &&
                isNumber(shapeArgs.y)) {
                x = shapeArgs.x;
                y = shapeArgs.y;
            }
            return inverted ? {
                chartX: yAxis.len + yAxis.pos - y,
                chartY: xAxis.len + xAxis.pos - x
            } : {
                chartX: x + xAxis.pos,
                chartY: y + yAxis.pos
            };
        }
        if (shapeArgs?.x && shapeArgs.y) {
            // E.g. pies do not have axes
            return {
                chartX: shapeArgs.x,
                chartY: shapeArgs.y
            };
        }
    }
    /**
     * Return the cached chartPosition if it is available on the Pointer,
     * otherwise find it. Running offset is quite expensive, so it should be
     * avoided when we know the chart hasn't moved.
     *
     * @function Highcharts.Pointer#getChartPosition
     *
     * @return {Highcharts.ChartPositionObject}
     * The offset of the chart container within the page
     */
    getChartPosition() {
        if (this.chartPosition) {
            return this.chartPosition;
        }
        const { container } = this.chart;
        const pos = offset(container);
        this.chartPosition = {
            left: pos.left,
            top: pos.top,
            scaleX: 1,
            scaleY: 1
        };
        const { offsetHeight, offsetWidth } = container;
        // #13342 - tooltip was not visible in Chrome, when chart
        // updates height.
        if (offsetWidth > 2 && // #13342
            offsetHeight > 2 // #13342
        ) {
            this.chartPosition.scaleX = pos.width / offsetWidth;
            this.chartPosition.scaleY = pos.height / offsetHeight;
        }
        return this.chartPosition;
    }
    /**
     * Get the click position in terms of axis values.
     *
     * @function Highcharts.Pointer#getCoordinates
     *
     * @param {Highcharts.PointerEventObject} e
     * Pointer event, extended with `chartX` and `chartY` properties.
     *
     * @return {Highcharts.PointerAxisCoordinatesObject}
     * Axis coordinates.
     */
    getCoordinates(e) {
        const coordinates = {
            xAxis: [],
            yAxis: []
        };
        for (const axis of this.chart.axes) {
            coordinates[axis.isXAxis ? 'xAxis' : 'yAxis'].push({
                axis,
                value: axis.toValue(e[axis.horiz ? 'chartX' : 'chartY'])
            });
        }
        return coordinates;
    }
    /**
     * Calculates what is the current hovered point/points and series.
     *
     * @internal
     * @function Highcharts.Pointer#getHoverData
     *
     * @param {Highcharts.Point|undefined} existingHoverPoint
     * The point currently being hovered.
     *
     * @param {Highcharts.Series|undefined} existingHoverSeries
     * The series currently being hovered.
     *
     * @param {Array<Highcharts.Series>} series
     * All the series in the chart.
     *
     * @param {boolean} isDirectTouch
     * Is the pointer directly hovering the point.
     *
     * @param {boolean|undefined} shared
     * Whether it is a shared tooltip or not.
     *
     * @param {Highcharts.PointerEventObject} [e]
     * The triggering event, containing chart coordinates of the pointer.
     *
     * @return {Object}
     * Object containing resulting hover data: hoverPoint, hoverSeries, and
     * hoverPoints.
     */
    getHoverData(existingHoverPoint, existingHoverSeries, series, isDirectTouch, shared, e) {
        const hoverPoints = [], useExisting = !!(isDirectTouch && existingHoverPoint), filter = function (s) {
            return (s.visible &&
                !(!shared && s.directTouch) && // #3821
                pick(s.options.enableMouseTracking, true));
        };
        let hoverSeries = existingHoverSeries, 
        // Which series to look in for the hover point
        searchSeries, 
        // Parameters needed for beforeGetHoverData event.
        eventArgs = {
            chartX: e ? e.chartX : void 0,
            chartY: e ? e.chartY : void 0,
            shared: shared
        };
        // Find chart.hoverPane and update filter method in polar.
        fireEvent(this, 'beforeGetHoverData', eventArgs);
        const notSticky = hoverSeries && !hoverSeries.stickyTracking;
        searchSeries = notSticky ?
            // Only search on hovered series if it has stickyTracking false
            [hoverSeries] :
            // Filter what series to look in.
            series.filter((s) => s.stickyTracking &&
                (eventArgs.filter || filter)(s));
        // Use existing hovered point or find the one closest to coordinates.
        const hoverPoint = useExisting || !e ?
            existingHoverPoint :
            this.findNearestKDPoint(searchSeries, shared, e);
        // Assign hover series
        hoverSeries = hoverPoint?.series;
        // If we have a hoverPoint, assign hoverPoints.
        if (hoverPoint) {
            // When tooltip is shared, it displays more than one point
            if (shared && !hoverSeries.noSharedTooltip) {
                searchSeries = series.filter(function (s) {
                    return eventArgs.filter ?
                        eventArgs.filter(s) : filter(s) && !s.noSharedTooltip;
                });
                // Get all points with the same x value as the hoverPoint
                searchSeries.forEach(function (s) {
                    const nullInteraction = s.options?.nullInteraction;
                    let point = find(s.points, function (p) {
                        return (p.x === hoverPoint.x && (!p.isNull ||
                            !!nullInteraction));
                    });
                    if (isObject(point)) {
                        /*
                        * Boost returns a minimal point. Convert it to a usable
                        * point for tooltip and states.
                        */
                        if (s.boosted && s.boost) {
                            point = s.boost.getPoint(point);
                        }
                        hoverPoints.push(point);
                    }
                });
            }
            else {
                hoverPoints.push(hoverPoint);
            }
        }
        // Check whether the hoverPoint is inside pane we are hovering over.
        eventArgs = { hoverPoint: hoverPoint };
        fireEvent(this, 'afterGetHoverData', eventArgs);
        return {
            hoverPoint: eventArgs.hoverPoint,
            hoverSeries: hoverSeries,
            hoverPoints: hoverPoints
        };
    }
    /** @internal */
    getPointFromEvent(e) {
        let target = e.target, point;
        while (target && !point) {
            point = target.point;
            target = target.parentNode;
        }
        return point;
    }
    /** @internal */
    onTrackerMouseOut(e) {
        const chart = this.chart;
        const relatedTarget = e.relatedTarget;
        const series = chart.hoverSeries;
        this.isDirectTouch = false;
        if (series &&
            relatedTarget &&
            !series.stickyTracking &&
            !this.inClass(relatedTarget, 'highcharts-tooltip') &&
            (!this.inClass(relatedTarget, 'highcharts-series-' + series.index) || // #2499, #4465, #5553
                !this.inClass(relatedTarget, 'highcharts-tracker'))) {
            series.onMouseOut();
        }
    }
    /**
     * Utility to detect whether an element has, or has a parent with, a
     * specific class name. Used on detection of tracker objects and on deciding
     * whether hovering the tooltip should cause the active series to mouse out.
     *
     * @function Highcharts.Pointer#inClass
     *
     * @param {Highcharts.SVGDOMElement|Highcharts.HTMLDOMElement} element
     * The element to investigate.
     *
     * @param {string} className
     * The class name to look for.
     *
     * @return {boolean|undefined}
     * True if either the element or one of its parents has the given class
     * name.
     */
    inClass(element, className) {
        let elem = element, elemClassName;
        while (elem) {
            elemClassName = attr(elem, 'class');
            if (elemClassName) {
                if (elemClassName.indexOf(className) !== -1) {
                    return true;
                }
                if (elemClassName.indexOf('highcharts-container') !== -1) {
                    return false;
                }
            }
            elem = elem.parentElement;
        }
    }
    /**
     * Initialize the Pointer.
     *
     * @internal
     * @function Highcharts.Pointer#init
     *
     * @param {Highcharts.Chart} chart
     * The Chart instance.
     *
     * @param {Highcharts.Options} options
     * The root options object. The pointer uses options from the chart and
     * tooltip structures.
     */
    constructor(chart, options) {
        /**
         * The distance by which the mouse has been dragged since the last
         * `mousedown` event.
         * @internal
         */
        this.hasDragged = 0;
        /**
         * A list of events to unbind when the pointer is destroyed.
         * @internal
         */
        this.pointerCaptureEventsToUnbind = [];
        /**
         * A list of events to unbind when the pointer is destroyed.
         * @internal
         */
        this.eventsToUnbind = [];
        // Store references
        this.options = options;
        this.chart = chart;
        // Do we need to handle click on a touch device?
        this.runChartClick = Boolean(options.chart.events?.click);
        this.pinchDown = [];
        this.setDOMEvents();
        fireEvent(this, 'afterInit');
    }
    /**
     * Takes a browser event object and extends it with custom Highcharts
     * properties `chartX` and `chartY` in order to work on the internal
     * coordinate system.
     *
     * On map charts, the properties `lon` and `lat` are added to the event
     * object given that the chart has projection information.
     *
     * @function Highcharts.Pointer#normalize
     *
     * @param {global.MouseEvent|global.PointerEvent|global.TouchEvent} e
     * Event object in standard browsers.
     *
     * @param {Highcharts.OffsetObject} [chartPosition]
     * Additional chart offset.
     *
     * @return {Highcharts.PointerEventObject}
     * A browser event with extended properties `chartX` and `chartY`.
     */
    normalize(e, chartPosition) {
        const touches = e.touches;
        // Position for iOS (#2757)
        const ePos = (touches ?
            touches.length ?
                touches.item(0) :
                (pick(// #13534
                touches.changedTouches, e.changedTouches))[0] :
            e);
        // Get mouse position
        if (!chartPosition) {
            chartPosition = this.getChartPosition();
        }
        let chartX = ePos.pageX - chartPosition.left, chartY = ePos.pageY - chartPosition.top;
        // #11329 - when there is scaling on a parent element, we need to take
        // this into account
        chartX /= chartPosition.scaleX;
        chartY /= chartPosition.scaleY;
        return extend(e, {
            chartX: Math.round(chartX),
            chartY: Math.round(chartY)
        });
    }
    /** @internal */
    onContainerClick(e) {
        const chart = this.chart;
        const hoverPoint = chart.hoverPoint;
        const pEvt = this.normalize(e);
        const plotLeft = chart.plotLeft;
        const plotTop = chart.plotTop;
        if (!chart.cancelClick) {
            // On tracker click, fire the series and point events. #783, #1583
            if (hoverPoint &&
                this.inClass(pEvt.target, 'highcharts-tracker')) {
                // The series click event
                fireEvent(hoverPoint.series, 'click', extend(pEvt, {
                    point: hoverPoint
                }));
                // The point click event
                if (chart.hoverPoint) { // It may be destroyed (#1844)
                    hoverPoint.firePointEvent('click', pEvt);
                }
                // When clicking outside a tracker, fire a chart event
            }
            else {
                extend(pEvt, this.getCoordinates(pEvt));
                // Fire a click event in the chart
                if (chart.isInsidePlot(pEvt.chartX - plotLeft, pEvt.chartY - plotTop, {
                    visiblePlotOnly: true
                })) {
                    fireEvent(chart, 'click', pEvt);
                }
            }
        }
    }
    /** @internal */
    onContainerMouseDown(e) {
        const isPrimaryButton = ((e.buttons || e.button) & 1) === 1;
        e = this.normalize(e);
        // #11635, Firefox does not reliably fire move event after click scroll
        if (Core_Globals.isFirefox &&
            e.button !== 0) {
            this.onContainerMouseMove(e);
        }
        // #11635, limiting to primary button
        if (typeof e.button === 'undefined' ||
            isPrimaryButton) {
            this.zoomOption(e);
            // #295, #13737 solve conflict between container drag and chart zoom
            if (isPrimaryButton) {
                e.preventDefault?.();
            }
            this.dragStart(e);
        }
    }
    /**
     * When mouse leaves the container, hide the tooltip.
     * @internal
     * @function Highcharts.Pointer#onContainerMouseLeave
     */
    onContainerMouseLeave(e) {
        const { pointer } = Pointer_charts[pick(Pointer.hoverChartIndex, -1)] || {};
        e = this.normalize(e);
        this.onContainerMouseMove(e);
        // #4886, MS Touch end fires mouseleave but with no related target
        if (pointer &&
            !this.inClass(e.relatedTarget, 'highcharts-tooltip')) {
            pointer.reset();
            // Also reset the chart position, used in #149 fix
            pointer.chartPosition = void 0;
        }
    }
    /**
     * When mouse enters the container, delete pointer's chartPosition.
     * @internal
     * @function Highcharts.Pointer#onContainerMouseEnter
     */
    onContainerMouseEnter() {
        delete this.chartPosition;
    }
    /**
     * The mousemove, touchmove and touchstart event handler
     * @internal
     * @function Highcharts.Pointer#onContainerMouseMove
     */
    onContainerMouseMove(e) {
        const chart = this.chart, tooltip = chart.tooltip, pEvt = this.normalize(e);
        this.setHoverChartIndex(e);
        if (chart.mouseIsDown === 'mousedown' || this.touchSelect(pEvt)) {
            this.drag(pEvt);
        }
        // Show the tooltip and run mouse over events (#977)
        if (!chart.exporting?.openMenu &&
            (this.inClass(pEvt.target, 'highcharts-tracker') ||
                chart.isInsidePlot(pEvt.chartX - chart.plotLeft, pEvt.chartY - chart.plotTop, {
                    visiblePlotOnly: true
                })) &&
            // If the tooltip has stickOnContact enabled, do nothing. This
            // applies regardless of any combinations of the `split` and
            // `useHTML` options.
            !tooltip?.shouldStickOnContact(pEvt)) {
            if (this.inClass(pEvt.target, 'highcharts-no-tooltip')) {
                this.reset(false, 0);
            }
            else {
                this.runPointActions(pEvt);
            }
        }
    }
    /** @internal */
    onDocumentTouchEnd(e) {
        this.onDocumentMouseUp(e);
    }
    /** @internal */
    onContainerTouchMove(e) {
        if (this.touchSelect(e)) {
            this.onContainerMouseMove(e);
        }
        else {
            this.touch(e);
        }
    }
    /** @internal */
    onContainerTouchStart(e) {
        if (this.touchSelect(e)) {
            this.onContainerMouseDown(e);
        }
        else {
            this.zoomOption(e);
            this.touch(e, true);
        }
    }
    /**
     * Special handler for mouse move that will hide the tooltip when the mouse
     * leaves the plotarea. Issue #149 workaround. The mouseleave event does not
     * always fire.
     * @internal
     * @function Highcharts.Pointer#onDocumentMouseMove
     */
    onDocumentMouseMove(e) {
        const chart = this.chart;
        const tooltip = chart.tooltip;
        const chartPosition = this.chartPosition;
        const pEvt = this.normalize(e, chartPosition);
        // If we're outside, hide the tooltip
        if (chartPosition &&
            !chart.isInsidePlot(pEvt.chartX - chart.plotLeft, pEvt.chartY - chart.plotTop, {
                visiblePlotOnly: true
            }) &&
            !tooltip?.shouldStickOnContact(pEvt) && (pEvt.target === chart.container.ownerDocument ||
            !this.inClass(pEvt.target, 'highcharts-tracker'))) {
            this.reset();
        }
    }
    /** @internal */
    onDocumentMouseUp(e) {
        // #17852, IOS devices sometimes reverts back to previous point when
        // dragging between points
        if (e?.touches && this.hasPinchMoved) {
            e?.preventDefault?.();
        }
        Pointer_charts[pick(Pointer.hoverChartIndex, -1)]
            ?.pointer
            ?.drop(e);
    }
    /**
     * Handle touch events with two touches
     * @internal
     * @function Highcharts.Pointer#pinch
     */
    pinch(e) {
        const pointer = this, { chart, hasZoom, lastTouches } = pointer, touches = [].map.call(e.touches || [], 
        // Normalize each touch
        (touch) => pointer.normalize(touch)), touchesLength = touches.length, fireClickEvent = touchesLength === 1 && ((pointer.inClass(e.target, 'highcharts-tracker') &&
            chart.runTrackerClick) ||
            pointer.runChartClick), followTouchMove = touchesLength === 1 &&
            (chart.tooltip?.options.followTouchMove ?? true);
        // Don't initiate panning until the user has pinched. This prevents us
        // from blocking page scrolling as users scroll down a long page
        // (#4210).
        if (touchesLength > 1) {
            pointer.initiated = true;
        }
        else if (followTouchMove) {
            // #16119: Prevent blocking scroll when single-finger panning is
            // not enabled
            pointer.initiated = false;
        }
        // On touch devices, only proceed to trigger click if a handler is
        // defined
        if (hasZoom &&
            pointer.initiated &&
            !fireClickEvent &&
            e.cancelable !== false) {
            e.preventDefault();
        }
        // Register the touch start position
        if (e.type === 'touchstart') {
            pointer.pinchDown = touches;
            pointer.res = true; // Reset on next move
            chart.mouseDownX = e.chartX;
            // Optionally move the tooltip on touchmove
        }
        else if (followTouchMove) {
            this.runPointActions(pointer.normalize(e));
            // Event type is touchmove, handle panning and pinching. The length can
            // be 0 when releasing, if touchend fires first
        }
        else if (lastTouches) {
            fireEvent(chart, 'touchpan', {
                originalEvent: e,
                touches
            }, () => {
                const boxFromTouches = (touches) => {
                    const finger0 = touches[0], finger1 = touches[1] || finger0;
                    return {
                        x: finger0.chartX,
                        y: finger0.chartY,
                        width: finger1.chartX - finger0.chartX,
                        height: finger1.chartY - finger0.chartY
                    };
                };
                chart.transform({
                    axes: chart.axes
                        .filter((axis) => axis.zoomEnabled &&
                        ((this.zoomHor && axis.horiz) ||
                            (this.zoomVert && !axis.horiz))),
                    to: boxFromTouches(touches),
                    from: boxFromTouches(lastTouches),
                    trigger: e.type
                });
            });
            if (pointer.res) {
                pointer.res = false;
                this.reset(false, 0);
            }
        }
        pointer.lastTouches = touches;
    }
    /**
     * Run translation operations
     * @internal
     * @function Highcharts.Pointer#pinchTranslate
     * /
    public pinchTranslate(
        pinchDown: Array<any>,
        touches: Array<PointerEvent>,
        transform: any,
        selectionMarker: any,
        clip: any,
        lastValidTouch: any
    ): void {
        if (this.zoomHor) {
            this.pinchTranslateDirection(
                true,
                pinchDown,
                touches,
                transform,
                selectionMarker,
                clip,
                lastValidTouch
            );
        }
        if (this.zoomVert) {
            this.pinchTranslateDirection(
                false,
                pinchDown,
                touches,
                transform,
                selectionMarker,
                clip,
                lastValidTouch
            );
        }
    }
    */
    /**
     * Run translation operations for each direction (horizontal and vertical)
     * independently.
     * @internal
     * @function Highcharts.Pointer#pinchTranslateDirection
     * /
    public pinchTranslateDirection(
        horiz: boolean,
        pinchDown: Array<any>,
        touches: Array<PointerEvent>,
        transform: any,
        selectionMarker: any,
        clip: any,
        lastValidTouch: any,
        forcedScale?: number
    ): void {
        const chart = this.chart,
            xy: ('x'|'y') = horiz ? 'x' : 'y',
            XY: ('X'|'Y') = horiz ? 'X' : 'Y',
            sChartXY: ('chartX'|'chartY') = ('chart' + XY) as any,
            wh = horiz ? 'width' : 'height',
            plotLeftTop = (chart as any)['plot' + (horiz ? 'Left' : 'Top')],
            inverted = chart.inverted,
            bounds = chart.bounds[horiz ? 'h' : 'v'],
            singleTouch = pinchDown.length === 1,
            touch0Start = pinchDown[0][sChartXY],
            touch1Start = !singleTouch && pinchDown[1][sChartXY],
            setScale = function (): void {
                // Don't zoom if fingers are too close on this axis
                if (
                    typeof touch1Now === 'number' &&
                    Math.abs(touch0Start - touch1Start) > 20
                ) {
                    scale = forcedScale ||
                        Math.abs(touch0Now - touch1Now) /
                        Math.abs(touch0Start - touch1Start);
                }

                clipXY = ((plotLeftTop - touch0Now) / scale) + touch0Start;
                selectionWH = (chart as any)[
                    'plot' + (horiz ? 'Width' : 'Height')
                ] / scale;
            };

        let selectionWH: any,
            selectionXY,
            clipXY: any,
            scale = forcedScale || 1,
            touch0Now = touches[0][sChartXY],
            touch1Now = !singleTouch && touches[1][sChartXY],
            outOfBounds;

        // Set the scale, first pass
        setScale();

        // The clip position (x or y) is altered if out of bounds, the selection
        // position is not
        selectionXY = clipXY;

        // Out of bounds
        if (selectionXY < bounds.min) {
            selectionXY = bounds.min;
            outOfBounds = true;
        } else if (selectionXY + selectionWH > bounds.max) {
            selectionXY = bounds.max - selectionWH;
            outOfBounds = true;
        }

        // Is the chart dragged off its bounds, determined by dataMin and
        // dataMax?
        if (outOfBounds) {

            // Modify the touchNow position in order to create an elastic drag
            // movement. This indicates to the user that the chart is responsive
            // but can't be dragged further.
            touch0Now -= 0.8 * (touch0Now - lastValidTouch[xy][0]);
            if (typeof touch1Now === 'number') {
                touch1Now -= 0.8 * (touch1Now - lastValidTouch[xy][1]);
            }

            // Set the scale, second pass to adapt to the modified touchNow
            // positions
            setScale();

        } else {
            lastValidTouch[xy] = [touch0Now, touch1Now];
        }

        // Set geometry for clipping, selection and transformation
        if (!inverted) {
            clip[xy] = clipXY - plotLeftTop;
            clip[wh] = selectionWH;
        }
        const scaleKey = inverted ?
            (horiz ? 'scaleY' : 'scaleX') : 'scale' + XY;
        const transformScale = inverted ? 1 / scale : scale;

        selectionMarker[wh] = selectionWH;
        selectionMarker[xy] = selectionXY;
        // Invert scale if needed (#19217)
        transform[scaleKey] = scale * (inverted && !horiz ? -1 : 1);
        transform['translate' + XY] = (transformScale * plotLeftTop) +
            (touch0Now - (transformScale * touch0Start));
    }
    */
    /**
     * Reset the tracking by hiding the tooltip, the hover series state and the
     * hover point.
     *
     * @function Highcharts.Pointer#reset
     *
     * @param {boolean} [allowMove]
     * Instead of destroying the tooltip altogether, allow moving it if
     * possible.
     *
     * @param {number} [delay]
     * The tooltip hide delay in ms.
     */
    reset(allowMove, delay) {
        const pointer = this, chart = pointer.chart, hoverSeries = chart.hoverSeries, hoverPoint = chart.hoverPoint, hoverPoints = chart.hoverPoints, tooltip = chart.tooltip, tooltipPoints = tooltip?.shared ?
            hoverPoints :
            hoverPoint;
        // Check if the points have moved outside the plot area (#1003, #4736,
        // #5101)
        if (allowMove && tooltipPoints) {
            splat(tooltipPoints).forEach(function (point) {
                if (point.series.isCartesian &&
                    typeof point.plotX === 'undefined') {
                    allowMove = false;
                }
            });
        }
        // Just move the tooltip, #349
        if (allowMove) {
            if (tooltip && tooltipPoints && splat(tooltipPoints).length) {
                tooltip.refresh(tooltipPoints);
                if (tooltip.shared && hoverPoints) { // #8284
                    hoverPoints.forEach(function (point) {
                        point.setState(point.state, true);
                        if (point.series.isCartesian) {
                            if (point.series.xAxis.crosshair) {
                                point.series.xAxis
                                    .drawCrosshair(null, point);
                            }
                            if (point.series.yAxis.crosshair) {
                                point.series.yAxis
                                    .drawCrosshair(null, point);
                            }
                        }
                    });
                }
                else if (hoverPoint) { // #2500
                    hoverPoint.setState(hoverPoint.state, true);
                    chart.axes.forEach(function (axis) {
                        if (axis.crosshair &&
                            hoverPoint.series[axis.coll] === axis) {
                            axis.drawCrosshair(null, hoverPoint);
                        }
                    });
                }
            }
            // Full reset
        }
        else {
            if (hoverPoint) {
                hoverPoint.onMouseOut();
            }
            if (hoverPoints) {
                hoverPoints.forEach(function (point) {
                    point.setState();
                });
            }
            if (hoverSeries) {
                hoverSeries.onMouseOut();
            }
            if (tooltip) {
                tooltip.hide(delay);
            }
            if (pointer.unDocMouseMove) {
                pointer.unDocMouseMove = pointer.unDocMouseMove();
            }
            // Remove crosshairs
            chart.axes.forEach(function (axis) {
                axis.hideCrosshair();
            });
            chart.hoverPoints = chart.hoverPoint = void 0;
        }
    }
    /**
     * With line type charts with a single tracker, get the point closest to the
     * mouse. Run Point.onMouseOver and display tooltip for the point or points.
     *
     * @internal
     * @function Highcharts.Pointer#runPointActions
     *
     * @emits Highcharts.Point#event:mouseOut
     * @emits Highcharts.Point#event:mouseOver
     */
    runPointActions(e, p, force) {
        const pointer = this, chart = pointer.chart, series = chart.series, tooltip = (chart.tooltip?.options.enabled ?
            chart.tooltip :
            void 0), shared = (tooltip ?
            tooltip.shared :
            false);
        let hoverPoint = p || chart.hoverPoint, hoverSeries = hoverPoint?.series || chart.hoverSeries;
        const // `onMouseOver` or already hovering a series with directTouch
        isDirectTouch = (!e || e.type !== 'touchmove') && (!!p || ((hoverSeries?.directTouch) &&
            pointer.isDirectTouch)), hoverData = this.getHoverData(hoverPoint, hoverSeries, series, isDirectTouch, shared, e);
        // Update variables from hoverData.
        hoverPoint = hoverData.hoverPoint;
        hoverSeries = hoverData.hoverSeries;
        const points = hoverData.hoverPoints, followPointer = hoverSeries?.tooltipOptions.followPointer &&
            !hoverSeries.tooltipOptions.split, useSharedTooltip = (shared &&
            hoverSeries &&
            !hoverSeries.noSharedTooltip);
        // Refresh tooltip for kdpoint if new hover point or tooltip was hidden
        // #3926, #4200
        if (hoverPoint &&
            (force ||
                hoverPoint !== chart.hoverPoint ||
                tooltip?.isHidden)) {
            (chart.hoverPoints || []).forEach(function (p) {
                if (points.indexOf(p) === -1) {
                    p.setState();
                }
            });
            // Set normal state to previous series
            if (chart.hoverSeries !== hoverSeries) {
                hoverSeries.onMouseOver();
            }
            pointer.applyInactiveState(points);
            // Do mouseover on all points (#3919, #3985, #4410, #5622)
            (points || []).forEach(function (p) {
                p.setState('hover');
            });
            // If tracking is on series in stead of on each point,
            // fire mouseOver on hover point. // #4448
            if (chart.hoverPoint) {
                chart.hoverPoint.firePointEvent('mouseOut');
            }
            // Hover point may have been destroyed in the event handlers (#7127)
            if (!hoverPoint.series) {
                return;
            }
            /**
             * Contains all hovered points.
             *
             * @name Highcharts.Chart#hoverPoints
             * @type {Array<Highcharts.Point>|null}
             */
            chart.hoverPoints = points;
            /**
             * Contains the original hovered point.
             *
             * @name Highcharts.Chart#hoverPoint
             * @type {Highcharts.Point|null}
             */
            chart.hoverPoint = hoverPoint;
            /**
             * Hover state should not be lost when axis is updated (#12569)
             * Axis.update runs pointer.reset which uses chart.hoverPoint.state
             * to apply state which does not exist in hoverPoint yet.
             * The mouseOver event should be triggered when hoverPoint
             * is correct.
             */
            hoverPoint.firePointEvent('mouseOver', void 0, () => {
                // Draw tooltip if necessary
                if (tooltip && hoverPoint) {
                    tooltip.refresh(useSharedTooltip ? points : hoverPoint, e);
                }
            });
            // Update positions (regardless of kdpoint or hoverPoint)
        }
        else if (followPointer && tooltip && !tooltip.isHidden) {
            const anchor = tooltip.getAnchor([{}], e);
            if (chart.isInsidePlot(anchor[0], anchor[1], {
                visiblePlotOnly: true
            })) {
                tooltip.updatePosition({ plotX: anchor[0], plotY: anchor[1] });
            }
        }
        // Start the event listener to pick up the tooltip and crosshairs
        if (!pointer.unDocMouseMove) {
            pointer.unDocMouseMove = addEvent(chart.container.ownerDocument, 'mousemove', (e) => Pointer_charts[Pointer.hoverChartIndex ?? -1]
                ?.pointer
                ?.onDocumentMouseMove(e));
            pointer.eventsToUnbind.push(pointer.unDocMouseMove);
        }
        // Issues related to crosshair #4927, #5269 #5066, #5658
        chart.axes.forEach(function drawAxisCrosshair(axis) {
            const snap = axis.crosshair?.snap ?? true;
            let point;
            if (snap) {
                point = chart.hoverPoint; // #13002
                if (!point || point.series[axis.coll] !== axis) {
                    point = find(points, (p) => p.series?.[axis.coll] === axis);
                }
            }
            // Axis has snapping crosshairs, and one of the hover points belongs
            // to axis. Always call drawCrosshair when it is not snap.
            if (point || !snap) {
                axis.drawCrosshair(e, point);
                // Axis has snapping crosshairs, but no hover point belongs to axis
            }
            else {
                axis.hideCrosshair();
            }
        });
    }
    /**
     * Set the JS DOM events on the container and document. This method should
     * contain a one-to-one assignment between methods and their handlers. Any
     * advanced logic should be moved to the handler reflecting the event's
     * name.
     * @internal
     * @function Highcharts.Pointer#setDOMEvents
     */
    setDOMEvents() {
        const container = this.chart.container, ownerDoc = container.ownerDocument, 
        // Get the parent element, including handling Shadow DOM (#23450)
        getParent = (el) => el.parentElement || el.getRootNode()?.host?.parentElement;
        container.onmousedown = this.onContainerMouseDown.bind(this);
        container.onmousemove = this.onContainerMouseMove.bind(this);
        container.onclick = this.onContainerClick.bind(this);
        this.eventsToUnbind.push(addEvent(container, 'mouseenter', this.onContainerMouseEnter.bind(this)), addEvent(container, 'mouseleave', this.onContainerMouseLeave.bind(this)));
        if (!Pointer.unbindDocumentMouseUp.some((el) => el.doc === ownerDoc)) {
            Pointer.unbindDocumentMouseUp.push({
                doc: ownerDoc,
                unbind: addEvent(ownerDoc, 'mouseup', this.onDocumentMouseUp.bind(this))
            });
        }
        // In case we are dealing with overflow, reset the chart position when
        // scrolling parent elements
        let parent = getParent(this.chart.renderTo);
        while (parent && parent.tagName !== 'BODY') {
            this.eventsToUnbind.push(addEvent(parent, 'scroll', () => {
                delete this.chartPosition;
            }));
            parent = getParent(parent);
        }
        this.eventsToUnbind.push(addEvent(container, 'touchstart', this.onContainerTouchStart.bind(this), { passive: false }), addEvent(container, 'touchmove', this.onContainerTouchMove.bind(this), { passive: false }));
        if (!Pointer.unbindDocumentTouchEnd) {
            Pointer.unbindDocumentTouchEnd = addEvent(ownerDoc, 'touchend', this.onDocumentTouchEnd.bind(this), { passive: false });
        }
        this.setPointerCapture();
        addEvent(this.chart, 'redraw', this.setPointerCapture.bind(this));
    }
    /**
     * Sets, or removes on update, pointer events using pointer capture for
     * tooltip.followTouchMove if any series has findNearestPointBy that
     * includes the y dimension.
     * @internal
     * @function Highcharts.Pointer#setPointerCapture
    */
    setPointerCapture() {
        // Only for touch
        if (!Pointer_isTouchDevice) {
            return;
        }
        const pointer = this, events = pointer.pointerCaptureEventsToUnbind, chart = pointer.chart, container = chart.container, followTouchMove = chart.options.tooltip?.followTouchMove ?? true, shouldHave = followTouchMove && chart.series.some((series) => series.options.findNearestPointBy
            .indexOf('y') > -1);
        if (!pointer.hasPointerCapture && shouldHave) {
            // Add
            // Bind
            events.push(addEvent(container, 'pointerdown', (e) => {
                if (e.target?.hasPointerCapture(e.pointerId)) {
                    e.target?.releasePointerCapture(e.pointerId);
                }
            }), addEvent(container, 'pointermove', (e) => {
                chart.pointer?.getPointFromEvent(e)?.onMouseOver(e);
            }));
            pointer.hasPointerCapture = true;
        }
        else if (pointer.hasPointerCapture && !shouldHave) {
            // Remove
            // Unbind
            events.forEach((e) => e());
            events.length = 0;
            pointer.hasPointerCapture = false;
        }
    }
    /**
     * Sets the index of the hovered chart and leaves the previous hovered
     * chart, to reset states like tooltip.
     * @internal
     * @function Highcharts.Pointer#setHoverChartIndex
     */
    setHoverChartIndex(e) {
        const chart = this.chart;
        const hoverChart = Core_Globals.charts[pick(Pointer.hoverChartIndex, -1)];
        if (hoverChart &&
            hoverChart !== chart) {
            const relatedTargetObj = { relatedTarget: chart.container };
            if (e && !e?.relatedTarget) {
                // #17192, Non-enumerable properties of "e" are dropped with
                // spreading (...e). Using Object.assign ensures integrity.
                Object.assign({}, e, relatedTargetObj);
            }
            hoverChart.pointer?.onContainerMouseLeave(e || relatedTargetObj);
        }
        if (!hoverChart?.mouseIsDown) {
            Pointer.hoverChartIndex = chart.index;
        }
    }
    /**
     * General touch handler shared by touchstart and touchmove.
     * @internal
     * @function Highcharts.Pointer#touch
     */
    touch(e, start) {
        const { chart, pinchDown = [] } = this;
        let hasMoved, isInside;
        this.setHoverChartIndex();
        e = this.normalize(e);
        if (e.touches.length === 1) {
            isInside = chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop, {
                visiblePlotOnly: true
            });
            if (isInside && !chart.exporting?.openMenu) {
                // Run mouse events and display tooltip etc
                if (start) {
                    this.runPointActions(e);
                }
                // Android fires touchmove events after the touchstart even if
                // the finger hasn't moved, or moved only a pixel or two. In iOS
                // however, the touchmove doesn't fire unless the finger moves
                // more than ~4px. So we emulate this behavior in Android by
                // checking how much it moved, and cancelling on small
                // distances. #3450. Tested and still relevant as of 2024.
                if (e.type === 'touchmove') {
                    this.hasPinchMoved = hasMoved = pinchDown[0] ? // #5266
                        (Math.pow(pinchDown[0].chartX - e.chartX, 2) +
                            Math.pow(pinchDown[0].chartY - e.chartY, 2)) >= 16 :
                        false;
                }
                if (pick(hasMoved, true)) {
                    this.pinch(e);
                }
            }
            else if (start) {
                // Hide the tooltip on touching outside the plot area (#1203)
                this.reset();
            }
            // If inside, capture touch-drag and display tooltip. If not inside,
            // allow dragging the finger to scroll the page
            if ((chart.tooltip?.options.followTouchMove ?? true) &&
                isInside) {
                e.preventDefault();
            }
        }
        else if (e.touches.length === 2) {
            this.pinch(e);
        }
    }
    /**
     * Returns true if the chart is set up for zooming by single touch and the
     * event is capable
     * @internal
     * @function Highcharts.Pointer#touchSelect
     */
    touchSelect(e) {
        return Boolean(this.chart.zooming.singleTouch &&
            e.touches &&
            e.touches.length === 1);
    }
    /**
     * Resolve the zoomType option, this is reset on all touch start and mouse
     * down events.
     * @internal
     * @function Highcharts.Pointer#zoomOption
     */
    zoomOption(e) {
        const chart = this.chart, inverted = chart.inverted;
        let zoomType = chart.zooming.type || '', zoomX, zoomY;
        // Look for the pinchType option
        if (/touch/.test(e.type)) {
            zoomType = pick(chart.zooming.pinchType, zoomType);
        }
        this.zoomX = zoomX = /x/.test(zoomType);
        this.zoomY = zoomY = /y/.test(zoomType);
        this.zoomHor = (zoomX && !inverted) || (zoomY && inverted);
        this.zoomVert = (zoomY && !inverted) || (zoomX && inverted);
        this.hasZoom = (zoomX || zoomY) &&
            (checkActionKey(e, chart.zooming.key));
    }
}
/** @internal */
Pointer.unbindDocumentMouseUp = [];
/* *
 *
 *  Class Namespace
 *
 * */
(function (Pointer) {
    /* *
     *
     *  Declarations
     *
     * */
    /* *
     *
     *  Functions
     *
     * */
    /** @internal */
    function compose(ChartClass) {
        if (pushUnique(Pointer_composed, 'Core.Pointer')) {
            addEvent(ChartClass, 'beforeRender', function () {
                /**
                 * The Pointer that keeps track of mouse and touch
                 * interaction.
                 *
                 * @memberof Highcharts.Chart
                 * @name pointer
                 * @type {Highcharts.Pointer}
                 * @instance
                 */
                this.pointer = new Pointer(this, this.options);
            });
        }
    }
    Pointer.compose = compose;
})(Pointer || (Pointer = {}));
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Core_Pointer = (Pointer);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * Chart position and scale.
 *
 * @interface Highcharts.ChartPositionObject
 */ /**
* @name Highcharts.ChartPositionObject#left
* @type {number}
*/ /**
* @name Highcharts.ChartPositionObject#scaleX
* @type {number}
*/ /**
* @name Highcharts.ChartPositionObject#scaleY
* @type {number}
*/ /**
* @name Highcharts.ChartPositionObject#top
* @type {number}
*/
/**
 * One position in relation to an axis.
 *
 * @interface Highcharts.PointerAxisCoordinateObject
 */ /**
* Related axis.
*
* @name Highcharts.PointerAxisCoordinateObject#axis
* @type {Highcharts.Axis}
*/ /**
* Axis value.
*
* @name Highcharts.PointerAxisCoordinateObject#value
* @type {number}
*/
/**
 * Positions in terms of axis values.
 *
 * @interface Highcharts.PointerAxisCoordinatesObject
 */ /**
* Positions on the x-axis.
* @name Highcharts.PointerAxisCoordinatesObject#xAxis
* @type {Array<Highcharts.PointerAxisCoordinateObject>}
*/ /**
* Positions on the y-axis.
* @name Highcharts.PointerAxisCoordinatesObject#yAxis
* @type {Array<Highcharts.PointerAxisCoordinateObject>}
*/
/**
 * Pointer coordinates.
 *
 * @interface Highcharts.PointerCoordinatesObject
 */ /**
* @name Highcharts.PointerCoordinatesObject#chartX
* @type {number}
*/ /**
* @name Highcharts.PointerCoordinatesObject#chartY
* @type {number}
*/
/**
 * A native browser mouse or touch event, extended with position information
 * relative to the {@link Chart.container}.
 *
 * @interface Highcharts.PointerEventObject
 * @extends global.PointerEvent
 */ /**
* The X coordinate of the pointer interaction relative to the chart.
*
* @name Highcharts.PointerEventObject#chartX
* @type {number}
*/ /**
* The Y coordinate of the pointer interaction relative to the chart.
*
* @name Highcharts.PointerEventObject#chartY
* @type {number}
*/
/**
 * Axis-specific data of a selection.
 *
 * @interface Highcharts.SelectDataObject
 */ /**
* The selected Axis.
* @name Highcharts.SelectDataObject#axis
* @type {Highcharts.Axis}
*/ /**
* The maximum axis value, either automatic or set manually.
* @name Highcharts.SelectDataObject#max
* @type {number}
*/ /**
* The minimum axis value, either automatic or set manually.
* @name Highcharts.SelectDataObject#min
* @type {number}
*/
/**
 * Object for select events.
 * The primary axes are `xAxis[0]` and `yAxis[0]`. Remember the unit of a
 * datetime axis is milliseconds since 1970-01-01 00:00:00.
 *
 * @interface Highcharts.SelectEventObject
 */ /**
* The related browser event.
* @name Highcharts.SelectEventObject#originalEvent
* @type {global.Event}
*/ /**
* Prevents the default action for the event, if called.
* @name Highcharts.SelectEventObject#preventDefault
* @type {Function}
*/ /**
* Indicates a reset event to restore default state.
* @name Highcharts.SelectEventObject#resetSelection
* @type {boolean|undefined}
*/ /**
* Arrays containing the axes of each dimension and each axis' min and max
* values.
* @name Highcharts.SelectEventObject#xAxis
* @type {Array<Highcharts.SelectDataObject>}
*/ /**
* Arrays containing the axes of each dimension and each axis' min and max
* values.
* @name Highcharts.SelectEventObject#yAxis
* @type {Array<Highcharts.SelectDataObject>}
*/
''; // Keeps doclets above in JS file

;// ./code/es-modules/Data/ColumnUtils.js
/* *
 *
 *  (c) 2020-2026 Highsoft AS
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 *  Authors:
 *  - Dawid Draguła
 *
 * */
/* *
*
* Functions
*
* */
/**
 * Sets the length of the column array.
 *
 * @param {DataTableColumn} column
 * Column to be modified.
 *
 * @param {number} length
 * New length of the column.
 *
 * @param {boolean} asSubarray
 * If column is a typed array, return a subarray instead of a new array. It
 * is faster `O(1)`, but the entire buffer will be kept in memory until all
 * views of it are destroyed. Default is `false`.
 *
 * @return {DataTableColumn}
 * Modified column.
 *
 * @private
 */
function setLength(column, length, asSubarray) {
    if (Array.isArray(column)) {
        column.length = length;
        return column;
    }
    return column[asSubarray ? 'subarray' : 'slice'](0, length);
}
/**
 * Splices a column array.
 *
 * @param {DataTableColumn} column
 * Column to be modified.
 *
 * @param {number} start
 * Index at which to start changing the array.
 *
 * @param {number} deleteCount
 * An integer indicating the number of old array elements to remove.
 *
 * @param {boolean} removedAsSubarray
 * If column is a typed array, return a subarray instead of a new array. It
 * is faster `O(1)`, but the entire buffer will be kept in memory until all
 * views to it are destroyed. Default is `true`.
 *
 * @param {Array<number>|TypedArray} items
 * The elements to add to the array, beginning at the start index. If you
 * don't specify any elements, `splice()` will only remove elements from the
 * array.
 *
 * @return {SpliceResult}
 * Object containing removed elements and the modified column.
 *
 * @private
 */
function splice(column, start, deleteCount, removedAsSubarray, items = []) {
    if (Array.isArray(column)) {
        if (!Array.isArray(items)) {
            items = Array.from(items);
        }
        return {
            removed: column.splice(start, deleteCount, ...items),
            array: column
        };
    }
    const Constructor = Object.getPrototypeOf(column)
        .constructor;
    const removed = column[removedAsSubarray ? 'subarray' : 'slice'](start, start + deleteCount);
    const newLength = column.length - deleteCount + items.length;
    const result = new Constructor(newLength);
    result.set(column.subarray(0, start), 0);
    result.set(items, start);
    result.set(column.subarray(start + deleteCount), start + items.length);
    return {
        removed: removed,
        array: result
    };
}
/**
 * Converts a cell value to a number.
 *
 * @param {DataTableCellType} value
 * Cell value to convert to a number.
 *
 * @param {boolean} useNaN
 * If `true`, returns `NaN` for non-numeric values; if `false`,
 * returns `null` instead.
 *
 * @return {number | null}
 * Number or `null` if the value is not a number.
 *
 * @private
 */
function convertToNumber(value, useNaN) {
    switch (typeof value) {
        case 'boolean':
            return (value ? 1 : 0);
        case 'number':
            return (isNaN(value) && !useNaN ? null : value);
        default:
            value = parseFloat(`${value ?? ''}`);
            return (isNaN(value) && !useNaN ? null : value);
    }
}
/* *
 *
 *  Default Export
 *
 * */
const ColumnUtils = {
    convertToNumber,
    setLength,
    splice
};
/* harmony default export */ const Data_ColumnUtils = (ColumnUtils);

;// ./code/es-modules/Data/DataTableCore.js
/* *
 *
 *  (c) 2009-2026 Highsoft AS
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 *  Authors:
 *  - Sophie Bremer
 *  - Gøran Slettemark
 *  - Torstein Hønsi
 *
 * */


const { setLength: DataTableCore_setLength, splice: DataTableCore_splice } = Data_ColumnUtils;


/* *
 *
 *  Class
 *
 * */
/**
 * Class to manage columns and rows in a table structure. It provides methods
 * to add, remove, and manipulate columns and rows, as well as to retrieve data
 * from specific cells.
 *
 * Highcharts allows passing a `DataTable` or a configuration object for a data
 * table in the `dataTable` property, either chart-level
 * [dataTable](https://api.highcharts.com/highcharts/dataTable) or as
 * [series.dataTable](https://api.highcharts.com/highcharts/series.dataTable).
 * The `DataTable` is then used as a source for the series data points, mapped
 * by the `series.dataMapping` option.
 *
 * After chart instantiation, the data table can be accessed from the series as
 * `series.dataTable`. CRUD operations on the data table will be reflected in
 * the chart.
 *
 * @example
 * const dataTable = new Highcharts.DataTable({
 *   columns: {
 *     year: [2020, 2021, 2022, 2023],
 *     cost: [11, 13, 12, 14],
 *     revenue: [12, 15, 14, 18]
 *   }
 * });
 *
 * @class
 * @name Highcharts.DataTable
 *
 * @param {Highcharts.DataTableOptionsObject} [options]
 * Options to initialize the new DataTable instance.
 */
class DataTableCore {
    constructor(options = {}) {
        this.isDataTable = true;
        this.autoId = !options.id;
        this.columns = {};
        this.id = (options.id || uniqueKey());
        this.rowCount = 0;
        this.versionTag = uniqueKey();
        let rowCount = 0;
        objectEach(options.columns || {}, (column, columnId) => {
            this.columns[columnId] = column.slice();
            rowCount = Math.max(rowCount, column.length);
        });
        this.applyRowCount(rowCount);
    }
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Applies a row count to the table by setting the `rowCount` property and
     * adjusting the length of all columns.
     *
     * @private
     * @param {number} rowCount The new row count.
     */
    applyRowCount(rowCount) {
        this.rowCount = rowCount;
        objectEach(this.columns, (column, columnId) => {
            if (column.length !== rowCount) {
                this.columns[columnId] = DataTableCore_setLength(column, rowCount);
            }
        });
    }
    /**
     * Delete rows. Simplified version of the full
     * `DataTable.deleteRows` method.
     *
     * @sample highcharts/datatable/live-chart/
     *       Add and delete rows in a live chart
     * @sample highcharts/datatable/shared-with-grid/
     *       Chart with data table CRUD operations
     *
     * @function Highcharts.DataTable#deleteRows
     *
     * @param {number} rowIndex
     * The start row index
     *
     * @param {number} [rowCount=1]
     * The number of rows to delete
     *
     * @return {void}
     *
     * @emits #afterDeleteRows
     */
    deleteRows(rowIndex, rowCount = 1) {
        if (rowCount > 0 && rowIndex < this.rowCount) {
            let length = 0;
            objectEach(this.columns, (column, columnId) => {
                this.columns[columnId] =
                    DataTableCore_splice(column, rowIndex, rowCount).array;
                length = column.length;
            });
            this.rowCount = length;
        }
        fireEvent(this, 'afterDeleteRows', { rowIndex, rowCount });
        this.versionTag = uniqueKey();
    }
    /**
     * Fetches the given column by the canonical column ID. Simplified version
     * of the full `DataTable.getRow` method, always returning by reference.
     *
     * @function Highcharts.DataTable#setColumn
     *
     * @param {string} columnId
     * ID of the column to get.
     *
     * @return {Highcharts.DataTableColumn|undefined}
     * A copy of the column, or `undefined` if not found.
     */
    getColumn(columnId, 
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    asReference) {
        return this.columns[columnId];
    }
    /**
     * Retrieves all or the given columns. Simplified version of the full
     * `DataTable.getColumns` method, always returning by reference.
     *
     * @function Highcharts.DataTable#getColumns
     *
     * @param {Array<string>} [columnIds]
     * Column ids to retrieve.
     *
     * @return {Highcharts.DataTableColumnCollection}
     * Collection of columns. If a requested column was not found, it is
     * `undefined`.
     */
    getColumns(columnIds, 
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    asReference) {
        return (columnIds || Object.keys(this.columns)).reduce((columns, columnId) => {
            columns[columnId] = this.columns[columnId];
            return columns;
        }, {});
    }
    /**
     * Retrieves the row at a given index.
     *
     * @function Highcharts.DataTable#getRowObject
     *
     * @param {number} rowIndex
     * Row index to retrieve. First row has index 0.
     *
     * @param {Array<string>} [columnNames]
     * Column names to retrieve.
     *
     * @return {Record<string, number|string|undefined>|undefined}
     * Returns the row values, or `undefined` if not found.
     */
    getRowObject(rowIndex, columnNames) {
        const row = {}, columns = this.columns;
        columnNames ?? (columnNames = Object.keys(this.columns));
        for (const columnName of columnNames) {
            row[columnName] = columns[columnName]?.[rowIndex];
        }
        return row;
    }
    /**
     * Sets cell values for a column. Will insert a new column, if not found.
     *
     * @function Highcharts.DataTable#setColumn
     *
     * @param {string} columnId
     * Column name to set.
     *
     * @param {Highcharts.DataTableColumn} [column]
     * Values to set in the column.
     *
     * @param {number} [rowIndex]
     * Index of the first row to change. (Default: 0)
     *
     * @param {Record<string, (boolean|number|string|null|undefined)>} [eventDetail]
     * Custom information for pending events.
     *
     * @emits #setColumns
     * @emits #afterSetColumns
     */
    setColumn(columnId, column = [], rowIndex = 0, eventDetail) {
        this.setColumns({ [columnId]: column }, rowIndex, eventDetail);
    }
    /**
     * Sets cell values for multiple columns. Will insert new columns, if not
     * found. Simplified version of the full `DataTable.setColumns`, limited
     * to full replacement of the columns (undefined `rowIndex`).
     *
     * @sample highcharts/datatable/shared-with-grid/
     *       Chart with data table CRUD operations
     *
     * @function Highcharts.DataTable#setColumns
     *
     * @param {Highcharts.DataTableColumnCollection} columns
     * Columns as a collection, where the keys are the column names.
     *
     * @param {number} [rowIndex]
     * Index of the first row to change. Ignored in the simplified `DataTable`,
     * as it always replaces the full column.
     *
     * @param {Record<string, (boolean|number|string|null|undefined)>} [eventDetail]
     * Custom information for pending events.
     *
     * @emits #setColumns
     * @emits #afterSetColumns
     */
    setColumns(columns, rowIndex, eventDetail) {
        let rowCount = this.rowCount;
        objectEach(columns, (column, columnId) => {
            this.columns[columnId] = column.slice();
            rowCount = column.length;
        });
        this.applyRowCount(rowCount);
        if (!eventDetail?.silent) {
            fireEvent(this, 'afterSetColumns');
            this.versionTag = uniqueKey();
        }
    }
    /**
     * Sets cell values of a row. Will insert a new row if no index was
     * provided, or if the index is higher than the total number of table rows.
     * A simplified version of the full `DateTable.setRow`, limited to objects.
     *
     * @sample highcharts/datatable/live-chart/
     *       Add and delete rows in a live chart
     * @sample stock/datatable/live-candlestick/
     *       Live candlestick
     * @sample highcharts/datatable/shared-with-grid/
     *       Chart with data table CRUD operations
     *
     * @function Highcharts.DataTable#setRow
     *
     * @param {Record<string, number|string|undefined>} row
     * Cell values to set.
     *
     * @param {number} [rowIndex]
     * Index of the row to set. Leave `undefined` to add as a new row.
     *
     * @param {boolean} [insert]
     * Whether to insert the row at the given index, or to overwrite the row.
     *
     * @param {Record<string, (boolean|number|string|null|undefined)>} [eventDetail]
     * Custom information for pending events.
     *
     * @emits #afterSetRows
     */
    setRow(row, rowIndex = this.rowCount, insert, eventDetail) {
        var _a;
        const { columns } = this, indexRowCount = insert ? this.rowCount + 1 : rowIndex + 1, rowKeys = Object.keys(row);
        if (eventDetail?.addColumns !== false) {
            for (let i = 0, iEnd = rowKeys.length; i < iEnd; i++) {
                columns[_a = rowKeys[i]] || (columns[_a] = new Array(this.rowCount));
            }
        }
        objectEach(columns, (column, columnId) => {
            if (column) {
                if (insert) {
                    column = DataTableCore_splice(column, rowIndex, 0, true, [row[columnId]]).array;
                }
                else {
                    column[rowIndex] =
                        // Preserve explicit null and undefined but fall back
                        // to existing value if the new row does not have the
                        // key
                        columnId in row ?
                            row[columnId] :
                            column[rowIndex];
                }
                columns[columnId] = column;
            }
        });
        this.applyRowCount(Math.max(indexRowCount, this.rowCount));
        if (!eventDetail?.silent) {
            fireEvent(this, 'afterSetRows', { rowIndex });
            this.versionTag = uniqueKey();
        }
    }
    /**
     * Returns the modified (clone) or the original data table if the modified
     * one does not exist.
     *
     * @return {Highcharts.DataTable}
     * The modified (clone) or the original data table.
     */
    getModified() {
        return this.modified || this;
    }
}
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Data_DataTableCore = (DataTableCore);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * A collection of data table columns defined by a object where the key is the
 * column ID and the value is an array of the column values. Typed arrays are
 * supported.
 *
 * @type {Highcharts.DataTableColumnCollection|undefined}
 * @apioption dataTable.columns
 */
/**
 * Custom ID to identify the new DataTable instance.
 *
 * @type {string|undefined}
 * @apioption dataTable.id
 */
/**
 * A typed array.
 * @typedef {Int8Array|Uint8Array|Uint8ClampedArray|Int16Array|Uint16Array|Int32Array|Uint32Array|Float32Array|Float64Array} Highcharts.TypedArray
 */ /**
* A column of values in a data table.
* @typedef {Array<boolean|null|number|string|undefined>|Highcharts.TypedArray} Highcharts.DataTableColumn
*/ /**
* A collection of data table columns defined by a object where the key is the
* column ID and the value is an array of the column values. Typed arrays are
* supported.
* @typedef {Record<string, Highcharts.DataTableColumn>} Highcharts.DataTableColumnCollection
*/
/**
 * Options for the `DataTable` or `DataTableCore` classes.
 * @interface Highcharts.DataTableOptionsObject
 */ /**
* The column options for the data table. The columns are defined by an object
* where the key is the column ID and the value is an array of the column
* values.
*
* @name Highcharts.DataTableOptionsObject.columns
* @type {Highcharts.DataTableColumnCollection|undefined}
*/ /**
* Custom ID to identify the new DataTable instance.
*
* @name Highcharts.DataTableOptionsObject.id
* @type {string|undefined}
*/
(''); // Keeps doclets above in JS file

;// ./code/es-modules/Core/Legend/LegendSymbol.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


/* *
 *
 *  Namespace
 *
 * */
var LegendSymbol;
(function (LegendSymbol) {
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Draw a line, a point marker and an area in the legend.
     *
     * @function Highcharts.LegendSymbolMixin.areaMarker
     *
     * @param {Highcharts.Legend} legend
     * The legend object.
     */
    function areaMarker(legend, item) {
        lineMarker.call(this, legend, item, true);
    }
    LegendSymbol.areaMarker = areaMarker;
    /**
     * Draw a line and a point marker in the legend.
     *
     * @function Highcharts.LegendSymbolMixin.lineMarker
     *
     * @param {Highcharts.Legend} legend
     * The legend object.
     */
    function lineMarker(legend, item, hasArea) {
        const legendItem = this.legendItem = this.legendItem || {}, { chart, options } = this, { baseline = 0, symbolWidth, symbolHeight } = legend, symbol = this.symbol || 'circle', generalRadius = symbolHeight / 2, renderer = chart.renderer, legendItemGroup = legendItem.group, verticalCenter = baseline - Math.round((legend.fontMetrics?.b || symbolHeight) *
            // Render line and marker slightly higher to make room for the
            // area
            (hasArea ? 0.4 : 0.3)), attr = {};
        let legendSymbol, markerOptions = options.marker, lineSizer = 0;
        // Draw the line
        if (!chart.styledMode) {
            attr['stroke-width'] = Math.min(options.lineWidth || 0, 24);
            if (options.dashStyle) {
                attr.dashstyle = options.dashStyle;
            }
            else if (options.linecap !== 'square') {
                attr['stroke-linecap'] = 'round';
            }
        }
        legendItem.line = renderer
            .path()
            .addClass('highcharts-graph')
            .attr(attr)
            .add(legendItemGroup);
        if (hasArea) {
            legendItem.area = renderer
                .path()
                .addClass('highcharts-area')
                .add(legendItemGroup);
        }
        if (attr['stroke-linecap']) {
            lineSizer = Math.min(legendItem.line.strokeWidth(), symbolWidth) / 2;
        }
        if (symbolWidth) {
            const d = [
                ['M', lineSizer, verticalCenter],
                ['L', symbolWidth - lineSizer, verticalCenter]
            ];
            legendItem.line.attr({ d });
            legendItem.area?.attr({
                d: [
                    ...d,
                    ['L', symbolWidth - lineSizer, baseline],
                    ['L', lineSizer, baseline]
                ]
            });
        }
        // Draw the marker
        if (markerOptions && markerOptions.enabled !== false && symbolWidth) {
            // Do not allow the marker to be larger than the symbolHeight
            let radius = Math.min(pick(markerOptions.radius, generalRadius), generalRadius);
            // Restrict symbol markers size
            if (symbol.indexOf('url') === 0) {
                markerOptions = merge(markerOptions, {
                    width: symbolHeight,
                    height: symbolHeight
                });
                radius = 0;
            }
            legendItem.symbol = legendSymbol = renderer
                .symbol(symbol, (symbolWidth / 2) - radius, verticalCenter - radius, 2 * radius, 2 * radius, extend({ context: 'legend' }, markerOptions))
                .addClass('highcharts-point')
                .add(legendItemGroup);
            legendSymbol.isMarker = true;
        }
    }
    LegendSymbol.lineMarker = lineMarker;
})(LegendSymbol || (LegendSymbol = {}));
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Legend_LegendSymbol = (LegendSymbol);

;// ./code/es-modules/Core/Series/SeriesDefaults.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */

/* *
 *
 *  API Options
 *
 * */
/**
 * General options for all series types.
 *
 * @optionparent plotOptions.series
 */
const seriesDefaults = {
    // Base series options
    /**
     * Pixel width of the graph line.
     *
     * @see In styled mode, the line stroke-width can be set with the
     *      `.highcharts-graph` class name.
     *
     * @sample {highcharts} highcharts/plotoptions/series-linewidth-general/
     *         On all series
     * @sample {highcharts} highcharts/plotoptions/series-linewidth-specific/
     *         On one single series
     *
     * @product highcharts highstock
     */
    lineWidth: 2,
    /**
     * For some series, there is a limit that shuts down animation
     * by default when the total number of points in the chart is too high.
     * For example, for a column chart and its derivatives, animation does
     * not run if there is more than 250 points totally. To disable this
     * cap, set `animationLimit` to `Infinity`. This option works if animation
     * is fired on individual points, not on a group of points like e.g. during
     * the initial animation.
     *
     * @sample {highcharts} highcharts/plotoptions/series-animationlimit/
     *         Animation limit on updating individual points
     *
     * @type      {number}
     * @apioption plotOptions.series.animationLimit
     */
    /**
     * Allow this series' points to be selected by clicking on the graphic
     * (columns, point markers, pie slices, map areas etc).
     *
     * The selected points can be handled by point select and unselect
     * events, or collectively by the [getSelectedPoints
     * ](/class-reference/Highcharts.Chart#getSelectedPoints) function.
     *
     * And alternative way of selecting points is through dragging.
     *
     * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-line/
     *         Line
     * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-column/
     *         Column
     * @sample {highcharts} highcharts/plotoptions/series-allowpointselect-pie/
     *         Pie
     * @sample {highcharts} highcharts/chart/events-selection-points/
     *         Select a range of points through a drag selection
     * @sample {highmaps} maps/plotoptions/series-allowpointselect/
     *         Map area
     * @sample {highmaps} maps/plotoptions/mapbubble-allowpointselect/
     *         Map bubble
     *
     * @since 1.2.0
     */
    allowPointSelect: false,
    /**
     * When true, each point or column edge is rounded to its nearest pixel
     * in order to render sharp on screen. In some cases, when there are a
     * lot of densely packed columns, this leads to visible difference
     * in column widths or distance between columns. In these cases,
     * setting `crisp` to `false` may look better, even though each column
     * is rendered blurry.
     *
     * @sample {highcharts} highcharts/plotoptions/column-crisp-false/
     *         Crisp is false
     *
     * @since   5.0.10
     * @product highcharts highstock gantt
     */
    crisp: true,
    /**
     * If true, a checkbox is displayed next to the legend item to allow
     * selecting the series. The state of the checkbox is determined by
     * the `selected` option.
     *
     * @productdesc {highmaps}
     * Note that if a `colorAxis` is defined, the color axis is represented
     * in the legend, not the series.
     *
     * @sample {highcharts} highcharts/plotoptions/series-showcheckbox-true/
     *         Show select box
     *
     * @since 1.2.0
     */
    showCheckbox: false,
    /**
     * Enable or disable the initial animation when a series is displayed.
     * The animation can also be set as a configuration object. Please
     * note that this option only applies to the initial animation of the
     * series itself. For other animations, see [chart.animation](
     * #chart.animation) and the animation parameter under the API methods.
     * The following properties are supported:
     *
     * - `defer`: The animation delay time in milliseconds.
     *
     * - `duration`: The duration of the animation in milliseconds. (Defaults to
     *   `1000`)
     *
     * - `easing`: Can be a string reference to an easing function set on
     *   the `Math` object or a function. See the _Custom easing function_
     *   demo below. (Defaults to `easeInOutSine`)
     *
     * Due to poor performance, animation is disabled in old IE browsers
     * for several chart types.
     *
     * @sample {highcharts} highcharts/plotoptions/series-animation-disabled/
     *         Animation disabled
     * @sample {highcharts} highcharts/plotoptions/series-animation-slower/
     *         Slower animation
     * @sample {highcharts} highcharts/plotoptions/series-animation-easing/
     *         Custom easing function
     * @sample {highstock} stock/plotoptions/animation-slower/
     *         Slower animation
     * @sample {highstock} stock/plotoptions/animation-easing/
     *         Custom easing function
     * @sample {highmaps} maps/plotoptions/series-animation-true/
     *         Animation enabled on map series
     * @sample {highmaps} maps/plotoptions/mapbubble-animation-false/
     *         Disabled on mapbubble series
     *
     * @type    {boolean|Highcharts.AnimationOptionsObject}
     * @default {highcharts} true
     * @default {highstock} true
     * @default {highmaps} false
     */
    animation: {
        /** @ignore-option */
        duration: 1000
    },
    /**
     * An additional class name to apply to the series' graphical elements.
     * This option does not replace default class names of the graphical
     * element. Changes to the series' color will also be reflected in a
     * chart's legend and tooltip.
     *
     * @sample {highcharts} highcharts/css/point-series-classname
     *         Series and point class name
     *
     * @basic
     * @type      {string}
     * @since     5.0.0
     * @apioption plotOptions.series.className
     */
    /**
     * Disable this option to allow series rendering in the whole plotting
     * area.
     *
     * **Note:** Clipping should be always enabled when
     * [chart.zoomType](#chart.zoomType) is set
     *
     * @sample {highcharts} highcharts/plotoptions/series-clip/
     *         Disabled clipping
     *
     * @default   true
     * @type      {boolean}
     * @since     3.0.0
     * @apioption plotOptions.series.clip
     */
    /**
     * The main color of the series. In line type series it applies to the
     * line and the point markers unless otherwise specified. In bar type
     * series it applies to the bars unless a color is specified per point.
     * The default value is pulled from the `options.colors` array.
     *
     * In styled mode, the color can be defined by the
     * [colorIndex](#plotOptions.series.colorIndex) option. Also, the series
     * color can be set with the `.highcharts-series`,
     * `.highcharts-color-{n}`, `.highcharts-{type}-series` or
     * `.highcharts-series-{n}` class, or individual classes given by the
     * `className` option.
     *
     * @productdesc {highmaps}
     * In maps, the series color is rarely used, as most choropleth maps use
     * the color to denote the value of each point. The series color can
     * however be used in a map with multiple series holding categorized
     * data.
     *
     * @sample {highcharts} highcharts/plotoptions/series-color-general/
     *         General plot option
     * @sample {highcharts} highcharts/plotoptions/series-color-specific/
     *         One specific series
     * @sample {highcharts} highcharts/plotoptions/series-color-area/
     *         Area color
     * @sample {highcharts} highcharts/series/infographic/
     *         Pattern fill
     * @sample {highmaps} maps/demo/category-map/
     *         Category map by multiple series
     *
     * @basic
     * @type      {Highcharts.ColorType}
     * @apioption plotOptions.series.color
     */
    /**
     * Styled mode only. A specific color index to use for the series, so its
     * graphic representations are given the class name `highcharts-color-{n}`.
     *
     * Since v11, CSS variables on the form `--highcharts-color-{n}` make
     * changing the color scheme very convenient.
     *
     * @sample    {highcharts} highcharts/css/colorindex/
     *            Series and point color index
     *
     * @type      {number}
     * @since     5.0.0
     * @apioption plotOptions.series.colorIndex
     */
    /**
     * Whether to connect a graph line across null points, or render a gap
     * between the two points on either side of the null.
     *
     * In stacked area chart, if `connectNulls` is set to true,
     * null points are interpreted as 0.
     *
     * @sample {highcharts} highcharts/plotoptions/series-connectnulls-false/
     *         False by default
     * @sample {highcharts} highcharts/plotoptions/series-connectnulls-true/
     *         True
     *
     * @type      {boolean}
     * @default   false
     * @product   highcharts highstock
     * @apioption plotOptions.series.connectNulls
     */
    /**
     * You can set the cursor to "pointer" if you have click events attached
     * to the series, to signal to the user that the points and lines can
     * be clicked.
     *
     * In styled mode, the series cursor can be set with the same classes
     * as listed under [series.color](#plotOptions.series.color).
     *
     * @sample {highcharts} highcharts/plotoptions/series-cursor-line/
     *         On line graph
     * @sample {highcharts} highcharts/plotoptions/series-cursor-column/
     *         On columns
     * @sample {highcharts} highcharts/plotoptions/series-cursor-scatter/
     *         On scatter markers
     * @sample {highstock} stock/plotoptions/cursor/
     *         Pointer on a line graph
     * @sample {highmaps} maps/plotoptions/series-allowpointselect/
     *         Map area
     * @sample {highmaps} maps/plotoptions/mapbubble-allowpointselect/
     *         Map bubble
     *
     * @type      {string|Highcharts.CursorValue}
     * @apioption plotOptions.series.cursor
     */
    /**
     * A reserved subspace to store options and values for customized
     * functionality. Here you can add additional data for your own event
     * callbacks and formatter callbacks.
     *
     * @sample {highcharts} highcharts/point/custom/
     *         Point and series with custom data
     *
     * @type      {Highcharts.Dictionary<*>}
     * @apioption plotOptions.series.custom
     */
    /**
     * Name of the dash style to use for the graph, or for some series types
     * the outline of each shape.
     *
     * In styled mode, the
     * [stroke dash-array](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/series-dashstyle/)
     * can be set with the same classes as listed under
     * [series.color](#plotOptions.series.color).
     *
     * @sample {highcharts} highcharts/plotoptions/series-dashstyle-all/
     *         Possible values demonstrated
     * @sample {highcharts} highcharts/plotoptions/series-dashstyle/
     *         Chart suitable for printing in black and white
     * @sample {highstock} highcharts/plotoptions/series-dashstyle-all/
     *         Possible values demonstrated
     * @sample {highmaps} highcharts/plotoptions/series-dashstyle-all/
     *         Possible values demonstrated
     * @sample {highmaps} maps/plotoptions/series-dashstyle/
     *         Dotted borders on a map
     *
     * @type      {Highcharts.DashStyleValue}
     * @default   Solid
     * @since     2.1
     * @apioption plotOptions.series.dashStyle
     */
    /**
     * Deprecated. Use
     * [plotOptions.series.accessibility.description](#plotOptions.series.accessibility.description)
     * instead.
     *
     * A description of the series to add to the screen reader information
     * about the series.
     *
     * @deprecated 8.0.0
     * @type      {string}
     * @since     5.0.0
     * @requires  modules/accessibility
     * @apioption plotOptions.series.description
     */
    /**
     * The mapping between the data table and the series data points. This is
     * used in conjunction with the `dataTable` option (on [chart](#dataTable)
     * or [series](#plotOptions.series.dataTable) level) to map columns from the
     * data table to the properties of the data points. The keys of the
     * `dataMapping` object correspond to the properties of the data points
     * (e.g. `x`, `y`, `name`), and the values are objects that specify which
     * column from which data table to use for that property.
     *
     * The keys can also be nested paths, for example `dataLabel.format`, to map
     * to nested properties of the data points.
     *
     * The values can also be strings, in which case they are interpreted as
     * column id's from the first data table.
     *
     * A typical use case is that multiple series share a common column, like
     * `name` or `x`. In this case, to avoid repetition, the common column can
     * be applied in `plotOptions.series.dataMapping` and the individual series
     * can specify only the columns that are unique to them.
     *
     * The series name defaults to the column ID of the main data column in the
     * mapping. The main data column is typically the `y` data for cartesian
     * series, or `value` for map series. For example, if the mapping is
     * `{ y: 'Cost' }`, the series name will be `Cost`.
     *
     * ```js
     * // Shorthand mapping with string
     * dataMapping: {
     *     y: 'Cost'
     * }
     *
     * // Full mapping with object
     * dataMapping: {
     *    y: {
     *       // Optional, defaults to the first data table. Can be either a data
     *       // table index or id.
     *       dataTable: 'dataTable1',
     *       // Can be either a column index or id.
     *       column: 'Cost'
     *    }
     * }
     * ```
     *
     * If the columns of the DataTable have keys matching the series keys, the
     * data mapping is not necessary. For example, this DataTable will connect
     * directly to the series' `x` and `y` keys:
     *
     * ```js
     * const dataTable = new Highcharts.DataTable({
     *     columns: {
     *         x: ['2026-05-04', '2026-05-05', '2026-05-06'],
     *         y: [1, 4, 2]
     *     }
     *  });
     * ```
     *
     * @type    {Highcharts.DataMappingOptionsObject}
     * @sample {highcharts} highcharts/datatable/series-datatable-multiple
     *         Series with two data tables
     * @sample {highcharts} highcharts/datatable/nested-keys
     *         Nested keys
     * @sample {highcharts} highcharts/datatable/chart-datatable-multiple
     *         Chart with two data tables
     *
     * @since     13.0.0
     * @apioption plotOptions.series.dataMapping
     */
    /**
     * Enable or disable the mouse tracking for a specific series. This
     * includes point tooltips and click events on graphs and points. For
     * large datasets it improves performance.
     *
     * @sample {highcharts} highcharts/plotoptions/series-enablemousetracking-false/
     *         No mouse tracking
     * @sample {highmaps} maps/plotoptions/series-enablemousetracking-false/
     *         No mouse tracking
     *
     * @type      {boolean}
     * @default   true
     * @apioption plotOptions.series.enableMouseTracking
     */
    enableMouseTracking: true,
    /**
     * Whether to use the Y extremes of the total chart width or only the
     * zoomed area when zooming in on parts of the X axis. By default, the
     * Y axis adjusts to the min and max of the visible data. Cartesian
     * series only.
     *
     * @type      {boolean}
     * @default   false
     * @since     4.1.6
     * @product   highcharts highstock gantt
     * @apioption plotOptions.series.getExtremesFromAll
     */
    /**
     * Highlight only the hovered point and fade the remaining points.
     *
     * Scatter-type series require enabling the 'inactive' marker state and
     * adjusting opacity. Note that this approach could affect performance
     * with large datasets.
     *
     * @sample {highcharts} highcharts/plotoptions/series-inactiveotherpoints-enabled/
     *         Chart with inactiveOtherPoints option enabled.
     *
     * @type      {boolean}
     * @default   false
     * @apioption plotOptions.series.inactiveOtherPoints
     */
    /**
     * An array specifying which option maps to which key in the data point
     * array. This makes it convenient to work with unstructured data arrays
     * from different sources.
     *
     * @see [series.data](#series.line.data)
     *
     * @sample {highcharts|highstock} highcharts/series/data-keys/
     *         An extended data array with keys
     * @sample {highcharts|highstock} highcharts/series/data-nested-keys/
     *         Nested keys used to access object properties
     *
     * @type      {Array<string>}
     * @since     4.1.6
     * @apioption plotOptions.series.keys
     */
    /**
     * The line cap used for line ends and line joins on the graph.
     *
     * @productdesc {highcharts|highstock}
     * The SVG value used for the `stroke-linecap` and `stroke-linejoin`
     * of a line graph. Round means that lines are rounded in the ends and
     * bends.
     *
     * @sample highcharts/series-line/linecap/
     *         Line cap comparison
     *
     * @type       {Highcharts.SeriesLinecapValue}
     * @default    round
     * @since      3.0.7
     * @apioption  plotOptions.series.linecap
     */
    /**
     * The [id](#series.id) of another series to link to. Additionally,
     * the value can be ":previous" to link to the previous series. When
     * two series are linked, only the first one appears in the legend.
     * Toggling the visibility of this also toggles the linked series.
     *
     * If master series uses data sorting and linked series does not have
     * its own sorting definition, the linked series will be sorted in the
     * same order as the master one.
     *
     * If a `compare` value is not set on a linked series, it will be inherited
     * from the parent series.
     *
     * @sample {highcharts|highstock} highcharts/demo/arearange-line/
     *         Linked series
     *
     * @type      {string}
     * @since     3.0
     * @product   highcharts highstock gantt
     * @apioption plotOptions.series.linkedTo
     */
    /**
     * Options for the corresponding navigator series if `showInNavigator`
     * is `true` for this series. Available options are the same as any
     * series, documented at [plotOptions](#plotOptions.series) and
     * [series](#series).
     *
     * These options are merged with options in [navigator.series](
     * #navigator.series), and will take precedence if the same option is
     * defined both places.
     *
     * @see [navigator.series](#navigator.series)
     *
     * @type      {Highcharts.PlotSeriesOptions}
     * @since     5.0.0
     * @product   highstock
     * @apioption plotOptions.series.navigatorOptions
     */
    /**
     * The color for the parts of the graph or points that are below the
     * [threshold](#plotOptions.series.threshold). Note that `zones` takes
     * precedence over the negative color. Using `negativeColor` is
     * equivalent to applying a zone with value of 0.
     *
     * @see In styled mode, a negative color is applied by setting this option
     *      to `true` combined with the `.highcharts-negative` class name.
     *
     * @sample {highcharts} highcharts/plotoptions/series-negative-color/
     *         Spline, area and column
     * @sample {highcharts} highcharts/plotoptions/arearange-negativecolor/
     *         Arearange
     * @sample {highcharts} highcharts/css/series-negative-color/
     *         Styled mode
     * @sample {highstock} highcharts/plotoptions/series-negative-color/
     *         Spline, area and column
     * @sample {highstock} highcharts/plotoptions/arearange-negativecolor/
     *         Arearange
     * @sample {highmaps} highcharts/plotoptions/series-negative-color/
     *         Spline, area and column
     * @sample {highmaps} highcharts/plotoptions/arearange-negativecolor/
     *         Arearange
     *
     * @type      {Highcharts.ColorType}
     * @since     3.0
     * @apioption plotOptions.series.negativeColor
     */
    /**
     * Whether or not data-points with the value of `null` should be interactive.
     * When this is set to `true`, tooltips may highlight these points, and this
     * option also enables keyboard navigation for such points. Format options
     * for such points include [`nullFormat`](#tooltip.nullFormat) and
     * [`nullFormatter`](#tooltip.nullFormatter). Works for these series:
     * `line`, `spline`, `area`, `area-spline`, `column`, `bar`, and
     * `timeline`.
     *
     * @sample {highcharts} highcharts/series/null-interaction/
     *         Chart with interactive `null` points
     *
     * @sample {highcharts} highcharts/series-timeline/null-interaction/
     *         Timeline series with `null` points
     *
     * @type      {boolean|undefined}
     * @product   highcharts highstock
     * @apioption plotOptions.series.nullInteraction
     */
    /**
     * Deprecated. Use
     * [series.accessibility.point.descriptionFormat](#plotOptions.series.accessibility.point.descriptionFormat)
     * instead.
     *
     * Same as
     * [accessibility.point.descriptionFormat](#accessibility.point.descriptionFormat),
     * but for an individual series. Overrides the chart wide configuration.
     *
     * @type       {Function}
     * @requires   modules/accessibility
     * @since      11.1.0
     * @deprecated 12.6.0
     * @apioption  plotOptions.series.pointDescriptionFormat
     */
    /**
     * Deprecated. Use
     * [series.accessibility.point.descriptionFormatter](#plotOptions.series.accessibility.point.descriptionFormatter)
     * instead.
     *
     * Same as
     * [accessibility.series.descriptionFormatter](#accessibility.series.descriptionFormatter),
     * but for an individual series. Overrides the chart wide configuration.
     *
     * @type       {Function}
     * @requires   modules/accessibility
     * @since      5.0.12
     * @deprecated 8.0.0
     * @apioption  plotOptions.series.pointDescriptionFormatter
     */
    /**
     * If no x values are given for the points in a series, `pointInterval`
     * defines the interval of the x values. For example, if a series
     * contains one value every decade starting from year 0, set
     * `pointInterval` to `10`. In true `datetime` axes, the `pointInterval`
     * is set in milliseconds.
     *
     * It can be also be combined with `pointIntervalUnit` to draw irregular
     * time intervals.
     *
     * If combined with `relativeXValue`, an x value can be set on each
     * point, and the `pointInterval` is added x times to the `pointStart`
     * setting.
     *
     * Please note that this options applies to the _series data_, not the
     * interval of the axis ticks, which is independent.
     *
     * @sample {highcharts} highcharts/plotoptions/series-pointstart-datetime/
     *         Datetime X axis
     * @sample {highcharts} highcharts/plotoptions/series-relativexvalue/
     *         Relative x value
     * @sample {highstock} stock/plotoptions/pointinterval-pointstart/
     *         Using pointStart and pointInterval
     * @sample {highstock} stock/plotoptions/relativexvalue/
     *         Relative x value
     *
     * @type      {number}
     * @default   1
     * @product   highcharts highstock gantt
     * @apioption plotOptions.series.pointInterval
     */
    /**
     * On datetime series, this allows for setting the
     * [pointInterval](#plotOptions.series.pointInterval) to irregular time
     * units, `day`, `month` and `year`. A day is usually the same as 24
     * hours, but `pointIntervalUnit` also takes the DST crossover into
     * consideration when dealing with local time. Combine this option with
     * `pointInterval` to draw weeks, quarters, 6 months, 10 years etc.
     *
     * Please note that this options applies to the _series data_, not the
     * interval of the axis ticks, which is independent.
     *
     * @sample {highcharts} highcharts/plotoptions/series-pointintervalunit/
     *         One point a month
     * @sample {highstock} highcharts/plotoptions/series-pointintervalunit/
     *         One point a month
     *
     * @type       {string}
     * @since      4.1.0
     * @product    highcharts highstock gantt
     * @validvalue ["day", "month", "year"]
     * @apioption  plotOptions.series.pointIntervalUnit
     */
    /**
     * Possible values: `"on"`, `"between"`, `number`.
     *
     * In a column chart, when pointPlacement is `"on"`, the point will not
     * create any padding of the X axis. In a polar column chart this means
     * that the first column points directly north. If the pointPlacement is
     * `"between"`, the columns will be laid out between ticks. This is
     * useful for example for visualizing an amount between two points in
     * time or in a certain sector of a polar chart.
     *
     * Since Highcharts 3.0.2, the point placement can also be numeric,
     * where 0 is on the axis value, -0.5 is between this value and the
     * previous, and 0.5 is between this value and the next. Unlike the
     * textual options, numeric point placement options won't affect axis
     * padding.
     *
     * Note that pointPlacement needs a [pointRange](
     * #plotOptions.series.pointRange) to work. For column series this is
     * computed, but for line-type series it needs to be set.
     *
     * For the `xrange` series type and gantt charts, if the Y axis is a
     * category axis, the `pointPlacement` applies to the Y axis rather than
     * the (typically datetime) X axis.
     *
     * Defaults to `undefined` in cartesian charts, `"between"` in polar
     * charts.
     *
     * @see [xAxis.tickmarkPlacement](#xAxis.tickmarkPlacement)
     *
     * @sample {highcharts|highstock} highcharts/plotoptions/series-pointplacement-between/
     *         Between in a column chart
     * @sample {highcharts|highstock} highcharts/plotoptions/series-pointplacement-numeric/
     *         Numeric placement for custom layout
     * @sample {highcharts|highstock} maps/plotoptions/heatmap-pointplacement/
     *         Placement in heatmap
     *
     * @type      {string|number}
     * @since     2.3.0
     * @product   highcharts highstock gantt
     * @apioption plotOptions.series.pointPlacement
     */
    /**
     * If no x values are given for the points in a series, `pointStart`
     * defines on what value to start. For example, if a series contains one
     * yearly value starting from 1945, set `pointStart` to 1945.
     *
     * The `pointStart` setting can be a number, or a datetime string that is
     * parsed according to the `time.timezone` setting.
     *
     * If combined with `relativeXValue`, an x value can be set on each
     * point. The x value from the point options is multiplied by
     * `pointInterval` and added to `pointStart` to produce a modified x
     * value.
     *
     * @sample {highcharts} highcharts/plotoptions/series-pointstart-linear/
     *         Linear
     * @sample {highcharts} highcharts/plotoptions/series-pointstart-datetime/
     *         Datetime
     * @sample {highcharts} highcharts/plotoptions/series-relativexvalue/
     *         Relative x value
     * @sample {highstock} stock/plotoptions/pointinterval-pointstart/
     *         Using pointStart and pointInterval
     * @sample {highstock} stock/plotoptions/relativexvalue/
     *         Relative x value
     *
     * @type      {number|string}
     * @default   0
     * @product   highcharts highstock gantt
     * @apioption plotOptions.series.pointStart
     */
    /**
     * When true, X values in the data set are relative to the current
     * `pointStart`, `pointInterval` and `pointIntervalUnit` settings. This
     * allows compression of the data for datasets with irregular X values.
     *
     * The real X values are computed on the formula `f(x) = ax + b`, where
     * `a` is the `pointInterval` (optionally with a time unit given by
     * `pointIntervalUnit`), and `b` is the `pointStart`.
     *
     * @sample {highcharts} highcharts/plotoptions/series-relativexvalue/
     *         Relative X value
     * @sample {highstock} stock/plotoptions/relativexvalue/
     *         Relative X value
     *
     * @type      {boolean}
     * @default   false
     * @product   highcharts highstock
     * @apioption plotOptions.series.relativeXValue
     */
    /**
     * Whether to select the series initially. If `showCheckbox` is true,
     * the checkbox next to the series name in the legend will be checked
     * for a selected series.
     *
     * @sample {highcharts} highcharts/plotoptions/series-selected/
     *         One out of two series selected
     *
     * @type      {boolean}
     * @default   false
     * @since     1.2.0
     * @apioption plotOptions.series.selected
     */
    /**
     * Whether to apply a drop shadow to the graph line. Since 2.3 the
     * shadow can be an object configuration containing `color`, `offsetX`,
     * `offsetY`, `opacity` and `width`.
     *
     * Note that in some cases, like stacked columns or other dense layouts, the
     * series may cast shadows on each other. In that case, the
     * `chart.seriesGroupShadow` allows applying a common drop shadow to the
     * whole series group.
     *
     * @sample {highcharts} highcharts/plotoptions/series-shadow/
     *         Shadow enabled
     *
     * @type      {boolean|Highcharts.ShadowOptionsObject}
     * @default   false
     * @apioption plotOptions.series.shadow
     */
    /**
     * Whether to display this particular series or series type in the
     * legend. Standalone series are shown in legend by default, and linked
     * series are not. Since v7.2.0 it is possible to show series that use
     * colorAxis by setting this option to `true`.
     *
     * @sample {highcharts} highcharts/plotoptions/series-showinlegend/
     *         One series in the legend, one hidden
     *
     * @type      {boolean}
     * @apioption plotOptions.series.showInLegend
     */
    /**
     * Whether or not to show the series in the navigator. Takes precedence
     * over [navigator.baseSeries](#navigator.baseSeries) if defined.
     *
     * @type      {boolean}
     * @since     5.0.0
     * @product   highstock
     * @apioption plotOptions.series.showInNavigator
     */
    /**
     * Deprecated. Use
     * [series.accessibility.keyboardNavigation](#plotOptions.series.accessibility.keyboardNavigation)
     * instead.
     *
     * If set to `true`, the accessibility module will skip past the points
     * in this series for keyboard navigation.
     *
     * @type       {boolean}
     * @since      5.0.12
     * @deprecated 8.0.0
     * @apioption  plotOptions.series.skipKeyboardNavigation
     */
    /**
     * This option allows grouping series in a stacked chart. The stack option
     * can be a string or anything else, as long as the grouped series' stack
     * options match each other after conversion into a string.
     *
     * @sample {highcharts} highcharts/series/stack/
     *         Stacked and grouped columns
     * @sample {highcharts} highcharts/series/stack-centerincategory/
     *         Stacked and grouped, centered in category
     *
     * @type      {number|string}
     * @since     2.1
     * @product   highcharts highstock
     * @apioption series.stack
     */
    /**
     * Whether to stack the values of each series on top of each other.
     * Possible values are null to disable, `"normal"` to stack by
     * value or `"percent"`.
     *
     * When stacking is enabled, data must be sorted
     * in ascending X order.
     *
     * Some stacking options are related to specific series types. In the
     * streamgraph series type, the stacking option is set to `"stream"`.
     * The second one is `"overlap"`, which only applies to waterfall
     * series.
     *
     * @see [yAxis.reversedStacks](#yAxis.reversedStacks)
     *
     * @sample {highcharts} highcharts/plotoptions/series-stacking-line/
     *         Line
     * @sample {highcharts} highcharts/plotoptions/series-stacking-column/
     *         Column
     * @sample {highcharts} highcharts/plotoptions/series-stacking-bar/
     *         Bar
     * @sample {highcharts} highcharts/plotoptions/series-stacking-area/
     *         Area
     * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-line/
     *         Line
     * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-column/
     *         Column
     * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-bar/
     *         Bar
     * @sample {highcharts} highcharts/plotoptions/series-stacking-percent-area/
     *         Area
     * @sample {highcharts} highcharts/plotoptions/series-waterfall-with-normal-stacking
     *         Waterfall with normal stacking
     * @sample {highcharts} highcharts/plotoptions/series-waterfall-with-overlap-stacking
     *         Waterfall with overlap stacking
     * @sample {highstock} stock/plotoptions/stacking/
     *         Area
     *
     * @type       {string}
     * @default    null
     * @product    highcharts highstock
     * @validvalue ["normal", "overlap", "percent", "stream", null]
     * @apioption  plotOptions.series.stacking
     */
    /**
     * Whether to apply steps to the line. Possible values are `left`,
     * `center` and `right`.
     *
     * @sample {highcharts} highcharts/plotoptions/line-step/
     *         Different step line options
     * @sample {highcharts} highcharts/plotoptions/area-step/
     *         Stepped, stacked area
     * @sample {highstock} stock/plotoptions/line-step/
     *         Step line
     *
     * @type       {string}
     * @since      1.2.5
     * @product    highcharts highstock
     * @validvalue ["left", "center", "right"]
     * @apioption  plotOptions.series.step
     */
    /**
     * The threshold, also called zero level or base level. For line type
     * series this is only used in conjunction with
     * [negativeColor](#plotOptions.series.negativeColor).
     *
     * @see [softThreshold](#plotOptions.series.softThreshold).
     *
     * @type      {number|null}
     * @default   0
     * @since     3.0
     * @product   highcharts highstock
     * @apioption plotOptions.series.threshold
     */
    /**
     * Set the initial visibility of the series.
     *
     * @sample {highcharts} highcharts/plotoptions/series-visible/
     *         Two series, one hidden and one visible
     * @sample {highstock} stock/plotoptions/series-visibility/
     *         Hidden series
     *
     * @type      {boolean}
     * @default   true
     * @apioption plotOptions.series.visible
     */
    /**
     * Defines the Axis on which the zones are applied.
     *
     * @see [zones](#plotOptions.series.zones)
     *
     * @sample {highcharts} highcharts/series/color-zones-zoneaxis-x/
     *         Zones on the X-Axis
     * @sample {highstock} highcharts/series/color-zones-zoneaxis-x/
     *         Zones on the X-Axis
     *
     * @type      {string}
     * @default   y
     * @since     4.1.0
     * @product   highcharts highstock
     * @apioption plotOptions.series.zoneAxis
     */
    /**
     * General event handlers for the series items. These event hooks can
     * also be attached to the series at run time using the
     * `Highcharts.addEvent` function.
     *
     * @basic
     * @declare Highcharts.SeriesEventsOptionsObject
     */
    events: {},
    /**
     * Fires after the series has finished its initial animation, or in case
     * animation is disabled, immediately as the series is displayed.
     *
     * @sample {highcharts} highcharts/plotoptions/series-events-afteranimate/
     *         Show label after animate
     * @sample {highstock} highcharts/plotoptions/series-events-afteranimate/
     *         Show label after animate
     *
     * @type      {Highcharts.SeriesAfterAnimateCallbackFunction}
     * @since     4.0
     * @product   highcharts highstock gantt
     * @context   Highcharts.Series
     * @apioption plotOptions.series.events.afterAnimate
     */
    /**
     * Fires when the checkbox next to the series' name in the legend is
     * clicked. One parameter, `event`, is passed to the function. The state
     * of the checkbox is found by `event.checked`. The checked item is
     * found by `event.item`. Return `false` to prevent the default action
     * which is to toggle the select state of the series.
     *
     * @sample {highcharts} highcharts/plotoptions/series-events-checkboxclick/
     *         Alert checkbox status
     *
     * @type      {Highcharts.SeriesCheckboxClickCallbackFunction}
     * @since     1.2.0
     * @context   Highcharts.Series
     * @apioption plotOptions.series.events.checkboxClick
     */
    /**
     * Fires when the series is clicked. One parameter, `event`, is passed
     * to the function, containing common event information. Additionally,
     * `event.point` holds a pointer to the nearest point on the graph.
     *
     * @sample {highcharts} highcharts/plotoptions/series-events-click/
     *         Alert click info
     * @sample {highstock} stock/plotoptions/series-events-click/
     *         Alert click info
     * @sample {highmaps} maps/plotoptions/series-events-click/
     *         Display click info in subtitle
     *
     * @type      {Highcharts.SeriesClickCallbackFunction}
     * @context   Highcharts.Series
     * @apioption plotOptions.series.events.click
     */
    /**
     * Fires when the series is hidden after chart generation time, either
     * by clicking the legend item or by calling `.hide()`.
     *
     * @sample {highcharts} highcharts/plotoptions/series-events-hide/
     *         Alert when the series is hidden by clicking the legend item
     *
     * @type      {Highcharts.SeriesHideCallbackFunction}
     * @since     1.2.0
     * @context   Highcharts.Series
     * @apioption plotOptions.series.events.hide
     */
    /**
     * Fires when the legend item belonging to the series is clicked. One
     * parameter, `event`, is passed to the function. The default action
     * is to toggle the visibility of the series. This can be prevented
     * by returning `false` or calling `event.preventDefault()`.
     *
     * **Note:** This option is deprecated in favor of
     * [legend.events.itemClick](#legend.events.itemClick).
     *
     * @type       {Highcharts.SeriesLegendItemClickCallbackFunction}
     * @deprecated 11.4.4
     * @context    Highcharts.Series
     * @apioption  plotOptions.series.events.legendItemClick
     */
    /**
     * Fires when the mouse leaves the graph. One parameter, `event`, is
     * passed to the function, containing common event information. If the
     * [stickyTracking](#plotOptions.series) option is true, `mouseOut`
     * doesn't happen before the mouse enters another graph or leaves the
     * plot area.
     *
     * @sample {highcharts} highcharts/plotoptions/series-events-mouseover-sticky/
     *         With sticky tracking by default
     * @sample {highcharts} highcharts/plotoptions/series-events-mouseover-no-sticky/
     *         Without sticky tracking
     *
     * @type      {Highcharts.SeriesMouseOutCallbackFunction}
     * @context   Highcharts.Series
     * @apioption plotOptions.series.events.mouseOut
     */
    /**
     * Fires when the mouse enters the graph. One parameter, `event`, is
     * passed to the function, containing common event information.
     *
     * @sample {highcharts} highcharts/plotoptions/series-events-mouseover-sticky/
     *         With sticky tracking by default
     * @sample {highcharts} highcharts/plotoptions/series-events-mouseover-no-sticky/
     *         Without sticky tracking
     *
     * @type      {Highcharts.SeriesMouseOverCallbackFunction}
     * @context   Highcharts.Series
     * @apioption plotOptions.series.events.mouseOver
     */
    /**
     * Fires when the series is shown after chart generation time, either
     * by clicking the legend item or by calling `.show()`.
     *
     * @sample {highcharts} highcharts/plotoptions/series-events-show/
     *         Alert when the series is shown by clicking the legend item.
     *
     * @type      {Highcharts.SeriesShowCallbackFunction}
     * @since     1.2.0
     * @context   Highcharts.Series
     * @apioption plotOptions.series.events.show
     */
    /**
     * Options for the point markers of line and scatter-like series. Properties
     * like `fillColor`, `lineColor` and `lineWidth` define the visual
     * appearance of the markers. The `symbol` option defines the shape. Other
     * series types, like column series, don't have markers, but have visual
     * options on the series level instead.
     *
     * In styled mode, the markers can be styled with the `.highcharts-point`,
     * `.highcharts-point-hover` and `.highcharts-point-select` class names.
     *
     * @declare Highcharts.PointMarkerOptionsObject
     *
     * @sample {highmaps} maps/demo/mappoint-mapmarker
     *         Using the mapmarker symbol for points
     */
    marker: {
        /**
         * Enable or disable the point marker. If `undefined`, the markers
         * are hidden when the data is dense, and shown for more widespread
         * data points.
         *
         * @sample {highcharts} highcharts/plotoptions/series-marker-enabled/
         *         Disabled markers
         * @sample {highcharts} highcharts/plotoptions/series-marker-enabled-false/
         *         Disabled in normal state but enabled on hover
         * @sample {highstock} stock/plotoptions/series-marker/
         *         Enabled markers
         *
         * @type      {boolean}
         * @default   {highcharts} undefined
         * @default   {highstock} false
         * @apioption plotOptions.series.marker.enabled
         */
        /**
         * The threshold for how dense the point markers should be before
         * they are hidden, given that `enabled` is not defined. The number
         * indicates the horizontal distance between the two closest points
         * in the series, as multiples of the `marker.radius`. In other
         * words, the default value of 2 means points are hidden if
         * overlapping horizontally.
         *
         * @sample highcharts/plotoptions/series-marker-enabledthreshold
         *         A higher threshold
         *
         * @since 6.0.5
         */
        enabledThreshold: 2,
        /**
         * The fill color of the point marker. When `undefined`, the series'
         * or point's color is used.
         *
         * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/
         *         White fill
         *
         * @type      {Highcharts.ColorType}
         * @apioption plotOptions.series.marker.fillColor
         */
        /**
         * Image markers only. Set the image width explicitly. When using
         * this option, a `width` must also be set.
         *
         * @sample {highcharts} highcharts/plotoptions/series-marker-width-height/
         *         Fixed width and height
         * @sample {highstock} highcharts/plotoptions/series-marker-width-height/
         *         Fixed width and height
         *
         * @type      {number}
         * @since     4.0.4
         * @apioption plotOptions.series.marker.height
         */
        /**
         * The color of the point marker's outline. When `undefined`, the
         * series' or point's color is used.
         *
         * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/
         *         Inherit from series color (undefined)
         *
         * @type {Highcharts.ColorType}
         */
        lineColor: 'var(--highcharts-background-color)',
        /**
         * The width of the point marker's outline.
         *
         * @sample {highcharts} highcharts/plotoptions/series-marker-fillcolor/
         *         2px blue marker
         */
        lineWidth: 0,
        /**
         * The radius of the point marker.
         *
         * @sample {highcharts} highcharts/plotoptions/series-marker-radius/
         *         Bigger markers
         *
         * @default {highstock} 2
         * @default {highcharts} 4
         *
         */
        radius: 4,
        /**
         * A predefined shape or symbol for the marker. When undefined, the
         * symbol is pulled from options.symbols. Other possible values are
         * `'circle'`, `'square'`,`'diamond'`, `'triangle'` and
         * `'triangle-down'`.
         *
         * Additionally, the URL to a graphic can be given on this form:
         * `'url(graphic.png)'`. Note that for the image to be applied to
         * exported charts, its URL needs to be accessible by the export
         * server.
         *
         * Custom callbacks for symbol path generation can also be added to
         * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then
         * used by its method name, as shown in the demo.
         *
         * @sample {highcharts} highcharts/plotoptions/series-marker-symbol/
         *         Predefined, graphic and custom markers
         * @sample {highstock} highcharts/plotoptions/series-marker-symbol/
         *         Predefined, graphic and custom markers
         * @sample {highmaps} maps/demo/mappoint-mapmarker
         *         Using the mapmarker symbol for points
         *
         * @type      {string}
         * @apioption plotOptions.series.marker.symbol
         */
        /**
         * Image markers only. Set the image width explicitly. When using
         * this option, a `height` must also be set.
         *
         * @sample {highcharts} highcharts/plotoptions/series-marker-width-height/
         *         Fixed width and height
         * @sample {highstock} highcharts/plotoptions/series-marker-width-height/
         *         Fixed width and height
         *
         * @type      {number}
         * @since     4.0.4
         * @apioption plotOptions.series.marker.width
         */
        /**
         * States for a single point marker.
         *
         * @declare Highcharts.PointStatesOptionsObject
         */
        states: {
            /**
             * The normal state of a single point marker. Currently only
             * used for setting animation when returning to normal state
             * from hover.
             *
             * @declare Highcharts.PointStatesNormalOptionsObject
             */
            normal: {
                /**
                 * Animation when returning to normal state after hovering.
                 *
                 * @type {boolean|Partial<Highcharts.AnimationOptionsObject>}
                 */
                animation: true
            },
            /**
             * The hover state for a single point marker.
             *
             * @declare Highcharts.PointStatesHoverOptionsObject
             */
            hover: {
                /**
                 * Animation when hovering over the marker.
                 *
                 * @type {boolean|Partial<Highcharts.AnimationOptionsObject>}
                 */
                animation: {
                    duration: 150
                },
                /**
                 * Enable or disable the point marker.
                 *
                 * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-enabled/
                 *         Disabled hover state
                 */
                enabled: true,
                /**
                 * The fill color of the marker in hover state. When
                 * `undefined`, the series' or point's fillColor for normal
                 * state is used.
                 *
                 * @type      {Highcharts.ColorType}
                 * @apioption plotOptions.series.marker.states.hover.fillColor
                 */
                /**
                 * The color of the point marker's outline. When
                 * `undefined`, the series' or point's lineColor for normal
                 * state is used.
                 *
                 * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-linecolor/
                 *         White fill color, black line color
                 *
                 * @type      {Highcharts.ColorType}
                 * @apioption plotOptions.series.marker.states.hover.lineColor
                 */
                /**
                 * The width of the point marker's outline. When
                 * `undefined`, the series' or point's lineWidth for normal
                 * state is used.
                 *
                 * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-linewidth/
                 *         3px line width
                 *
                 * @type      {number}
                 * @apioption plotOptions.series.marker.states.hover.lineWidth
                 */
                /**
                 * The radius of the point marker. In hover state, it
                 * defaults to the normal state's radius + 2 as per the
                 * [radiusPlus](#plotOptions.series.marker.states.hover.radiusPlus)
                 * option.
                 *
                 * @sample {highcharts} highcharts/plotoptions/series-marker-states-hover-radius/
                 *         10px radius
                 *
                 * @type      {number}
                 * @apioption plotOptions.series.marker.states.hover.radius
                 */
                /**
                 * The number of pixels to increase the radius of the
                 * hovered point.
                 *
                 * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/
                 *         5 pixels greater radius on hover
                 * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/
                 *         5 pixels greater radius on hover
                 *
                 * @since 4.0.3
                 */
                radiusPlus: 2,
                /**
                 * The additional line width for a hovered point.
                 *
                 * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/
                 *         2 pixels wider on hover
                 * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/
                 *         2 pixels wider on hover
                 *
                 * @since 4.0.3
                 */
                lineWidthPlus: 1
            },
            /**
             * The appearance of the point marker when selected. In order to
             * allow a point to be selected, set the
             * `series.allowPointSelect` option to true.
             *
             * @declare Highcharts.PointStatesSelectOptionsObject
             */
            select: {
                /**
                 * Enable or disable visible feedback for selection.
                 *
                 * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-enabled/
                 *         Disabled select state
                 *
                 * @type      {boolean}
                 * @default   true
                 * @apioption plotOptions.series.marker.states.select.enabled
                 */
                /**
                 * The radius of the point marker. In hover state, it
                 * defaults to the normal state's radius + 2.
                 *
                 * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-radius/
                 *         10px radius for selected points
                 *
                 * @type      {number}
                 * @apioption plotOptions.series.marker.states.select.radius
                 */
                /**
                 * The fill color of the point marker.
                 *
                 * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-fillcolor/
                 *         Solid red discs for selected points
                 *
                 * @type {Highcharts.ColorType}
                 */
                fillColor: 'var(--highcharts-neutral-color-20)',
                /**
                 * The color of the point marker's outline. When
                 * `undefined`, the series' or point's color is used.
                 *
                 * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-linecolor/
                 *         Red line color for selected points
                 *
                 * @type {Highcharts.ColorType}
                 */
                lineColor: 'var(--highcharts-neutral-color-100)',
                /**
                 * The width of the point marker's outline.
                 *
                 * @sample {highcharts} highcharts/plotoptions/series-marker-states-select-linewidth/
                 *         3px line width for selected points
                 */
                lineWidth: 2
            }
        }
    },
    /**
     * Properties for each single point.
     *
     * @declare Highcharts.PlotSeriesPointOptions
     */
    point: {
        /**
         * Fires when a point is clicked. One parameter, `event`, is passed
         * to the function, containing common event information.
         *
         * If the `series.allowPointSelect` option is true, the default
         * action for the point's click event is to toggle the point's
         * select state. Returning `false` cancels this action.
         *
         * @sample {highcharts} highcharts/plotoptions/series-point-events-click/
         *         Click marker to alert values
         * @sample {highcharts} highcharts/plotoptions/series-point-events-click-column/
         *         Click column
         * @sample {highcharts} highcharts/plotoptions/series-point-events-click-url/
         *         Go to URL
         * @sample {highmaps} maps/plotoptions/series-point-events-click/
         *         Click marker to display values
         * @sample {highmaps} maps/plotoptions/series-point-events-click-url/
         *         Go to URL
         *
         * @type      {Highcharts.PointClickCallbackFunction}
         * @context   Highcharts.Point
         * @apioption plotOptions.series.point.events.click
         */
        /**
         * Fires when the mouse leaves the area close to the point. One
         * parameter, `event`, is passed to the function, containing common
         * event information.
         *
         * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/
         *         Show values in the chart's corner on mouse over
         *
         * @type      {Highcharts.PointMouseOutCallbackFunction}
         * @context   Highcharts.Point
         * @apioption plotOptions.series.point.events.mouseOut
         */
        /**
         * Fires when the mouse enters the area close to the point. One
         * parameter, `event`, is passed to the function, containing common
         * event information.
         *
         * Returning `false` cancels the default behavior, which is to show a
         * tooltip for the point.
         *
         * @sample {highcharts} highcharts/plotoptions/series-point-events-mouseover/
         *         Show values in the chart's corner on mouse over
         *
         * @type      {Highcharts.PointMouseOverCallbackFunction}
         * @context   Highcharts.Point
         * @apioption plotOptions.series.point.events.mouseOver
         */
        /**
         * Fires when the point is removed using the `.remove()` method. One
         * parameter, `event`, is passed to the function. Returning `false`
         * cancels the operation.
         *
         * @sample {highcharts} highcharts/plotoptions/series-point-events-remove/
         *         Remove point and confirm
         *
         * @type      {Highcharts.PointRemoveCallbackFunction}
         * @since     1.2.0
         * @context   Highcharts.Point
         * @apioption plotOptions.series.point.events.remove
         */
        /**
         * Fires when the point is selected either programmatically or
         * following a click on the point. One parameter, `event`, is passed
         * to the function. Returning `false` cancels the operation.
         *
         * @sample {highcharts} highcharts/plotoptions/series-point-events-select/
         *         Report the last selected point
         * @sample {highmaps} maps/plotoptions/series-allowpointselect/
         *         Report select and unselect
         *
         * @type      {Highcharts.PointSelectCallbackFunction}
         * @since     1.2.0
         * @context   Highcharts.Point
         * @apioption plotOptions.series.point.events.select
         */
        /**
         * Fires when the point is unselected either programmatically or
         * following a click on the point. One parameter, `event`, is passed
         * to the function.
         *  Returning `false` cancels the operation.
         *
         * @sample {highcharts} highcharts/plotoptions/series-point-events-unselect/
         *         Report the last unselected point
         * @sample {highmaps} maps/plotoptions/series-allowpointselect/
         *         Report select and unselect
         *
         * @type      {Highcharts.PointUnselectCallbackFunction}
         * @since     1.2.0
         * @context   Highcharts.Point
         * @apioption plotOptions.series.point.events.unselect
         */
        /**
         * Fires when the point is updated programmatically through the
         * `.update()` method. One parameter, `event`, is passed to the
         * function. The new point options can be accessed through
         * `event.options`. Returning `false` cancels the operation.
         *
         * @sample {highcharts} highcharts/plotoptions/series-point-events-update/
         *         Confirm point updating
         *
         * @type      {Highcharts.PointUpdateCallbackFunction}
         * @since     1.2.0
         * @context   Highcharts.Point
         * @apioption plotOptions.series.point.events.update
         */
        /**
         * Events for each single point.
         *
         * @declare Highcharts.PointEventsOptionsObject
         */
        events: {}
    },
    /**
     * Options for the series data labels, appearing next to each data point.
     *
     * Since v6.2.0, multiple data labels can be applied to each single
     * point by defining them as an array of configs.
     *
     * In styled mode, the data labels can be styled with the
     * `.highcharts-data-label-box` and `.highcharts-data-label` class names
     * ([see example](https://www.highcharts.com/samples/highcharts/css/series-datalabels)).
     *
     * @sample {highcharts} highcharts/plotoptions/series-datalabels-enabled
     *         Data labels enabled
     * @sample {highcharts} highcharts/plotoptions/series-datalabels-multiple
     *         Multiple data labels on a bar series
     * @sample {highcharts} highcharts/css/series-datalabels
     *         Styled mode example
     * @sample {highmaps} maps/demo/color-axis
     *         Choropleth map with data labels
     * @sample {highmaps} maps/demo/mappoint-datalabels-mapmarker
     *         Using data labels as map markers
     *
     * @type    {*|Array<*>}
     * @product highcharts highstock highmaps gantt
     */
    dataLabels: {
        /**
         * Enable or disable the initial animation when a series is displayed
         * for the `dataLabels`. The animation can also be set as a
         * configuration object. Please note that this option only applies to
         * the initial animation.
         *
         * For other animations, see [chart.animation](#chart.animation) and the
         * animation parameter under the API methods. The following properties
         * are supported:
         *
         * - `defer`: The animation delay time in milliseconds.
         *
         * @sample {highcharts} highcharts/plotoptions/animation-defer/
         *          Animation defer settings
         *
         * @type      {boolean|Partial<Highcharts.AnimationOptionsObject>}
         * @since     8.2.0
         * @apioption plotOptions.series.dataLabels.animation
         */
        animation: {},
        /**
         * The animation delay time in milliseconds. Set to `0` to render the
         * data labels immediately. As `undefined` inherits defer time from the
         * [series.animation.defer](#plotOptions.series.animation.defer).
         *
         * @type      {number}
         * @since     8.2.0
         * @apioption plotOptions.series.dataLabels.animation.defer
         */
        /**
         * The alignment of the data label compared to the point. If `right`,
         * the right side of the label should be touching the point. For points
         * with an extent, like columns, the alignments also dictates how to
         * align it inside the box, as given with the
         * [inside](#plotOptions.column.dataLabels.inside) option. Can be one of
         * `left`, `center` or `right`.
         *
         * @sample {highcharts}
         *         highcharts/plotoptions/series-datalabels-align-left/ Left
         *         aligned
         * @sample {highcharts}
         *         highcharts/plotoptions/bar-datalabels-align-inside-bar/ Data
         *         labels inside the bar
         *
         * @type {Highcharts.AlignValue|null}
         */
        align: 'center',
        /**
         * Alignment method for data labels. If set to `plotEdges`, the labels
         * are aligned within the plot area in the direction of the y-axis. So
         * in a regular column chart, the labels are aligned vertically
         * according to the `verticalAlign` setting. In a bar chart, which is
         * inverted, the labels are aligned horizontally according to the
         * `align` setting. Applies to cartesian series only.
         *
         * @sample {highcharts} highcharts/series-bar/datalabels-alignto/
         *         Align to plot edges
         *
         * @type      {string}
         * @since 11.4.2
         * @apioption plotOptions.series.dataLabels.alignTo
         */
        /**
         * Whether to allow data labels to overlap. To make the labels less
         * sensitive for overlapping, the
         * [dataLabels.padding](#plotOptions.series.dataLabels.padding)
         * can be set to 0.
         *
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-allowoverlap-false/
         *         Don't allow overlap
         *
         * @type      {boolean}
         * @default   false
         * @since     4.1.0
         * @apioption plotOptions.series.dataLabels.allowOverlap
         */
        /**
         * The background color or gradient for the data label. In addition to
         * regular colors, there are two special setting for this option:
         * - `auto` will set the background color the point's color.
         * - `contrast` will set it to a contrast against the text color, with
         *   an opacity allowing to see the underlying content. The contrast is
         *   great enough to ensure readability for the text according to
         *   accessibility standards.
         *
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/
         *         Data labels box options
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-background-contrast/
         *         Contrast background color in stacked column
         * @sample {highcharts} highcharts/series-pie/datalabels-background-contrast/
         *         Contrast background color in pie
         * @sample {highmaps} maps/plotoptions/series-datalabels-box/
         *         Data labels box options
         * @sample {highmaps} maps/demo/mappoint-datalabels-mapmarker
         *         Data labels as map markers
         *
         * @type      {Highcharts.ColorType}
         * @since     2.2.1
         * @apioption plotOptions.series.dataLabels.backgroundColor
         */
        /**
         * The border color for the data label. Setting it to `auto` will use
         * the point's color. Defaults to `undefined`.
         *
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/
         *         Data labels box options
         *
         * @type      {Highcharts.ColorType}
         * @since     2.2.1
         * @apioption plotOptions.series.dataLabels.borderColor
         */
        /**
         * The border radius in pixels for the data label.
         *
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/
         *         Data labels box options
         * @sample {highmaps} maps/plotoptions/series-datalabels-box/
         *         Data labels box options
         *
         * @type      {number}
         * @default   3
         * @since     2.2.1
         * @apioption plotOptions.series.dataLabels.borderRadius
         */
        /**
         * The border width in pixels for the data label.
         *
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/
         *         Data labels box options
         *
         * @type      {number}
         * @default   0
         * @since     2.2.1
         * @apioption plotOptions.series.dataLabels.borderWidth
         */
        borderWidth: 0,
        /**
         * A class name for the data label. Particularly in styled mode,
         * this can be used to give each series' or point's data label
         * unique styling. In addition to this option, a default color class
         * name is added so that we can give the labels a contrast text
         * shadow.
         *
         * @sample {highcharts} highcharts/css/data-label-contrast/
         *         Contrast text shadow
         * @sample {highcharts} highcharts/css/series-datalabels/
         *         Styling by CSS
         *
         * @type      {string}
         * @since     5.0.0
         * @apioption plotOptions.series.dataLabels.className
         */
        /**
         * This options is deprecated.
         * Use [style.color](#plotOptions.series.dataLabels.style) instead.
         *
         * The text color for the data labels. Defaults to `undefined`. For
         * certain series types, like column or map, the data labels can be
         * drawn inside the points. In this case the data label will be
         * drawn with maximum contrast by default. Additionally, it will be
         * given a `text-outline` style with the opposite color, to further
         * increase the contrast. This can be overridden by setting the
         * `text-outline` style to `none` in the `dataLabels.style` option.
         *
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-color/
         *         Red data labels
         * @sample {highmaps} maps/demo/color-axis/
         *         White data labels
         *
         * @see [style.color](#plotOptions.series.dataLabels.style)
         *
         * @type       {Highcharts.ColorType}
         * @deprecated 10.3
         * @apioption  plotOptions.series.dataLabels.color
         */
        /**
         * Whether to hide data labels that are outside the plot area. By
         * default, the data label is moved inside the plot area according
         * to the
         * [overflow](#plotOptions.series.dataLabels.overflow)
         * option.
         *
         * @type      {boolean}
         * @default   true
         * @since     2.3.3
         * @apioption plotOptions.series.dataLabels.crop
         */
        /**
         * Whether to defer displaying the data labels until the initial
         * series animation has finished. Setting to `false` renders the
         * data label immediately. If set to `true` inherits the defer
         * time set in [plotOptions.series.animation](#plotOptions.series.animation).
         *
         * @since     4.0.0
         * @type      {boolean}
         * @product   highcharts highstock gantt
         */
        defer: true,
        /**
         * The distance of the data label from the data point. Note that the
         * `padding` setting also affects the rendered distance, but is not
         * visible unless the data label has a border or background.
         *
         * @type      {number}
         * @product   highcharts highstock gantt
         */
        distance: 4,
        /**
         * Enable or disable the data labels.
         *
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-enabled/
         *         Data labels enabled
         * @sample {highmaps} maps/demo/color-axis/
         *         Data labels enabled
         *
         * @type      {boolean}
         * @default   false
         * @apioption plotOptions.series.dataLabels.enabled
         */
        /**
         * A declarative filter to control of which data labels to display.
         * The declarative filter is designed for use when callback
         * functions are not available, like when the chart options require
         * a pure JSON structure or for use with graphical editors. For
         * programmatic control, use the `formatter` instead, and return
         * `undefined` to disable a single data label.
         *
         * ```js
         * filter: {
         *     property: 'percentage',
         *     operator: '>',
         *     value: 4
         * }
         * ```
         *
         * @sample {highcharts} highcharts/demo/pie-monochrome
         *         Data labels filtered by percentage
         *
         * @declare   Highcharts.DataLabelsFilterOptionsObject
         * @since     6.0.3
         * @apioption plotOptions.series.dataLabels.filter
         */
        /**
         * The operator to compare by. Can be one of `>`, `<`, `>=`, `<=`,
         * `==`, `===`, `!=` and `!==`.
         *
         * @type       {string}
         * @validvalue [">", "<", ">=", "<=", "==", "===", "!=", "!=="]
         * @apioption  plotOptions.series.dataLabels.filter.operator
         */
        /**
         * The point property to filter by. Point options are passed
         * directly to properties, additionally there are `y` value,
         * `percentage` and others listed under {@link Highcharts.Point}
         * members.
         *
         * @type      {string}
         * @apioption plotOptions.series.dataLabels.filter.property
         */
        /**
         * The value to compare against.
         *
         * @type      {number}
         * @apioption plotOptions.series.dataLabels.filter.value
         */
        /**
         * A
         * [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
         * for the data label. Available variables are the same as for
         * `formatter`.
         *
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/
         *         Add a unit
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-format-subexpression/
         *         Complex logic in the format string
         * @sample {highmaps} maps/plotoptions/series-datalabels-format/
         *         Formatted value in the data label
         *
         * @type      {string}
         * @default   y
         * @default   point.value
         * @since     3.0
         * @apioption plotOptions.series.dataLabels.format
         */
        /**
         * Callback JavaScript function to format the data label. Note that if a
         * `format` is defined, the format takes precedence and the formatter is
         * ignored.
         *
         * @sample {highmaps} maps/plotoptions/series-datalabels-format/
         *         Formatted value
         *
         * @type {Highcharts.DataLabelsFormatterCallbackFunction}
         */
        formatter: function () {
            const { numberFormatter } = this.series.chart;
            return typeof this.y !== 'number' ?
                '' : numberFormatter(this.y, -1);
        },
        /**
         * For points with an extent, like columns or map areas, whether to
         * align the data label inside the box or to the actual value point.
         * Defaults to `false` in most cases, `true` in stacked columns.
         *
         * @type      {boolean}
         * @since     3.0
         * @apioption plotOptions.series.dataLabels.inside
         */
        /**
         * The rank for this point's data label in case of collision. If two
         * data labels are about to overlap, only the one with the highest
         * `labelrank` will be drawn.
         *
         * @type      {number}
         * @apioption plotOptions.series.dataLabels.labelrank
         */
        /**
         * Format for points with the value of null. Works analogously to
         * [format](#plotOptions.series.dataLabels.format). `nullFormat` can
         * be applied only to series which support displaying null points.
         * `heatmap` and `tilemap` supports `nullFormat` by default while the
         * following series requires [#series.nullInteraction] set to `true`:
         * `line`, `spline`, `area`, `area-spline`, `column`, `bar`, and
         * `timeline`. Does not work with series that don't display null
         * points, like `pie`.
         *
         * @sample {highcharts} highcharts/series/null-interaction/
         *         Line chart with null interaction
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-nullformat/
         *         Heatmap with null interaction
         *
         * @type      {boolean|string}
         * @since     7.1.0
         * @apioption plotOptions.series.dataLabels.nullFormat
         */
        /**
         * Callback JavaScript function that defines formatting for points
         * with the value of null. Works analogously to
         * [formatter](#plotOptions.series.dataLabels.formatter).
         * `nullFormatter` can be applied only to series which support
         * displaying null points. `heatmap` and `tilemap` supports
         * `nullFormatter` by default while the following series requires
         * (series.nullInteraction)[#series.nullInteraction]
         * set to `true`: `line`, `spline`, `area`, `area-spline`, `column`,
         * `bar`, and `timeline`. Does not work with series that don't display
         * null points, like `pie`.
         *
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-nullformat/
         *         Format data label for null points in heat map
         *
         * @type      {Highcharts.DataLabelsFormatterCallbackFunction}
         * @since     7.1.0
         * @apioption plotOptions.series.dataLabels.nullFormatter
         */
        /**
         * How to handle data labels that flow outside the plot area. The
         * default is `"justify"`, which aligns them inside the plot area.
         * For columns and bars, this means it will be moved inside the bar.
         * To display data labels outside the plot area, set `crop` to
         * `false` and `overflow` to `"allow"`.
         *
         * @type       {Highcharts.DataLabelsOverflowValue}
         * @default    justify
         * @since      3.0.6
         * @apioption  plotOptions.series.dataLabels.overflow
         */
        /**
         * When either the `borderWidth` or the `backgroundColor` is set,
         * this is the padding within the box.
         *
         * An array of numbers sets padding for the respective sides. An array
         * of two numbers repeats the values for the horizontal and vertical
         * sides.
         *
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/
         *         Data labels box options
         * @sample {highmaps} maps/plotoptions/series-datalabels-box/
         *         Data labels box options
         *
         * @since 2.2.1
         */
        padding: [1, 3],
        /**
         * Aligns data labels relative to points. If `center` alignment is
         * not possible, it defaults to `right`.
         *
         * @type      {Highcharts.AlignValue}
         * @default   center
         * @apioption plotOptions.series.dataLabels.position
         */
        /**
         * Text rotation in degrees. Note that due to a more complex
         * structure, backgrounds, borders and padding will be lost on a
         * rotated data label.
         *
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/
         *         Vertical labels
         *
         * @type      {number}
         * @default   0
         * @apioption plotOptions.series.dataLabels.rotation
         */
        /**
         * The shadow of the box. Works best with `borderWidth` or
         * `backgroundColor`. Since 2.3 the shadow can be an object
         * configuration containing `color`, `offsetX`, `offsetY`, `opacity`
         * and `width`.
         *
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-box/
         *         Data labels box options
         *
         * @type      {boolean|Highcharts.ShadowOptionsObject}
         * @default   false
         * @since     2.2.1
         * @apioption plotOptions.series.dataLabels.shadow
         */
        /**
         * The name of a symbol to use for the border around the label.
         * Symbols are predefined functions on the Renderer object.
         *
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-shape/
         *         A callout for annotations
         *
         * @type      {string}
         * @default   square
         * @since     4.1.2
         * @apioption plotOptions.series.dataLabels.shape
         */
        /**
         * Styles for the label. The default `color` setting is
         * `"contrast"`, which is a pseudo color that Highcharts picks up
         * and applies the maximum contrast to the underlying point item,
         * for example the bar in a bar chart.
         *
         * The `textOutline` is a pseudo property that applies an outline of
         * the given width with the given color, which by default is the
         * maximum contrast to the text. So a bright text color will result
         * in a black text outline for maximum readability on a mixed
         * background. In some cases, especially with grayscale text, the
         * text outline doesn't work well, in which cases it can be disabled
         * by setting it to `"none"`. When `useHTML` is true, the
         * `textOutline` will not be picked up. In this, case, the same
         * effect can be achieved through the `text-shadow` CSS property. As a
         * complementary or alternative to the `textOutline`, a
         * `dataLabels.backgroundColor` can be used. It provides a more calm
         * impression and ensures readable text label, at the cost of a risk of
         * overshadowing the underlying chart elements.
         *
         * For some series types, where each point has an extent, like for
         * example tree maps, the data label may overflow the point. There
         * are two strategies for handling overflow. By default, the text
         * will wrap to multiple lines. The other strategy is to set
         * `style.textOverflow` to `ellipsis`, which will keep the text on
         * one line plus it will break inside long words.
         *
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-style/
         *         Bold labels
         * @sample {highcharts} highcharts/plotoptions/pie-datalabels-overflow/
         *         Long labels truncated with an ellipsis in a pie
         * @sample {highcharts} highcharts/plotoptions/pie-datalabels-overflow-wrap/
         *         Long labels are wrapped in a pie
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-background-contrast/
         *         Default text outline and contrast background color
         * @sample {highmaps} maps/demo/color-axis/
         *         Bold labels
         *
         * @type      {Highcharts.CSSObject}
         * @since     4.1.0
         * @default   { fontSize: '0.7em', fontWeight: 'bold', color: 'contrast', textOutline: '1px contrast' }
         * @apioption plotOptions.series.dataLabels.style
         */
        style: {
            /** @internal */
            fontSize: '0.7em',
            /** @internal */
            fontWeight: 'bold',
            /** @internal */
            color: 'contrast',
            /** @internal */
            textOutline: '1px contrast'
        },
        /**
         * Whether to
         * [use HTML](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting#html)
         * to render the labels.
         *
         * @type      {boolean}
         * @default   false
         * @apioption plotOptions.series.dataLabels.useHTML
         */
        /**
         * The vertical alignment of a data label. Can be one of `top`,
         * `middle` or `bottom`. The default value depends on the data, for
         * instance in a column chart, the label is above positive values
         * and below negative values.
         *
         * @type  {Highcharts.VerticalAlignValue|null}
         * @since 2.3.3
         */
        verticalAlign: 'bottom',
        // Loose JSDoc doclet placed last are ignored, so placed unsorted here.
        /**
         * The z index of the data labels group. Does not apply below series
         * level options.
         *
         * Use a `zIndex` of 6 to display it above the series,
         * or use a `zIndex` of 2 to display it behind the series.
         *
         * @type      {number}
         * @default   6
         * @since     2.3.5
         * @apioption plotOptions.series.dataLabels.zIndex
         */
        /**
         * The x position offset of the label relative to the point in
         * pixels.
         *
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/
         *         Vertical and positioned
         * @sample {highcharts} highcharts/plotoptions/bar-datalabels-align-inside-bar/
         *         Data labels inside the bar
         */
        x: 0,
        /**
         * The y position offset of the label relative to the point in
         * pixels.
         *
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-rotation/
         *         Vertical and positioned
         */
        y: 0
    },
    /**
     * When the series contains less points than the crop threshold, all
     * points are drawn, even if the points fall outside the visible plot
     * area at the current zoom. The advantage of drawing all points
     * (including markers and columns), is that animation is performed on
     * updates. On the other hand, when the series contains more points than
     * the crop threshold, the series data is cropped to only contain points
     * that fall within the plot area. The advantage of cropping away
     * invisible points is to increase performance on large series.
     *
     * @since   2.2
     * @product highcharts highstock
     */
    cropThreshold: 300,
    /**
     * Opacity of a series parts: line, fill (e.g. area) and dataLabels.
     *
     * @see [states.inactive.opacity](#plotOptions.series.states.inactive.opacity)
     *
     * @since 7.1.0
     */
    opacity: 1,
    /**
     * The width of each point on the x axis. For example in a column chart
     * with one value each day, the pointRange would be 1 day (= 24 * 3600
     * * 1000 milliseconds). This is normally computed automatically, but
     * this option can be used to override the automatic value.
     *
     * @product highstock
     */
    pointRange: 0,
    /**
     * When this is true, the series will not cause the Y axis to cross
     * the zero plane (or [threshold](#plotOptions.series.threshold) option)
     * unless the data actually crosses the plane.
     *
     * For example, if `softThreshold` is `false`, a series of 0, 1, 2,
     * 3 will make the Y axis show negative values according to the
     * `minPadding` option. If `softThreshold` is `true`, the Y axis starts
     * at 0.
     *
     * @since   4.1.9
     * @product highcharts highstock
     */
    softThreshold: true,
    /**
     * A collection of options for different series states.
     *
     * @declare Highcharts.SeriesStatesOptionsObject
     */
    states: {
        /**
         * The normal state of a series, or for point items in column, pie
         * and similar series. Currently only used for setting animation
         * when returning to normal state from hover.
         *
         * @declare Highcharts.SeriesStatesNormalOptionsObject
         */
        normal: {
            /**
             * Animation when returning to normal state after hovering.
             *
             * @type {boolean|Partial<Highcharts.AnimationOptionsObject>}
             */
            animation: true
        },
        /**
         * Options for the hovered series. These settings override the
         * normal state options when a series is moused over or touched.
         *
         * @declare Highcharts.SeriesStatesHoverOptionsObject
         */
        hover: {
            /**
             * Enable separate styles for the hovered series to visualize
             * that the user hovers either the series itself or the legend.
             *
             * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled/
             *         Line
             * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled-column/
             *         Column
             * @sample {highcharts} highcharts/plotoptions/series-states-hover-enabled-pie/
             *         Pie
             *
             * @type      {boolean}
             * @default   true
             * @since     1.2
             * @apioption plotOptions.series.states.hover.enabled
             */
            /**
             * Animation setting for hovering the graph in line-type series.
             *
             * @type {boolean|Partial<Highcharts.AnimationOptionsObject>}
             * @since   5.0.8
             * @product highcharts highstock
             */
            animation: {
                /**
                 * The duration of the hover animation in milliseconds. By
                 * default the hover state animates quickly in, and slowly
                 * back to normal.
                 */
                duration: 150
            },
            /**
             * Pixel width of the graph line. By default this property is
             * undefined, and the `lineWidthPlus` property dictates how much
             * to increase the linewidth from normal state.
             *
             * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidth/
             *         5px line on hover
             *
             * @type      {number}
             * @product   highcharts highstock
             * @apioption plotOptions.series.states.hover.lineWidth
             */
            /**
             * The additional line width for the graph of a hovered series.
             *
             * @sample {highcharts} highcharts/plotoptions/series-states-hover-linewidthplus/
             *         5 pixels wider
             * @sample {highstock} highcharts/plotoptions/series-states-hover-linewidthplus/
             *         5 pixels wider
             *
             * @since   4.0.3
             * @product highcharts highstock
             */
            lineWidthPlus: 1,
            /**
             * In Highcharts 1.0, the appearance of all markers belonging
             * to the hovered series. For settings on the hover state of the
             * individual point, see
             * [marker.states.hover](#plotOptions.series.marker.states.hover).
             *
             * @deprecated 2
             *
             * @extends   plotOptions.series.marker
             * @excluding states, symbol
             * @product   highcharts highstock
             */
            marker: {
            // `lineWidth: base + 1`,
            // `radius: base + 1`
            },
            /**
             * Options for the halo appearing around the hovered point in
             * line-type series as well as outside the hovered slice in pie
             * charts. By default the halo is filled by the current point or
             * series color with an opacity of 0.25\. The halo can be
             * disabled by setting the `halo` option to `null`.
             *
             * In styled mode, the halo is styled with the
             * `.highcharts-halo` class, with colors inherited from
             * `.highcharts-color-{n}`.
             *
             * @sample {highcharts} highcharts/plotoptions/halo/
             *         Halo options
             * @sample {highstock} highcharts/plotoptions/halo/
             *         Halo options
             *
             * @declare Highcharts.SeriesStatesHoverHaloOptionsObject
             * @type    {null|*}
             * @since   4.0
             * @product highcharts highstock
             */
            halo: {
                /**
                 * A collection of SVG attributes to override the appearance
                 * of the halo, for example `fill`, `stroke` and
                 * `stroke-width`.
                 *
                 * @type      {Highcharts.SVGAttributes}
                 * @since     4.0
                 * @product   highcharts highstock
                 * @apioption plotOptions.series.states.hover.halo.attributes
                 */
                /**
                 * The pixel size of the halo. For point markers this is the
                 * radius of the halo. For pie slices it is the width of the
                 * halo outside the slice. For bubbles it defaults to 5 and
                 * is the width of the halo outside the bubble.
                 *
                 * @since   4.0
                 * @product highcharts highstock
                 */
                size: 10,
                /**
                 * Opacity for the halo unless a specific fill is overridden
                 * using the `attributes` setting.
                 *
                 * @since   4.0
                 * @product highcharts highstock
                 */
                opacity: 0.25
            }
        },
        /**
         * Specific options for point in selected states, after being
         * selected by
         * [allowPointSelect](#plotOptions.series.allowPointSelect)
         * or programmatically.
         *
         * @sample maps/plotoptions/series-allowpointselect/
         *         Allow point select demo
         *
         * @declare   Highcharts.SeriesStatesSelectOptionsObject
         * @extends   plotOptions.series.states.hover
         * @excluding brightness
         */
        select: {
            animation: {
                duration: 0
            }
        },
        /**
         * The opposite state of a hover for series.
         *
         * @sample highcharts/plotoptions/series-states-inactive-disabled
         *         Disabled inactive state
         *
         * @declare Highcharts.SeriesStatesInactiveOptionsObject
         */
        inactive: {
            /**
             * Enable or disable the inactive state for a series
             *
             * @sample highcharts/plotoptions/series-states-inactive-disabled
             *         Disabled inactive state
             *
             * @type {boolean}
             * @default true
             * @apioption plotOptions.series.states.inactive.enabled
             */
            /**
             * The animation for entering the inactive state.
             *
             * @type {boolean|Partial<Highcharts.AnimationOptionsObject>}
             */
            animation: {
                duration: 150
            },
            /**
             * Opacity of series elements (dataLabels, line, area).
             *
             * @type {number}
             */
            opacity: 0.2
        }
    },
    /**
     * Sticky tracking of mouse events. When true, the `mouseOut` event on a
     * series isn't triggered until the mouse moves over another series, or
     * out of the plot area. When false, the `mouseOut` event on a series is
     * triggered when the mouse leaves the area around the series' graph or
     * markers. This also implies the tooltip when not shared. When
     * `stickyTracking` is false and `tooltip.shared` is false, the tooltip
     * will be hidden when moving the mouse between series. Defaults to true
     * for line and area type series, but to false for columns, pies etc.
     *
     * **Note:** The boost module will force this option because of
     * technical limitations.
     *
     * @sample {highcharts} highcharts/plotoptions/series-stickytracking-true/
     *         True by default
     * @sample {highcharts} highcharts/plotoptions/series-stickytracking-false/
     *         False
     *
     * @default {highcharts} true
     * @default {highstock} true
     * @default {highmaps} false
     * @since   2.0
     */
    stickyTracking: true,
    /**
     * A configuration object for the tooltip rendering of each single
     * series. Properties are inherited from [tooltip](#tooltip), but only
     * the following properties can be defined on a series level.
     *
     * @declare   Highcharts.SeriesTooltipOptionsObject
     * @since     2.3
     * @extends   tooltip
     * @excluding animation, backgroundColor, borderColor, borderRadius,
     *            borderWidth, className, crosshairs, enabled, fixed, formatter,
     *            headerShape, hideDelay, outside, padding, positioner,
     *            shadow, shape, shared, snap, split, stickOnContact,
     *            style, useHTML
     * @apioption plotOptions.series.tooltip
     */
    /**
     * When a series contains a `data` array that is longer than this, the
     * Series class looks for data configurations of plain numbers or arrays of
     * numbers. The first and last valid points are checked. If found, the rest
     * of the data is assumed to be the same. This saves expensive data checking
     * and indexing in long series, and makes data-heavy charts render faster.
     *
     * Set it to `0` disable.
     *
     * Note:
     * - In boost mode turbo threshold is forced. Only array of numbers or two
     *   dimensional arrays are allowed.
     * - In version 11.4.3 and earlier, if object configurations were passed
     *   beyond the turbo threshold, a warning was logged in the console and the
     *   data series didn't render.
     *
     * @since   2.2
     * @product highcharts highstock gantt
     */
    turboThreshold: 1000,
    /**
     * An array defining zones within a series. Zones can be applied to the
     * X axis, Y axis or Z axis for bubbles, according to the `zoneAxis`
     * option. The zone definitions have to be in ascending order regarding
     * to the value.
     *
     * In styled mode, the color zones are styled with the
     * `.highcharts-zone-{n}` class, or custom classed from the `className`
     * option
     * ([view live demo](https://jsfiddle.net/gh/get/library/pure/highcharts/highcharts/tree/master/samples/highcharts/css/color-zones/)).
     *
     * @see [zoneAxis](#plotOptions.series.zoneAxis)
     *
     * @sample {highcharts} highcharts/series/color-zones-simple/
     *         Color zones
     * @sample {highstock} highcharts/series/color-zones-simple/
     *         Color zones
     *
     * @declare   Highcharts.SeriesZonesOptionsObject
     * @type      {Array<*>}
     * @since     4.1.0
     * @product   highcharts highstock
     * @apioption plotOptions.series.zones
     */
    /**
     * Styled mode only. A custom class name for the zone.
     *
     * @sample highcharts/css/color-zones/
     *         Zones styled by class name
     *
     * @type      {string}
     * @since     5.0.0
     * @apioption plotOptions.series.zones.className
     */
    /**
     * Defines the color of the series.
     *
     * @see [series color](#plotOptions.series.color)
     *
     * @type      {Highcharts.ColorType}
     * @since     4.1.0
     * @product   highcharts highstock
     * @apioption plotOptions.series.zones.color
     */
    /**
     * A name for the dash style to use for the graph.
     *
     * @see [plotOptions.series.dashStyle](#plotOptions.series.dashStyle)
     *
     * @sample {highcharts|highstock} highcharts/series/color-zones-dashstyle-dot/
     *         Dashed line indicates prognosis
     *
     * @type      {Highcharts.DashStyleValue}
     * @since     4.1.0
     * @product   highcharts highstock
     * @apioption plotOptions.series.zones.dashStyle
     */
    /**
     * Defines the fill color for the series (in area type series)
     *
     * @see [fillColor](#plotOptions.area.fillColor)
     *
     * @type      {Highcharts.ColorType}
     * @since     4.1.0
     * @product   highcharts highstock
     * @apioption plotOptions.series.zones.fillColor
     */
    /**
     * The value up to where the zone extends, if undefined the zones
     * stretches to the last value in the series.
     *
     * @type      {number}
     * @since     4.1.0
     * @product   highcharts highstock
     * @apioption plotOptions.series.zones.value
     */
    /**
     * When using dual or multiple color axes, this number defines which
     * colorAxis the particular series is connected to. It refers to
     * either the
     * {@link #colorAxis.id|axis id}
     * or the index of the axis in the colorAxis array, with 0 being the
     * first. Set this option to false to prevent a series from connecting
     * to the default color axis.
     *
     * Since v7.2.0 the option can also be an axis id or an axis index
     * instead of a boolean flag.
     *
     * @sample highcharts/coloraxis/coloraxis-with-pie/
     *         Color axis with pie series
     * @sample highcharts/coloraxis/multiple-coloraxis/
     *         Multiple color axis
     *
     * @type      {number|string|boolean}
     * @default   0
     * @product   highcharts highstock highmaps
     * @apioption plotOptions.series.colorAxis
     */
    /**
     * Determines what data value should be used to calculate point color
     * if `colorAxis` is used. Requires to set `min` and `max` if some
     * custom point property is used or if approximation for data grouping
     * is set to `'sum'`.
     *
     * @sample highcharts/coloraxis/custom-color-key/
     *         Custom color key
     * @sample highcharts/coloraxis/color-key-with-stops/
     *         Custom colorKey with color axis stops
     * @sample highcharts/coloraxis/changed-default-color-key/
     *         Changed default color key
     *
     * @type      {string}
     * @default   y
     * @since     7.2.0
     * @product   highcharts highstock highmaps
     * @apioption plotOptions.series.colorKey
     */
    /**
     * What type of legend symbol to render for this series. Can be one of
     * `areaMarker`, `lineMarker` or `rectangle`.
     *
     * @validvalue ["areaMarker", "lineMarker", "rectangle"]
     *
     * @sample {highcharts} highcharts/series/legend-symbol/
     *         Change the legend symbol
     *
     * @type      {string}
     * @default   rectangle
     * @since     11.0.1
     * @apioption plotOptions.series.legendSymbol
     */
    /**
     * Defines the color of the legend symbol for this series. Defaults to
     * undefined, in which case the series color is used. Does not work with
     * styled mode.
     *
     * @sample {highcharts|highstock} highcharts/series/legend-symbol-color/
     *         Change the legend symbol color
     *
     * @type      {Highcharts.ColorType}
     * @default   undefined
     * @since 12.0.0
     * @product   highcharts highstock highmaps
     * @apioption plotOptions.series.legendSymbolColor
     */
    /**
     * Determines whether the series should look for the nearest point
     * in both dimensions or just the x-dimension when hovering the series.
     * Defaults to `'xy'` for scatter series and `'x'` for most other
     * series. If the data has duplicate x-values, it is recommended to
     * set this to `'xy'` to allow hovering over all points.
     *
     * Applies only to series types using nearest neighbor search (not
     * direct hover) for tooltip.
     *
     * @sample {highcharts} highcharts/series/findnearestpointby/
     *         Different hover behaviors
     * @sample {highstock} highcharts/series/findnearestpointby/
     *         Different hover behaviors
     * @sample {highmaps} highcharts/series/findnearestpointby/
     *         Different hover behaviors
     *
     * @since      5.0.10
     * @validvalue ["x", "xy"]
     */
    findNearestPointBy: 'x'
};
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const SeriesDefaults = (seriesDefaults);

;// ./code/es-modules/Core/Series/SeriesRegistry.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */



const { defaultOptions: SeriesRegistry_defaultOptions } = Defaults;


/* *
 *
 *  Namespace
 *
 * */
var SeriesRegistry;
(function (SeriesRegistry) {
    /* *
     *
     *  Properties
     *
     * */
    /**
     * @internal
     * @todo Move `Globals.seriesTypes` code to her.
     */
    SeriesRegistry.seriesTypes = Core_Globals.seriesTypes;
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Registers class pattern of a series.
     *
     * @param {string} seriesType
     * The series type as an identifier string in lower case.
     *
     * @param {Function} SeriesClass
     * The series class as a class pattern or a constructor function with
     * prototype.
     *
     * @return {boolean}
     * True if series type was added, false if it already exists.
     */
    function registerSeriesType(seriesType, SeriesClass) {
        const defaultPlotOptions = SeriesRegistry_defaultOptions.plotOptions || {}, seriesOptions = SeriesClass.defaultOptions, seriesProto = SeriesClass.prototype;
        seriesProto.type = seriesType;
        if (!seriesProto.pointClass) {
            seriesProto.pointClass = Series_Point;
        }
        if (SeriesRegistry.seriesTypes[seriesType]) {
            return false;
        }
        if (seriesOptions) {
            defaultPlotOptions[seriesType] = seriesOptions;
        }
        SeriesRegistry.seriesTypes[seriesType] = SeriesClass;
        return true;
    }
    SeriesRegistry.registerSeriesType = registerSeriesType;
    /**
     * Old factory to create new series prototypes.
     *
     * @deprecated 9.0.0
     * @function Highcharts.seriesType
     *
     * @param {string} type
     * The series type name.
     *
     * @param {string} parent
     * The parent series type name. Use `line` to inherit from the basic
     * {@link Series} object.
     *
     * @param {Highcharts.SeriesOptionsType|Highcharts.Dictionary<*>} options
     * The additional default options that are merged with the parent's options.
     *
     * @param {Highcharts.Dictionary<*>} [seriesProto]
     * The properties (functions and primitives) to set on the new prototype.
     *
     * @param {Highcharts.Dictionary<*>} [pointProto]
     * Members for a series-specific extension of the {@link Point} prototype if
     * needed.
     *
     * @return {Highcharts.Series}
     * The newly created prototype as extended from {@link Series} or its
     * derivatives.
     */
    function seriesType(type, parent, options, seriesProto, pointProto) {
        const defaultPlotOptions = SeriesRegistry_defaultOptions.plotOptions || {};
        parent = parent || '';
        // Merge the options
        defaultPlotOptions[type] = merge(defaultPlotOptions[parent], options);
        // Create the class
        delete SeriesRegistry.seriesTypes[type];
        const parentClass = (SeriesRegistry.seriesTypes[parent] ||
            Core_Globals.Series), childClass = extendClass(parentClass, seriesProto);
        registerSeriesType(type, childClass);
        SeriesRegistry.seriesTypes[type].prototype.type = type;
        // Create the point class if needed
        if (pointProto) {
            class PointClass extends Series_Point {
            }
            extend(PointClass.prototype, pointProto);
            SeriesRegistry.seriesTypes[type].prototype.pointClass = PointClass;
        }
        return SeriesRegistry.seriesTypes[type];
    }
    SeriesRegistry.seriesType = seriesType;
})(SeriesRegistry || (SeriesRegistry = {}));
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Series_SeriesRegistry = (SeriesRegistry);

;// ./code/es-modules/Core/Series/Series.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { animObject: Series_animObject, setAnimation: Series_setAnimation } = AnimationUtilities;


const { defaultOptions: Series_defaultOptions } = Defaults;

const { registerEventOptions: Series_registerEventOptions } = Core_Foundation;

const { svg: Series_svg, win: Series_win } = Core_Globals;




const { seriesTypes } = Series_SeriesRegistry;


const { format: Series_format } = Core_Templating;


/* *
 *
 *  Class
 *
 * */
/**
 * This is the base series prototype that all other series types inherit from.
 * A new series is initialized either through the
 * [series](https://api.highcharts.com/highcharts/series)
 * option structure, or after the chart is initialized, through
 * {@link Highcharts.Chart#addSeries}.
 *
 * The object can be accessed in a number of ways. All series and point event
 * handlers give a reference to the `series` object. The chart object has a
 * {@link Highcharts.Chart#series|series} property that is a collection of all
 * the chart's series. The point objects and axis objects also have the same
 * reference.
 *
 * Another way to reference the series programmatically is by `id`. Add an id
 * in the series configuration options, and get the series object by
 * {@link Highcharts.Chart#get}.
 *
 * Configuration options for the series are given in three levels. Options for
 * all series in a chart are given in the
 * [plotOptions.series](https://api.highcharts.com/highcharts/plotOptions.series)
 * object. Then options for all series of a specific type
 * are given in the plotOptions of that type, for example `plotOptions.line`.
 * Next, options for one single series are given in the series array, or as
 * arguments to `chart.addSeries`.
 *
 * The data in the series is stored in various arrays.
 *
 * - First, `series.options.data` contains all the original config options for
 *   each point whether added by options or methods like `series.addPoint`.
 *
 * - The `series.dataTable` refers to an instance of [DataTableCore](https://api.highcharts.com/class-reference/Highcharts.Data)
 *   or `DataTable` that contains the data in a tabular format. Individual
 *   columns can be read from `series.getColumn()`.
 *
 * - Next, `series.data` contains those values converted to points, but in case
 *   the series data length exceeds the `cropThreshold`, or if the data is
 *   grouped, `series.data` doesn't contain all the points. It only contains the
 *   points that have been created on demand.
 *
 * - Then there's `series.points` that contains all currently visible point
 *   objects. In case of cropping, the cropped-away points are not part of this
 *   array. The `series.points` array starts at `series.cropStart` compared to
 *   `series.data` and `series.options.data`. If however the series data is
 *   grouped, these can't be correlated one to one.
 *
 * @class
 * @name Highcharts.Series
 *
 * @param {Highcharts.Chart} chart
 * The chart instance.
 *
 * @param {Highcharts.SeriesOptionsType|object} options
 * The series options.
 */
class Series {
    constructor() {
        /* *
         *
         *  Static Properties
         *
         * */
        /** @internal */
        this.zoneAxis = 'y';
        // eslint-enable valid-jsdoc
    }
    /* *
     *
     *  API JSDoc doclet copies for uninitialized properties
     *
     * */
    /**
     * Read only. The chart that the series belongs to.
     *
     * @name Highcharts.Series#chart
     * @type {Highcharts.Chart}
     */
    /**
     * Series color as used by the legend and some series types.
     * @name Highcharts.Series#color
     * @type {Highcharts.ColorType|undefined}
     */
    /**
     * Read only. An array containing those values converted to points.
     * In case the series data length exceeds the `cropThreshold`, or if
     * the data is grouped, `series.data` doesn't contain all the
     * points. Also, in case a series is hidden, the `data` array may be
     * empty. In case of cropping, the `data` array may contain `undefined`
     * values, instead of points. To access raw values,
     * `series.options.data` will always be up to date. `Series.data` only
     * contains the points that have been created on demand. To modify the
     * data, use
     * {@link Highcharts.Series#setData} or
     * {@link Highcharts.Point#update}.
     *
     * @see Series.points
     *
     * @name Highcharts.Series#data
     * @type {Array<Highcharts.Point>}
     */
    /**
     * Contains the maximum value of the series' data point. Some series
     * types like `networkgraph` do not support this property as they
     * lack a `y`-value.
     * @name Highcharts.Series#dataMax
     * @type {number|undefined}
     * @readonly
     */
    /**
     * Contains the minimum value of the series' data point. Some series
     * types like `networkgraph` do not support this property as they
     * lack a `y`-value.
     * @name Highcharts.Series#dataMin
     * @type {number|undefined}
     * @readonly
     */
    /**
     * Contains the series' index in the `Chart.series` array.
     *
     * @name Highcharts.Series#index
     * @type {number}
     * @readonly
     */
    /**
     * The parent series of the current series, if the current
     * series has a [linkedTo](https://api.highcharts.com/highcharts/series.line.linkedTo)
     * setting.
     *
     * @name Highcharts.Series#linkedParent
     * @type {Highcharts.Series}
     * @readonly
     */
    /**
     * All child series that are linked to the current series through the
     * [linkedTo](https://api.highcharts.com/highcharts/series.line.linkedTo)
     * option.
     *
     * @name Highcharts.Series#linkedSeries
     * @type {Array<Highcharts.Series>}
     * @readonly
     */
    /**
     * The series name as given in the options. Defaults to
     * "Series {n}".
     *
     * @name Highcharts.Series#name
     * @type {string}
     */
    /**
     * Read only. The series' current options. To update, use
     * {@link Series#update}.
     *
     * @name Highcharts.Series#options
     * @type {Highcharts.SeriesOptionsType}
     */
    /**
     * An array containing all currently visible point objects. In case
     * of cropping, the cropped-away points are not part of this array.
     * The `series.points` array starts at `series.cropStart` compared
     * to `series.data` and `series.options.data`. If however the series
     * data is grouped, these can't be correlated one to one. To modify
     * the data, use {@link Highcharts.Series#setData} or
     * {@link Highcharts.Point#update}.
     *
     * @name Highcharts.Series#points
     * @type {Array<Highcharts.Point>}
     */
    /**
     * Read only. The series' selected state as set by {@link
     * Highcharts.Series#select}.
     *
     * @name Highcharts.Series#selected
     * @type {boolean}
     */
    /**
     * Read only. The series' type, like "line", "area", "column" etc.
     * The type in the series options anc can be altered using
     * {@link Series#update}.
     *
     * @name Highcharts.Series#type
     * @type {string}
     */
    /**
     * Read only. The series' visibility state as set by
     * {@link Series#show}, {@link Series#hide}, or in the initial
     * configuration. True by default.
     *
     * @name Highcharts.Series#visible
     * @type {boolean}
     * @default true
     */
    /**
     * Read only. The unique xAxis object associated
     * with the series.
     *
     * @name Highcharts.Series#xAxis
     * @type {Highcharts.Axis}
     */
    /**
     * Read only. The unique yAxis object associated
     * with the series.
     *
     * @name Highcharts.Series#yAxis
     * @type {Highcharts.Axis}
     */
    /**
     * Contains series options by the user without defaults.
     * @name Highcharts.Series#userOptions
     * @type {Highcharts.SeriesOptionsType}
     */
    /* *
     *
     *  Functions
     *
     * */
    /** @internal */
    init(chart, userOptions) {
        fireEvent(this, 'init', { options: userOptions });
        const series = this, chartSeries = chart.series;
        // The 'eventsToUnbind' property moved from prototype into the
        // Series init to avoid reference to the same array between
        // the different series and charts. #12959, #13937
        this.eventsToUnbind = [];
        this.condemnedPoints || (this.condemnedPoints = []);
        /**
         * Read only. The chart that the series belongs to.
         *
         * @name Highcharts.Series#chart
         * @type {Highcharts.Chart}
         */
        series.chart = chart;
        /**
         * Read only. The series' type, like "line", "area", "column" etc.
         * The type in the series options anc can be altered using
         * {@link Series#update}.
         *
         * @name Highcharts.Series#type
         * @type {string}
         */
        /**
         * Read only. The series' current options. To update, use
         * {@link Series#update}.
         *
         * @name Highcharts.Series#options
         * @type {Highcharts.SeriesOptionsType}
         */
        series.options = series.setOptions(userOptions);
        const options = series.options, visible = options.visible !== false;
        // Create the data table
        this.dataTable ?? (this.dataTable = options.dataTable?.isDataTable ?
            options.dataTable :
            new Data_DataTableCore(options.dataTable));
        /**
         * All child series that are linked to the current series through the
         * [linkedTo](https://api.highcharts.com/highcharts/series.line.linkedTo)
         * option.
         *
         * @name Highcharts.Series#linkedSeries
         * @type {Array<Highcharts.Series>}
         * @readonly
         */
        series.linkedSeries = [];
        // Bind the axes
        series.bindAxes();
        extend(series, {
            /**
             * The series name as given in the options. Defaults to
             * "Series {n}".
             *
             * @name Highcharts.Series#name
             * @type {string}
             */
            name: options.name,
            state: '',
            /**
             * Read only. The series' visibility state as set by {@link
             * Series#show}, {@link Series#hide}, or in the initial
             * configuration.
             *
             * @name Highcharts.Series#visible
             * @type {boolean}
             */
            visible, // True by default
            /**
             * Read only. The series' selected state as set by {@link
             * Highcharts.Series#select}.
             *
             * @name Highcharts.Series#selected
             * @type {boolean}
             */
            selected: options.selected === true // False by default
        });
        Series_registerEventOptions(this, options);
        const events = options.events;
        if (events?.click ||
            options.point?.events?.click ||
            options.allowPointSelect) {
            chart.runTrackerClick = true;
        }
        series.getColor();
        series.getSymbol();
        // Mark cartesian
        if (series.isCartesian) {
            chart.hasCartesianSeries = true;
        }
        // Get the index and register the series in the chart. The index is
        // one more than the current latest series index (#5960).
        let lastSeries;
        if (chartSeries.length) {
            lastSeries = chartSeries[chartSeries.length - 1];
        }
        series._i = pick(lastSeries?._i, -1) + 1;
        series.opacity = series.options.opacity;
        // Insert the series and re-order all series above the insertion
        // point.
        chart.orderItems('series', insertItem(this, chartSeries));
        if (!series.points && !series.data) {
            series.setData(options.data, false);
        }
        fireEvent(this, 'afterInit');
    }
    /**
     * Check whether the series item is itself or inherits from a certain
     * series type.
     *
     * @function Highcharts.Series#is
     * @param {string} type The type of series to check for, can be either
     *        featured or custom series types. For example `column`, `pie`,
     *        `ohlc` etc.
     *
     * @return {boolean}
     *        True if this item is or inherits from the given type.
     */
    // TODO: Runtime checks `instanceof`, so this also confirms inheritance.
    // The type guard currently narrows to the requested type only. Aligning
    // typing 1:1 with runtime should be easier after pending TS cleanups.
    is(type) {
        return seriesTypes[type] && this instanceof seriesTypes[type];
    }
    /**
     * Set the xAxis and yAxis properties of cartesian series, and register
     * the series in the `axis.series` array.
     *
     * @internal
     * @function Highcharts.Series#bindAxes
     */
    bindAxes() {
        const series = this, seriesOptions = series.options, chart = series.chart;
        let axisOptions;
        fireEvent(this, 'bindAxes', null, function () {
            // Repeat for xAxis and yAxis
            (series.axisTypes || []).forEach(function (coll) {
                // Loop through the chart's axis objects
                (chart[coll] || []).forEach(function (axis) {
                    axisOptions = axis.options;
                    // Apply if the series xAxis or yAxis option matches
                    // the number of the axis, or if undefined, use the
                    // first axis
                    if (pick(seriesOptions[coll], 0) === axis.index ||
                        (typeof seriesOptions[coll] !==
                            'undefined' &&
                            seriesOptions[coll] === axisOptions.id)) {
                        // Register this series in the axis.series lookup
                        insertItem(series, axis.series);
                        // Set this series.xAxis or series.yAxis reference
                        series[coll] = axis;
                        // Mark dirty for redraw
                        axis.isDirty = true;
                    }
                });
                // The series needs an X and an Y axis
                if (!series[coll] &&
                    series.optionalAxis !== coll) {
                    error(18, true, chart);
                }
            });
        });
        fireEvent(this, 'afterBindAxes');
    }
    /**
     * Define hasData functions for series. These return true if there
     * are data points on this series within the plot area.
     *
     * @internal
     * @function Highcharts.Series#hasData
     */
    hasData() {
        return ((this.visible &&
            typeof this.dataMax !== 'undefined' &&
            typeof this.dataMin !== 'undefined') || ( // #3703
        this.visible &&
            this.dataTable.rowCount > 0 // #9758
        ));
    }
    /**
     * Determine whether the marker in a series has changed.
     *
     * @internal
     * @function Highcharts.Series#hasMarkerChanged
     */
    hasMarkerChanged(options, oldOptions) {
        const marker = options.marker, oldMarker = oldOptions.marker || {};
        return marker && ((oldMarker.enabled && !marker.enabled) ||
            oldMarker.symbol !== marker.symbol || // #10870, #15946
            oldMarker.height !== marker.height || // #16274
            oldMarker.width !== marker.width // #16274
        );
    }
    /**
     * Return an auto incremented x value based on the pointStart and
     * pointInterval options. This is only used if an x value is not given
     * for the point that calls autoIncrement.
     *
     * @internal
     * @function Highcharts.Series#autoIncrement
     */
    autoIncrement(x) {
        const options = this.options, { pointIntervalUnit, relativeXValue } = this.options, time = this.chart.time, xIncrement = this.xIncrement ??
            time.parse(options.pointStart) ??
            0;
        let pointInterval;
        this.pointInterval = pointInterval = pick(this.pointInterval, options.pointInterval, 1);
        if (relativeXValue && isNumber(x)) {
            pointInterval *= x;
        }
        // Added code for pointInterval strings
        if (pointIntervalUnit) {
            const d = time.toParts(xIncrement);
            if (pointIntervalUnit === 'day') {
                d[2] += pointInterval;
            }
            else if (pointIntervalUnit === 'month') {
                d[1] += pointInterval;
            }
            else if (pointIntervalUnit === 'year') {
                d[0] += pointInterval;
            }
            pointInterval = time.makeTime.apply(time, d) - xIncrement;
        }
        if (relativeXValue && isNumber(x)) {
            return xIncrement + pointInterval;
        }
        this.xIncrement = xIncrement + pointInterval;
        return xIncrement;
    }
    /**
     * Set the series options by merging from the options tree. Called
     * internally on initializing and updating series. This function will
     * not redraw the series. For API usage, use {@link Series#update}.
     *
     * @internal
     * @function Highcharts.Series#setOptions
     * @param {Highcharts.SeriesOptionsType} itemOptions
     * The series options.
     * @emits Highcharts.Series#event:afterSetOptions
     */
    setOptions(itemOptions) {
        const chart = this.chart, chartOptions = chart.options, plotOptions = chartOptions.plotOptions, userOptions = chart.userOptions || {}, seriesUserOptions = merge(itemOptions), styledMode = chart.styledMode, e = {
            plotOptions: plotOptions,
            userOptions: seriesUserOptions
        };
        let zone;
        fireEvent(this, 'setOptions', e);
        // These may be modified by the event
        const typeOptions = e.plotOptions[this.type], userPlotOptions = (userOptions.plotOptions || {}), userPlotOptionsSeries = userPlotOptions.series || {}, defaultPlotOptionsType = (Series_defaultOptions.plotOptions[this.type] || {}), userPlotOptionsType = userPlotOptions[this.type] || {};
        // Merge in multiple data label options from the plot option. (#21928)
        typeOptions.dataLabels = this.mergeArrays(defaultPlotOptionsType.dataLabels, typeOptions.dataLabels);
        // Use copy to prevent undetected changes (#9762)
        this.userOptions = e.userOptions;
        const options = merge(typeOptions, plotOptions.series, 
        // #3881, chart instance plotOptions[type] should trump
        // plotOptions.series
        userPlotOptionsType, seriesUserOptions), 
        // Handle color zones
        { negativeColor, negativeFillColor, zoneAxis = 'y', zones } = options, 
        // #20440, create deep copy of zones options
        zonesCopy = (zones || []).map((z) => ({ ...z }));
        // The tooltip options are merged between global and series specific
        // options. Importance in ascending order:
        // globals: (1)tooltip, (2)plotOptions.series,
        // (3)plotOptions[this.type]
        // init userOptions with possible later updates: 4-6 like 1-3 and
        // (7)this series options
        this.tooltipOptions = merge(Series_defaultOptions.tooltip, // 1
        Series_defaultOptions.plotOptions.series?.tooltip, // 2
        defaultPlotOptionsType?.tooltip, // 3
        chart.userOptions.tooltip, // 4
        userPlotOptions.series?.tooltip, // 5
        userPlotOptionsType.tooltip, // 6
        seriesUserOptions.tooltip // 7
        );
        // When shared tooltip, stickyTracking is true by default,
        // unless user says otherwise.
        this.stickyTracking = pick(seriesUserOptions.stickyTracking, userPlotOptionsType.stickyTracking, userPlotOptionsSeries.stickyTracking, (this.tooltipOptions.shared && !this.noSharedTooltip ?
            true :
            options.stickyTracking));
        // Delete marker object if not allowed (#1125)
        if (typeOptions.marker === null) {
            delete options.marker;
        }
        this.zones || (this.zones = zonesCopy);
        this.zoneAxis = zoneAxis;
        if ((negativeColor || negativeFillColor) &&
            !zones) {
            zone = {
                value: options[zoneAxis + 'Threshold'] ||
                    options.threshold ||
                    0,
                className: 'highcharts-negative'
            };
            if (!styledMode) {
                // Styled mode allows boolean
                if (typeof negativeColor !== 'boolean') {
                    zone.color = negativeColor;
                }
                zone.fillColor = negativeFillColor;
            }
            zonesCopy.push(zone);
        }
        // Push one extra zone for the rest
        if (zonesCopy.length &&
            defined(zonesCopy[zonesCopy.length - 1].value)) {
            zonesCopy.push(styledMode ? {} : {
                color: this.color,
                fillColor: this.fillColor
            });
        }
        fireEvent(this, 'afterSetOptions', { options });
        return options;
    }
    /**
     * Return the name for the series. Looks for a `name` in the options. If not
     * found, looks for a column name in the data mapping. If not found, returns
     * a default name based on the series type and index in `Series {n}`"
     * format. This method can be simply overridden as series name format can
     * vary (e.g. technical indicators).
     *
     * @function Highcharts.Series#getName
     *
     * @return {string} The series name.
     */
    getName() {
        const { chart, options } = this, { dataMapping, name } = options, valueMapping = dataMapping?.y || dataMapping?.value, columnKey = isString(valueMapping) ?
            valueMapping : valueMapping?.column;
        // #4119
        return name ?? (isString(columnKey) ?
            columnKey :
            Series_format(chart.options.lang.seriesName, this, chart));
    }
    /**
     * Set series-specific properties for color and symbol. Called internally
     * from Series.update().
     *
     * @internal
     * @function Highcharts.Series#getCyclic
     *
     * @param {'color'|'symbol'} prop
     *        The property to set, either `color` or `symbol`.
     * @param {*} [value]
     *        The value to set. If not given, the next available value is used.
     * @param {Highcharts.Dictionary<*>} [defaults]
     *        The default values.
     */
    getCyclic(prop, value, defaults) {
        const chart = this.chart, indexName = `${prop}Index`, counterName = `${prop}Counter`, len = (
        // Symbol count
        defaults?.length ||
            // Color count
            chart.options.chart.colorCount);
        let i, setting;
        if (!value) {
            // Pick up either the colorIndex option, or the series.colorIndex
            // after Series.update()
            setting = pick(prop === 'color' ? this.options.colorIndex : void 0, this[indexName]);
            if (defined(setting)) { // After Series.update()
                i = setting;
            }
            else {
                // #6138
                if (!chart.series.length) {
                    chart[counterName] = 0;
                }
                i = chart[counterName] % len;
                chart[counterName] += 1;
            }
            if (defaults) {
                value = defaults[i];
            }
        }
        // Set the colorIndex
        if (typeof i !== 'undefined') {
            this[indexName] = i;
        }
        this[prop] = value;
    }
    /**
     * Get the series' color based on either the options or pulled from
     * global options.
     *
     * @internal
     * @function Highcharts.Series#getColor
     */
    getColor() {
        const chart = this.chart;
        if (chart.styledMode) {
            this.getCyclic('color');
        }
        else if (this.options.colorByPoint) {
            this.color = 'var(--highcharts-neutral-color-20)';
        }
        else {
            this.getCyclic('color', this.options.color ||
                Series_defaultOptions.plotOptions[this.type]?.color, chart.options.colors);
        }
    }
    /**
     * Get all points' instances created for this series.
     *
     * @internal
     * @function Highcharts.Series#getPointsCollection
     */
    getPointsCollection() {
        return (this.hasGroupedData ? this.points : this.data) || [];
    }
    /**
     * Get the series' symbol based on either the options or pulled from
     * global options.
     *
     * @internal
     * @function Highcharts.Series#getSymbol
     */
    getSymbol() {
        const seriesMarkerOption = this.options.marker;
        this.getCyclic('symbol', seriesMarkerOption.symbol, this.chart.options.symbols);
    }
    /**
     * Shorthand to get one of the series' data columns from `Series.dataTable`.
     *
     * @internal
     * @function Highcharts.Series#getColumn
     */
    getColumn(columnId, modified, matchLength) {
        const table = modified ? this.dataTable.getModified() : this.dataTable, rowCount = table.rowCount, usingModified = this.dataTable !== table, column = table.getColumn(columnId, true);
        // When there is no x column in the data set, generate an internal x
        // column for the series. The `xColumn` array is cached and reused, but
        // cleared on series update.
        if (columnId === 'x' && !usingModified) {
            // Return cached xColumn if it exists
            if (this.xColumn) {
                return this.xColumn;
            }
            const nameColumn = table.getColumn('name', true), options = this.options, 
            // Check for empty or non-numeric x values. A for loop is faster
            // than Array.prototype.some, and covers empty slots. Cache the
            // result for faster subsequent checks.
            isNumbers = (arr) => {
                if (this.xColumnIsNumbers !== void 0) {
                    return this.xColumnIsNumbers;
                }
                for (const x of arr) {
                    if (typeof x !== 'number') {
                        return (this.xColumnIsNumbers = false);
                    }
                }
                return (this.xColumnIsNumbers = true);
            };
            // Reset the counter
            this.xIncrement = null;
            // Under these conditions, we need to generate the x data
            if (!column ||
                this.xAxis?.hasNames ||
                options.relativeXValue ||
                // X column exists in the data table, but has gaps or strings
                (column.length < (options.turboThreshold || Infinity) &&
                    !this.boosted &&
                    !isNumbers(column))) {
                const xColumn = [];
                for (let i = 0; i < rowCount; i++) {
                    const xOption = column?.[i];
                    if (!defined(xOption)) {
                        // When x values are missing, make sure we
                        // auto-increment from the last point, not from zero.
                        // Otherwise date-axes would be extended from
                        // 1970-01-01.
                        this.xIncrement ?? (this.xIncrement = xColumn[xColumn.length - 1] ?? null);
                    }
                    xColumn.push(this.getX(xOption, nameColumn?.[i]));
                }
                return (this.xColumn = xColumn);
            }
        }
        return column || Array(matchLength ? rowCount : 0);
    }
    /**
     * Get the x value for a given point.
     *
     * @internal
     */
    getX(xOption, name) {
        if (this.xAxis?.hasNames &&
            this.dataTable.getColumn('name', true) &&
            defined(name)) {
            return this.xAxis.nameToX({ name, series: this }, xOption);
        }
        if (typeof xOption === 'undefined' ||
            (isNumber(xOption) && this.options.relativeXValue)) {
            return this.autoIncrement(xOption);
        }
        // If x is a string, try to parse it to a datetime
        if (typeof xOption === 'string') {
            xOption = this.chart.time.parse(xOption);
            if (isNumber(xOption)) {
                return xOption;
            }
        }
        return xOption;
    }
    /**
     * Internal function called from setData. If the point count is the same
     * as it was, or if there are overlapping X values, just run
     * Point.update which is cheaper, allows animation, and keeps references
     * to points. This also allows adding or removing points if the X-es
     * don't match.
     *
     * @internal
     * @function Highcharts.Series#matchPoints
     */
    matchPoints(oldXColumn, oldIdColumn, oldNameColumn, 
    // Index matching is used by the data-sorting module
    oldIndexColumn) {
        const { dataTable, options, requireSorting } = this, dataSorting = options.dataSorting, oldData = this.data, rowsToAdd = [], rowsToUpdate = [], equalLength = dataTable.rowCount === oldData.length;
        let hasUpdatedByKey, i, point, lastIndex = 0, succeeded = true;
        this.xIncrement = null;
        delete this.xColumn;
        const newXColumn = dataTable.getColumn('x'), newIdColumn = dataTable.getColumn('id'), newNameColumn = dataSorting?.matchByName ?
            dataTable.getColumn('name') : void 0, newIndexColumn = dataTable.getColumn('index');
        // Iterate the new data
        for (i = 0; i < dataTable.rowCount; i++) {
            const x = newXColumn?.[i], id = newIdColumn?.[i], name = newNameColumn?.[i], index = newIndexColumn?.[i], [needle, haystack] = id && oldIdColumn ?
                [id, oldIdColumn] :
                name && oldNameColumn ?
                    [name, oldNameColumn] :
                    defined(index) && oldIndexColumn ?
                        [index, oldIndexColumn] :
                        defined(x) && oldXColumn ?
                            [x, oldXColumn] :
                            [];
            let pointIndex = -1;
            // We have a needle and a haystack to search for matching points
            if (haystack) {
                pointIndex = haystack.indexOf(needle, lastIndex);
                // Matching X not found or used already due to non-unique x
                // values (#8995), add point (but later)
                if (pointIndex === -1) {
                    const optionsX = newXColumn?.[i];
                    let newIndex = oldXColumn?.length ?? dataTable.rowCount;
                    while (newIndex &&
                        oldXColumn &&
                        typeof optionsX === 'number' &&
                        oldXColumn[newIndex - 1] > optionsX) {
                        newIndex--;
                    }
                    rowsToAdd.push({ newIndex, oldIndex: i });
                    // Matching X found, update
                }
                else if (oldData[pointIndex] /* &&
                pOptions === oldData[pointIndex]?.options*/) {
                    rowsToUpdate.push({
                        newIndex: pointIndex,
                        oldIndex: i
                    });
                    // Mark it touched, below we will remove all points that
                    // are not touched.
                    oldData[pointIndex].touched = true;
                    // Speed optimize by only searching after last known
                    // index. Performs ~20% better on large data sets.
                    if (requireSorting) {
                        lastIndex = pointIndex + 1;
                    }
                    // Point exists, no changes, don't remove it
                } /*/ else if (oldData[pointIndex]) {
                    oldData[pointIndex].touched = true;
                }*/
                // If the length is equal and some of the nodes had a
                // match in the same position, we don't want to remove
                // non-matches.
                if (!equalLength ||
                    i !== pointIndex ||
                    dataSorting?.enabled ||
                    this.hasDerivedData) {
                    hasUpdatedByKey = true;
                }
            }
            else {
                // Gather all points that are not matched
                rowsToAdd.push({ newIndex: i, oldIndex: i });
            }
        }
        // Remove points that don't exist in the updated data set
        if (hasUpdatedByKey) {
            // Update matching points
            rowsToUpdate.forEach((row) => {
                oldData[row.newIndex].applyOptions(dataTable.getRowObject(row.oldIndex));
            });
            // Add new points
            rowsToAdd.sort((a, b) => b.newIndex - a.newIndex);
            rowsToAdd.forEach((data) => {
                // Splice in an undefined item, `generatePoints` will pick it
                // up and create the point
                oldData.splice(data.newIndex, 0, void 0);
            });
            // Remove points not touched
            i = oldData.length;
            while (i--) {
                point = oldData[i];
                if (point && !point.touched) {
                    point.destroy();
                    oldData.splice(i, 1);
                }
            }
            this.isDirtyData = this.isDirty = true;
            // If we did not find keys (ids or x-values), and the length is the
            // same, update one-to-one
        }
        else if (equalLength && !dataSorting?.enabled) {
            for (i = 0; i < dataTable.rowCount; i++) {
                if (!oldData[i].destroyed && !oldData[i].condemned) {
                    const pOptions = dataTable.getRowObject(i);
                    if (pOptions) {
                        Object.keys(pOptions).forEach((key) => {
                            if (!defined(pOptions[key]) /* ||
                            pOptions[key] === oldData[i].options[key]*/) {
                                delete pOptions[key];
                            }
                        });
                        if (Object.keys(pOptions).length) {
                            oldData[i].update(pOptions, false, void 0, false);
                        }
                    }
                }
            }
            // Did not succeed in updating data
        }
        else {
            succeeded = false;
        }
        oldData.forEach((point) => {
            if (point) {
                point.touched = false;
            }
        });
        if (!succeeded) {
            return false;
        }
        const xData = this.getColumn('x');
        if (this.xIncrement === null &&
            xData.length) {
            this.xIncrement = arrayMax(xData);
            this.autoIncrement();
        }
        return true;
    }
    getDataColumnKeys() {
        return this.dataColumnKeys || ['x', ...(this.pointArrayMap || ['y'])];
    }
    /**
     * Apply a new set of data to the series and optionally redraw it. The new
     * data array is passed by reference (except in case of `updatePoints`), and
     * may later be mutated when updating the chart data.
     *
     * Note the difference in behavior when setting the same amount of points,
     * or a different amount of points, as handled by the `updatePoints`
     * parameter.
     *
     * @sample highcharts/members/series-setdata/
     *         Set new data from a button
     * @sample highcharts/members/series-setdata-pie/
     *         Set data in a pie
     * @sample stock/members/series-setdata/
     *         Set new data in Highcharts Stock
     * @sample maps/members/series-setdata/
     *         Set new data in Highmaps
     *
     * @function Highcharts.Series#setData
     *
     * @param {Array<Highcharts.PointOptionsType>|Highcharts.DataTableOptionsObject|Highcharts.DataTable|undefined} data
     *        Takes an array of data in the same format as described under
     *        `series.{type}.data` for the given series type, for example a line
     *        series would take data in the form described under
     *        [series.line.data](https://api.highcharts.com/highcharts/series.line.data).
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart after the series is altered. If doing
     *        more operations on the chart, it is a good idea to set redraw to
     *        false and call {@link Chart#redraw} after.
     *
     * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
     *        When the updated data is the same length as the existing data,
     *        points will be updated by default, and animation visualizes how
     *        the points are changed. Set false to disable animation, or a
     *        configuration object to set duration or easing.
     *
     * @param {boolean} [updatePoints=true]
     *        When this is true, points will be updated instead of replaced
     *        whenever possible. This occurs a) when the updated data is the
     *        same length as the existing data, b) when points are matched by
     *        their id's, or c) when points can be matched by X values. This
     *        allows updating with animation and performs better. In this case,
     *        the original array is not passed by reference. Set `false` to
     *        prevent.
     */
    setData(data, redraw = true, animation, updatePoints) {
        const series = this, table = this.dataTable, options = series.options, oldData = series.points, oldDataLength = oldData?.length || 0, oldXColumn = table.getColumn('x'), oldIdColumn = table.getColumn('id'), oldNameColumn = (
        // To get the bar race right. When data sorting is enabled, the
        // point order is not in sync with the table order. Could this
        // be done in a better way, maybe in the data sorting module?
        options.dataSorting?.matchByName &&
            oldData?.map((point) => point.name)) || table.getColumn('name'), oldIndexColumn = table.getColumn('index'), chart = series.chart, xAxis = series.xAxis;
        let updatedData, i, copiedData;
        if (!chart.options.chart.allowMutatingData) { // #4259
            // Remove old reference
            if (options.data) {
                delete series.options.data;
            }
            if (series.userOptions.data) {
                delete series.userOptions.data;
            }
            copiedData = merge(true, data);
        }
        data = copiedData || data;
        // Reset properties
        series.xIncrement = null;
        delete series.xColumn;
        delete series.xColumnIsNumbers;
        if (table !== options.dataTable) {
            delete table.columns.x;
        }
        series.colorCounter = 0; // For series with colorByPoint (#1547)
        // Array passed as option
        if (isArray(data)) {
            this.setDataFromArray(data);
            // Data table passed as option, either on series or chart
        }
        else {
            this.setDataFromTable(data);
        }
        if (updatePoints !== false &&
            oldDataLength &&
            !series.cropped &&
            !series.hasGroupedData &&
            series.visible &&
            // Soft updating has no benefit in boost, and causes JS error
            // (#8355)
            !series.boosted) {
            updatedData = this.matchPoints(oldXColumn, oldIdColumn, oldNameColumn, oldIndexColumn);
        }
        if (!updatedData) {
            // Forgetting to cast strings to numbers is a common caveat when
            // handling CSV or JSON
            if (isString(this.getColumn('y')[0])) {
                error(14, true, chart);
            }
            series.data = [];
            // Destroy old points
            i = oldDataLength;
            while (i--) {
                oldData[i]?.destroy();
            }
            // Reset minRange (#878)
            if (xAxis) {
                xAxis.minRange = xAxis.userMinRange;
            }
            // Redraw
            series.isDirty = chart.isDirtyBox = true;
            series.isDirtyData = !!oldData;
            animation = false;
        }
        if (isArray(data)) {
            series.options.data = series.userOptions.data = data;
        }
        // Typically for pie series, points need to be processed and generated
        // prior to rendering the legend
        if (options.legendType === 'point') {
            this.processData();
            this.generatePoints();
        }
        if (redraw) {
            chart.redraw(animation);
        }
    }
    /**
     * Internal function to set data from an array of point options - objects,
     * arrays or numbers. This corresponds to the `data` series option. Called
     * from the official `setData` method.
     *
     * @param {Array<Highcharts.PointOptionsType>} data
     *        The data array
     * @internal
     */
    setDataFromArray(data) {
        const dataLength = data.length, { keys, turboThreshold } = this.options, { pointValKey = 'y', pointArrayMap = [] } = this, valueCount = pointArrayMap.length, table = this.dataTable, dataColumnKeys = this.getDataColumnKeys();
        // In turbo mode, look for one- or twodimensional arrays of numbers. The
        // first and the last valid value are tested, and we assume that all the
        // rest are defined the same way. Although the 'for' loops are similar,
        // they are repeated inside each if-else conditional for max
        // performance.
        let runTurbo = turboThreshold && dataLength > turboThreshold, indexOfX = 0, indexOfY = 1;
        if (runTurbo) {
            const firstPoint = this.getFirstValidPoint(data), lastPoint = this.getFirstValidPoint(data, dataLength - 1, -1), isShortArray = (a) => Boolean(isArray(a) && (keys || isNumber(a[0])));
            // Assume all points are numbers
            if (isNumber(firstPoint) && isNumber(lastPoint)) {
                table.setColumn(pointValKey, data);
                // Assume all points are arrays when first point is
            }
            else if (isShortArray(firstPoint) &&
                isShortArray(lastPoint)) {
                if (valueCount) { // [x, low, high] or [x, o, h, l, c]
                    // When autoX is 1, the x is skipped: [low, high]. When
                    // autoX is 0, the x is included: [x, low, high]
                    const autoX = firstPoint.length === valueCount, colArray = new Array(firstPoint.length)
                        .fill(0).map(() => []);
                    for (const pt of data) {
                        for (let j = 0; j <= valueCount; j++) {
                            colArray[j]?.push(pt[j]);
                        }
                    }
                    table.setColumns((autoX ? pointArrayMap : dataColumnKeys).reduce((columns, columnId, i) => {
                        columns[columnId] = colArray[i];
                        return columns;
                    }, {}));
                }
                else { // [x, y]
                    if (keys) {
                        indexOfX = keys.indexOf('x');
                        indexOfY = keys.indexOf('y');
                        indexOfX = indexOfX >= 0 ? indexOfX : 0;
                        indexOfY = indexOfY >= 0 ? indexOfY : 1;
                    }
                    if (firstPoint.length === 1) {
                        indexOfY = 0;
                    }
                    const xData = [], valueData = [];
                    if (indexOfX === indexOfY) {
                        for (const pt of data) {
                            valueData.push(pt[indexOfY]);
                        }
                        table.setColumn(pointValKey, valueData);
                    }
                    else {
                        for (const pt of data) {
                            xData.push(pt[indexOfX]);
                            valueData.push(pt[indexOfY]);
                        }
                        table.setColumns({
                            x: xData,
                            [pointValKey]: valueData
                        });
                    }
                }
            }
            else {
                // Highcharts expects configs to be numbers or arrays in turbo
                // mode
                runTurbo = false;
            }
        }
        if (!runTurbo) {
            const columns = {};
            for (let i = 0; i < dataLength; i++) {
                const ptOptions = this.pointClass.prototype
                    .optionsToObject
                    .call({ series: this }, data[i]);
                for (const key of Object.keys(ptOptions)) {
                    columns[key] || (columns[key] = new Array(dataLength));
                    columns[key][i] = ptOptions[key];
                }
            }
            // Empty data, clear table
            if (dataLength) {
                table.setColumns(columns);
            }
            else {
                table.deleteRows(0, table.rowCount);
            }
        }
    }
    /**
     * Internal function to set data from a data table, either an instance or
     * options object. This corresponds to the `data` series option. Called
     * from the official `setData` method.
     *
     * @param {Highcharts.DataTableOptionsObject|Highcharts.DataTable} data
     *        The data array
     * @internal
     */
    setDataFromTable(data) {
        const { chart, options, dataTable: table } = this, seriesDataTable = chart.getDataTable(options), dataTables = data ? [data] : (
        // Use either dataTable from series options or from the chart
        seriesDataTable.length ?
            seriesDataTable :
            chart.dataTable), dataColumnKeys = this.getDataColumnKeys(), mapping = options.dataMapping, keys = dataColumnKeys.slice();
        // Extend the data column keys with the keys from the column assignment
        if (mapping) {
            Object.keys(mapping).forEach((key) => {
                pushUnique(keys, key);
            });
            this.dataColumnKeys = keys;
        }
        dataTables.forEach((dataTable, dtIndex) => {
            // Resolve the data mapping
            const columns = keys
                .reduce((targetColumns, key) => {
                const mappingItem = mapping?.[key], srcColumns = dataTable.columns || {}, dtId = dataTable.id, column = isString(mappingItem) ?
                    // String definition points directly to a column id
                    // on the first data table
                    (dtIndex === 0 && srcColumns[mappingItem]) :
                    // Object definition, check for matching data table
                    // and column id/index
                    ((mappingItem?.dataTable || 0) === dtIndex ||
                        (dtId && mappingItem?.dataTable === dtId)) &&
                        (isNumber(mappingItem?.column) ?
                            Object.values(srcColumns)[mappingItem.column] :
                            srcColumns[mappingItem?.column || key]);
                if (column) {
                    targetColumns[key] = column;
                }
                return targetColumns;
            }, {});
            // If a DataTable is passed and no column assignment is set, use it
            // directly
            if (mapping || dataTable) {
                // Set the columns
                table.setColumns(columns);
            }
            // If a DataTable is passed directly by reference, bind events to
            // keep the series updated
            if (dataTable.isDataTable) {
                this.bindDataTableEvents(dataTable, columns);
            }
        });
    }
    /**
     * Bind data table events to keep the series updated when changes occur to
     * the data table.
     *
     * @internal
     */
    bindDataTableEvents(dataTable, columns) {
        if (this.hasBoundDataTableEvents) {
            return;
        }
        const { chart, eventsToUnbind } = this, queueRedraw = () => {
            clearTimeout(chart.redrawTimeout);
            chart.redrawTimeout = setTimeout(() => chart.renderer && chart.redraw(), 0);
        };
        eventsToUnbind.push(addEvent(dataTable, 'afterSetRows', (e) => {
            const rowIndex = e.rowIndex;
            if (isNumber(rowIndex)) {
                const row = Data_DataTableCore.prototype.getRowObject.call({ columns }, rowIndex), point = this.data[rowIndex];
                if (row) {
                    if (this.currentDataGrouping) {
                        // Set data with immediate redraw because it
                        // destroys points
                        this.setData();
                    }
                    else {
                        if (point) {
                            point.update(row, false);
                        }
                        else {
                            this.addPoint(row, false);
                        }
                        queueRedraw();
                    }
                }
            }
        }));
        eventsToUnbind.push(addEvent(dataTable, 'afterDeleteRows', (e) => {
            const { rowCount, rowIndex } = e;
            if (isNumber(rowIndex)) {
                for (let i = rowIndex + rowCount - 1; i >= rowIndex; i--) {
                    this.removePoint(i, false);
                }
                queueRedraw();
            }
        }));
        eventsToUnbind.push(addEvent(dataTable, 'afterSetColumns', (e) => {
            this.setData(e.target);
        }));
        this.hasBoundDataTableEvents = true;
    }
    /**
     * Internal function to process the data by cropping away unused data
     * points if the series is longer than the crop threshold. This saves
     * computing time for large series.
     *
     * @internal
     * @function Highcharts.Series#getProcessedData
     * @param {boolean} [forceExtremesFromAll]
     * Force getting extremes of a total series data range.
     */
    getProcessedData(forceExtremesFromAll) {
        const series = this, { dataTable: table, isCartesian, options, xAxis } = series, cropThreshold = options.cropThreshold, getExtremesFromAll = forceExtremesFromAll ||
            // X-range series etc, #21003
            series.getExtremesFromAll, logarithmic = xAxis?.logarithmic, dataLength = table.rowCount;
        let croppedData, cropped, cropStart = 0, xExtremes, min, max, xData = series.getColumn('x'), modified = table, updatingNames = false;
        if (xAxis) {
            // Corrected for log axis (#3053)
            xExtremes = xAxis.getExtremes();
            min = xExtremes.min;
            max = xExtremes.max;
            updatingNames = !!(xAxis.categories && !xAxis.names.length);
            // Optionally filter out points outside the plot area
            if (isCartesian &&
                series.sorted &&
                !getExtremesFromAll &&
                (!cropThreshold ||
                    dataLength > cropThreshold ||
                    series.forceCrop)) {
                // It's outside current extremes
                if (xData[dataLength - 1] < min ||
                    xData[0] > max) {
                    modified = new Data_DataTableCore();
                    // Only crop if it's actually spilling out
                }
                else if (
                // Don't understand why this condition is needed
                series.getColumn(series.pointValKey || 'y').length && (xData[0] < min ||
                    xData[dataLength - 1] > max)) {
                    croppedData = this.cropData(table, min, max);
                    modified = croppedData.modified;
                    cropStart = croppedData.start;
                    cropped = true;
                }
            }
        }
        // Find the closest distance between processed points
        xData = this.getColumn('x', true);
        const closestPointRange = getClosestDistance([
            logarithmic ?
                xData.map(logarithmic.log2lin) :
                xData
        ], 
        // Unsorted data is not supported by the line tooltip, as well as
        // data grouping and navigation in Stock charts (#725) and width
        // calculation of columns (#1900). Avoid warning during the
        // premature processing pass in updateNames (#16104).
        () => (series.requireSorting &&
            !updatingNames &&
            error(15, false, series.chart)));
        return {
            modified,
            cropped,
            cropStart,
            closestPointRange
        };
    }
    /**
     * Internal function to apply processed data.
     * In Highcharts Stock, this function is extended to provide data grouping.
     *
     * @internal
     * @function Highcharts.Series#processData
     * @param {boolean} [force]
     * Force data grouping.
     */
    processData(force) {
        const series = this, xAxis = series.xAxis, table = series.dataTable;
        // If the series data or axes haven't changed, don't go through
        // this. Return false to pass the message on to override methods
        // like in data grouping.
        if (series.isCartesian &&
            !series.isDirty &&
            !xAxis.isDirty &&
            !series.yAxis.isDirty &&
            !force) {
            return false;
        }
        const processedData = series.getProcessedData();
        // Record the properties
        table.modified = processedData.modified;
        series.cropped = processedData.cropped; // Undefined or true
        series.cropStart = processedData.cropStart;
        series.closestPointRange = (series.basePointRange = processedData.closestPointRange);
        fireEvent(series, 'afterProcessData');
    }
    /**
     * Iterate over xData and crop values between min and max. Returns
     * object containing crop start/end cropped xData with corresponding
     * part of yData, dataMin and dataMax within the cropped range.
     *
     * @internal
     * @function Highcharts.Series#cropData
     */
    cropData(table, min, max) {
        const xData = table === this.dataTable ?
            this.getColumn('x') :
            table.getColumn('x', true), dataLength = xData.length;
        let i, j, start = 0, end = dataLength;
        // Iterate up to find slice start
        for (i = 0; i < dataLength; i++) {
            if (xData[i] >= min) {
                start = Math.max(0, i - 1);
                break;
            }
        }
        // Proceed to find slice end
        for (j = i; j < dataLength; j++) {
            if (xData[j] > max) {
                end = j + 1;
                break;
            }
        }
        // Slice all the columns and return a copy
        const columns = Object.keys(table.columns)
            .reduce((columns, key) => {
            columns[key] = (table.getColumn(key, true) || []).slice(start, end);
            return columns;
        }, {});
        // Add cropped x data to modified table
        columns.x = xData.slice(start, end);
        return {
            modified: new Data_DataTableCore({ columns }),
            start,
            end
        };
    }
    /**
     * Generate the data point after the data has been processed by cropping
     * away unused points and optionally grouped in Highcharts Stock.
     *
     * @internal
     * @function Highcharts.Series#generatePoints
     */
    generatePoints() {
        const series = this, options = series.options, dataOptions = series.hasProcessedDataTable ? void 0 : options.data, table = series.dataTable.getModified(), xData = series.getColumn('x', true), PointClass = series.pointClass, processedDataLength = table.rowCount, cropStart = series.cropStart || 0, hasGroupedData = series.hasGroupedData, keys = options.keys, points = [], groupCropStartIndex = (options.dataGrouping?.groupAll ?
            cropStart :
            0), 
        // Create a configuration object out of a data row
        dataColumnKeys = this.getDataColumnKeys();
        let dataLength, cursor, point, i, data = series.data, pOptions;
        if (!data && !hasGroupedData) {
            const arr = [];
            arr.length = dataOptions?.length || 0;
            data = series.data = arr;
        }
        if (keys && hasGroupedData) {
            // Grouped data has already applied keys (#6590)
            series.options.keys = false;
        }
        for (i = 0; i < processedDataLength; i++) {
            cursor = cropStart + i;
            if (!hasGroupedData) {
                point = data[cursor];
                pOptions = dataOptions ?
                    // We could use table.getRowObject(i) here, but that would
                    // be less performant
                    dataOptions[cursor] :
                    table.getRowObject(i);
                // #970
                if (!point && pOptions !== void 0) {
                    data[cursor] = point = new PointClass(series, pOptions, xData[i]);
                }
                else if (point) {
                    // Point properties for convenient access in tooltip and
                    // data labels might need to be updated.
                    point.category = series.xAxis?.categories?.[point.x] ??
                        point.x;
                    point.key = point.name ?? point.category;
                }
            }
            else {
                // Splat the y data in case of ohlc data array
                point = new PointClass(series, table.getRowObject(i, dataColumnKeys));
                point.dataGroup = series.groupMap?.[groupCropStartIndex + i];
                if (point.dataGroup?.options) {
                    point.options = point.dataGroup.options;
                    extend(point, point.dataGroup.options);
                    // Collision of props and options (#9770)
                    delete point.dataLabels;
                    // Update key in case name changed
                    point.key = point.name ?? point.category;
                }
            }
            if (point) { // #6279
                // For faster access in Point.update
                point.index = hasGroupedData ?
                    (groupCropStartIndex + i) : cursor;
                points[i] = point;
            }
        }
        // Restore keys options (#6590)
        series.options.keys = keys;
        // Hide cropped-away points - this only runs when the number of
        // points is above cropThreshold, or when switching view from
        // non-grouped data to grouped data (#637)
        if (data &&
            (processedDataLength !== (dataLength = data.length) ||
                hasGroupedData)) {
            for (i = 0; i < dataLength; i++) {
                // When has grouped data, clear all points
                if (i === cropStart && !hasGroupedData) {
                    i += processedDataLength;
                }
                if (data[i]) {
                    data[i].destroyElements();
                    data[i].plotX = void 0; // #1003
                }
            }
        }
        series.data = data;
        series.points = points;
        fireEvent(this, 'afterGeneratePoints');
    }
    /**
     * Get current X extremes for the visible data.
     *
     * @internal
     * @function Highcharts.Series#getXExtremes
     * @param {Array<number>} xData
     * The data to inspect. Defaults to the current data within the visible
     * range.
     */
    getXExtremes(xData) {
        return {
            min: arrayMin(xData),
            max: arrayMax(xData)
        };
    }
    /**
     * Calculate Y extremes for the visible data. The result is returned
     * as an object with `dataMin` and `dataMax` properties.
     *
     * @internal
     * @function Highcharts.Series#getExtremes
     * @param {Array<number>} [yData]
     * The data to inspect. Defaults to the current data within the visible
     * range.
     * @param {boolean} [forceExtremesFromAll]
     * Force getting extremes of a total series data range.
     */
    getExtremes(yData, forceExtremesFromAll) {
        const { xAxis, yAxis } = this, getExtremesFromAll = forceExtremesFromAll ||
            this.getExtremesFromAll ||
            this.options.getExtremesFromAll, // #4599, #21003
        table = getExtremesFromAll && this.cropped ?
            this.dataTable :
            this.dataTable.getModified(), rowCount = table.rowCount, customData = yData || this.stackedYData, yAxisData = customData ?
            [customData] :
            (this.keysAffectYAxis || this.pointArrayMap || ['y'])?.map((key) => table.getColumn(key, true) || []) || [], xData = this.getColumn('x', true), activeYData = [], 
        // Handle X outside the viewed area. This does not work with
        // non-sorted data like scatter (#7639).
        shoulder = this.requireSorting && !this.is('column') ?
            1 : 0, 
        // #2117, need to compensate for log X axis
        positiveValuesOnly = yAxis ? yAxis.positiveValuesOnly : false, doAll = getExtremesFromAll ||
            this.cropped ||
            !xAxis; // For colorAxis support
        let xExtremes, x, i, xMin = 0, xMax = 0;
        if (xAxis) {
            xExtremes = xAxis.getExtremes();
            xMin = xExtremes.min;
            xMax = xExtremes.max;
        }
        for (i = 0; i < rowCount; i++) {
            x = xData[i];
            // Check if it is within the selected x axis range
            if (doAll ||
                ((xData[i + shoulder] || x) >= xMin &&
                    (xData[i - shoulder] || x) <= xMax)) {
                for (const values of yAxisData) {
                    const val = values[i];
                    // For points within the visible range, including the
                    // first point outside the visible range (#7061),
                    // consider y extremes.
                    if (isNumber(val) &&
                        (val > 0 || !positiveValuesOnly)) {
                        activeYData.push(val);
                    }
                }
            }
        }
        const dataExtremes = {
            activeYData, // Needed for Stock Cumulative Sum
            dataMin: arrayMin(activeYData),
            dataMax: arrayMax(activeYData)
        };
        fireEvent(this, 'afterGetExtremes', { dataExtremes });
        return dataExtremes;
    }
    /**
     * Set the current data extremes as `dataMin` and `dataMax` on the
     * Series item. Use this only when the series properties should be
     * updated.
     *
     * @internal
     * @function Highcharts.Series#applyExtremes
     */
    applyExtremes() {
        const dataExtremes = this.getExtremes();
        this.dataMin = dataExtremes.dataMin;
        this.dataMax = dataExtremes.dataMax;
        return dataExtremes;
    }
    /**
     * Find and return the first non nullish point in the data
     *
     * @internal
     * @function Highcharts.Series.getFirstValidPoint
     * @param {Array<Highcharts.PointOptionsType>} data
     *        Array of options for points
     * @param {number} [start=0]
     *        Index to start searching from
     * @param {number} [increment=1]
     *        Index increment, set -1 to search backwards
     */
    getFirstValidPoint(data, start = 0, increment = 1) {
        const dataLength = data.length;
        let i = start;
        while (i >= 0 && i < dataLength) {
            if (defined(data[i])) {
                return data[i];
            }
            i += increment;
        }
    }
    /**
     * Translate data points from raw data values to chart specific
     * positioning data needed later in the `drawPoints` and `drawGraph`
     * functions. This function can be overridden in plugins and custom
     * series type implementations.
     *
     * @function Highcharts.Series#translate
     *
     * @emits Highcharts.Series#events:translate
     */
    translate() {
        this.generatePoints();
        const series = this, { options, xAxis, yAxis } = series, { stacking, threshold } = options, { hasRendered, polar } = series.chart, points = series.points.concat(series.condemnedPoints), dataLength = points.length, pointPlacement = series.pointPlacementToXValue(), // #7860
        dynamicallyPlaced = Boolean(pointPlacement), stackThreshold = options.startFromThreshold ? threshold : 0, nullYSubstitute = (options?.nullInteraction &&
            yAxis.len);
        let i, plotX, lastPlotX, stackIndicator, closestPointRangePx = Number.MAX_VALUE;
        /**
         * Plotted coordinates need to be within a limited range. Drawing
         * too far outside the viewport causes various rendering issues
         * (#3201, #3923, #7555).
         * @internal
         */
        function limitedRange(val) {
            return clamp(val, -1e9, 1e9);
        }
        const getPlotX = (x, old) => {
            const plotX = xAxis.translate(x, false, false, old, true, pointPlacement);
            return isNumber(plotX) ? correctFloat(limitedRange(plotX)) : void 0;
        };
        const getPlotY = (yValue, point, old) => {
            // Set the plotY value, reset it for redraws #3201, #18422
            if (isNumber(yValue) && point.plotX !== void 0) {
                const plotY = yAxis.translate(yValue, false, true, old, true);
                return isNumber(plotY) ? limitedRange(plotY) : void 0;
            }
            if (!isNumber(yValue) && nullYSubstitute) {
                return nullYSubstitute;
            }
        };
        // Translate each point
        for (i = 0; i < dataLength; i++) {
            const point = points[i], xValue = point.x;
            let stackItem, stackValues, yValue = point.y, lowValue = point.low;
            const stacks = stacking && yAxis.stacking?.stacks[(series.negStacks &&
                yValue <
                    (stackThreshold ? 0 : threshold) ?
                '-' :
                '') + series.stackKey];
            point.plotX = plotX = getPlotX(xValue);
            // Calculate the bottom y value for stacked series
            if (stacking &&
                series.visible &&
                stacks &&
                stacks[xValue]) {
                stackIndicator = series.getStackIndicator(stackIndicator, xValue, series.index);
                if (!point.isNull && stackIndicator.key) {
                    stackItem = stacks[xValue];
                    stackValues = stackItem.points[stackIndicator.key];
                }
                if (stackItem && isArray(stackValues)) {
                    lowValue = stackValues[0];
                    yValue = stackValues[1];
                    if (lowValue === stackThreshold &&
                        stackIndicator.key === stacks[xValue].base) {
                        lowValue = pick(isNumber(threshold) ? threshold : yAxis.min);
                    }
                    // #1200, #1232
                    if (yAxis.positiveValuesOnly &&
                        defined(lowValue) &&
                        lowValue <= 0) {
                        lowValue = void 0;
                    }
                    point.total = point.stackTotal = pick(stackItem.total);
                    point.percentage = defined(point.y) && stackItem.total ?
                        (point.y / stackItem.total * 100) : void 0;
                    point.stackY = yValue;
                    // In case of variwide series (where widths of points are
                    // different in most cases), stack labels are positioned
                    // wrongly, so the call of the setOffset is omitted here and
                    // labels are correctly positioned later, at the end of the
                    // variwide's translate function (#10962)
                    if (!series.irregularWidths) {
                        stackItem.setOffset(series.pointXOffset || 0, series.barW || 0, void 0, void 0, void 0, series.xAxis);
                    }
                }
            }
            // Set translated yBottom or remove it
            point.yBottom = defined(lowValue) ?
                limitedRange(yAxis.translate(lowValue, false, true, false, true)) :
                void 0;
            // General hook, used for Highcharts Stock compare and cumulative
            if (series.dataModify) {
                yValue = series.dataModify.modifyValue(yValue, i);
            }
            point.plotY = getPlotY(yValue, point);
            // Set the starting point of point entrance animation
            point.origin = hasRendered && !point.graphic && !polar ? {
                x: getPlotX(xValue, true),
                y: getPlotY(yValue, point, true)
            } : void 0;
            point.isInside = this.isPointInside(point);
            // Set client related positions for mouse tracking
            point.clientX = dynamicallyPlaced ?
                correctFloat(xAxis.translate(xValue, false, false, false, true, pointPlacement)) :
                plotX; // #1514, #5383, #5518
            // Negative points #19028
            point.negative = (point.y || 0) < (threshold || 0);
            // Determine auto enabling of markers (#3635, #5099)
            if (!point.isNull && point.visible !== false && isNumber(plotX)) {
                if (isNumber(lastPlotX)) {
                    closestPointRangePx = Math.min(closestPointRangePx, Math.abs((plotX) - lastPlotX));
                }
                lastPlotX = plotX;
            }
            // Find point zone
            point.zone = this.zones.length ? point.getZone() : void 0;
        }
        series.closestPointRangePx = closestPointRangePx;
        fireEvent(this, 'afterTranslate');
    }
    /**
     * Return the series points with null points filtered out.
     *
     * @function Highcharts.Series#getValidPoints
     *
     * @param {Array<Highcharts.Point>} [points]
     * The points to inspect, defaults to {@link Series.points}.
     *
     * @param {boolean} [insideOnly=false]
     * Whether to inspect only the points that are inside the visible view.
     *
     * @param {boolean} [allowNull=false]
     * Whether to allow null points to pass as valid points.
     *
     * @return {Array<Highcharts.Point>}
     * The valid points.
     */
    getValidPoints(points, insideOnly, allowNull) {
        const chart = this.chart;
        // #3916, #5029, #5085
        return (points || this.points || []).filter(function (point) {
            const { plotX, plotY } = point, 
            // Undefined plotY is treated as null when negative values
            // in log axis (#18422)
            asNull = !allowNull && (point.isNull || !isNumber(plotY));
            if (asNull || (insideOnly && !chart.isInsidePlot(plotX, plotY, { inverted: chart.inverted }))) {
                return false;
            }
            return point.visible !== false;
        });
    }
    /**
     * Get the shared clip key, creating it if it doesn't exist.
     *
     * @internal
     * @function Highcharts.Series#getSharedClipKey
     */
    getSharedClipKey() {
        this.sharedClipKey = (this.options.xAxis || 0) + ',' +
            (this.options.yAxis || 0);
        return this.sharedClipKey;
    }
    /**
     * Set the clipping for the series. For animated series the clip is later
     * modified.
     *
     * @internal
     * @function Highcharts.Series#setClip
     */
    setClip() {
        const { chart, group, markerGroup, options, plotClipGroup } = this, sharedClips = chart.sharedClips, renderer = chart.renderer, clipBox = chart.getClipBox(this), clip = options.clip ?? true, sharedClipKey = this.getSharedClipKey(); // #4526
        let clipRect = sharedClips[sharedClipKey];
        // If a clipping rectangle for the same set of axes does not exist,
        // create it
        if (!clipRect) {
            sharedClips[sharedClipKey] = clipRect = renderer.clipRect(clipBox);
            // When setting chart size, or when the series is rendered again before
            // starting animating, in compliance to a responsive rule
        }
        else {
            clipRect.animate(clipBox);
        }
        // When clip is false, reset to no clip after animation
        group?.clip(clip ? clipRect : void 0);
        // Unclip temporary animation clip
        markerGroup?.clip();
        // Apply plotBorderRadius clipping
        plotClipGroup?.clip(
        // Navigator y-axis is not clippable
        clip && this.yAxis.clippable ?
            chart.plotClipInner :
            void 0);
    }
    /**
     * Animate in the series. Called internally twice. First with the `init`
     * parameter set to true, which sets up the initial state of the
     * animation. Then when ready, it is called with the `init` parameter
     * undefined, in order to perform the actual animation.
     *
     * @function Highcharts.Series#animate
     *
     * @param {boolean} [init]
     * Initialize the animation.
     */
    animate(init) {
        const { chart, group, markerGroup } = this, inverted = chart.inverted, animation = Series_animObject(this.options.animation), 
        // The key for temporary animation clips
        animationClipKey = [
            this.getSharedClipKey(),
            animation.duration,
            animation.easing,
            animation.defer
        ].join(',');
        let animationClipRect = chart.sharedClips[animationClipKey], markerAnimationClipRect = chart.sharedClips[animationClipKey + 'm'];
        // Initialize the animation. Set up the clipping rectangle.
        if (init && group) {
            const clipBox = chart.getClipBox(this);
            // Create temporary animation clips
            if (!animationClipRect) {
                clipBox.width = 0;
                if (inverted) {
                    clipBox.x = chart.plotHeight;
                }
                animationClipRect = chart.renderer.clipRect(clipBox);
                chart.sharedClips[animationClipKey] = animationClipRect;
                // The marker clip box. The number 99 is a safe margin to avoid
                // markers being clipped during animation.
                const markerClipBox = {
                    x: inverted ? -99 : -99,
                    y: inverted ? -99 : -99,
                    width: inverted ? chart.plotWidth + 199 : 99,
                    height: inverted ? 99 : chart.plotHeight + 199
                };
                markerAnimationClipRect = chart.renderer.clipRect(markerClipBox);
                chart.sharedClips[animationClipKey + 'm'] = markerAnimationClipRect;
            }
            else {
                // When height changes during animation, typically due to
                // responsive settings
                animationClipRect.attr('height', clipBox.height);
            }
            group.clip(animationClipRect);
            markerGroup?.clip(markerAnimationClipRect);
            // Run the animation
        }
        else if (animationClipRect &&
            // Only first series in this pane
            !animationClipRect.hasClass('highcharts-animating')) {
            const finalBox = chart.getClipBox(this), step = animation.step;
            // Only do this when there are actually markers, or we have multiple
            // series (#20473)
            if (markerGroup?.element.childNodes.length ||
                chart.series.length > 1) {
                // To provide as smooth animation as possible, update the marker
                // group clipping in steps of the main group animation
                animation.step = function (val, fx) {
                    if (step) {
                        step.apply(fx, arguments);
                    }
                    if (fx.prop === 'width' &&
                        markerAnimationClipRect?.element) {
                        markerAnimationClipRect.attr(inverted ? 'height' : 'width', val + 99);
                    }
                };
            }
            animationClipRect
                .addClass('highcharts-animating')
                .animate(finalBox, animation);
        }
    }
    /**
     * This runs after animation to land on the final plot clipping.
     *
     * @internal
     * @function Highcharts.Series#afterAnimate
     *
     * @emits Highcharts.Series#event:afterAnimate
     */
    afterAnimate() {
        this.setClip();
        // Destroy temporary clip rectangles that are no longer in use
        objectEach(this.chart.sharedClips, (clip, key, sharedClips) => {
            if (clip && !this.chart.container.querySelector(`[clip-path="url(#${clip.id})"]`)) {
                clip.destroy();
                delete sharedClips[key];
            }
        });
        this.finishedAnimating = true;
        fireEvent(this, 'afterAnimate');
    }
    /**
     * Draw the markers for line-like series types, and columns or other
     * graphical representation for {@link Highcharts.Point} objects for other
     * series types. The resulting element is typically stored as
     * {@link Highcharts.Point#graphic}, and is created on the first call and
     * updated and moved on subsequent calls.
     *
     * @function Highcharts.Series#drawPoints
     */
    drawPoints(points) {
        points || (points = this.points.concat(this.condemnedPoints));
        const series = this, chart = series.chart, styledMode = chart.styledMode, { colorAxis, options } = series, seriesMarkerOptions = options.marker, nullInteraction = options.nullInteraction, markerGroup = series[series.specialGroup || 'markerGroup'], xAxis = series.xAxis, globallyEnabled = pick(seriesMarkerOptions.enabled, !xAxis || xAxis.isRadial ? true : null, 
        // Use larger or equal as radius is null in bubbles (#6321)
        series.closestPointRangePx >= (seriesMarkerOptions.enabledThreshold *
            seriesMarkerOptions.radius));
        let i, point, graphic, verb, pointMarkerOptions, hasPointMarker, markerAttribs;
        if (seriesMarkerOptions.enabled !== false ||
            series._hasPointMarkers) {
            for (i = 0; i < points.length; i++) {
                point = points[i];
                graphic = point.graphic;
                verb = graphic ? 'animate' : 'attr';
                pointMarkerOptions = point.marker || {};
                hasPointMarker = !!point.marker;
                const isNull = point.isNull, shouldDrawMarker = ((globallyEnabled &&
                    !defined(pointMarkerOptions.enabled)) || pointMarkerOptions.enabled) &&
                    (!isNull || nullInteraction) &&
                    point.visible !== false;
                // Only draw the point if y is defined
                if (shouldDrawMarker) {
                    // Shortcuts
                    const symbol = pick(pointMarkerOptions.symbol, series.symbol, 'rect');
                    markerAttribs = series.markerAttribs(point, (point.selected && 'select'));
                    const isInside = point.isInside !== false;
                    if (!graphic &&
                        isInside &&
                        ((markerAttribs.width || 0) > 0 || point.hasImage)) {
                        point.graphic = graphic = chart.renderer
                            .symbol(symbol, markerAttribs.x, markerAttribs.y, markerAttribs.width, markerAttribs.height, hasPointMarker ?
                            pointMarkerOptions :
                            seriesMarkerOptions)
                            .add(markerGroup);
                        // New points fade in from old axis position
                        if (point.origin) {
                            const attr = point.getOrigin(point.origin, markerAttribs);
                            if (!styledMode) {
                                attr.opacity = 0;
                            }
                            graphic.attr(attr);
                            verb = 'animate';
                        }
                    }
                    if (graphic) {
                        // Updating pre-existing point
                        if (verb === 'animate') {
                            graphic?.animate(markerAttribs);
                        }
                        // Presentational attributes
                        const pointAttr = series.pointAttribs(point, ((styledMode || !point.selected) ?
                            void 0 :
                            'select'));
                        if (!styledMode) {
                            graphic[verb](pointAttr);
                        }
                        else if (colorAxis) { // #14114
                            graphic['css']({
                                fill: pointAttr.fill
                            });
                        }
                        graphic.addClass(point.getClassName(), true);
                    }
                }
                else if (graphic) {
                    point.graphic = graphic.destroy(); // #1269
                }
            }
        }
    }
    /**
     * Get non-presentational attributes for a point. Used internally for
     * both styled mode and classic. Can be overridden for different series
     * types.
     *
     * @see Series#pointAttribs
     *
     * @function Highcharts.Series#markerAttribs
     *
     * @param {Highcharts.Point} point
     * The Point to inspect.
     *
     * @param {string} [state]
     * The state, can be either `hover`, `select` or undefined.
     *
     * @return {Highcharts.SVGAttributes}
     * A hash containing those attributes that are not settable from CSS.
     */
    markerAttribs(point, state) {
        const seriesOptions = this.options, seriesMarkerOptions = seriesOptions.marker, pointMarkerOptions = point.marker || {}, symbol = (pointMarkerOptions.symbol ||
            seriesMarkerOptions.symbol), attribs = {};
        let seriesStateOptions, pointStateOptions, radius = pick(pointMarkerOptions.radius, seriesMarkerOptions?.radius);
        // Handle hover and select states
        if (state) {
            seriesStateOptions = seriesMarkerOptions?.states?.[state];
            pointStateOptions = pointMarkerOptions.states?.[state];
            radius = pick(pointStateOptions?.radius, seriesStateOptions?.radius, radius && radius + (seriesStateOptions?.radiusPlus ||
                0));
        }
        point.hasImage = symbol && symbol.indexOf('url') === 0;
        if (point.hasImage) {
            radius = 0; // And subsequently width and height is not set
        }
        const pos = point.pos();
        if (isNumber(radius) && pos) {
            if (seriesOptions.crisp) {
                pos[0] = crisp(pos[0], point.hasImage ?
                    0 :
                    symbol === 'rect' ?
                        // Rectangle symbols need crisp edges, others don't
                        seriesMarkerOptions?.lineWidth || 0 :
                        1);
            }
            attribs.x = pos[0] - radius;
            attribs.y = pos[1] - radius;
        }
        if (radius) {
            attribs.width = attribs.height = 2 * radius;
        }
        return attribs;
    }
    /**
     * Internal function to get presentational attributes for each point.
     * Unlike {@link Series#markerAttribs}, this function should return
     * those attributes that can also be set in CSS. In styled mode,
     * `pointAttribs` won't be called.
     *
     * @internal
     * @function Highcharts.Series#pointAttribs
     *
     * @param {Highcharts.Point} [point]
     * The point instance to inspect.
     *
     * @param {string} [state]
     * The point state, can be either `hover`, `select` or 'normal'. If
     * undefined, normal state is assumed.
     *
     * @return {Highcharts.SVGAttributes}
     * The presentational attributes to be set on the point.
     */
    pointAttribs(point, state) {
        const options = this.options, seriesMarkerOptions = options.marker, pointOptions = point?.options, pointMarkerOptions = pointOptions?.marker || {}, pointColorOption = pointOptions?.color, pointColor = point?.color, zoneColor = point?.zone?.color;
        let seriesStateOptions, pointStateOptions, color = this.color, fill, stroke, strokeWidth = pick(pointMarkerOptions.lineWidth, seriesMarkerOptions.lineWidth), opacity = (point?.isNull && options.nullInteraction) ? 0 : 1;
        color = (pointColorOption ||
            zoneColor ||
            pointColor ||
            color);
        fill = (pointMarkerOptions.fillColor ||
            seriesMarkerOptions.fillColor ||
            color);
        stroke = (pointMarkerOptions.lineColor ||
            seriesMarkerOptions.lineColor ||
            color);
        // Handle hover and select states
        state = state || 'normal';
        if (state) {
            seriesStateOptions = (seriesMarkerOptions.states[state] || {});
            pointStateOptions = (pointMarkerOptions.states &&
                pointMarkerOptions.states[state]) || {};
            strokeWidth = pick(pointStateOptions.lineWidth, seriesStateOptions.lineWidth, strokeWidth + pick(pointStateOptions.lineWidthPlus, seriesStateOptions.lineWidthPlus, 0));
            fill = (pointStateOptions.fillColor ||
                seriesStateOptions.fillColor ||
                fill);
            stroke = (pointStateOptions.lineColor ||
                seriesStateOptions.lineColor ||
                stroke);
            opacity = pick(pointStateOptions.opacity, seriesStateOptions.opacity, opacity);
        }
        return {
            'stroke': stroke,
            'stroke-width': strokeWidth,
            'fill': fill,
            'opacity': point?.condemned || point?.isInside === false ?
                0 : opacity
        };
    }
    /**
     * Clear DOM objects and free up memory.
     *
     * @internal
     * @function Highcharts.Series#destroy
     *
     * @emits Highcharts.Series#event:destroy
     */
    destroy(keepEventsForUpdate) {
        const series = this, chart = series.chart, issue134 = /AppleWebKit\/533/.test(Series_win.navigator.userAgent), data = series.data || [];
        let destroy, i, axis;
        // Add event hook
        fireEvent(series, 'destroy', { keepEventsForUpdate });
        // Remove events
        this.removeEvents(keepEventsForUpdate);
        // Erase from axes
        (series.axisTypes || []).forEach(function (AXIS) {
            axis = series[AXIS];
            if (axis?.series) {
                erase(axis.series, series);
                axis.isDirty = axis.forceRedraw = true;
            }
        });
        // Remove legend items
        if (series.legendItem) {
            series.chart.legend.destroyItem(series);
        }
        // Destroy all points with their elements
        i = data.length;
        while (i--) {
            data[i]?.destroy?.();
        }
        for (const zone of series.zones || []) {
            // Destroy SVGElement's but preserve primitive props (#20426)
            destroyObjectProperties(zone, void 0, true);
        }
        // Clear the animation timeout if we are destroying the series
        // during initial animation
        internalClearTimeout(series.animationTimeout);
        // Destroy all SVGElements associated to the series
        objectEach(series, function (val, prop) {
            // Survive provides a hook for not destroying
            if (val instanceof SVG_SVGElement && !val.survive) {
                // Issue 134 workaround
                destroy = issue134 && prop === 'group' ?
                    'hide' :
                    'destroy';
                val[destroy]();
            }
        });
        // Remove from hoverSeries
        if (chart.hoverSeries === series) {
            chart.hoverSeries = void 0;
        }
        erase(chart.series, series);
        chart.orderItems('series');
        // Clear all members
        objectEach(series, function (val, prop) {
            if (!keepEventsForUpdate || prop !== 'hcEvents') {
                delete series[prop];
            }
        });
    }
    /**
     * Clip the graphs into zones for colors and styling.
     *
     * @internal
     * @function Highcharts.Series#applyZones
     */
    applyZones() {
        const series = this, { area, chart, graph, zones, points, xAxis, yAxis, zoneAxis } = series, { inverted, renderer } = chart, axis = this[`${zoneAxis}Axis`], { isXAxis, len = 0, minPointOffset = 0 } = axis || {}, halfWidth = (graph?.strokeWidth() || 0) / 2 + 1, 
        // Avoid points that are so close to the threshold that the graph
        // line would be split
        avoidClose = (zone, plotX = 0, plotY = 0) => {
            if (inverted) {
                plotY = len - plotY;
            }
            const { translated = 0, lineClip, value } = zone, distance = plotY - translated;
            lineClip?.push([
                'L',
                plotX,
                Math.abs(distance) < halfWidth ?
                    plotY - halfWidth * (distance < 0 ||
                        (distance === 0 && defined(value)) ?
                        -1 :
                        1) :
                    translated
            ]);
        };
        if (zones.length &&
            (graph || area) &&
            axis &&
            isNumber(axis.min)) {
            const axisMax = axis.getExtremes().max + minPointOffset, 
            // Invert the x and y coordinates of inverted charts
            invertPath = (path) => {
                path.forEach((segment, i) => {
                    if (segment[0] === 'M' || segment[0] === 'L') {
                        path[i] = [
                            segment[0],
                            isXAxis ? len - segment[1] : segment[1],
                            isXAxis ? segment[2] : len - segment[2]
                        ];
                    }
                });
            };
            // Reset
            zones.forEach((zone) => {
                zone.lineClip = [];
                zone.translated = clamp(axis.toPixels(pick(zone.value, axisMax), true) || 0, 0, len);
            });
            // The use of the Color Threshold assumes there are no gaps so it is
            // safe to hide the original graph and area unless it is not
            // waterfall series, then use showLine property to set lines between
            // columns to be visible (#7862)
            if (graph && !this.showLine) {
                graph.hide();
            }
            if (area) {
                area.hide();
            }
            // Prepare for adaptive clips, avoiding segments close to the
            // threshold (#19709)
            if (zoneAxis === 'y' &&
                // Overheat protection
                points.length < xAxis.len) {
                for (const point of points) {
                    const { plotX, plotY, zone } = point, zoneBelow = zone && zones[zones.indexOf(zone) - 1];
                    // Close to upper boundary
                    if (zone) {
                        avoidClose(zone, plotX, plotY);
                    }
                    // Close to lower boundary
                    if (zoneBelow) {
                        avoidClose(zoneBelow, plotX, plotY);
                    }
                }
            }
            // Compute and apply the clips
            let lastLineClip = [], 
            // Starting point of the first zone. Offset for category axis
            // (#22188).
            lastTranslated = axis.toPixels(axis.getExtremes().min - minPointOffset, true);
            zones.forEach((zone) => {
                const lineClip = zone.lineClip || [], translated = Math.round(zone.translated || 0);
                if (xAxis.reversed) {
                    lineClip.reverse();
                }
                let { clip, simpleClip } = zone, x1 = 0, y1 = 0, x2 = xAxis.len, y2 = yAxis.len;
                if (isXAxis) {
                    x1 = translated;
                    x2 = lastTranslated;
                }
                else {
                    y1 = translated;
                    y2 = lastTranslated;
                }
                // Adaptive clips
                const simplePath = [
                    ['M', x1, y1],
                    ['L', x2, y1],
                    ['L', x2, y2],
                    ['L', x1, y2],
                    ['Z']
                ], adaptivePath = [
                    simplePath[0],
                    ...lineClip,
                    simplePath[1],
                    simplePath[2],
                    ...lastLineClip,
                    simplePath[3],
                    simplePath[4]
                ];
                lastLineClip = lineClip.reverse();
                lastTranslated = translated;
                if (inverted) {
                    invertPath(adaptivePath);
                    if (area) {
                        invertPath(simplePath);
                    }
                }
                /* Debug clip paths
                zone.path?.destroy();
                zone.path = chart.renderer.path(adaptivePath)
                    .attr({
                        stroke: zone.color || this.color || 'gray',
                        'stroke-width': 1,
                        'dashstyle': 'Dash'
                    })
                    .add(series.group);
                // */
                if (clip) {
                    clip.animate({ d: adaptivePath });
                    simpleClip?.animate({ d: simplePath });
                }
                else {
                    clip = zone.clip = renderer.path(adaptivePath);
                    if (area) {
                        simpleClip = zone.simpleClip = renderer.path(simplePath);
                    }
                }
                // When no data, graph zone is not applied and after setData
                // clip was ignored. As a result, it should be applied each
                // time.
                if (graph) {
                    zone.graph?.clip(clip);
                }
                if (area) {
                    zone.area?.clip(simpleClip);
                }
            });
        }
        else if (series.visible) {
            // If zones were removed, restore graph and area
            if (graph) {
                graph.show();
            }
            if (area) {
                area.show();
            }
        }
    }
    /**
     * General abstraction for creating plot groups like series.group,
     * series.dataLabelsGroup and series.markerGroup. On subsequent calls,
     * the group will only be adjusted to the updated plot size.
     *
     * @internal
     * @function Highcharts.Series#plotGroup
     */
    plotGroup(prop, name, visibility, zIndex, parent) {
        let group = this[prop];
        const isNew = !group, attrs = {
            visibility,
            zIndex: zIndex || 0.1 // Pointer logic uses this
        };
        // Avoid setting undefined opacity, or in styled mode
        if (defined(this.opacity) &&
            !this.chart.styledMode && this.state !== 'inactive' // #13719
        ) {
            attrs.opacity = this.opacity;
        }
        // Generate it on first call
        if (!group) {
            this[prop] = group = this.chart.renderer
                .g()
                .add(parent);
        }
        // Add the class names, and replace existing ones as response to
        // Series.update (#6660)
        group.addClass(('highcharts-' + name +
            ' highcharts-series-' + this.index +
            ' highcharts-' + this.type + '-series ' +
            (defined(this.colorIndex) ?
                'highcharts-color-' + this.colorIndex + ' ' :
                '') +
            (this.options.className || '') +
            (group.hasClass('highcharts-tracker') ?
                ' highcharts-tracker' :
                '')), true);
        // Place it on first and subsequent (redraw) calls
        group.attr(attrs)[isNew ? 'attr' : 'animate'](this.getPlotBox(name));
        return group;
    }
    /**
     * Get the translation and scale for the plot area of this series.
     *
     * @function Highcharts.Series#getPlotBox
     */
    getPlotBox(name) {
        let horAxis = this.xAxis, vertAxis = this.yAxis;
        const chart = this.chart, inverted = (chart.inverted &&
            !chart.polar &&
            horAxis &&
            this.invertible &&
            name === 'series');
        // Swap axes for inverted (#2339)
        if (chart.inverted) {
            horAxis = vertAxis;
            vertAxis = this.xAxis;
        }
        const params = {
            scale: 1,
            translateX: horAxis ? horAxis.left : chart.plotLeft,
            translateY: vertAxis ? vertAxis.top : chart.plotTop,
            name
        };
        fireEvent(this, 'getPlotBox', params);
        const { scale, translateX, translateY } = params;
        return {
            translateX,
            translateY,
            rotation: inverted ? 90 : 0,
            rotationOriginX: inverted ?
                scale * (horAxis.len - vertAxis.len) / 2 :
                0,
            rotationOriginY: inverted ?
                scale * (horAxis.len + vertAxis.len) / 2 :
                0,
            scaleX: inverted ? -scale : scale, // #1623
            scaleY: scale
        };
    }
    /**
     * Removes the event handlers attached previously with addEvents.
     * @internal
     * @function Highcharts.Series#removeEvents
     */
    removeEvents(keepEventsForUpdate) {
        const { eventsToUnbind } = this;
        if (!keepEventsForUpdate) {
            // Remove all events
            removeEvent(this);
        }
        if (eventsToUnbind.length) {
            // Remove only internal events for proper update. #12355 solves
            // problem with multiple destroy events
            eventsToUnbind.forEach((unbind) => {
                unbind();
            });
            eventsToUnbind.length = 0;
        }
    }
    /**
     * Render the graph and markers. Called internally when first rendering
     * and later when redrawing the chart. This function can be extended in
     * plugins, but normally shouldn't be called directly.
     *
     * @function Highcharts.Series#render
     *
     * @emits Highcharts.Series#event:afterRender
     */
    render() {
        const series = this, { chart, options, hasRendered } = series, animOptions = Series_animObject(options.animation), visibility = series.visible ?
            'inherit' : 'hidden', // #2597
        zIndex = options.zIndex, chartSeriesGroup = chart.seriesGroup;
        let animDuration = series.finishedAnimating ?
            0 : animOptions.duration;
        fireEvent(this, 'render');
        // If we have a plot border radius, create a separate parent group where
        // the clip is later applied. This is necessary to handle multi-pane
        // layouts and entrance animation.
        if (chart.plotClipInner) {
            series.plotClipGroup || (series.plotClipGroup = chart.renderer.g().add(chartSeriesGroup));
        }
        // The group
        series.plotGroup('group', 'series', visibility, zIndex, series.plotClipGroup || chartSeriesGroup);
        series.markerGroup = series.plotGroup('markerGroup', 'markers', visibility, zIndex, chartSeriesGroup);
        // Initial clipping, applies to columns etc. (#3839).
        if (options.clip !== false) {
            series.setClip();
        }
        // Initialize the animation
        if (animDuration) {
            series.animate?.(true);
        }
        // Draw the graph if any
        if (series.drawGraph) {
            series.drawGraph();
            series.applyZones();
        }
        // Draw the points
        if (series.visible) {
            series.drawPoints();
        }
        // Draw the data labels
        series.drawDataLabels?.();
        // In pie charts, slices are added to the DOM, but actual rendering
        // is postponed until labels reserved their space
        series.redrawPoints?.();
        // Draw the mouse tracking area
        if (options.enableMouseTracking) {
            series.drawTracker?.();
        }
        // Run the animation
        if (animDuration) {
            series.animate?.();
        }
        // Call the afterAnimate function on animation complete (but don't
        // overwrite the animation.complete option which should be available
        // to the user).
        if (!hasRendered) {
            // Additional time if defer is defined before afterAnimate
            // will be triggered
            if (animDuration && animOptions.defer) {
                animDuration += animOptions.defer;
            }
            series.animationTimeout = syncTimeout(() => {
                series.afterAnimate();
            }, animDuration || 0);
        }
        // Means data is in accordance with what you see
        series.isDirty = false;
        // (See #322) series.isDirty = series.isDirtyData = false; // means
        // data is in accordance with what you see
        series.hasRendered = true;
        fireEvent(series, 'afterRender');
    }
    /**
     * Redraw the series. This function is called internally from
     * `chart.redraw` and normally shouldn't be called directly.
     *
     * @internal
     * @function Highcharts.Series#redraw
     */
    redraw() {
        // Cache it here as it is set to false in render, but used after
        const wasDirty = this.isDirty || this.isDirtyData;
        this.translate();
        this.render();
        if (wasDirty) { // #3868, #3945
            delete this.kdTree;
        }
        this.condemnedPoints.length = 0;
    }
    /**
     * Whether to reserve space for the series, either because it is visible or
     * because the `chart.ignoreHiddenSeries` option is false.
     *
     * @internal
     */
    reserveSpace() {
        return this.visible || !this.chart.options.chart.ignoreHiddenSeries;
    }
    /**
     * Find the nearest point from a pointer event. This applies to series that
     * use k-d-trees to get the nearest point. Native pointer events must be
     * normalized using `Pointer.normalize`, that adds `chartX` and `chartY`
     * properties.
     *
     * @sample highcharts/demo/synchronized-charts
     *         Synchronized charts with tooltips
     *
     * @function Highcharts.Series#searchPoint
     *
     * @param {Highcharts.PointerEvent} e
     *        The normalized pointer event
     * @param {boolean} [compareX=false]
     *        Search only by the X value, not Y
     *
     * @return {Point|undefined}
     *        The closest point to the pointer event
     */
    searchPoint(e, compareX) {
        const { xAxis, yAxis } = this, inverted = this.chart.inverted;
        return this.searchKDTree({
            clientX: inverted ?
                xAxis.len - e.chartY + xAxis.pos :
                e.chartX - xAxis.pos,
            plotY: inverted ?
                yAxis.len - e.chartX + yAxis.pos :
                e.chartY - yAxis.pos
        }, compareX, e);
    }
    /**
     * Build the k-d-tree that is used by mouse and touch interaction to get
     * the closest point. Line-like series typically have a one-dimensional
     * tree where points are searched along the X axis, while scatter-like
     * series typically search in two dimensions, X and Y.
     *
     * @internal
     * @function Highcharts.Series#buildKDTree
     */
    buildKDTree(e) {
        // Prevent multiple k-d-trees from being built simultaneously
        // (#6235)
        this.buildingKdTree = true;
        const series = this, seriesOptions = series.options, dimensions = seriesOptions.findNearestPointBy
            .indexOf('y') > -1 ? 2 : 1;
        /**
         * Internal function
         * @internal
         */
        function kdtree(points, depth, dimensions) {
            const length = points?.length;
            let axis, median;
            if (length) {
                // Alternate between the axis
                axis = series.kdAxisArray[depth % dimensions];
                // Sort point array
                points.sort((a, b) => (a[axis] || 0) - (b[axis] || 0));
                median = Math.floor(length / 2);
                // Build and return node
                return {
                    point: points[median],
                    left: kdtree(points.slice(0, median), depth + 1, dimensions),
                    right: kdtree(points.slice(median + 1), depth + 1, dimensions)
                };
            }
        }
        /**
         * Start the recursive build process with a clone of the points
         * array and null points filtered out. (#3873)
         * @internal
         */
        function startRecursive() {
            series.kdTree = kdtree(series.getValidPoints(void 0, 
            // For line-type series restrict to plot area, but
            // column-type series not (#3916, #4511)
            !series.directTouch, seriesOptions?.nullInteraction), dimensions, dimensions);
            series.buildingKdTree = false;
        }
        delete series.kdTree;
        // For testing tooltips, don't build async. Also if touchstart, we may
        // be dealing with click events on mobile, so don't delay (#6817).
        syncTimeout(startRecursive, seriesOptions.kdNow || e?.type === 'touchstart' ? 0 : 1);
    }
    /**
     * Search the k-d-tree for the point closest to the given point.
     *
     * @internal
     * @function Highcharts.Series#searchKDTree
     *
     * @param {Highcharts.KDPointSearchObject} point
     *        The point to search for.
     * @param {boolean} [compareX=false]
     *        Search only by the X value, not Y.
     * @param {Highcharts.PointerEvent} [e]
     *        The normalized pointer event.
     * @param {Function} [suppliedPointEvaluator]
     *        A custom point evaluator function.
     * @param {Function} [suppliedBSideCheckEvaluator]
     *        A custom b-side check evaluator function.
     *
     * @return {Highcharts.Point|undefined}
     *         The closest point found.
     */
    searchKDTree(point, compareX, e, suppliedPointEvaluator, suppliedBSideCheckEvaluator) {
        const series = this, [kdX, kdY] = this.kdAxisArray, kdComparer = compareX ? 'distX' : 'dist', kdDimensions = (series.options.findNearestPointBy || '')
            .indexOf('y') > -1 ? 2 : 1, useRadius = !!series.isBubble, pointEvaluator = suppliedPointEvaluator || ((p1, p2, comparisonProp) => {
            const p1Val = p1[comparisonProp] || 0, p2Val = p2[comparisonProp] || 0;
            return [
                ((p1Val === p2Val && p1.index > p2.index) ||
                    p1Val < p2Val) ?
                    p1 :
                    p2,
                false
            ];
        }), bSideCheckEvaluator = suppliedBSideCheckEvaluator || ((a, b) => a < b);
        /**
         * Set the one and two dimensional distance on the point object.
         * @internal
         */
        function setDistance(p1, p2) {
            const p1kdX = p1[kdX], p2kdX = p2[kdX], x = (defined(p1kdX) && defined(p2kdX)) ? p1kdX - p2kdX : null, p1kdY = p1[kdY], p2kdY = p2[kdY], y = (defined(p1kdY) && defined(p2kdY)) ? p1kdY - p2kdY : 0, radius = useRadius ? (p2.marker?.radius || 0) : 0;
            p2.dist = Math.sqrt(((x && x * x) || 0) + y * y) - radius;
            p2.distX = defined(x) ? (Math.abs(x) - radius) : Number.MAX_VALUE;
        }
        /**
         * Search the kd-tree.
         *
         * @internal
         * @function doSearch
         *
         * @param {Highcharts.KDPointSearchObject} search
         *        The point to search for.
         * @param {Highcharts.KDNode} tree
         *        The kd-tree structure to search.
         * @param {number} depth
         *        The depth in the tree.
         * @param {number} dimensions
         *        The dimensions in the tree.
         *
         * @return {Highcharts.Point}
         *         The closest point found.
         */
        function doSearch(search, tree, depth, dimensions) {
            const point = tree.point, axis = series.kdAxisArray[depth % dimensions];
            let ret = point, flip = false;
            setDistance(search, point);
            // Pick side based on distance to splitting point
            const tdist = (search[axis] || 0) - (point[axis] || 0) +
                (useRadius ? (point.marker?.radius || 0) : 0), sideA = tdist < 0 ? 'left' : 'right', sideB = tdist < 0 ? 'right' : 'left';
            // End of tree
            if (tree[sideA]) {
                [ret, flip] = pointEvaluator(point, doSearch(search, tree[sideA], depth + 1, dimensions), kdComparer);
            }
            if (tree[sideB]) {
                const sqrtTDist = Math.sqrt(tdist * tdist), retDist = ret[kdComparer];
                // Compare distance to current best to splitting point to decide
                // whether to check side B or no
                if (bSideCheckEvaluator(sqrtTDist, retDist, flip)) {
                    ret = pointEvaluator(ret, doSearch(search, tree[sideB], depth + 1, dimensions), kdComparer)[0];
                }
            }
            return ret;
        }
        if (!this.kdTree && !this.buildingKdTree) {
            this.buildKDTree(e);
        }
        if (this.kdTree) {
            return doSearch(point, this.kdTree, kdDimensions, kdDimensions);
        }
    }
    /**
     * Return the value of pointPlacement relative to the point's x value.
     *
     * @internal
     * @function Highcharts.Series#pointPlacementToXValue
     */
    pointPlacementToXValue() {
        const { options, xAxis } = this;
        let factor = options.pointPlacement;
        // Point placement is relative to each series pointRange (#5889)
        if (factor === 'between') {
            factor = xAxis.reversed ? -0.5 : 0.5; // #11955
        }
        return isNumber(factor) ?
            factor * (options.pointRange || xAxis.pointRange) :
            0;
    }
    /**
     * Check whether a point is inside the plot area.
     *
     * @internal
     * @function Highcharts.Series#isPointInside
     *
     * @param {Highcharts.Dictionary<number>|Highcharts.Point} point
     * A point-like object with `plotX` and `plotY` properties.
     *
     * @return {boolean}
     * True if the point is inside the plot area.
     */
    isPointInside(point) {
        const { chart, xAxis, yAxis } = this, { plotX = -1, plotY = -1 } = point, isInside = (plotY >= 0 &&
            plotY <= (yAxis ? yAxis.len : chart.plotHeight) &&
            plotX >= 0 &&
            plotX <= (xAxis ? xAxis.len : chart.plotWidth));
        return isInside;
    }
    /**
     * Draw the tracker object that sits above all data labels and markers to
     * track mouse events on the graph or points. For the line type charts
     * the tracker uses the same graphPath, but with a greater stroke width
     * for better control.
     * @internal
     */
    drawTracker() {
        const series = this, options = series.options, trackByArea = options.trackByArea, trackerPath = [].concat((trackByArea ? series.areaPath : series.graphPath) || []), chart = series.chart, pointer = chart.pointer, renderer = chart.renderer, snap = chart.options.tooltip?.snap || 0, onMouseOver = () => {
            if (options.enableMouseTracking &&
                chart.hoverSeries !== series) {
                series.onMouseOver();
            }
        }, 
        /*
         * Empirical lowest possible opacities for TRACKER_FILL for an
         * element to stay invisible but clickable
         * IE9: 0.00000000001 (unlimited)
         * IE10: 0.0001 (exporting only)
         * FF: 0.00000000001 (unlimited)
         * Chrome: 0.000001
         * Safari: 0.000001
         * Opera: 0.00000000001 (unlimited)
         */
        TRACKER_FILL = 'rgba(192,192,192,' + (Series_svg ? 0.0001 : 0.002) + ')';
        let tracker = series.tracker;
        // Draw the tracker
        if (tracker) {
            tracker.attr({ d: trackerPath });
        }
        else if (series.graph) { // Create
            series.tracker = tracker = renderer.path(trackerPath)
                .attr({
                visibility: series.visible ? 'inherit' : 'hidden',
                zIndex: 2
            })
                .addClass(trackByArea ?
                'highcharts-tracker-area' :
                'highcharts-tracker-line')
                .add(series.group);
            if (!chart.styledMode) {
                tracker.attr({
                    'stroke-linecap': 'round',
                    'stroke-linejoin': 'round', // #1225
                    stroke: TRACKER_FILL,
                    fill: trackByArea ? TRACKER_FILL : 'none',
                    'stroke-width': series.graph.strokeWidth() +
                        (trackByArea ? 0 : 2 * snap)
                });
            }
            // The tracker is added to the series group, which is clipped, but
            // is covered by the marker group. So the marker group also needs to
            // capture events.
            [
                series.tracker,
                series.markerGroup,
                ...(series.dataLabelsGroups || [])
            ].forEach((tracker) => {
                if (tracker) {
                    tracker.addClass('highcharts-tracker')
                        .on('mouseover', onMouseOver)
                        .on('mouseout', (e) => {
                        pointer?.onTrackerMouseOut(e);
                    });
                    if (options.cursor && !chart.styledMode) {
                        tracker.css({ cursor: options.cursor });
                    }
                    tracker.on('touchstart', onMouseOver);
                }
            });
        }
        fireEvent(this, 'afterDrawTracker');
    }
    /**
     * Add a point to the series after render time. The point can be added at
     * the end, or by giving it an X value, to the start or in the middle of the
     * series.
     *
     * Alternatively, when the chart is using data table for the data, a point
     * can be added by running `dataTable.setRow`.
     *
     * @sample highcharts/members/series-addpoint-append/
     *         Append point
     * @sample highcharts/members/series-addpoint-append-and-shift/
     *         Append and shift
     * @sample highcharts/members/series-addpoint-shift-column/
     *         Append and shift columns
     * @sample highcharts/datatable/live-chart/
     *         Append and shift with data table
     * @sample highcharts/members/series-addpoint-x-and-y/
     *         Both X and Y values given
     * @sample highcharts/members/series-addpoint-pie/
     *         Append pie slice
     * @sample stock/members/series-addpoint/
     *         Append 100 points in Highcharts Stock
     * @sample stock/members/series-addpoint-shift/
     *         Append and shift in Highcharts Stock
     * @sample maps/members/series-addpoint/
     *         Add a point in Highmaps
     *
     * @function Highcharts.Series#addPoint
     *
     * @param {Highcharts.PointOptionsType} options
     *        The point options. If options is a single number, a point with
     *        that y value is appended to the series. If it is an array, it will
     *        be interpreted as x and y values respectively. If it is an
     *        object, advanced options as outlined under `series.data` are
     *        applied.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart after the point is added. When adding
     *        more than one point, it is highly recommended that the redraw
     *        option be set to false, and instead {@link Chart#redraw} is
     *        explicitly called after the adding of points is finished.
     *        Otherwise, the chart will redraw after adding each point.
     *
     * @param {boolean} [shift=false]
     *        If true, a point is shifted off the start of the series as one is
     *        appended to the end.
     *
     * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
     *        Whether to apply animation, and optionally animation
     *        configuration.
     *
     * @param {boolean} [withEvent=true]
     *        Used internally, whether to fire the series `addPoint` event.
     *
     * @emits Highcharts.Series#event:addPoint
     */
    addPoint(options, redraw, shift, animation, withEvent) {
        const series = this, seriesOptions = series.options, { chart, data, dataTable: table, xAxis } = series, names = xAxis?.hasNames && xAxis.names, dataOptions = seriesOptions.data, xData = series.getColumn('x');
        let isInTheMiddle, i;
        // Optional redraw, defaults to true
        redraw = pick(redraw, true);
        // Get options and push the point to xData, yData and series.options. In
        // series.generatePoints the Point instance will be created on demand
        // and pushed to the series.data array.
        const { applyOptions, optionsToObject } = series.pointClass.prototype, point = { series }, pointOptions = optionsToObject.call(point, options);
        applyOptions.call(point, pointOptions, void 0, false);
        const x = point.x;
        // Get the insertion point
        i = xData.length;
        if (series.requireSorting && x < xData[i - 1]) {
            isInTheMiddle = true;
            while (i && xData[i - 1] > x) {
                i--;
            }
        }
        // Insert the row at the given index
        table.setRow(pointOptions, i, true);
        this.xColumn?.splice(i, 0, this.getX(x));
        if (names && point.name) {
            names[x] = point.name;
        }
        dataOptions?.splice(i, 0, options);
        if (isInTheMiddle ||
            // When hasProcessedData is true we need to splice an empty slot
            // into series.data, otherwise generatePoints won't pick it up.
            series.hasProcessedDataTable) {
            series.data.splice(i, 0, null);
            series.processData();
        }
        // Generate points to be added to the legend (#1329)
        if (seriesOptions.legendType === 'point') {
            series.generatePoints();
        }
        // Shift the first point off the parallel arrays
        if (shift) {
            if (data[0] && !!data[0].remove) {
                data[0].remove(false);
            }
            else {
                [
                    data,
                    dataOptions,
                    this.xColumn
                ].filter(defined).forEach((coll) => {
                    coll.shift();
                });
                table.deleteRows(0);
            }
        }
        // Fire event
        if (withEvent !== false) {
            fireEvent(series, 'addPoint', { point: point });
        }
        // Redraw
        series.isDirty = true;
        series.isDirtyData = true;
        if (redraw) {
            chart.redraw(animation); // Animation is set anyway on redraw, #5665
        }
    }
    /**
     * Remove a point from the series. Unlike the
     * {@link Highcharts.Point#remove} method, this can also be done on a point
     * that is not instantiated because it is outside the view or subject to
     * Highcharts Stock data grouping.
     *
     * @sample highcharts/members/series-removepoint/
     *         Remove cropped point
     *
     * @function Highcharts.Series#removePoint
     *
     * @param {number} i
     *        The index of the point in the {@link Highcharts.Series.data|data}
     *        array.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart after the point is added. When
     *        removing more than one point, it is highly recommended that the
     *        `redraw` option be set to `false`, and instead {@link
     *        Highcharts.Chart#redraw} is explicitly called after the adding of
     *        points is finished.
     *
     * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
     *        Whether and optionally how the series should be animated.
     *
     * @emits Highcharts.Point#event:remove
     */
    removePoint(i, redraw, animation) {
        const series = this, { chart, data, points, dataTable: table } = series, point = data[i], remove = function () {
            // Splice out the point's data from all parallel arrays
            [
                // #4935
                points?.length === data.length ? points : void 0,
                data,
                series.options.data,
                series.xColumn
            ].filter(defined).forEach((coll) => {
                coll.splice(i, 1);
            });
            table.deleteRows(i);
            // Reset modified data (shouldn't this happen automagically?)
            table.modified = table;
            point?.destroy();
            // Redraw
            series.isDirty = true;
            series.isDirtyData = true;
            if (redraw) {
                chart.redraw();
            }
        };
        Series_setAnimation(animation, chart);
        redraw = pick(redraw, true);
        // Fire the event with a default handler of removing the point
        if (point) {
            point.firePointEvent('remove', null, remove);
        }
        else {
            remove();
        }
    }
    /**
     * Remove a series and optionally redraw the chart.
     *
     * @sample highcharts/members/series-remove/
     *         Remove first series from a button
     *
     * @function Highcharts.Series#remove
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart or wait for an explicit call to
     *        {@link Highcharts.Chart#redraw}.
     *
     * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
     *        Whether to apply animation, and optionally animation
     *        configuration.
     *
     * @param {boolean} [withEvent=true]
     *        Used internally, whether to fire the series `remove` event.
     *
     * @emits Highcharts.Series#event:remove
     */
    remove(redraw, animation, withEvent, keepEvents) {
        const series = this, chart = series.chart;
        /**
         * Remove the series.
         *
         * @internal
         */
        function remove() {
            // Destroy elements
            series.destroy(keepEvents);
            // Redraw
            chart.isDirtyLegend = chart.isDirtyBox = true;
            chart.linkSeries(keepEvents);
            if (pick(redraw, true)) {
                chart.redraw(animation);
            }
        }
        // Fire the event with a default handler of removing the point
        if (withEvent !== false) {
            fireEvent(series, 'remove', null, remove);
        }
        else {
            remove();
        }
    }
    /**
     * Update the series with a new set of options. For a clean and precise
     * handling of new options, all methods and elements from the series are
     * removed, and it is initialized from scratch. Therefore, this method is
     * more performance expensive than some other utility methods like {@link
     * Series#setData} or {@link Series#setVisible}.
     *
     * Note that `Series.update` may mutate the passed `data` options.
     *
     * @sample highcharts/members/series-update/
     *         Updating series options
     * @sample maps/members/series-update/
     *         Update series options in Highmaps
     *
     * @function Highcharts.Series#update
     *
     * @param {Highcharts.SeriesOptionsType} options
     *        New options that will be merged with the series' existing options.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart after the series is altered. If doing
     *        more operations on the chart, it is a good idea to set redraw to
     *        false and call {@link Chart#redraw} after.
     *
     * @emits Highcharts.Series#event:update
     * @emits Highcharts.Series#event:afterUpdate
     */
    update(options, redraw) {
        options = diffObjects(options, this.userOptions);
        fireEvent(this, 'update', { options: options });
        const series = this, chart = series.chart, 
        // Must use user options when changing type because series.options
        // is merged in with type specific plotOptions
        oldOptions = series.userOptions, initialType = series.initialType || series.type, plotOptions = chart.options.plotOptions, initialSeriesProto = seriesTypes[initialType].prototype, groups = [
            'dataLabelsGroup',
            'dataLabelsGroups',
            'dataLabelsParentGroups',
            'group',
            'markerGroup',
            'plotClipGroup',
            'transformGroup'
        ], optionsToCheck = [
            'dataGrouping',
            'pointStart',
            'pointInterval',
            'pointIntervalUnit',
            'keys'
        ], 
        // Animation must be enabled when calling update before the initial
        // animation has first run. This happens when calling update
        // directly after chart initialization, or when applying responsive
        // rules (#6912).
        animation = series.finishedAnimating && { animation: false }, kinds = {}, dataOptions = options.data || options.dataTable;
        let seriesOptions, n, keepProps = Series.keepProps.slice(), newType = (options.type ||
            oldOptions.type ||
            chart.options.chart.type);
        const keepPoints = !(
        // Indicators etc recalculate the data. It should be
        // possible to omit this.
        this.hasDerivedData ||
            // New type requires new point classes
            (newType && newType !== this.type) ||
            // New options affecting how the data points are built
            typeof options.keys !== 'undefined' ||
            typeof options.pointStart !== 'undefined' ||
            typeof options.pointInterval !== 'undefined' ||
            typeof options.relativeXValue !== 'undefined' ||
            options.joinBy ||
            options.mapData || // #11636
            // Changes to data grouping requires new points in new group
            optionsToCheck.some((option) => series.hasOptionChanged(option)));
        newType = newType || initialType;
        if (keepPoints) {
            keepProps.push.apply(keepProps, Series.keepPropsForPoints);
            if (options.visible !== false) {
                keepProps.push('area', 'graph');
                if (!('zones' in options)) {
                    keepProps.push('zones');
                }
            }
            series.parallelArrays.forEach(function (key) {
                keepProps.push(key + 'Data');
            });
            if (dataOptions) {
                // `setData` uses `dataSorting` options so we need to update
                // them earlier
                if (options.dataSorting) {
                    extend(series.options.dataSorting, options.dataSorting);
                }
                this.setData(dataOptions, false);
            }
        }
        else {
            this.dataTable.modified = this.dataTable;
        }
        // Merge in multiple data label options (#23560)
        if (options.dataLabels && oldOptions.dataLabels) {
            options.dataLabels = this.mergeArrays(oldOptions.dataLabels, options.dataLabels);
        }
        // Do the merge, with some forced options
        options = merge(oldOptions, {
            // When oldOptions.index is null it should't be cleared.
            // Otherwise navigator series will have wrong indexes (#10193).
            index: oldOptions.index === void 0 ?
                series.index : oldOptions.index,
            pointStart: 
            // When updating from blank (#7933)
            plotOptions?.series?.pointStart ??
                oldOptions.pointStart
        }, !keepPoints && { data: series.options.data }, options, animation);
        // Merge does not merge arrays, but replaces them. Since points were
        // updated, `series.options.data` has correct merged options, use it:
        if (keepPoints && options.data) {
            options.data = series.options.data;
        }
        // Make sure preserved properties are not destroyed (#3094)
        keepProps = groups.concat(keepProps);
        keepProps.forEach(function (prop) {
            keepProps[prop] = series[prop];
            delete series[prop];
        });
        let casting = false;
        if (seriesTypes[newType]) {
            casting = newType !== series.type;
            // Destroy the series and delete all properties, it will be
            // reinserted within the `init` call below
            series.remove(false, false, false, true);
            if (casting) {
                // #20264: Re-detect a certain chart properties from new series
                chart.propFromSeries();
                // Modern browsers including IE11
                if (Object.setPrototypeOf) {
                    Object.setPrototypeOf(series, seriesTypes[newType].prototype);
                    // Legacy (IE < 11)
                }
                else {
                    const ownEvents = Object.hasOwnProperty.call(series, 'hcEvents') && series.hcEvents;
                    for (n in initialSeriesProto) { // eslint-disable-line guard-for-in
                        series[n] = void 0;
                    }
                    // Reinsert all methods and properties from the new type
                    // prototype (#2270, #3719).
                    extend(series, seriesTypes[newType].prototype);
                    // The events are tied to the prototype chain, don't copy if
                    // they're not the series' own
                    if (ownEvents) {
                        series.hcEvents = ownEvents;
                    }
                    else {
                        delete series.hcEvents;
                    }
                }
            }
        }
        else {
            error(17, true, chart, { missingModuleFor: newType });
        }
        // Re-register groups (#3094) and other preserved properties
        keepProps.forEach(function (prop) {
            series[prop] = keepProps[prop];
        });
        series.init(chart, options);
        // Remove particular elements of the points. Check `series.options`
        // because we need to consider the options being set on plotOptions as
        // well.
        if (keepPoints && this.points) {
            seriesOptions = series.options;
            // What kind of elements to destroy
            if (seriesOptions.visible === false) {
                kinds.graphic = 1;
                kinds.dataLabel = 1;
            }
            else {
                // If the marker got disabled or changed its symbol, width or
                // height - destroy
                if (this.hasMarkerChanged(seriesOptions, oldOptions)) {
                    kinds.graphic = 1;
                }
                if (!series.hasDataLabels?.()) {
                    kinds.dataLabel = 1;
                }
            }
            for (const point of this.points) {
                if (point?.series) {
                    point.resolveColor();
                    // Destroy elements in order to recreate based on updated
                    // series options.
                    if (Object.keys(kinds).length) {
                        point.destroyElements(kinds);
                    }
                    if (seriesOptions.showInLegend === false &&
                        point.legendItem) {
                        chart.legend.destroyItem(point);
                    }
                }
            }
        }
        series.initialType = initialType;
        chart.linkSeries(); // Links are lost in series.remove (#3028)
        // #15383: Fire updatedData if the type has changed to keep linked
        // series such as indicators updated
        if (casting && series.linkedSeries.length) {
            series.isDirtyData = true;
        }
        fireEvent(this, 'afterUpdate');
        if (pick(redraw, true)) {
            chart.redraw(keepPoints ? void 0 : false);
        }
    }
    /**
     * Used from within series.update
     * @internal
     */
    setName(name) {
        this.name = this.options.name = this.userOptions.name = name;
        this.chart.isDirtyLegend = true;
    }
    /**
     * Check if the option has changed.
     * @internal
     */
    hasOptionChanged(optionName) {
        const chart = this.chart, option = this.options[optionName], plotOptions = chart.options.plotOptions, oldOption = this.userOptions[optionName], plotOptionsOption = pick(plotOptions?.[this.type]?.[optionName], plotOptions?.series?.[optionName]);
        // Check if `plotOptions` are defined already, #19203
        if (oldOption && !defined(plotOptionsOption)) {
            return option !== oldOption;
        }
        return option !== pick(plotOptionsOption, option);
    }
    /**
     * Runs on mouse over the series graphical items.
     *
     * @function Highcharts.Series#onMouseOver
     * @emits Highcharts.Series#event:mouseOver
     */
    onMouseOver() {
        const series = this, chart = series.chart, hoverSeries = chart.hoverSeries, pointer = chart.pointer;
        pointer?.setHoverChartIndex();
        // Set normal state to previous series
        if (hoverSeries && hoverSeries !== series) {
            hoverSeries.onMouseOut();
        }
        // Trigger the event
        fireEvent(series, 'mouseOver');
        // Hover this
        series.setState('hover');
        /**
         * Contains the original hovered series.
         *
         * @name Highcharts.Chart#hoverSeries
         * @type {Highcharts.Series|null}
         */
        chart.hoverSeries = series;
    }
    /**
     * Runs on mouse out of the series graphical items.
     *
     * @function Highcharts.Series#onMouseOut
     *
     * @emits Highcharts.Series#event:mouseOut
     */
    onMouseOut() {
        // Trigger the event only if listeners exist
        const series = this, chart = series.chart, tooltip = chart.tooltip, hoverPoint = chart.hoverPoint;
        // #182, set to null before the mouseOut event fires
        chart.hoverSeries = null;
        // Trigger mouse out on the point, which must be in this series
        if (hoverPoint) {
            hoverPoint.onMouseOut();
        }
        // Fire the mouse out event
        fireEvent(series, 'mouseOut');
        // Hide the tooltip
        if (tooltip &&
            !series.stickyTracking &&
            (!tooltip.shared || series.noSharedTooltip)) {
            tooltip.hide();
        }
        // Reset all inactive states
        chart.series.forEach(function (s) {
            s.setState('', true);
        });
    }
    /**
     * Set the state of the series. Called internally on mouse interaction
     * operations, but it can also be called directly to visually
     * highlight a series.
     *
     * @function Highcharts.Series#setState
     *
     * @param {Highcharts.SeriesStateValue|""} [state]
     *        The new state, can be either `'hover'`, `'inactive'`, `'select'`,
     *        or `''` (an empty string), `'normal'` or `undefined` to set to
     *        normal state.
     * @param {boolean} [inherit]
     *        Determines if state should be inherited by points too.
     */
    setState(state, inherit) {
        const series = this, { graph, options } = series, { inactiveOtherPoints, states: stateOptions } = options, 
        // By default a quick animation to hover/inactive,
        // slower to un-hover
        stateAnimation = pick(stateOptions?.[state || 'normal']?.animation, series.chart.options.chart.animation);
        let { lineWidth, opacity } = options;
        state = state || '';
        if (series.state !== state) {
            // Toggle class names
            [
                series.group,
                series.markerGroup,
                ...(series.dataLabelsGroups || [])
            ].forEach(function (group) {
                if (group) {
                    // Old state
                    if (series.state) {
                        group.removeClass('highcharts-series-' + series.state);
                    }
                    // New state
                    if (state) {
                        group.addClass('highcharts-series-' + state);
                    }
                }
            });
            series.state = state;
            if (!series.chart.styledMode) {
                if (stateOptions[state]?.enabled === false) {
                    return;
                }
                if (state) {
                    lineWidth = (stateOptions[state].lineWidth ||
                        lineWidth + (stateOptions[state].lineWidthPlus || 0)); // #4035
                    opacity = pick(stateOptions[state].opacity, opacity);
                }
                if (graph && !graph.dashstyle && isNumber(lineWidth)) {
                    // Animate the graph stroke-width
                    for (const graphElement of [
                        graph,
                        ...this.zones.map((zone) => zone.graph)
                    ]) {
                        graphElement?.animate({
                            'stroke-width': lineWidth
                        }, stateAnimation);
                    }
                }
                // For some types (pie, networkgraph, sankey) opacity is
                // resolved on a point level
                if (!inactiveOtherPoints) {
                    [
                        series.group,
                        series.markerGroup,
                        ...(series.dataLabelsGroups || []),
                        series.labelBySeries
                    ].forEach(function (group) {
                        group?.animate({ opacity }, stateAnimation);
                    });
                }
            }
        }
        // Don't loop over points on a series that doesn't apply inactive state
        // to siblings markers (e.g. line, column)
        if (inherit && inactiveOtherPoints && series.points) {
            series.setAllPointsToState(state || void 0);
        }
    }
    /**
     * Set the state for all points in the series.
     *
     * @function Highcharts.Series#setAllPointsToState
     *
     * @internal
     *
     * @param {string} [state]
     *        Can be either `hover` or undefined to set to normal state.
     */
    setAllPointsToState(state) {
        this.points.forEach(function (point) {
            if (point.setState) {
                point.setState(state);
            }
        });
    }
    /**
     * Show or hide the series.
     *
     * @function Highcharts.Series#setVisible
     *
     * @param {boolean} [visible]
     * True to show the series, false to hide. If undefined, the visibility is
     * toggled.
     *
     * @param {boolean} [redraw=true]
     * Whether to redraw the chart after the series is altered. If doing more
     * operations on the chart, it is a good idea to set redraw to false and
     * call {@link Chart#redraw|chart.redraw()} after.
     *
     * @emits Highcharts.Series#event:hide
     * @emits Highcharts.Series#event:show
     */
    setVisible(visible, redraw) {
        const series = this, chart = series.chart, ignoreHiddenSeries = chart.options.chart.ignoreHiddenSeries, oldVisibility = series.visible;
        // If called without an argument, toggle visibility
        series.visible =
            visible =
                series.options.visible =
                    series.userOptions.visible =
                        typeof visible === 'undefined' ? !oldVisibility : visible; // #5618
        const showOrHide = visible ? 'show' : 'hide';
        [
            'group',
            'markerGroup',
            'tracker',
            'tt'
        ].forEach((key) => {
            series[key]?.[showOrHide]();
        });
        series.dataLabelsGroups?.forEach((g) => {
            g?.[showOrHide]();
        });
        // Hide tooltip (#1361)
        if (chart.hoverSeries === series ||
            chart.hoverPoint?.series === series) {
            series.onMouseOut();
        }
        if (series.legendItem) {
            chart.legend.colorizeItem(series, visible);
        }
        // Rescale or adapt to resized chart
        series.isDirty = true;
        // In a stack, all other series are affected
        if (series.options.stacking) {
            chart.series.forEach((otherSeries) => {
                if (otherSeries.options.stacking && otherSeries.visible) {
                    otherSeries.isDirty = true;
                }
            });
        }
        // Show or hide linked series
        series.linkedSeries.forEach((otherSeries) => {
            otherSeries.setVisible(visible, false);
        });
        if (ignoreHiddenSeries) {
            chart.isDirtyBox = true;
        }
        fireEvent(series, showOrHide);
        if (redraw !== false) {
            chart.redraw();
        }
    }
    /**
     * Show the series if hidden.
     *
     * @sample highcharts/members/series-hide/
     *         Toggle visibility from a button
     *
     * @function Highcharts.Series#show
     * @emits Highcharts.Series#event:show
     */
    show() {
        this.setVisible(true);
    }
    /**
     * Hide the series if visible. If the
     * [chart.ignoreHiddenSeries](https://api.highcharts.com/highcharts/chart.ignoreHiddenSeries)
     * option is true, the chart is redrawn without this series.
     *
     * @sample highcharts/members/series-hide/
     *         Toggle visibility from a button
     *
     * @function Highcharts.Series#hide
     * @emits Highcharts.Series#event:hide
     */
    hide() {
        this.setVisible(false);
    }
    /**
     * Select or unselect the series. This means its
     * {@link Highcharts.Series.selected|selected}
     * property is set, the checkbox in the legend is toggled and when selected,
     * the series is returned by the {@link Highcharts.Chart#getSelectedSeries}
     * function.
     *
     * @sample highcharts/members/series-select/
     *         Select a series from a button
     *
     * @function Highcharts.Series#select
     *
     * @param {boolean} [selected]
     * True to select the series, false to unselect. If undefined, the selection
     * state is toggled.
     *
     * @emits Highcharts.Series#event:select
     * @emits Highcharts.Series#event:unselect
     */
    select(selected) {
        const series = this;
        series.selected =
            selected =
                this.options.selected = (typeof selected === 'undefined' ?
                    !series.selected :
                    selected);
        if (series.checkbox) {
            series.checkbox.checked = selected;
        }
        fireEvent(series, selected ? 'select' : 'unselect');
    }
    /**
     * Checks if a tooltip should be shown for a given point.
     *
     * @internal
     */
    shouldShowTooltip(plotX, plotY, options = {}) {
        options.series = this;
        options.visiblePlotOnly = true;
        return this.chart.isInsidePlot(plotX, plotY, options);
    }
    /**
     * Draws the legend symbol based on the legendSymbol user option.
     *
     * @internal
     */
    drawLegendSymbol(legend, item) {
        const renderer = this.chart.renderer, legendSymbol = this.options.legendSymbol || 'rectangle', legendItem = item.legendItem || {}, { options, symbolHeight, symbolWidth } = legend, squareSymbol = options.squareSymbol, adjustedSymbolWidth = squareSymbol ? symbolHeight : symbolWidth, x = squareSymbol ? (symbolWidth - symbolHeight) / 2 : 0, y = (legend.baseline || 0) - symbolHeight + 1, w = adjustedSymbolWidth, h = symbolHeight, r = options.symbolRadius ?? symbolHeight;
        const symbol = legendSymbol === 'rectangle' ?
            // For the rectangle, use a true `rect` element because it renders
            // sharper than a symbol with `path` and arcs
            renderer.rect(x, y, w, h, r) :
            (renderer.symbols[legendSymbol] &&
                renderer.symbol(legendSymbol, x, y, w, h, { r }));
        // Rectangle or SVGRenderer symbol
        if (symbol) {
            legendItem.symbol = symbol
                .addClass('highcharts-point')
                .attr({
                zIndex: 3
            })
                .add(legendItem.group);
            // Symbol function defined in LegendSymbol
        }
        else {
            Legend_LegendSymbol[legendSymbol]
                ?.call(this, legend, item);
        }
    }
}
/** @internal */
Series.defaultOptions = SeriesDefaults;
/**
 * Registry of all available series types.
 *
 * @name Highcharts.Series.types
 * @type {Highcharts.Dictionary<typeof_Highcharts.Series>}
 */
Series.types = Series_SeriesRegistry.seriesTypes;
/* *
 *
 *  Static Functions
 *
 * */
/**
 * Registers a series class to be accessible via `Series.types`.
 *
 * @function Highcharts.Series.registerType
 *
 * @param {string} seriesType
 * The series type as an identifier string in lower case.
 *
 * @param {Function} SeriesClass
 * The series class as a class pattern or a constructor function with
 * prototype.
 */
Series.registerType = Series_SeriesRegistry.registerSeriesType;
/**
 * Properties to keep after update.
 *
 * @internal
 */
Series.keepProps = [
    'colorIndex',
    'eventOptions',
    'navigatorSeries',
    'symbolIndex',
    'baseSeries'
];
/**
 * Properties to keep after update if the point instances should be
 * preserved.
 *
 * @internal
 */
Series.keepPropsForPoints = [
    'data',
    'isDirtyData',
    // GeoHeatMap interpolation
    'isDirtyCanvas',
    'points',
    'condemnedPoints',
    'dataTable',
    'hasProcessedDataTable', // #17057
    'xIncrement',
    'cropped',
    '_hasPointMarkers',
    'hasDataLabels',
    // Networkgraph (#14397)
    'nodes',
    'layout',
    // Treemap
    'level',
    // Map specific, consider moving it to series-specific preserve-
    // properties (#10617)
    'mapMap',
    'mapData',
    'minY',
    'maxY',
    'minX',
    'maxX',
    'transformGroups' // #18857
];
extend(Series.prototype, {
    axisTypes: ['xAxis', 'yAxis'],
    coll: 'series',
    colorCounter: 0,
    directTouch: false,
    invertible: true,
    isCartesian: true,
    kdAxisArray: ['clientX', 'plotY'],
    // Each point's x and y values are stored in this.xData and this.yData:
    parallelArrays: ['x', 'y'],
    pointClass: Series_Point,
    requireSorting: true,
    // Requires the data to be sorted:
    sorted: true
});
/* *
 *
 *  Registry
 *
 * */
Series_SeriesRegistry.series = Series;
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Series_Series = (Series);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * This is a placeholder type of the possible series options for
 * [Highcharts](../highcharts/series), [Highcharts Stock](../highstock/series),
 * [Highmaps](../highmaps/series), and [Gantt](../gantt/series).
 *
 * In TypeScript is this dynamically generated to reference all possible types
 * of series options.
 *
 * @ignore-declaration
 * @typedef {Highcharts.SeriesOptions|Highcharts.Dictionary<*>} Highcharts.SeriesOptionsType
 */
/**
 * Options for `dataMapping`. The data mapping options allow you to define
 * how data from a data table should be mapped to series points. The object
 * keys refer to the point property that should be mapped, and the values refer
 * to the `dataTable` and `column` that should be used for the mapping.
 *
 * Values can be either a string pointing directly to a column in the first
 * data table, or an object with `dataTable` and `column` properties to specify
 * the mapping in more detail.
 *
 * @example
 * Highcharts.chart('container', {
 *     dataTable: {
 *         columns: {
 *             Year: [2020, 2021, 2022, 2023],
 *             Cost: [11, 13, 12, 14],
 *             Revenue: [12, 15, 14, 18]
 *         }
 *     },
 *     plotOptions: {
 *         series: {
 *             // Mapping to a shared column goes in plotOptions
 *             dataMapping: {
 *                 x: 'Year'
 *             }
 *         }
 *     },
 *     series: [{
 *         dataMapping: {
 *             y: 'Cost'
 *         }
 *     }, {
 *         dataMapping: {
 *             y: 'Revenue'
 *         }
 *     }]
 * });
 *
 * @interface Highcharts.DataMappingOptionsObject
 * @since 13.0.0
 */
/**
 * Function callback when a series has been animated.
 *
 * @callback Highcharts.SeriesAfterAnimateCallbackFunction
 *
 * @param {Highcharts.Series} this
 *        The series where the event occurred.
 *
 * @param {Highcharts.SeriesAfterAnimateEventObject} event
 *        Event arguments.
 *
 * @param {Highcharts.Series} [ctx]
 *        Since v12.6.0, the series context passed as an extra argument for
 *        arrow functions.
 */
/**
 * Event information regarding completed animation of a series.
 *
 * @interface Highcharts.SeriesAfterAnimateEventObject
 */ /**
* Animated series.
* @name Highcharts.SeriesAfterAnimateEventObject#target
* @type {Highcharts.Series}
*/ /**
* Event type.
* @name Highcharts.SeriesAfterAnimateEventObject#type
* @type {"afterAnimate"}
*/
/**
 * Function callback when the checkbox next to the series' name in the legend is
 * clicked.
 *
 * @callback Highcharts.SeriesCheckboxClickCallbackFunction
 *
 * @param {Highcharts.Series} this
 *        The series where the event occurred.
 *
 * @param {Highcharts.SeriesCheckboxClickEventObject} event
 *        Event arguments.
 */
/**
 * Event information regarding check of a series box.
 *
 * @interface Highcharts.SeriesCheckboxClickEventObject
 */ /**
* Whether the box has been checked.
* @name Highcharts.SeriesCheckboxClickEventObject#checked
* @type {boolean}
*/ /**
* Related series.
* @name Highcharts.SeriesCheckboxClickEventObject#item
* @type {Highcharts.Series}
*/ /**
* Related series.
* @name Highcharts.SeriesCheckboxClickEventObject#target
* @type {Highcharts.Series}
*/ /**
* Event type.
* @name Highcharts.SeriesCheckboxClickEventObject#type
* @type {"checkboxClick"}
*/
/**
 * Function callback when a series is clicked. Return false to cancel toggle
 * actions.
 *
 * @callback Highcharts.SeriesClickCallbackFunction
 *
 * @param {Highcharts.Series} this
 *        The series where the event occurred.
 *
 * @param {Highcharts.SeriesClickEventObject} event
 *        Event arguments.
 *
 * @param {Highcharts.Series} [ctx]
 *        Since v12.6.0, the series context passed as an extra argument for
 *        arrow functions.
 */
/**
 * Common information for a click event on a series.
 *
 * @interface Highcharts.SeriesClickEventObject
 * @extends global.Event
 */ /**
* Nearest point on the graph.
* @name Highcharts.SeriesClickEventObject#point
* @type {Highcharts.Point}
*/
/**
 * Gets fired when the series is hidden after chart generation time, either by
 * clicking the legend item or by calling `.hide()`.
 *
 * @callback Highcharts.SeriesHideCallbackFunction
 *
 * @param {Highcharts.Series} this
 *        The series where the event occurred.
 *
 * @param {global.Event} event
 *        The event that occurred.
 *
 * @param {Highcharts.Series} [ctx]
 *        Since v12.6.0, the series context passed as an extra argument for
 *        arrow functions.
 */
/**
 * The SVG value used for the `stroke-linecap` and `stroke-linejoin` of a line
 * graph.
 *
 * @typedef {"butt"|"round"|"square"|string} Highcharts.SeriesLinecapValue
 */
/**
 * Gets fired when the mouse leaves the graph.
 *
 * @callback Highcharts.SeriesMouseOutCallbackFunction
 *
 * @param {Highcharts.Series} this
 *        Series where the event occurred.
 *
 * @param {global.PointerEvent} event
 *        Event that occurred.
 *
 * @param {Highcharts.Series} [ctx]
 *        Since v12.6.0, the series context passed as an extra argument for
 *        arrow functions.
 */
/**
 * Gets fired when the mouse enters the graph.
 *
 * @callback Highcharts.SeriesMouseOverCallbackFunction
 *
 * @param {Highcharts.Series} this
 *        Series where the event occurred.
 *
 * @param {global.PointerEvent} event
 *        Event that occurred.
 *
 * @param {Highcharts.Series} [ctx]
 *        Since v12.6.0, the series context passed as an extra argument for
 *        arrow functions.
 */
/**
 * Translation and scale for the plot area of a series.
 *
 * @interface Highcharts.SeriesPlotBoxObject
 */ /**
* @name Highcharts.SeriesPlotBoxObject#scaleX
* @type {number}
*/ /**
* @name Highcharts.SeriesPlotBoxObject#scaleY
* @type {number}
*/ /**
* @name Highcharts.SeriesPlotBoxObject#translateX
* @type {number}
*/ /**
* @name Highcharts.SeriesPlotBoxObject#translateY
* @type {number}
*/
/**
 * Gets fired when the series is shown after chart generation time, either by
 * clicking the legend item or by calling `.show()`.
 *
 * @callback Highcharts.SeriesShowCallbackFunction
 *
 * @param {Highcharts.Series} this
 *        Series where the event occurred.
 *
 * @param {global.Event} event
 *        Event that occurred.
 *
 * @param {Highcharts.Series} [ctx]
 *        Since v12.6.0, the series context passed as an extra argument for
 *        arrow functions.
 */
/**
 * Possible key values for the series state options.
 *
 * @typedef {"hover"|"inactive"|"normal"|"select"} Highcharts.SeriesStateValue
 */
''; // Detach doclets above
/* *
 *
 *  API Options
 *
 * */
/**
 * Series options for specific data and the data itself. In TypeScript you
 * have to cast the series options to specific series types, to get all
 * possible options for a series.
 *
 * ```ts
 * // TypeScript example
 * Highcharts.chart('container', {
 *     series: [{
 *         color: '#06C',
 *         data: [[0, 1], [2, 3]]
 *     } as Highcharts.SeriesLineOptions ]
 * });
 * ```
 *
 * @type      {Array<*>}
 * @apioption series
 */
/**
 * An id for the series. This can be used after render time to get a pointer
 * to the series object through `chart.get()`.
 *
 * @sample {highcharts} highcharts/plotoptions/series-id/
 *         Get series by id
 *
 * @basic
 * @type      {string}
 * @since     1.2.0
 * @apioption series.id
 */
/**
 * The index of the series in the chart, affecting the internal index in the
 * `chart.series` array, the visible Z index as well as the order in the
 * legend.
 *
 * @basic
 * @type      {number}
 * @since     2.3.0
 * @apioption series.index
 */
/**
 * The sequential index of the series in the legend.
 *
 * @see [legend.reversed](#legend.reversed),
 *      [yAxis.reversedStacks](#yAxis.reversedStacks)
 *
 * @sample {highcharts|highstock} highcharts/series/legendindex/
 *         Legend in opposite order
 *
 * @type      {number}
 * @apioption series.legendIndex
 */
/**
 * The name of the series as shown in the legend, tooltip etc. If a `dataTable`
 * and `dataMapping` are used, the name defaults to the id of the primary data
 * table column. Otherwise, it defaults to "Series {n}", where n is the index of
 * the series, starting at 1.
 *
 * @sample {highcharts} highcharts/series/name/
 *         Series name
 * @sample {highmaps} maps/demo/category-map/
 *         Series name
 *
 * @basic
 * @type      {string}
 * @apioption series.name
 */
/**
 * The type of series, for example `line` or `column`. By default, the
 * series type is inherited from [chart.type](#chart.type), so unless the
 * chart is a combination of series types, there is no need to set it on the
 * series level.
 *
 * @sample {highcharts} highcharts/series/type/
 *         Line and column in the same chart
 * @sample highcharts/series/type-dynamic/
 *         Dynamic types with button selector
 * @sample {highmaps} maps/demo/mapline-mappoint/
 *         Multiple types in the same map
 *
 * @basic
 * @type      {string}
 * @apioption series.type
 */
/**
 * When using dual or multiple x axes, this number defines which xAxis the
 * particular series is connected to. It refers to either the
 * {@link #xAxis.id|axis id}
 * or the index of the axis in the xAxis array, with 0 being the first.
 *
 * @type      {number|string}
 * @default   0
 * @product   highcharts highstock
 * @apioption series.xAxis
 */
/**
 * When using dual or multiple y axes, this number defines which yAxis the
 * particular series is connected to. It refers to either the
 * {@link #yAxis.id|axis id}
 * or the index of the axis in the yAxis array, with 0 being the first.
 *
 * @sample {highcharts} highcharts/series/yaxis/
 *         Apply the column series to the secondary Y axis
 *
 * @type      {number|string}
 * @default   0
 * @product   highcharts highstock
 * @apioption series.yAxis
 */
/**
 * Define the visual z index of the series.
 *
 * @sample {highcharts} highcharts/plotoptions/series-zindex-default/
 *         With no z index, the series defined last are on top
 * @sample {highcharts} highcharts/plotoptions/series-zindex/
 *         With a z index, the series with the highest z index is on top
 * @sample {highstock} highcharts/plotoptions/series-zindex-default/
 *         With no z index, the series defined last are on top
 * @sample {highstock} highcharts/plotoptions/series-zindex/
 *         With a z index, the series with the highest z index is on top
 *
 * @type      {number}
 * @product   highcharts highstock
 * @apioption series.zIndex
 */
/**
 * Contains the point's index in the `Series.points` array.
 *
 * @name Highcharts.Point#index
 * @type {number}
 * @readonly
 */
/**
 * The translated X value for the point in terms of pixels. Relative
 * to the X axis position if the series has one, otherwise relative
 * to the plot area. Depending on the series type this value might
 * not be defined.
 *
 * In an inverted chart the x-axis is going from the bottom to the
 * top so the `plotX` value is the number of pixels from the bottom
 * of the axis.
 *
 * @see Highcharts.Point#pos
 * @name Highcharts.Point#plotX
 * @type {number|undefined}
 */
/**
 * The translated Y value for the point in terms of pixels. Relative
 * to the Y axis position if the series has one, otherwise relative
 * to the plot area. Depending on the series type this value might
 * not be defined.
 *
 * In an inverted chart the y-axis is going from right to left
 * so the `plotY` value is the number of pixels from the right
 * of the `yAxis`.
 *
 * @see Highcharts.Point#pos
 * @name Highcharts.Point#plotY
 * @type {number|undefined}
 */
''; // Include precedent doclets in transpiled

;// ./code/es-modules/Core/Legend/Legend.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { animObject: Legend_animObject, setAnimation: Legend_setAnimation } = AnimationUtilities;

const { registerEventOptions: Legend_registerEventOptions } = Core_Foundation;

const { composed: Legend_composed, marginNames } = Core_Globals;



const { distribute: Legend_distribute } = Renderer_RendererUtilities;

const { format: Legend_format } = Core_Templating;

/* *
 *
 *  Class
 *
 * */
/**
 * The overview of the chart's series. The legend object is instantiated
 * internally in the chart constructor, and is available from the `chart.legend`
 * property. Each chart has only one legend.
 *
 * @class
 * @name Highcharts.Legend
 *
 * @param {Highcharts.Chart} chart
 * The chart instance.
 *
 * @param {Highcharts.LegendOptions} options
 * Legend options.
 */
class Legend {
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Initialize the legend.
     *
     * @internal
     * @function Highcharts.Legend#init
     *
     * @param {Highcharts.Chart} chart
     * The chart instance.
     *
     * @param {Highcharts.LegendOptions} options
     * Legend options.
     */
    constructor(chart, options) {
        /* *
         *
         *  Properties
         *
         * */
        this.allItems = [];
        /** @internal */
        this.initialItemY = 0;
        /** @internal */
        this.itemHeight = 0;
        /** @internal */
        this.itemMarginBottom = 0;
        /** @internal */
        this.itemMarginTop = 0;
        /** @internal */
        this.itemX = 0;
        /** @internal */
        this.itemY = 0;
        /** @internal */
        this.lastItemY = 0;
        /** @internal */
        this.lastLineHeight = 0;
        /** @internal */
        this.legendHeight = 0;
        /** @internal */
        this.legendWidth = 0;
        /** @internal */
        this.maxItemWidth = 0;
        /** @internal */
        this.maxLegendWidth = 0;
        /** @internal */
        this.offsetWidth = 0;
        /** @internal */
        this.padding = 0;
        /** @internal */
        this.pages = [];
        /** @internal */
        this.symbolHeight = 0;
        /** @internal */
        this.symbolWidth = 0;
        /** @internal */
        this.titleHeight = 0;
        /** @internal */
        this.totalItemWidth = 0;
        /** @internal */
        this.widthOption = 0;
        /**
         * Chart of this legend.
         *
         * @readonly
         * @name Highcharts.Legend#chart
         * @type {Highcharts.Chart}
         */
        this.chart = chart;
        this.setOptions(options);
        if (options.enabled) {
            // Render it
            this.render();
            Legend_registerEventOptions(this, options);
            // Move checkboxes
            addEvent(this.chart, 'endResize', function () {
                this.legend.positionCheckboxes();
            });
        }
        // On Legend.init and Legend.update, make sure that proximate layout
        // events are either added or removed (#18362).
        addEvent(this.chart, 'render', () => {
            if (this.options.enabled && this.proximate) {
                this.proximatePositions();
                this.positionItems();
            }
        });
    }
    /**
     * @internal
     * @function Highcharts.Legend#setOptions
     * @param {Highcharts.LegendOptions} options
     */
    setOptions(options) {
        const padding = pick(options.padding, 8);
        /**
         * Legend options.
         *
         * @readonly
         * @name Highcharts.Legend#options
         * @type {Highcharts.LegendOptions}
         */
        this.options = options;
        if (!this.chart.styledMode) {
            this.itemStyle = options.itemStyle;
            this.itemHiddenStyle = merge(this.itemStyle, options.itemHiddenStyle);
        }
        this.itemMarginTop = options.itemMarginTop;
        this.itemMarginBottom = options.itemMarginBottom;
        this.padding = padding;
        this.initialItemY = padding - 5; // 5 is pixels above the text
        this.symbolWidth = pick(options.symbolWidth, 16);
        this.pages = [];
        this.proximate = options.layout === 'proximate' && !this.chart.inverted;
        // #12705: baseline has to be reset on every update
        this.baseline = void 0;
    }
    /**
     * Update the legend with new options. Equivalent to running `chart.update`
     * with a legend configuration option.
     *
     * @sample highcharts/legend/legend-update/
     *         Legend update
     *
     * @function Highcharts.Legend#update
     *
     * @param {Highcharts.LegendOptions} options
     * Legend options.
     *
     * @param {boolean} [redraw=true]
     * Whether to redraw the chart after the axis is altered. If doing more
     * operations on the chart, it is a good idea to set redraw to false and
     * call {@link Chart#redraw} after. Whether to redraw the chart.
     *
     * @emits Highcharts.Legends#event:afterUpdate
     */
    update(options, redraw) {
        const chart = this.chart;
        this.setOptions(merge(true, this.options, options));
        if ('events' in this.options) {
            // Legend event handlers
            Legend_registerEventOptions(this, this.options);
        }
        this.destroy();
        chart.isDirtyLegend = chart.isDirtyBox = true;
        if (pick(redraw, true)) {
            chart.redraw();
        }
        fireEvent(this, 'afterUpdate', { redraw });
    }
    /**
     * Set the colors for the legend item.
     *
     * @internal
     * @function Highcharts.Legend#colorizeItem
     *
     * @param {Highcharts.BubbleLegendItem|Highcharts.Point|Highcharts.Series} item
     * A Series or Point instance.
     *
     * @param {boolean} [visible=false]
     * Dimmed or colored.
     *
     * @todo
     * Make events official: Fires the event `afterColorizeItem`.
     */
    colorizeItem(item, visible) {
        const originalColor = item.color, { area, group, label, line, symbol } = item.legendItem || {};
        if (item instanceof Series_Series || item instanceof Series_Point) {
            item.color = item.options?.legendSymbolColor || originalColor;
        }
        group?.[visible ? 'removeClass' : 'addClass']('highcharts-legend-item-hidden');
        if (!this.chart.styledMode) {
            const { itemHiddenStyle = {} } = this, hiddenColor = itemHiddenStyle.color, { fillColor, lineColor } = item.options, colorizeHidden = (attr) => {
                if (!visible) {
                    if (attr.fill) {
                        attr.fill = hiddenColor;
                    }
                    if (attr.stroke) {
                        attr.stroke = hiddenColor;
                    }
                }
                return attr;
            };
            label?.css(merge(visible ? this.itemStyle : itemHiddenStyle));
            line?.attr(colorizeHidden({ stroke: lineColor || item.color }));
            // Apply legend symbol attributes
            symbol?.attr(colorizeHidden(item.series ?
                // When `legendType` is `point`, like pie series
                item.series.pointAttribs?.(item) :
                // When `legendType` is `series`, like line or column series
                item.pointAttribs?.() || { fill: item.color }));
            area?.attr(colorizeHidden({
                fill: fillColor || item.color,
                'fill-opacity': fillColor ?
                    1 :
                    (item.options.fillOpacity ?? 0.75)
            }));
        }
        item.color = originalColor;
        fireEvent(this, 'afterColorizeItem', { item, visible });
    }
    /**
     * @internal
     * @function Highcharts.Legend#positionItems
     */
    positionItems() {
        // Now that the legend width and height are established, put the items
        // in the final position
        this.allItems.forEach(this.positionItem, this);
        if (!this.chart.isResizing) {
            this.positionCheckboxes();
        }
    }
    /**
     * Position the legend item.
     *
     * @internal
     * @function Highcharts.Legend#positionItem
     * @param {Highcharts.BubbleLegendItem|Highcharts.Point|Highcharts.Series} item
     * The item to position
     */
    positionItem(item) {
        const legend = this, { group, x = 0, y = 0 } = item.legendItem || {}, options = legend.options, symbolPadding = options.symbolPadding, ltr = !options.rtl, checkbox = item.checkbox;
        if (group?.element) {
            const attribs = {
                translateX: ltr ?
                    x :
                    legend.legendWidth - x - 2 * symbolPadding - 4,
                translateY: y
            };
            const complete = () => {
                fireEvent(this, 'afterPositionItem', { item });
            };
            group[defined(group.translateY) ? 'animate' : 'attr'](attribs, void 0, complete);
        }
        if (checkbox) {
            checkbox.x = x;
            checkbox.y = y;
        }
    }
    /**
     * Destroy a single legend item, used internally on removing series items.
     *
     * @internal
     * @function Highcharts.Legend#destroyItem
     * @param {Highcharts.BubbleLegendItem|Highcharts.Point|Highcharts.Series} item
     * The item to remove
     */
    destroyItem(item) {
        const legendItem = item.legendItem || {};
        // Destroy SVG elements
        for (const key of ['group', 'label', 'line', 'symbol']) {
            if (legendItem[key]) {
                legendItem[key] = legendItem[key].destroy();
            }
        }
        item.checkbox = discardElement(item.checkbox);
        item.legendItem = void 0;
    }
    /**
     * Destroy the legend. Used internally. To reflow objects, `chart.redraw`
     * must be called after destruction.
     *
     * @internal
     * @function Highcharts.Legend#destroy
     */
    destroy() {
        const legend = this;
        // Destroy items
        for (const item of this.getAllItems()) {
            this.destroyItem(item);
        }
        // Destroy legend elements
        for (const key of [
            'clipRect',
            'up',
            'down',
            'pager',
            'nav',
            'box',
            'title',
            'group'
        ]) {
            if (legend[key]) {
                legend[key] = legend[key].destroy();
            }
        }
        this.display = null; // Reset in .render on update.
    }
    /**
     * Position the checkboxes after the width is determined.
     *
     * @internal
     * @function Highcharts.Legend#positionCheckboxes
     */
    positionCheckboxes() {
        const alignAttr = this.group?.alignAttr, clipHeight = this.clipHeight || this.legendHeight, titleHeight = this.titleHeight;
        let translateY;
        if (alignAttr) {
            translateY = alignAttr.translateY;
            this.allItems.forEach(function (item) {
                const checkbox = item.checkbox;
                let top;
                if (checkbox) {
                    top = translateY + titleHeight + checkbox.y +
                        (this.scrollOffset || 0) + 3;
                    css(checkbox, {
                        left: (alignAttr.translateX + item.checkboxOffset +
                            checkbox.x - 20) + 'px',
                        top: top + 'px',
                        display: this.proximate || (top > translateY - 6 &&
                            top < translateY + clipHeight - 6) ?
                            '' :
                            'none'
                    });
                }
            }, this);
        }
    }
    /**
     * Render the legend title on top of the legend.
     *
     * @internal
     * @function Highcharts.Legend#renderTitle
     */
    renderTitle() {
        const options = this.options, padding = this.padding, titleOptions = options.title;
        let bBox, titleHeight = 0;
        if (titleOptions.text) {
            if (!this.title) {
                /**
                 * SVG element of the legend title.
                 *
                 * @readonly
                 * @name Highcharts.Legend#title
                 * @type {Highcharts.SVGElement}
                 */
                this.title = this.chart.renderer.label(titleOptions.text, padding - 3, padding - 4, void 0, void 0, void 0, options.useHTML, void 0, 'legend-title')
                    .attr({ zIndex: 1 });
                if (!this.chart.styledMode) {
                    this.title.css(titleOptions.style);
                }
                this.title.add(this.group);
            }
            // Set the max title width (#7253)
            if (!titleOptions.width) {
                this.title.css({
                    width: this.maxLegendWidth + 'px'
                });
            }
            bBox = this.title.getBBox();
            titleHeight = bBox.height;
            this.offsetWidth = bBox.width; // #1717
            this.contentGroup.attr({ translateY: titleHeight });
        }
        this.titleHeight = titleHeight;
    }
    /**
     * Set the legend item text.
     *
     * @function Highcharts.Legend#setText
     *
     * @param {Highcharts.Point|Highcharts.Series} item
     * The item for which to update the text in the legend.
     */
    setText(item) {
        const options = this.options;
        item.legendItem.label.attr({
            text: options.labelFormat ?
                Legend_format(options.labelFormat, item, this.chart) :
                options.labelFormatter.call(item, item)
        });
    }
    /**
     * Render a single specific legend item. Called internally from the `render`
     * function.
     *
     * @internal
     * @function Highcharts.Legend#renderItem
     * @param {Highcharts.BubbleLegendItem|Highcharts.Point|Highcharts.Series} item
     * The item to render.
     */
    renderItem(item) {
        const legend = this, legendItem = item.legendItem = item.legendItem || {}, chart = legend.chart, renderer = chart.renderer, options = legend.options, horizontal = options.layout === 'horizontal', symbolWidth = legend.symbolWidth, symbolPadding = options.symbolPadding || 0, itemStyle = legend.itemStyle, itemHiddenStyle = legend.itemHiddenStyle, itemDistance = horizontal ? pick(options.itemDistance, 20) : 0, ltr = !options.rtl, isSeries = !item.series, series = !isSeries && item.series.drawLegendSymbol ?
            item.series :
            item, seriesOptions = series.options, showCheckbox = (!!legend.createCheckboxForItem &&
            seriesOptions &&
            seriesOptions.showCheckbox), useHTML = options.useHTML, itemClassName = item.options.className;
        let label = legendItem.label, 
        // Full width minus text width
        itemExtraWidth = symbolWidth + symbolPadding +
            itemDistance + (showCheckbox ? 20 : 0);
        if (!label) { // Generate it once, later move it
            // Generate the group box, a group to hold the symbol and text. Text
            // is to be appended in Legend class.
            legendItem.group = renderer
                .g('legend-item')
                .addClass('highcharts-' + series.type + '-series ' +
                'highcharts-color-' + item.colorIndex +
                (itemClassName ? ' ' + itemClassName : '') +
                (isSeries ?
                    ' highcharts-series-' + item.index :
                    ''))
                .attr({ zIndex: 1 })
                .add(legend.scrollGroup);
            // Generate the list item text and add it to the group
            legendItem.label = label = renderer.text('', ltr ?
                symbolWidth + symbolPadding :
                -symbolPadding, legend.baseline || 0, useHTML);
            if (!chart.styledMode) {
                // Merge to prevent modifying original (#1021)
                label.css(merge(item.visible ?
                    itemStyle :
                    itemHiddenStyle));
            }
            label
                .attr({
                align: ltr ? 'left' : 'right',
                zIndex: 2
            })
                .add(legendItem.group);
            // Get the baseline for the first item - the font size is equal for
            // all
            if (!legend.baseline) {
                legend.fontMetrics = renderer.fontMetrics(label);
                legend.baseline =
                    legend.fontMetrics.f + 3 + legend.itemMarginTop;
                label.attr('y', legend.baseline);
                legend.symbolHeight =
                    pick(options.symbolHeight, legend.fontMetrics.f);
                if (options.squareSymbol) {
                    legend.symbolWidth = pick(options.symbolWidth, Math.max(legend.symbolHeight, 16));
                    itemExtraWidth = legend.symbolWidth + symbolPadding +
                        itemDistance + (showCheckbox ? 20 : 0);
                    if (ltr) {
                        label.attr('x', legend.symbolWidth + symbolPadding);
                    }
                }
            }
            // Draw the legend symbol inside the group box
            series.drawLegendSymbol(legend, item);
            if (legend.setItemEvents) {
                legend.setItemEvents(item, label, useHTML);
            }
        }
        // Add the HTML checkbox on top
        if (showCheckbox && !item.checkbox && legend.createCheckboxForItem) {
            legend.createCheckboxForItem(item);
        }
        // Colorize the items
        legend.colorizeItem(item, item.visible);
        // Take care of max width and text overflow (#6659)
        if (chart.styledMode || !itemStyle.width) {
            label.css({
                width: (Math.min(options.itemWidth ||
                    legend.widthOption ||
                    chart.spacingBox.width, options.maxWidth ? relativeLength(options.maxWidth, chart.chartWidth) : Infinity) - itemExtraWidth) + 'px'
            });
        }
        // Always update the text
        legend.setText(item);
        // Calculate the positions for the next line
        const bBox = label.getBBox();
        const fontMetricsH = legend.fontMetrics?.h || 0;
        item.itemWidth = item.checkboxOffset =
            options.itemWidth ||
                legendItem.labelWidth ||
                bBox.width + itemExtraWidth;
        legend.maxItemWidth = Math.max(legend.maxItemWidth, item.itemWidth);
        legend.totalItemWidth += item.itemWidth;
        legend.itemHeight = item.itemHeight = Math.round(legendItem.labelHeight ||
            // Use bBox for multiline (#16398)
            (bBox.height > fontMetricsH * 1.5 ? bBox.height : fontMetricsH));
    }
    /**
     * Get the position of the item in the layout. We now know the
     * maxItemWidth from the previous loop.
     *
     * @internal
     * @function Highcharts.Legend#layoutItem
     * @param {Highcharts.BubbleLegendItem|Highcharts.Point|Highcharts.Series} item
     */
    layoutItem(item) {
        const options = this.options, padding = this.padding, horizontal = options.layout === 'horizontal', itemHeight = item.itemHeight, itemMarginBottom = this.itemMarginBottom, itemMarginTop = this.itemMarginTop, itemDistance = horizontal ? pick(options.itemDistance, 20) : 0, maxLegendWidth = this.maxLegendWidth, itemWidth = (options.alignColumns &&
            this.totalItemWidth > maxLegendWidth) ?
            this.maxItemWidth :
            item.itemWidth, legendItem = item.legendItem || {};
        // If the item exceeds the width, start a new line
        if (horizontal &&
            this.itemX - padding + itemWidth > maxLegendWidth) {
            this.itemX = padding;
            if (this.lastLineHeight) { // Not for the first line (#10167)
                this.itemY += (itemMarginTop +
                    this.lastLineHeight +
                    itemMarginBottom);
            }
            this.lastLineHeight = 0; // Reset for next line (#915, #3976)
        }
        // Set the edge positions
        this.lastItemY = itemMarginTop + this.itemY + itemMarginBottom;
        this.lastLineHeight = Math.max(// #915
        itemHeight, this.lastLineHeight);
        // Cache the position of the newly generated or reordered items
        legendItem.x = this.itemX;
        legendItem.y = this.itemY;
        // Advance
        if (horizontal) {
            this.itemX += itemWidth;
        }
        else {
            this.itemY +=
                itemMarginTop + itemHeight + itemMarginBottom;
            this.lastLineHeight = itemHeight;
        }
        // The width of the widest item
        this.offsetWidth = this.widthOption || Math.max((horizontal ? this.itemX - padding - (item.checkbox ?
            // Decrease by itemDistance only when no checkbox #4853
            0 :
            itemDistance) : itemWidth) + padding, this.offsetWidth);
    }
    /**
     * Get all items, which is one item per series for most series and one
     * item per point for pie series and its derivatives. Fires the event
     * `afterGetAllItems`.
     *
     * @internal
     * @function Highcharts.Legend#getAllItems
     * @return {Array<(Highcharts.BubbleLegendItem|Highcharts.Point|Highcharts.Series)>}
     * The current items in the legend.
     * @emits Highcharts.Legend#event:afterGetAllItems
     */
    getAllItems() {
        let allItems = [];
        this.chart.series.forEach(function (series) {
            const seriesOptions = series?.options;
            // Handle showInLegend. If the series is linked to another series,
            // defaults to false.
            if (series && pick(seriesOptions.showInLegend, !defined(seriesOptions.linkedTo) ? void 0 : false, true)) {
                // Use points or series for the legend item depending on
                // legendType
                allItems = allItems.concat(series.legendItem?.labels ||
                    (seriesOptions.legendType === 'point' ?
                        series.data :
                        series));
            }
        });
        fireEvent(this, 'afterGetAllItems', { allItems });
        return allItems;
    }
    /**
     * Get a short, three letter string reflecting the alignment and layout.
     *
     * @internal
     * @function Highcharts.Legend#getAlignment
     * @return {string}
     * The alignment, empty string if floating
     */
    getAlignment() {
        const options = this.options;
        // Use the first letter of each alignment option in order to detect
        // the side. (#4189 - use charAt(x) notation instead of [x] for IE7)
        if (this.proximate) {
            return options.align.charAt(0) + 'tv';
        }
        return options.floating ? '' : (options.align.charAt(0) +
            options.verticalAlign.charAt(0) +
            options.layout.charAt(0));
    }
    /**
     * Adjust the chart margins by reserving space for the legend on only one
     * side of the chart. If the position is set to a corner, top or bottom is
     * reserved for horizontal legends and left or right for vertical ones.
     *
     * @internal
     * @function Highcharts.Legend#adjustMargins
     * @param {Array<number>} margin
     * @param {Array<number>} spacing
     */
    adjustMargins(margin, spacing) {
        const chart = this.chart, options = this.options, alignment = this.getAlignment();
        if (alignment) {
            ([
                /(lth|ct|rth)/,
                /(rtv|rm|rbv)/,
                /(rbh|cb|lbh)/,
                /(lbv|lm|ltv)/
            ]).forEach((alignments, side) => {
                if (alignments.test(alignment) && !defined(margin[side])) {
                    // Now we have detected on which side of the chart we should
                    // reserve space for the legend
                    chart[marginNames[side]] = Math.max(chart[marginNames[side]], (chart.legend[(side + 1) % 2 ? 'legendHeight' : 'legendWidth'] +
                        [1, -1, -1, 1][side] * (options[(side % 2) ? 'x' : 'y']) +
                        (options.margin ?? 12) +
                        spacing[side] +
                        (chart.titleOffset[side] || 0)));
                }
            });
        }
    }
    /**
     * @internal
     * @function Highcharts.Legend#proximatePositions
     */
    proximatePositions() {
        const chart = this.chart, boxes = [], alignLeft = this.options.align === 'left';
        this.allItems.forEach(function (item) {
            let lastPoint, height, useFirstPoint = alignLeft, target, top;
            if (item.yAxis) {
                if (item.xAxis.options.reversed) {
                    useFirstPoint = !useFirstPoint;
                }
                if (item.points) {
                    lastPoint = find(useFirstPoint ?
                        item.points :
                        item.points.slice(0).reverse(), function (item) {
                        return isNumber(item.plotY);
                    });
                }
                height = this.itemMarginTop +
                    item.legendItem.label.getBBox().height +
                    this.itemMarginBottom;
                top = item.yAxis.top - chart.plotTop;
                if (item.visible) {
                    target = lastPoint ?
                        lastPoint.plotY :
                        item.yAxis.height;
                    target += top - 0.3 * height;
                }
                else {
                    target = top + item.yAxis.height;
                }
                boxes.push({
                    target: target,
                    size: height,
                    item
                });
            }
        }, this);
        let legendItem;
        for (const box of Legend_distribute(boxes, chart.plotHeight)) {
            legendItem = box.item.legendItem || {};
            if (isNumber(box.pos)) {
                legendItem.y = chart.plotTop - chart.spacing[0] + box.pos;
            }
        }
    }
    /**
     * Render the legend. This method can be called both before and after
     * `chart.render`. If called after, it will only rearrange items instead
     * of creating new ones. Called internally on initial render and after
     * redraws.
     *
     * @internal
     * @function Highcharts.Legend#render
     */
    render() {
        const legend = this, chart = legend.chart, chartSpacingBoxWidth = chart.spacingBox.width, renderer = chart.renderer, options = legend.options, padding = legend.padding, 
        // Add each series or point
        allItems = legend.getAllItems();
        let display, legendWidth, legendHeight, legendGroup = legend.group, allowedWidth, box = legend.box;
        legend.itemX = padding;
        legend.itemY = legend.initialItemY;
        legend.offsetWidth = 0;
        legend.lastItemY = 0;
        legend.widthOption = relativeLength(options.width, chartSpacingBoxWidth - padding);
        // Compute how wide the legend is allowed to be
        allowedWidth = chartSpacingBoxWidth - 2 * padding - options.x;
        if (['rm', 'lm'].indexOf(legend.getAlignment().substring(0, 2)) > -1) {
            allowedWidth /= 2;
        }
        legend.maxLegendWidth = legend.widthOption || allowedWidth;
        if (!legendGroup) {
            /**
             * SVG group of the legend.
             *
             * @readonly
             * @name Highcharts.Legend#group
             * @type {Highcharts.SVGElement}
             */
            legend.group = legendGroup = renderer
                .g('legend')
                .addClass(options.className || '')
                .attr({ zIndex: 7 })
                .add();
            legend.contentGroup = renderer
                .g()
                .attr({ zIndex: 1 }) // Above background
                .add(legendGroup);
            legend.scrollGroup = renderer
                .g()
                .add(legend.contentGroup);
        }
        legend.renderTitle();
        // Sort by legendIndex
        stableSort(allItems, (a, b) => (a.options?.legendIndex || 0) -
            (b.options?.legendIndex || 0));
        // Reversed legend
        if (options.reversed) {
            allItems.reverse();
        }
        /**
         * All items for the legend, which is an array of series for most series
         * and an array of points for pie series and its derivatives.
         *
         * @readonly
         * @name Highcharts.Legend#allItems
         * @type {Array<(Highcharts.Point|Highcharts.Series)>}
         */
        legend.allItems = allItems;
        legend.display = display = !!allItems.length;
        // Render the items. First we run a loop to set the text and properties
        // and read all the bounding boxes. The next loop computes the item
        // positions based on the bounding boxes.
        legend.lastLineHeight = 0;
        legend.maxItemWidth = 0;
        legend.totalItemWidth = 0;
        legend.itemHeight = 0;
        allItems.forEach(legend.renderItem, legend);
        allItems.forEach(legend.layoutItem, legend);
        // Get the box
        legendWidth = (options.maxWidth ?
            Math.min((legend.widthOption || legend.offsetWidth), allowedWidth, relativeLength(options.maxWidth, chart.chartWidth) || Infinity) :
            (legend.widthOption || legend.offsetWidth)) + padding;
        legendHeight = legend.lastItemY + legend.lastLineHeight +
            legend.titleHeight;
        legendHeight = legend.handleOverflow(legendHeight);
        legendHeight += padding;
        // Draw the border and/or background
        if (!box) {
            /**
             * SVG element of the legend box.
             *
             * @readonly
             * @name Highcharts.Legend#box
             * @type {Highcharts.SVGElement}
             */
            legend.box = box = renderer.rect()
                .addClass('highcharts-legend-box')
                .attr({
                r: options.borderRadius
            })
                .add(legendGroup);
        }
        // Presentational
        if (!chart.styledMode) {
            box
                .attr({
                stroke: options.borderColor,
                'stroke-width': options.borderWidth || 0,
                fill: options.backgroundColor || 'none'
            })
                .shadow(options.shadow);
        }
        if (legendWidth > 0 && legendHeight > 0) {
            box[box.placed ? 'animate' : 'attr'](box.crisp.call({}, {
                x: 0,
                y: 0,
                width: legendWidth,
                height: legendHeight
            }, box.strokeWidth()));
        }
        // Hide the border if no items
        legendGroup[display ? 'show' : 'hide']();
        // Open for responsiveness
        if (chart.styledMode && legendGroup.getStyle('display') === 'none') {
            legendWidth = legendHeight = 0;
        }
        legend.legendWidth = legendWidth;
        legend.legendHeight = legendHeight;
        if (display) {
            legend.align();
        }
        if (!this.proximate) {
            this.positionItems();
        }
        fireEvent(this, 'afterRender');
    }
    /**
     * Align the legend to chart's box.
     *
     * @internal
     * @function Highcharts.align
     * @param {Highcharts.BBoxObject} alignTo
     */
    align(alignTo = this.chart.spacingBox) {
        const chart = this.chart, options = this.options;
        // If aligning to the top and the layout is horizontal, adjust for
        // the title (#7428)
        let y = alignTo.y;
        if (/(lth|ct|rth)/.test(this.getAlignment()) &&
            chart.titleOffset[0] > 0) {
            y += chart.titleOffset[0];
        }
        else if (/(lbh|cb|rbh)/.test(this.getAlignment()) &&
            chart.titleOffset[2] > 0) {
            y -= chart.titleOffset[2];
        }
        if (y !== alignTo.y) {
            alignTo = merge(alignTo, { y });
        }
        if (!chart.hasRendered) {
            // Avoid animation when adjusting alignment for responsiveness and
            // colorAxis label layout
            this.group.placed = false;
        }
        this.group.align(merge(options, {
            width: this.legendWidth,
            height: this.legendHeight,
            verticalAlign: this.proximate ? 'top' : options.verticalAlign
        }), true, alignTo);
    }
    /**
     * Set up the overflow handling by adding navigation with up and down arrows
     * below the legend.
     *
     * @internal
     * @function Highcharts.Legend#handleOverflow
     */
    handleOverflow(legendHeight) {
        const legend = this, chart = this.chart, renderer = chart.renderer, options = this.options, optionsY = options.y, alignTop = options.verticalAlign === 'top', padding = this.padding, maxHeight = options.maxHeight, navOptions = options.navigation, animation = pick(navOptions.animation, true), arrowSize = navOptions.arrowSize || 12, pages = this.pages, allItems = this.allItems, clipToHeight = function (height) {
            if (typeof height === 'number') {
                clipRect.attr({
                    height: height
                });
            }
            else if (clipRect) { // Reset (#5912)
                legend.clipRect = clipRect.destroy();
                legend.contentGroup.clip();
            }
            // Use HTML
            if (legend.contentGroup.div) {
                legend.contentGroup.div.style.clip = height ?
                    'rect(' + padding + 'px,9999px,' +
                        (padding + height) + 'px,0)' :
                    'auto';
            }
        }, addTracker = function (key) {
            legend[key] = renderer
                .circle(0, 0, arrowSize * 1.3)
                .translate(arrowSize / 2, arrowSize / 2)
                .add(nav);
            if (!chart.styledMode) {
                legend[key].attr('fill', 'rgba(0,0,0,0.0001)');
            }
            return legend[key];
        };
        let clipHeight, lastY, legendItem, lastLegendItem, spaceHeight = (chart.spacingBox.height +
            (alignTop ? -optionsY : optionsY) - padding), nav = this.nav, clipRect = this.clipRect;
        // Adjust the height
        if (options.layout === 'horizontal' &&
            options.verticalAlign !== 'middle' &&
            !options.floating) {
            spaceHeight /= 2;
        }
        if (maxHeight) {
            spaceHeight = Math.min(spaceHeight, maxHeight);
        }
        // Reset the legend height and adjust the clipping rectangle
        pages.length = 0;
        if (legendHeight &&
            spaceHeight > 0 &&
            legendHeight > spaceHeight &&
            navOptions.enabled !== false) {
            this.clipHeight = clipHeight =
                Math.max(spaceHeight - 20 - this.titleHeight - padding, 0);
            this.currentPage = pick(this.currentPage, 1);
            this.fullHeight = legendHeight;
            // Fill pages with Y positions so that the top of each a legend item
            // defines the scroll top for each page (#2098)
            allItems.forEach((item, i) => {
                legendItem = item.legendItem || {};
                const y = legendItem.y || 0, h = Math.round(legendItem.label.getBBox().height);
                let len = pages.length;
                if (!len || (y - pages[len - 1] > clipHeight &&
                    (lastY || y) !== pages[len - 1])) {
                    pages.push(lastY || y);
                    len++;
                }
                // Keep track of which page each item is on
                legendItem.pageIx = len - 1;
                if (lastY && lastLegendItem) {
                    lastLegendItem.pageIx = len - 1;
                }
                // Add the last page if needed (#2617, #13683)
                if (
                // Check the last item
                i === allItems.length - 1 &&
                    // If adding next page is needed (#18768)
                    y + h - pages[len - 1] > clipHeight &&
                    y > pages[len - 1]) {
                    pages.push(y);
                    legendItem.pageIx = len;
                }
                if (y !== lastY) {
                    lastY = y;
                }
                lastLegendItem = legendItem;
            });
            // Only apply clipping if needed. Clipping causes blurred legend in
            // PDF export (#1787)
            if (!clipRect) {
                clipRect = legend.clipRect =
                    renderer.clipRect(0, padding - 2, 9999, 0);
                legend.contentGroup.clip(clipRect);
            }
            clipToHeight(clipHeight);
            // Add navigation elements
            if (!nav) {
                this.nav = nav = renderer.g()
                    .attr({ zIndex: 1 })
                    .add(this.group);
                this.up = renderer
                    .symbol('triangle', 0, 0, arrowSize, arrowSize)
                    .add(nav);
                addTracker('upTracker')
                    .on('click', function () {
                    legend.scroll(-1, animation);
                });
                this.pager = renderer.text('', 15, 10)
                    .addClass('highcharts-legend-navigation');
                if (!chart.styledMode && navOptions.style) {
                    this.pager.css(navOptions.style);
                }
                this.pager.add(nav);
                this.down = renderer
                    .symbol('triangle-down', 0, 0, arrowSize, arrowSize)
                    .add(nav);
                addTracker('downTracker')
                    .on('click', function () {
                    legend.scroll(1, animation);
                });
            }
            // Set initial position
            legend.scroll(0);
            legendHeight = spaceHeight;
            // Reset
        }
        else if (nav) {
            clipToHeight();
            this.nav = nav.destroy(); // #6322
            this.scrollGroup.attr({
                translateY: 1
            });
            this.clipHeight = 0; // #1379
        }
        return legendHeight;
    }
    /**
     * Scroll the legend by a number of pages.
     *
     * @internal
     * @function Highcharts.Legend#scroll
     *
     * @param {number} scrollBy
     * The number of pages to scroll.
     *
     * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
     * Whether and how to apply animation.
     */
    scroll(scrollBy, animation) {
        const chart = this.chart, pages = this.pages, pageCount = pages.length, clipHeight = this.clipHeight, navOptions = this.options.navigation, pager = this.pager, padding = this.padding;
        let currentPage = this.currentPage + scrollBy;
        // When resizing while looking at the last page
        if (currentPage > pageCount) {
            currentPage = pageCount;
        }
        if (currentPage > 0) {
            if (typeof animation !== 'undefined') {
                Legend_setAnimation(animation, chart);
            }
            this.nav.attr({
                translateX: padding,
                translateY: clipHeight + this.padding + 7 + this.titleHeight,
                visibility: 'inherit'
            });
            [this.up, this.upTracker].forEach(function (elem) {
                elem.attr({
                    'class': currentPage === 1 ?
                        'highcharts-legend-nav-inactive' :
                        'highcharts-legend-nav-active'
                });
            });
            pager.attr({
                text: currentPage + '/' + pageCount
            });
            [this.down, this.downTracker].forEach(function (elem) {
                elem.attr({
                    // Adjust to text width
                    x: 18 + this.pager.getBBox().width,
                    'class': currentPage === pageCount ?
                        'highcharts-legend-nav-inactive' :
                        'highcharts-legend-nav-active'
                });
            }, this);
            if (!chart.styledMode) {
                this.up
                    .attr({
                    fill: currentPage === 1 ?
                        navOptions.inactiveColor :
                        navOptions.activeColor
                });
                this.upTracker
                    .css({
                    cursor: currentPage === 1 ? 'default' : 'pointer'
                });
                this.down
                    .attr({
                    fill: currentPage === pageCount ?
                        navOptions.inactiveColor :
                        navOptions.activeColor
                });
                this.downTracker
                    .css({
                    cursor: currentPage === pageCount ?
                        'default' :
                        'pointer'
                });
            }
            this.scrollOffset = -pages[currentPage - 1] + this.initialItemY;
            this.scrollGroup.animate({
                translateY: this.scrollOffset
            });
            this.currentPage = currentPage;
            this.positionCheckboxes();
            // Fire event after scroll animation is complete
            const animOptions = Legend_animObject(pick(animation, chart.renderer.globalAnimation, true));
            syncTimeout(() => {
                fireEvent(this, 'afterScroll', { currentPage });
            }, animOptions.duration);
        }
    }
    /**
     * @internal
     * @function Highcharts.Legend#setItemEvents
     * @param {Highcharts.BubbleLegendItem|Point|Highcharts.Series} item
     * @param {Highcharts.SVGElement} legendLabel
     * @param {boolean} [useHTML=false]
     * @emits Highcharts.Legend#event:itemClick
     */
    setItemEvents(item, legendLabel, useHTML) {
        const legend = this, legendItem = item.legendItem || {}, boxWrapper = legend.chart.renderer.boxWrapper, isPoint = item instanceof Series_Point, isSeries = item instanceof Series_Series, activeClass = 'highcharts-legend-' +
            (isPoint ? 'point' : 'series') + '-active', styledMode = legend.chart.styledMode, 
        // When `useHTML`, the symbol is rendered in other group, so
        // we need to apply events listeners to both places
        legendElements = useHTML ?
            [legendLabel, legendItem.symbol] :
            [legendItem.group];
        const setOtherItemsState = (state) => {
            legend.allItems.forEach((otherItem) => {
                if (item !== otherItem) {
                    [otherItem]
                        .concat(otherItem.linkedSeries || [])
                        .forEach((otherItem) => {
                        otherItem.setState(state, !isPoint);
                    });
                }
            });
        };
        // Set the events on the item group, or in case of useHTML, the item
        // itself (#1249)
        for (const element of legendElements) {
            if (element) {
                element
                    .on('mouseover', function () {
                    if (item.visible) {
                        setOtherItemsState('inactive');
                    }
                    item.setState('hover');
                    // A CSS class to dim or hide other than the hovered
                    // series.
                    // Works only if hovered series is visible (#10071).
                    if (item.visible) {
                        boxWrapper.addClass(activeClass);
                    }
                    if (!styledMode) {
                        legendLabel.css(legend.options.itemHoverStyle);
                    }
                })
                    .on('mouseout', function () {
                    if (!legend.chart.styledMode) {
                        legendLabel.css(merge(item.visible ?
                            legend.itemStyle :
                            legend.itemHiddenStyle));
                    }
                    setOtherItemsState('');
                    // A CSS class to dim or hide other than the hovered
                    // series.
                    boxWrapper.removeClass(activeClass);
                    item.setState();
                })
                    .on('click', function (event) {
                    const defaultItemClick = function () {
                        if (item.setVisible) {
                            item.setVisible();
                        }
                        // Reset inactive state
                        setOtherItemsState(item.visible ? 'inactive' : '');
                    };
                    // A CSS class to dim or hide other than the hovered
                    // series. Event handling in iOS causes the activeClass
                    // to be added prior to click in some cases (#7418).
                    boxWrapper.removeClass(activeClass);
                    fireEvent(legend, 'itemClick', {
                        // Pass over the click/touch event. #4.
                        browserEvent: event,
                        legendItem: item,
                        // Provide context in case user arrow function
                        context: legend
                    }, defaultItemClick);
                    // Deprecated logic
                    // Click the name or symbol
                    if (isPoint) {
                        item.firePointEvent('legendItemClick', {
                            browserEvent: event
                        });
                    }
                    else if (isSeries) {
                        fireEvent(item, 'legendItemClick', {
                            browserEvent: event
                        });
                    }
                });
            }
        }
    }
    /**
     * @internal
     * @function Highcharts.Legend#createCheckboxForItem
     * @param {Highcharts.BubbleLegendItem|Point|Highcharts.Series} item
     * @emits Highcharts.Series#event:checkboxClick
     */
    createCheckboxForItem(item) {
        const legend = this;
        item.checkbox = createElement('input', {
            type: 'checkbox',
            className: 'highcharts-legend-checkbox',
            checked: item.selected,
            defaultChecked: item.selected // Required by IE7
        }, legend.options.itemCheckboxStyle, legend.chart.container);
        addEvent(item.checkbox, 'click', function (event) {
            const target = event.target;
            fireEvent(item.series || item, 'checkboxClick', {
                checked: target.checked,
                item: item
            }, function () {
                item.select();
            });
        });
    }
}
/* *
 *
 *  Class Namespace
 *
 * */
(function (Legend) {
    /* *
     *
     *  Declarations
     *
     * */
    /* *
     *
     *  Functions
     *
     * */
    /** @internal */
    function compose(ChartClass) {
        if (pushUnique(Legend_composed, 'Core.Legend')) {
            addEvent(ChartClass, 'beforeMargins', function () {
                /**
                 * The legend contains an interactive overview over chart items,
                 * usually individual series or points depending on the series
                 * type. The color axis and bubble legend are also rendered in
                 * the chart legend.
                 *
                 * @name Highcharts.Chart#legend
                 * @type {Highcharts.Legend}
                 */
                this.legend = new Legend(this, this.options.legend);
            });
        }
    }
    Legend.compose = compose;
})(Legend || (Legend = {}));
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Legend_Legend = (Legend);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * @interface Highcharts.LegendItemObject
 */ /**
* @name Highcharts.LegendItemObject#item
* @type {Highcharts.SVGElement|undefined}
*/ /**
* @name Highcharts.LegendItemObject#line
* @type {Highcharts.SVGElement|undefined}
*/ /**
* @name Highcharts.LegendItemObject#symbol
* @type {Highcharts.SVGElement|undefined}
*/ /**
* @name Highcharts.LegendItemObject#label
* @type {Highcharts.SVGElement|undefined}
*/
/**
 * Gets fired when the legend item is clicked. The default
 * action is to toggle the visibility of the series or point. This can be
 * prevented by returning `false` or calling `event.preventDefault()`.
 *
 * @callback Highcharts.LegendItemClickCallbackFunction
 *
 * @param {Highcharts.Legend} this
 * The legend on which the event occurred.
 *
 * @param {Highcharts.LegendItemClickEventObject} event
 * The event that occurred.
 *
 * @param {Highcharts.Legend} [ctx]
 * Since v12.6.0, the legend context passed as an extra argument for arrow
 * functions.
 */
/**
 * Information about the legend click event.
 *
 * @interface Highcharts.LegendItemClickEventObject
 */ /**
* Related browser event.
* @name Highcharts.LegendItemClickEventObject#browserEvent
* @type {Highcharts.PointerEvent}
*/ /**
* Prevent the default action of toggle the visibility of the series or point.
* @name Highcharts.LegendItemClickEventObject#preventDefault
* @type {Function}
* */ /**
* Related legend item, it can be series, point, color axis or data class from
* color axis.
* @name Highcharts.LegendItemClickEventObject#legendItem
* @type {Highcharts.Series|Highcharts.Point|Highcharts.LegendItemObject}
* */ /**
* Related legend.
* @name Highcharts.LegendItemClickEventObject#target
* @type {Highcharts.Legend}
*/ /**
* Event type.
* @name Highcharts.LegendItemClickEventObject#type
* @type {"itemClick"}
*/
/**
 * Gets fired when the legend item belonging to a point is clicked. The default
 * action is to toggle the visibility of the point. This can be prevented by
 * returning `false` or calling `event.preventDefault()`.
 *
 * **Note:** This option is deprecated in favor of
 * Highcharts.LegendItemClickCallbackFunction.
 *
 * @deprecated 11.4.4
 * @callback Highcharts.PointLegendItemClickCallbackFunction
 *
 * @param {Highcharts.Point} this
 * The point on which the event occurred.
 *
 * @param {Highcharts.PointLegendItemClickEventObject} event
 * The event that occurred.
 *
 * @param {Highcharts.Point} [ctx]
 * Since v12.6.0, the point context passed as an extra argument for arrow
 * functions.
 */
/**
 * Information about the legend click event.
 *
 * **Note:** This option is deprecated in favor of
 * Highcharts.LegendItemClickEventObject.
 *
 * @deprecated 11.4.4
 * @interface Highcharts.PointLegendItemClickEventObject
 */ /**
* Related browser event.
* @name Highcharts.PointLegendItemClickEventObject#browserEvent
* @type {Highcharts.PointerEvent}
*/ /**
* Whether the default action has been prevented (`true`) or not.
* @name Highcharts.PointLegendItemClickEventObject#defaultPrevented
* @type {boolean|undefined}
*/ /**
* Prevent the default action of toggle the visibility of the point.
* @name Highcharts.PointLegendItemClickEventObject#preventDefault
* @type {Function}
*/ /**
* Related point.
* @name Highcharts.PointLegendItemClickEventObject#target
* @type {Highcharts.Point}
*/ /**
* Event type.
* @name Highcharts.PointLegendItemClickEventObject#type
* @type {"legendItemClick"}
*/
/**
 * Legend data for the series.
 * @name Highcharts.Series#legendItem
 * @type {Highcharts.LegendItemObject|undefined}
 * @since 10.3.0
 */
/**
 * Gets fired when the legend item belonging to a series is clicked. The default
 * action is to toggle the visibility of the series. This can be prevented by
 * returning `false` or calling `event.preventDefault()`.
 *
 * **Note:** This option is deprecated in favor of
 * Highcharts.LegendItemClickCallbackFunction.
 *
 * @deprecated 11.4.4
 * @callback Highcharts.SeriesLegendItemClickCallbackFunction
 *
 * @param {Highcharts.Series} this
 * The series where the event occurred.
 *
 * @param {Highcharts.SeriesLegendItemClickEventObject} event
 * The event that occurred.
 *
 * @param {Highcharts.Series} [ctx]
 * Since v12.6.0, the series context passed as an extra argument for arrow
 * functions.
 */
/**
 * Information about the legend click event.
 *
 * **Note:** This option is deprecated in favor of
 * Highcharts.LegendItemClickEventObject.
 *
 * @deprecated 11.4.4
 * @interface Highcharts.SeriesLegendItemClickEventObject
 */ /**
* Related browser event.
* @name Highcharts.SeriesLegendItemClickEventObject#browserEvent
* @type {Highcharts.PointerEvent}
*/ /**
* Whether the default action has been prevented (`true`) or not.
* @name Highcharts.SeriesLegendItemClickEventObject#defaultPrevented
* @type {boolean|undefined}
*/ /**
* Prevent the default action of toggle the visibility of the series.
* @name Highcharts.SeriesLegendItemClickEventObject#preventDefault
* @type {Function}
*/ /**
* Related series.
* @name Highcharts.SeriesLegendItemClickEventObject#target
* @type {Highcharts.Series}
*/ /**
* Event type.
* @name Highcharts.SeriesLegendItemClickEventObject#type
* @type {"legendItemClick"}
*/
(''); // Keeps doclets above in JS file

;// ./code/es-modules/Core/Chart/Chart.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { animate: Chart_animate, animObject: Chart_animObject, setAnimation: Chart_setAnimation } = AnimationUtilities;


const { defaultOptions: Chart_defaultOptions } = Defaults;


const { numberFormat: Chart_numberFormat } = Core_Templating;

const { registerEventOptions: Chart_registerEventOptions } = Core_Foundation;

const { charts: Chart_charts, doc: Chart_doc, marginNames: Chart_marginNames, win: Chart_win } = Core_Globals;


const { seriesTypes: Chart_seriesTypes } = Series_SeriesRegistry;






/* *
 *
 *  Class
 *
 * */
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
 * The Chart class. The recommended constructor is {@link Highcharts#chart}.
 *
 * @example
 * let chart = Highcharts.chart('container', {
 *        title: {
 *               text: 'My chart'
 *        },
 *        series: [{
 *            data: [1, 3, 2, 4]
 *        }]
 * })
 *
 * @class
 * @name Highcharts.Chart
 *
 * @param {string|Highcharts.HTMLDOMElement} [renderTo]
 *        The DOM element to render to, or its id.
 *
 * @param {Highcharts.Options} options
 *        The chart options structure.
 *
 * @param {Highcharts.ChartCallbackFunction|true} [callback]
 *        Function to run when the chart has loaded and all external images
 *        are loaded. Defining a
 *        [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load)
 *        handler is equivalent. Set to `true` to return a promise that resolves
 *        when the chart is ready.
 */
class Chart {
    /* eslint-disable jsdoc/check-param-names */
    /**
     * Factory function for basic charts.
     *
     * @example
     * // Render a chart into div#container
     * const chart = Highcharts.chart('container', {
     *     title: {
     *         text: 'My chart'
     *     },
     *     series: [{
     *         data: [1, 3, 2, 4]
     *     }]
     * });
     *
     * @function Highcharts.chart
     *
     * @param {string|Highcharts.HTMLDOMElement} [renderTo]
     * The DOM element to render to, or its id.
     *
     * @param {Highcharts.Options} options
     * The chart options structure.
     *
     * @param {Highcharts.ChartCallbackFunction|true} [callback]
     * Function to run when the chart has loaded and all external images are
     * loaded. Defining a
     * [chart.events.load](https://api.highcharts.com/highcharts/chart.events.load)
     * handler is equivalent. Set to `true` to return a promise that resolves
     * when the chart is ready.
     *
     * @return {Highcharts.Chart}
     * Returns the Chart object.
     */
    static chart(a, b, c) {
        const chart = new Chart(a, b, c);
        return chart.promise || chart;
    }
    // Implementation
    constructor(a, 
    /* eslint-disable @typescript-eslint/no-unused-vars */
    b, c
    /* eslint-enable @typescript-eslint/no-unused-vars */
    ) {
        /** @internal */
        this.sharedClips = {};
        // Return early if there's no browser API (server environment).
        if (!Chart_doc) {
            error(36, false, this);
            return;
        }
        const args = [
            // ES5 builds fail unless we cast it to an Array
            ...arguments
        ];
        // Remove the optional first argument, renderTo, and set it on this.
        if (isString(a) || a.nodeName) {
            this.renderTo = args.shift();
        }
        this.init(args[0], args[1]);
    }
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Function setting zoom options after chart init and after chart update.
     * Offers support for deprecated options.
     *
     * @internal
     * @function Highcharts.Chart#setZoomOptions
     */
    setZoomOptions() {
        const chart = this, options = chart.options.chart, zooming = options.zooming;
        chart.zooming = {
            ...zooming,
            type: pick(options.zoomType, zooming.type),
            key: pick(options.zoomKey, zooming.key),
            pinchType: pick(options.pinchType, zooming.pinchType),
            singleTouch: pick(options.zoomBySingleTouch, zooming.singleTouch, false),
            resetButton: merge(zooming.resetButton, options.resetZoomButton)
        };
    }
    /**
     * Overridable function that initializes the chart. The constructor's
     * arguments are passed on directly.
     *
     * @function Highcharts.Chart#init
     *
     * @param {Highcharts.Options} userOptions
     *        Custom options.
     *
     * @param {Function|true} [callback]
     *        Function to run when the chart has loaded and all external
     *        images are loaded. Set to `true` to return a promise that
     *        resolves when the chart is ready.
     *
     * @emits Highcharts.Chart#event:init
     * @emits Highcharts.Chart#event:afterInit
     */
    init(userOptions, callback) {
        // Fire the event with a default function
        fireEvent(this, 'init', { args: arguments }, function () {
            const options = merge(Chart_defaultOptions, userOptions), optionsChart = options.chart, renderTo = this.renderTo || optionsChart.renderTo;
            /**
             * The original options given to the constructor or a chart factory
             * like {@link Highcharts.chart} and {@link Highcharts.stockChart}.
             * The original options are shallow copied to avoid mutation. The
             * copy, `chart.userOptions`, may later be mutated to reflect
             * updated options throughout the lifetime of the chart.
             *
             * For collections, like `series`, `xAxis` and `yAxis`, the chart
             * user options should always be reflected by the item user option,
             * so for example the following should always be true:
             *
             * `chart.xAxis[0].userOptions === chart.userOptions.xAxis[0]`
             *
             * @name Highcharts.Chart#userOptions
             * @type {Highcharts.Options}
             */
            this.userOptions = extend({}, userOptions);
            if (!(this.renderTo = (isString(renderTo) ?
                Chart_doc.getElementById(renderTo) :
                renderTo))) {
                // Display an error if the renderTo is wrong
                error(13, true, this);
            }
            this.margin = [];
            this.spacing = [];
            // An array of functions that returns labels that should be
            // considered for anti-collision
            this.labelCollectors = [];
            // Create a promise to be resolved later
            if (callback === true) {
                this.promise = new Promise((resolve) => {
                    this.callback = resolve;
                });
            }
            else {
                this.callback = callback;
            }
            this.isResizing = 0;
            /**
             * The options structure for the chart after merging
             * {@link #defaultOptions} and {@link #userOptions}. It contains
             * members for the sub elements like series, legend, tooltip etc.
             *
             * @name Highcharts.Chart#options
             * @type {Highcharts.Options}
             */
            this.options = options;
            /**
             * All the axes in the chart.
             *
             * @see  Highcharts.Chart.xAxis
             * @see  Highcharts.Chart.yAxis
             *
             * @name Highcharts.Chart#axes
             * @type {Array<Highcharts.Axis>}
             */
            this.axes = [];
            /**
             * All the current series in the chart.
             *
             * @name Highcharts.Chart#series
             * @type {Array<Highcharts.Series>}
             */
            this.series = [];
            this.locale = options.lang.locale ??
                this.renderTo.closest('[lang]')?.lang;
            /**
             * The `Time` object associated with the chart. Since v6.0.5,
             * time settings can be applied individually for each chart. If
             * no individual settings apply, the `Time` object is shared by
             * all instances.
             *
             * @name Highcharts.Chart#time
             * @type {Highcharts.Time}
             */
            this.time = new Core_Time(extend(options.time || {}, {
                locale: this.locale
            }), options.lang);
            options.time = this.time.options;
            /**
             * Callback function to override the default function that formats
             * all the numbers in the chart. Returns a string with the formatted
             * number.
             *
             * @name Highcharts.Chart#numberFormatter
             * @type {Highcharts.NumberFormatterCallbackFunction}
             */
            this.numberFormatter = (optionsChart.numberFormatter || Chart_numberFormat).bind(this);
            /**
             * Whether the chart is in styled mode, meaning all presentational
             * attributes are avoided.
             *
             * @name Highcharts.Chart#styledMode
             * @type {boolean}
             */
            this.styledMode = optionsChart.styledMode;
            this.hasCartesianSeries = optionsChart.showAxes;
            const chart = this;
            /**
             * Index position of the chart in the {@link Highcharts#charts}
             * property.
             *
             * @name Highcharts.Chart#index
             * @type {number}
             * @readonly
             */
            chart.index = Chart_charts.length; // Add the chart to the global lookup
            // The chart.dataTable option
            chart.dataTable = chart.getDataTable(options);
            Chart_charts.push(chart);
            Core_Globals.chartCount++;
            // Chart event handlers
            Chart_registerEventOptions(this, optionsChart);
            /**
             * A collection of the X axes in the chart.
             *
             * @name Highcharts.Chart#xAxis
             * @type {Array<Highcharts.Axis>}
             */
            chart.xAxis = [];
            /**
             * A collection of the Y axes in the chart.
             *
             * @name Highcharts.Chart#yAxis
             * @type {Array<Highcharts.Axis>}
             *
             * @todo
             * Make events official: Fire the event `afterInit`.
             */
            chart.yAxis = [];
            chart.pointCount = chart.colorCounter = chart.symbolCounter = 0;
            this.setZoomOptions();
            // Fire after init but before first render, before axes and series
            // have been initialized.
            fireEvent(chart, 'afterInit');
            chart.firstRender();
        });
    }
    getDataTable(options) {
        return (options.dataTable ? splat(options.dataTable) : []).map((dataTableOptions) => (dataTableOptions.isDataTable ?
            dataTableOptions :
            new Data_DataTableCore(dataTableOptions)));
    }
    /**
     * Internal function to initialize an individual series.
     *
     * @internal
     * @function Highcharts.Chart#initSeries
     */
    initSeries(options) {
        const chart = this, optionsChart = chart.options.chart, type = (options.type ||
            optionsChart.type), SeriesClass = Chart_seriesTypes[type];
        // No such series type
        if (!SeriesClass) {
            error(17, true, chart, { missingModuleFor: type });
        }
        const series = new SeriesClass();
        if (typeof series.init === 'function') {
            series.init(chart, options);
        }
        return series;
    }
    /**
     * Order all series or axes above a given index. When series or axes are
     * added and ordered by configuration, only the last series is handled
     * (#248, #1123, #2456, #6112). This function is called on series and axis
     * initialization and destroy.
     *
     * @internal
     * @function Highcharts.Chart#orderItems
     *
     * @param {string} coll
     * The collection name.
     *
     * @param {number} [fromIndex=0]
     * If this is given, only the series above this index are handled.
     */
    orderItems(coll, fromIndex = 0) {
        const collection = this[coll], 
        // Item options should be reflected in chart.options.series,
        // chart.options.yAxis etc
        optionsArray = this.options[coll] = splat(this.options[coll])
            .slice(), userOptionsArray = this.userOptions[coll] = this.userOptions[coll] ?
            splat(this.userOptions[coll]).slice() :
            [];
        if (this.hasRendered) {
            // Remove all above index
            optionsArray.splice(fromIndex);
            userOptionsArray.splice(fromIndex);
        }
        if (collection) {
            for (let i = fromIndex, iEnd = collection.length; i < iEnd; ++i) {
                const item = collection[i];
                if (item) {
                    item.index = i;
                    if (item instanceof Series_Series) {
                        item.name = item.getName();
                    }
                    if (!item.options.isInternal) {
                        optionsArray[i] = item.options;
                        userOptionsArray[i] = item.userOptions;
                    }
                }
            }
        }
    }
    /**
     * Get the clipping for a series. Could be called for a series to initialize
     * animating the clip or to set the final clip (only width and x).
     *
     * @internal
     * @function Highcharts.Chart#getClipBox
     */
    getClipBox(series, chartCoords) {
        const inverted = this.inverted, { xAxis, yAxis } = series || {};
        // If no axes on the series, or series undefined, use global clipBox
        let { x, y, width, height } = merge(this.clipBox);
        if (series) {
            // Otherwise, use clipBox.width which is corrected for
            // plotBorderWidth and clipOffset
            if (xAxis && xAxis.len !== this.plotSizeX && !xAxis.isRadial) {
                width = xAxis.len;
            }
            if (yAxis && yAxis.len !== this.plotSizeY && !yAxis.isRadial) {
                height = yAxis.len;
            }
            // If the chart is inverted and the series is not invertible, the
            // chart clip box should be inverted, but not the series clip box
            // (#20264)
            if (inverted && !series.invertible) {
                [width, height] = [height, width];
            }
        }
        if (chartCoords) {
            x += (inverted ? yAxis : xAxis)?.pos ?? this.plotLeft;
            y += (inverted ? xAxis : yAxis)?.pos ?? this.plotTop;
        }
        return { x, y, width, height };
    }
    /**
     * Check whether a given point is within the plot area.
     *
     * @function Highcharts.Chart#isInsidePlot
     *
     * @param {number} plotX
     * Pixel x relative to the plot area.
     *
     * @param {number} plotY
     * Pixel y relative to the plot area.
     *
     * @param {Highcharts.ChartIsInsideOptionsObject} [options]
     * Options object.
     *
     * @return {boolean}
     * Returns true if the given point is inside the plot area.
     */
    isInsidePlot(plotX, plotY, options = {}) {
        const { inverted, plotBox, plotLeft, plotTop, scrollablePlotBox } = this, { scrollLeft = 0, scrollTop = 0 } = (options.visiblePlotOnly &&
            this.scrollablePlotArea?.scrollingContainer) || {}, series = options.series, box = (options.visiblePlotOnly && scrollablePlotBox) || plotBox, x = options.inverted ? plotY : plotX, y = options.inverted ? plotX : plotY, e = {
            x,
            y,
            isInsidePlot: true,
            options
        };
        if (!options.ignoreX) {
            const xAxis = (series &&
                (inverted && !this.polar ? series.yAxis : series.xAxis)) || {
                pos: plotLeft,
                len: Infinity
            };
            const chartX = options.paneCoordinates ?
                xAxis.pos + x : plotLeft + x;
            if (!(chartX >= Math.max(scrollLeft + plotLeft, xAxis.pos) &&
                chartX <= Math.min(scrollLeft + plotLeft + box.width, xAxis.pos + xAxis.len))) {
                e.isInsidePlot = false;
            }
        }
        if (!options.ignoreY && e.isInsidePlot) {
            const yAxis = (!inverted && options.axis &&
                !options.axis.isXAxis && options.axis) || (series && (inverted ? series.xAxis : series.yAxis)) || {
                pos: plotTop,
                len: Infinity
            };
            const chartY = options.paneCoordinates ?
                yAxis.pos + y : plotTop + y;
            if (!(chartY >= Math.max(scrollTop + plotTop, yAxis.pos) &&
                chartY <= Math.min(scrollTop + plotTop + box.height, yAxis.pos + yAxis.len))) {
                e.isInsidePlot = false;
            }
        }
        fireEvent(this, 'afterIsInsidePlot', e);
        return e.isInsidePlot;
    }
    /**
     * Redraw the chart after changes have been done to the data, axis extremes
     * chart size or chart elements. All methods for updating axes, series or
     * points have a parameter for redrawing the chart. This is `true` by
     * default. But in many cases you want to do more than one operation on the
     * chart before redrawing, for example add a number of points. In those
     * cases it is a waste of resources to redraw the chart for each new point
     * added. So you add the points and call `chart.redraw()` after.
     *
     * @function Highcharts.Chart#redraw
     *
     * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
     * If or how to apply animation to the redraw. When `undefined`, it applies
     * the animation that is set in the `chart.animation` option.
     *
     * @emits Highcharts.Chart#event:afterSetExtremes
     * @emits Highcharts.Chart#event:beforeRedraw
     * @emits Highcharts.Chart#event:predraw
     * @emits Highcharts.Chart#event:redraw
     * @emits Highcharts.Chart#event:render
     * @emits Highcharts.Chart#event:updatedData
     */
    redraw(animation) {
        fireEvent(this, 'beforeRedraw');
        const chart = this, axes = chart.hasCartesianSeries ? chart.axes : chart.colorAxis || [], series = chart.series, pointer = chart.pointer, legend = chart.legend, legendUserOptions = chart.userOptions.legend, renderer = chart.renderer, isHiddenChart = renderer.isHidden(), afterRedraw = [];
        let hasDirtyStacks, hasStackedSeries, i, isDirtyBox = chart.isDirtyBox, redrawLegend = chart.isDirtyLegend, serie;
        renderer.rootFontSize = renderer.boxWrapper.getStyle('font-size');
        // Handle responsive rules, not only on resize (#6130)
        if (chart.setResponsive) {
            chart.setResponsive(false);
        }
        // Set the global animation. When chart.hasRendered is not true, the
        // redraw call comes from a responsive rule and animation should not
        // occur.
        Chart_setAnimation(chart.hasRendered ? animation : false, chart);
        if (isHiddenChart) {
            chart.temporaryDisplay();
        }
        // Adjust title layout (reflow multiline text)
        chart.layOutTitles(false);
        // Link stacked series
        i = series.length;
        while (i--) {
            serie = series[i];
            if (serie.options.stacking || serie.options.centerInCategory) {
                hasStackedSeries = true;
                if (serie.isDirty) {
                    hasDirtyStacks = true;
                    break;
                }
            }
        }
        if (hasDirtyStacks) { // Mark others as dirty
            i = series.length;
            while (i--) {
                serie = series[i];
                if (serie.options.stacking) {
                    serie.isDirty = true;
                }
            }
        }
        // Handle updated data in the series
        series.forEach(function (serie) {
            if (serie.isDirty) {
                if (serie.options.legendType === 'point') {
                    if (typeof serie.updateTotals === 'function') {
                        serie.updateTotals();
                    }
                    redrawLegend = true;
                }
                else if (legendUserOptions &&
                    (!!legendUserOptions.labelFormatter ||
                        legendUserOptions.labelFormat)) {
                    redrawLegend = true; // #2165
                }
            }
            if (serie.isDirtyData) {
                fireEvent(serie, 'updatedData');
            }
        });
        // Handle added or removed series
        if (redrawLegend && legend && legend.options.enabled) {
            // Draw legend graphics
            legend.render();
            chart.isDirtyLegend = false;
        }
        // Reset stacks
        if (hasStackedSeries) {
            chart.getStacks();
        }
        // Set axes scales
        axes.forEach(function (axis) {
            axis.updateNames();
            axis.setScale();
        });
        chart.getMargins(); // #3098
        // If one axis is dirty, all axes must be redrawn (#792, #2169)
        axes.forEach(function (axis) {
            if (axis.isDirty) {
                isDirtyBox = true;
            }
        });
        // Redraw axes
        axes.forEach(function (axis) {
            // Fire 'afterSetExtremes' only if extremes are set
            const key = axis.min + ',' + axis.max;
            if (axis.extKey !== key) { // #821, #4452
                axis.extKey = key;
                // Prevent a recursive call to chart.redraw() (#1119)
                afterRedraw.push(function () {
                    fireEvent(axis, 'afterSetExtremes', extend(axis.eventArgs, axis.getExtremes())); // #747, #751
                    delete axis.eventArgs;
                });
            }
            if (isDirtyBox || hasStackedSeries) {
                axis.redraw();
            }
        });
        // The plot areas size has changed
        if (isDirtyBox) {
            chart.drawChartBox();
        }
        // Fire an event before redrawing series, used by the boost module to
        // clear previous series renderings.
        fireEvent(chart, 'predraw');
        // Redraw affected series
        series.forEach(function (serie) {
            if ((isDirtyBox || serie.isDirty) && serie.visible) {
                serie.redraw();
            }
            // Set it here, otherwise we will have unlimited 'updatedData' calls
            // for a hidden series after setData(). Fixes #6012
            serie.isDirtyData = false;
        });
        // Move tooltip or reset
        if (pointer) {
            pointer.reset(true);
        }
        // Redraw if canvas
        renderer.draw();
        // Save the existing state
        axes.forEach((axis) => {
            axis.saveOld();
        });
        // Fire the events
        fireEvent(chart, 'redraw');
        fireEvent(chart, 'render');
        if (isHiddenChart) {
            chart.temporaryDisplay(true);
        }
        // Fire callbacks that are put on hold until after the redraw
        afterRedraw.forEach(function (callback) {
            callback.call();
        });
    }
    /**
     * Get an axis, series or point object by `id` as given in the configuration
     * options. Returns `undefined` if no item is found.
     *
     * @sample highcharts/plotoptions/series-id/
     *         Get series by id
     *
     * @function Highcharts.Chart#get
     *
     * @param {string} id
     * The id as given in the configuration options.
     *
     * @return {Highcharts.Axis|Highcharts.Series|Highcharts.Point|undefined}
     * The retrieved item.
     */
    get(id) {
        const series = this.series;
        /** @internal */
        function itemById(item) {
            return (item.id === id ||
                (item.options && item.options.id === id));
        }
        let ret = 
        // Search axes
        find(this.axes, itemById) ||
            // Search series
            find(this.series, itemById);
        // Search points
        for (let i = 0; !ret && i < series.length; i++) {
            ret = find(series[i].points || [], itemById);
        }
        return ret;
    }
    /**
     * Create the Axis instances based on the config options.
     *
     * @internal
     * @function Highcharts.Chart#createAxes
     * @emits Highcharts.Chart#event:afterCreateAxes
     * @emits Highcharts.Chart#event:createAxes
     */
    createAxes() {
        const options = this.userOptions;
        fireEvent(this, 'createAxes');
        for (const coll of ['xAxis', 'yAxis']) {
            const arr = options[coll] = splat(options[coll] || {});
            for (const axisOptions of arr) {
                // eslint-disable-next-line no-new
                new Axis_Axis(this, axisOptions, coll);
            }
        }
        fireEvent(this, 'afterCreateAxes');
    }
    /**
     * Returns an array of all currently selected points in the chart. Points
     * can be selected by clicking or programmatically by the
     * {@link Highcharts.Point#select}
     * function.
     *
     * @sample highcharts/plotoptions/series-allowpointselect-line/
     *         Get selected points
     * @sample highcharts/members/point-select-lasso/
     *         Lasso selection
     * @sample highcharts/chart/events-selection-points/
     *         Rectangle selection
     *
     * @function Highcharts.Chart#getSelectedPoints
     *
     * @return {Array<Highcharts.Point>}
     *         The currently selected points.
     */
    getSelectedPoints() {
        return this.series.reduce((acc, series) => {
            // For one-to-one points inspect series.data in order to retrieve
            // points outside the visible range (#6445). For grouped data,
            // inspect the generated series.points.
            series.getPointsCollection()
                .forEach((point) => {
                if (pick(point.selectedStaging, point.selected)) {
                    acc.push(point);
                }
            });
            return acc;
        }, []);
    }
    /**
     * Returns an array of all currently selected series in the chart. Series
     * can be selected either programmatically by the
     * {@link Highcharts.Series#select}
     * function or by checking the checkbox next to the legend item if
     * [series.showCheckBox](https://api.highcharts.com/highcharts/plotOptions.series.showCheckbox)
     * is true.
     *
     * @sample highcharts/members/chart-getselectedseries/
     *         Get selected series
     *
     * @function Highcharts.Chart#getSelectedSeries
     *
     * @return {Array<Highcharts.Series>}
     *         The currently selected series.
     */
    getSelectedSeries() {
        return this.series.filter((s) => s.selected);
    }
    /**
     * Set a new title or subtitle for the chart.
     *
     * @sample highcharts/members/chart-settitle/
     *         Set title text and styles
     *
     * @function Highcharts.Chart#setTitle
     *
     * @param {Highcharts.TitleOptions} [titleOptions]
     *        New title options. The title text itself is set by the
     *        `titleOptions.text` property.
     *
     * @param {Highcharts.SubtitleOptions} [subtitleOptions]
     *        New subtitle options. The subtitle text itself is set by the
     *        `subtitleOptions.text` property.
     *
     * @param {boolean} [redraw]
     *        Whether to redraw the chart or wait for a later call to
     *        `chart.redraw()`.
     */
    setTitle(titleOptions, subtitleOptions, redraw) {
        this.applyDescription('title', titleOptions);
        this.applyDescription('subtitle', subtitleOptions);
        // The initial call also adds the caption. On update, chart.update will
        // relay to Chart.setCaption.
        this.applyDescription('caption', void 0);
        this.layOutTitles(redraw);
    }
    /**
     * Apply a title, subtitle or caption for the chart
     *
     * @internal
     * @function Highcharts.Chart#applyDescription
     *
     * @param {string} key
     * Either title, subtitle or caption
     *
     * @param {Highcharts.TitleOptions|Highcharts.SubtitleOptions|Highcharts.CaptionOptions|undefined} explicitOptions
     * The options to set, will be merged with default options.
     */
    applyDescription(key, explicitOptions) {
        const chart = this;
        // Merge default options with explicit options
        const options = this.options[key] = merge(this.options[key], explicitOptions);
        let elem = this[key];
        if (elem && explicitOptions) {
            this[key] = elem = elem.destroy(); // Remove old
        }
        if (options && !elem) {
            elem = this.renderer.text(options.text, 0, 0, options.useHTML)
                .attr({
                align: options.align,
                'class': 'highcharts-' + key,
                zIndex: options.zIndex || 4
            })
                .css({
                textOverflow: 'ellipsis',
                whiteSpace: 'nowrap'
            })
                .add();
            // Update methods, relay to `applyDescription`
            elem.update = function (updateOptions, redraw) {
                chart.applyDescription(key, updateOptions);
                chart.layOutTitles(redraw);
            };
            // Presentational
            if (!this.styledMode) {
                elem.css(extend(key === 'title' ? {
                    // #2944
                    fontSize: this.options.isStock ? '1em' : '1.2em'
                } : {}, options.style));
            }
            // Get unwrapped text length and reset
            elem.textPxLength = elem.getBBox().width;
            elem.css({ whiteSpace: options.style?.whiteSpace });
            /**
             * The chart title. The title has an `update` method that allows
             * modifying the options directly or indirectly via
             * `chart.update`.
             *
             * @sample highcharts/members/title-update/
             *         Updating titles
             *
             * @name Highcharts.Chart#title
             * @type {Highcharts.TitleObject}
             */
            /**
             * The chart subtitle. The subtitle has an `update` method that
             * allows modifying the options directly or indirectly via
             * `chart.update`.
             *
             * @name Highcharts.Chart#subtitle
             * @type {Highcharts.SubtitleObject}
             */
            this[key] = elem;
        }
    }
    /**
     * Internal function to lay out the chart title, subtitle and caption, and
     * cache the full offset height for use in `getMargins`. The result is
     * stored in `this.titleOffset`.
     *
     * @internal
     * @function Highcharts.Chart#layOutTitles
     *
     * @param {boolean} [redraw=true]
     *
     * @emits Highcharts.Chart#event:afterLayOutTitles
     */
    layOutTitles(redraw = true) {
        const titleOffset = [0, 0, 0], { options, renderer, spacingBox } = this;
        // Lay out the title, subtitle and caption respectively
        ['title', 'subtitle', 'caption'].forEach((key) => {
            const desc = this[key], descOptions = this.options[key], alignTo = merge(spacingBox), textPxLength = desc?.textPxLength || 0;
            if (desc && descOptions) {
                // Provide a hook for the exporting button to shift the title
                fireEvent(this, 'layOutTitle', { alignTo, key, textPxLength });
                const fontMetrics = renderer.fontMetrics(desc), baseline = fontMetrics.b, lineHeight = fontMetrics.h, verticalAlign = descOptions.verticalAlign || 'top', topAligned = verticalAlign === 'top', 
                // Use minScale only for top-aligned titles. It is not
                // likely that we will need scaling for other positions, but
                // if it is requested, we need to adjust the vertical
                // position to the scale.
                minScale = topAligned && descOptions.minScale || 1, offset = key === 'title' ?
                    topAligned ? -3 : 0 :
                    // Floating subtitle (#6574)
                    topAligned ? titleOffset[0] + 2 : 0, uncappedScale = Math.min(alignTo.width / textPxLength, 1), scale = Math.max(minScale, uncappedScale), alignAttr = merge({
                    y: verticalAlign === 'bottom' ?
                        baseline :
                        offset + baseline
                }, descOptions), width = (descOptions.width || ((uncappedScale > minScale ?
                    // One line
                    this.chartWidth :
                    // Allow word wrap
                    alignTo.width) / scale)) + 'px';
                // Handle auto alignment
                alignAttr.align ?? (alignAttr.align = key === 'title' ?
                    // Title defaults to center for short titles,
                    // left for word-wrapped titles
                    (uncappedScale < minScale ? 'left' : 'center') :
                    // Subtitle defaults to the title.align
                    this.title?.alignValue);
                // No animation when switching alignment
                if (desc.alignValue !== alignAttr.align) {
                    desc.placed = false;
                }
                // Set the width and read the height
                const height = Math.round(desc
                    .css({ width })
                    // Skip the cache for HTML (#3481, #11666)
                    .getBBox(descOptions.useHTML).height);
                alignAttr.height = height;
                // Perform scaling and alignment
                desc
                    .align(alignAttr, false, alignTo)
                    .attr({
                    align: alignAttr.align,
                    scaleX: scale,
                    scaleY: scale,
                    'transform-origin': `${alignTo.x +
                        textPxLength *
                            scale *
                            getAlignFactor(alignAttr.align)} ${lineHeight}`
                });
                // Adjust the rendered title offset
                if (!descOptions.floating) {
                    const offset = height * (
                    // When scaling down the title, preserve the offset as
                    // long as it's only one line, but scale down the offset
                    // if the title wraps to multiple lines.
                    height < lineHeight * 1.2 ? 1 : scale);
                    if (verticalAlign === 'top') {
                        titleOffset[0] = Math.ceil(titleOffset[0] + offset);
                    }
                    else if (verticalAlign === 'bottom') {
                        titleOffset[2] = Math.ceil(titleOffset[2] + offset);
                    }
                }
            }
        }, this);
        // Handle title.margin and caption.margin
        if (titleOffset[0] &&
            (options.title?.verticalAlign || 'top') === 'top') {
            titleOffset[0] += options.title?.margin || 0;
        }
        if (titleOffset[2] &&
            options.caption?.verticalAlign === 'bottom') {
            titleOffset[2] += options.caption?.margin || 0;
        }
        const requiresDirtyBox = (!this.titleOffset ||
            this.titleOffset.join(',') !== titleOffset.join(','));
        // Used in getMargins
        this.titleOffset = titleOffset;
        fireEvent(this, 'afterLayOutTitles');
        if (!this.isDirtyBox && requiresDirtyBox) {
            this.isDirtyBox = this.isDirtyLegend = requiresDirtyBox;
            // Redraw if necessary (#2719, #2744)
            if (this.hasRendered && redraw && this.isDirtyBox) {
                this.redraw();
            }
        }
    }
    /**
     * Internal function to get the available size of the container element
     *
     * @internal
     * @function Highcharts.Chart#getContainerBox
     */
    getContainerBox() {
        // Temporarily hide support divs from a11y and others, #21888
        const nonContainers = [].map.call(this.renderTo.children, (child) => {
            if (child !== this.container) {
                const display = child.style.display;
                child.style.display = 'none';
                return [child, display];
            }
        }), box = {
            width: getStyle(this.renderTo, 'width', true) || 0,
            height: (getStyle(this.renderTo, 'height', true) || 0)
        };
        // Restore the non-containers
        nonContainers.filter(Boolean).forEach(([div, display]) => {
            div.style.display = display;
        });
        return box;
    }
    /**
     * Internal function to get the chart width and height according to options
     * and container size. Sets {@link Chart.chartWidth} and
     * {@link Chart.chartHeight}.
     *
     * @internal
     * @function Highcharts.Chart#getChartSize
     */
    getChartSize() {
        const chart = this, optionsChart = chart.options.chart, widthOption = optionsChart.width, heightOption = optionsChart.height, containerBox = chart.getContainerBox(), enableDefaultHeight = containerBox.height <= 1 ||
            ( // #21510, prevent infinite reflow
            !chart.renderTo.parentElement?.style.height &&
                chart.renderTo.style.height === '100%');
        /**
         * The current pixel width of the chart.
         *
         * @name Highcharts.Chart#chartWidth
         * @type {number}
         */
        chart.chartWidth = Math.max(// #1393
        0, widthOption || containerBox.width || 600 // #1460
        );
        /**
         * The current pixel height of the chart.
         *
         * @name Highcharts.Chart#chartHeight
         * @type {number}
         */
        chart.chartHeight = Math.max(0, relativeLength(heightOption, chart.chartWidth) ||
            (enableDefaultHeight ? 400 : containerBox.height));
        chart.containerBox = containerBox;
    }
    /**
     * If the renderTo element has no offsetWidth, most likely one or more of
     * its parents are hidden. Loop up the DOM tree to temporarily display the
     * parents, then save the original display properties, and when the true
     * size is retrieved, reset them. Used on first render and on redraws.
     *
     * @internal
     * @function Highcharts.Chart#temporaryDisplay
     *
     * @param {boolean} [revert]
     * Revert to the saved original styles.
     */
    temporaryDisplay(revert) {
        let node = this.renderTo, tempStyle;
        if (!revert) {
            while (node?.style) {
                // When rendering to a detached node, it needs to be temporarily
                // attached in order to read styling and bounding boxes (#5783,
                // #7024).
                if (!Chart_doc.body.contains(node) && !node.parentNode) {
                    node.hcOrigDetached = true;
                    Chart_doc.body.appendChild(node);
                }
                if (getStyle(node, 'display', false) === 'none' ||
                    node.hcOrigDetached) {
                    node.hcOrigStyle = {
                        display: node.style.display,
                        height: node.style.height,
                        overflow: node.style.overflow
                    };
                    tempStyle = {
                        display: 'block',
                        overflow: 'hidden'
                    };
                    if (node !== this.renderTo) {
                        tempStyle.height = 0;
                    }
                    css(node, tempStyle);
                    // If it still doesn't have an offset width after setting
                    // display to block, it probably has an !important priority
                    // #2631, 6803
                    if (!node.offsetWidth) {
                        node.style.setProperty('display', 'block', 'important');
                    }
                }
                node = node.parentNode;
                if (node === Chart_doc.body) {
                    break;
                }
            }
        }
        else {
            while (node?.style) {
                if (node.hcOrigStyle) {
                    css(node, node.hcOrigStyle);
                    delete node.hcOrigStyle;
                }
                if (node.hcOrigDetached) {
                    Chart_doc.body.removeChild(node);
                    node.hcOrigDetached = false;
                }
                node = node.parentNode;
            }
        }
    }
    /**
     * Set the {@link Chart.container|chart container's} class name, in
     * addition to `highcharts-container`.
     *
     * @function Highcharts.Chart#setClassName
     *
     * @param {string} [className]
     * The additional class name.
     */
    setClassName(className) {
        this.container.className = 'highcharts-container ' + (className || '');
    }
    /**
     * Get the containing element, determine the size and create the inner
     * container div to hold the chart.
     *
     * @internal
     * @function Highcharts.Chart#afterGetContainer
     * @emits Highcharts.Chart#event:afterGetContainer
     */
    getContainer() {
        const chart = this, options = chart.options, optionsChart = options.chart, indexAttrName = 'data-highcharts-chart', containerId = uniqueKey(), renderTo = chart.renderTo;
        let containerStyle;
        // If the container already holds a chart, destroy it. The check for
        // hasRendered is there because web pages that are saved to disk from
        // the browser, will preserve the data-highcharts-chart attribute and
        // the SVG contents, but not an interactive chart. So in this case,
        // charts[oldChartIndex] will point to the wrong chart if any (#2609).
        const oldChartIndex = pInt(attr(renderTo, indexAttrName));
        if (isNumber(oldChartIndex) &&
            Chart_charts[oldChartIndex] &&
            Chart_charts[oldChartIndex].hasRendered) {
            Chart_charts[oldChartIndex].destroy();
        }
        // Make a reference to the chart from the div
        attr(renderTo, indexAttrName, chart.index);
        // Remove previous chart
        renderTo.innerHTML = HTML_AST.emptyHTML;
        // If the container doesn't have an offsetWidth, it has or is a child of
        // a node that has display:none. We need to temporarily move it out to a
        // visible state to determine the size, else the legend and tooltips
        // won't render properly. The skipClone option is used in sparklines as
        // a micro optimization, saving about 1-2 ms each chart.
        if (!optionsChart.skipClone && !renderTo.offsetWidth) {
            chart.temporaryDisplay();
        }
        // Get the width and height
        chart.getChartSize();
        const chartHeight = chart.chartHeight;
        let chartWidth = chart.chartWidth;
        // Allow table cells and flex-boxes to shrink without the chart
        // blocking them out (#6427) but skip in styled mode so inline styles
        // don't override user CSS on renderTo
        if (!chart.styledMode) {
            css(renderTo, { overflow: 'hidden' });
        }
        // Create the inner container
        if (!chart.styledMode) {
            containerStyle = extend({
                position: 'relative',
                // Needed for context menu (avoidscrollbars) and content
                // overflow in IE
                overflow: 'hidden',
                width: chartWidth + 'px',
                height: chartHeight + 'px',
                textAlign: 'left',
                lineHeight: 'normal', // #427
                zIndex: 0, // #1072
                '-webkit-tap-highlight-color': 'rgba(0,0,0,0)',
                userSelect: 'none', // #13503
                'touch-action': 'manipulation',
                outline: 'none',
                padding: '0px'
            }, optionsChart.style || {});
        }
        /**
         * The containing HTML element of the chart. The container is
         * dynamically inserted into the element given as the `renderTo`
         * parameter in the {@link Highcharts#chart} constructor.
         *
         * @name Highcharts.Chart#container
         * @type {Highcharts.HTMLDOMElement}
         */
        const container = createElement('div', {
            id: containerId
        }, containerStyle, renderTo);
        chart.container = container;
        // Adjust width if setting height affected it (#20334)
        chart.getChartSize();
        if (chartWidth !== chart.chartWidth) {
            chartWidth = chart.chartWidth;
            if (!chart.styledMode) {
                css(container, {
                    width: pick(optionsChart.style?.width, chartWidth + 'px')
                });
            }
        }
        // Cache the cursor (#1650)
        chart._cursor = container.style.cursor;
        /**
         * The renderer instance of the chart. Each chart instance has only one
         * associated renderer.
         *
         * @name Highcharts.Chart#renderer
         * @type {Highcharts.SVGRenderer}
         */
        chart.renderer = new SVG_SVGRenderer(container, chartWidth, chartHeight, void 0, optionsChart.forExport, options.exporting?.allowHTML, chart.styledMode, options.palette, chart.index);
        // Measure after the SVG is appended. In styled mode the inner
        // container's CSS `height: 100%` otherwise yields 0 and causes a false
        // first ResizeObserver reflow
        chart.containerBox = chart.getContainerBox();
        // Set the initial animation from the options
        Chart_setAnimation(void 0, chart);
        chart.setClassName(optionsChart.className);
        if (!chart.styledMode) {
            chart.renderer.setStyle(optionsChart.style);
            this.palette = chart.renderer.palette;
        }
        else {
            // Initialize definitions
            for (const key in options.defs) { // eslint-disable-line guard-for-in
                this.renderer.definition(options.defs[key]);
            }
        }
        fireEvent(this, 'afterGetContainer');
    }
    /**
     * Calculate margins by rendering axis labels in a preliminary position.
     * Title, subtitle and legend have already been rendered at this stage, but
     * will be moved into their final positions.
     *
     * @internal
     * @function Highcharts.Chart#getMargins
     * @emits Highcharts.Chart#event:getMargins
     */
    getMargins(skipAxes) {
        const { spacing, margin, titleOffset } = this;
        this.resetMargins();
        // Adjust for title and subtitle
        if (titleOffset[0] && !defined(margin[0])) {
            this.plotTop = Math.max(this.plotTop, titleOffset[0] + spacing[0]);
        }
        if (titleOffset[2] && !defined(margin[2])) {
            this.marginBottom = Math.max(this.marginBottom, titleOffset[2] + spacing[2]);
        }
        // Adjust for legend
        if (this.legend?.display) {
            this.legend.adjustMargins(margin, spacing);
        }
        fireEvent(this, 'getMargins');
        if (!skipAxes) {
            this.getAxisMargins();
        }
    }
    /**
     * @internal
     * @function Highcharts.Chart#getAxisMargins
     */
    getAxisMargins() {
        const chart = this, 
        // [top, right, bottom, left]
        axisOffset = chart.axisOffset = [0, 0, 0, 0], colorAxis = chart.colorAxis, margin = chart.margin, getOffset = (axes) => {
            axes.forEach((axis) => {
                if (axis.visible) {
                    axis.getOffset();
                }
            });
        };
        // Pre-render axes to get labels offset width
        if (chart.hasCartesianSeries) {
            getOffset(chart.axes);
        }
        else if (colorAxis?.length) {
            getOffset(colorAxis);
        }
        // Add the axis offsets
        Chart_marginNames.forEach((marginName, side) => {
            if (!defined(margin[side])) {
                chart[marginName] += axisOffset[side];
            }
        });
        chart.setChartSize();
    }
    /**
     * Return the current options of the chart, but only those that differ from
     * default options. Items that can be either an object or an array of
     * objects, like `series`, `xAxis` and `yAxis`, are always returned as
     * array.
     *
     * @sample highcharts/members/chart-getoptions
     *
     * @function Highcharts.Chart#getOptions
     *
     * @since 11.1.0
     */
    getOptions() {
        return diffObjects(this.userOptions, Chart_defaultOptions);
    }
    /**
     * Reflows the chart to its container. By default, the Resize Observer is
     * attached to the chart's div which allows to reflows the chart
     * automatically to its container, as per the
     * [chart.reflow](https://api.highcharts.com/highcharts/chart.reflow)
     * option.
     *
     * @sample highcharts/chart/events-container/
     *         Pop up and reflow
     *
     * @function Highcharts.Chart#reflow
     *
     * @param {global.Event} [e]
     *        Event arguments. Used primarily when the function is called
     *        internally as a response to window resize.
     */
    reflow(e) {
        const chart = this, oldBox = chart.containerBox, containerBox = chart.getContainerBox();
        delete chart.pointer?.chartPosition;
        // Width and height checks for display:none. Target is doc in Opera
        // and win in Firefox, Chrome and IE9.
        if (!chart.exporting?.isPrinting &&
            !chart.isResizing &&
            oldBox &&
            // When fired by resize observer inside hidden container
            containerBox.width) {
            if (containerBox.width !== oldBox.width ||
                containerBox.height !== oldBox.height) {
                internalClearTimeout(chart.reflowTimeout);
                // When called from window.resize, e is set, else it's called
                // directly (#2224)
                chart.reflowTimeout = syncTimeout(function () {
                    // Set size, it may have been destroyed in the meantime
                    // (#1257)
                    if (chart.container) {
                        chart.setSize(void 0, void 0, false);
                        // #23712: sync containerBox with reflowed height to
                        // break infinite loop
                        const box = chart.containerBox;
                        if (box) {
                            box.height = chart.chartHeight;
                        }
                    }
                }, e ? 100 : 0);
            }
            chart.containerBox = containerBox;
        }
    }
    /**
     * Toggle the event handlers necessary for auto resizing, depending on the
     * `chart.reflow` option.
     *
     * @internal
     * @function Highcharts.Chart#setReflow
     */
    setReflow() {
        const chart = this;
        const runReflow = (e) => {
            if (chart.options?.chart.reflow && chart.hasLoaded) {
                chart.reflow(e);
            }
        };
        if (typeof ResizeObserver === 'function') {
            (new ResizeObserver(runReflow)).observe(chart.renderTo);
            // Fallback for more legacy browser versions.
        }
        else {
            const unbind = addEvent(Chart_win, 'resize', runReflow);
            addEvent(this, 'destroy', unbind);
        }
    }
    /**
     * Resize the chart to a given width and height. In order to set the width
     * only, the height argument may be skipped. To set the height only, pass
     * `undefined` for the width.
     *
     * @sample highcharts/members/chart-setsize-button/
     *         Test resizing from buttons
     * @sample highcharts/members/chart-setsize-jquery-resizable/
     *         Add a jQuery UI resizable
     * @sample stock/members/chart-setsize/
     *         Highcharts Stock with UI resizable
     *
     * @function Highcharts.Chart#setSize
     *
     * @param {number|null} [width]
     *        The new pixel width of the chart. Since v4.2.6, the argument can
     *        be `undefined` in order to preserve the current value (when
     *        setting height only), or `null` to adapt to the width of the
     *        containing element.
     *
     * @param {number|null} [height]
     *        The new pixel height of the chart. Since v4.2.6, the argument can
     *        be `undefined` in order to preserve the current value, or `null`
     *        in order to adapt to the height of the containing element.
     *
     * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
     *        Whether and how to apply animation. When `undefined`, it applies
     *        the animation that is set in the `chart.animation` option.
     *
     *
     * @emits Highcharts.Chart#event:endResize
     * @emits Highcharts.Chart#event:resize
     */
    setSize(width, height, animation) {
        const chart = this, renderer = chart.renderer;
        // Handle the isResizing counter
        chart.isResizing += 1;
        // Set the animation for the current process
        Chart_setAnimation(animation, chart);
        const globalAnimation = renderer.globalAnimation;
        chart.oldChartHeight = chart.chartHeight;
        chart.oldChartWidth = chart.chartWidth;
        if (typeof width !== 'undefined') {
            chart.options.chart.width = width;
        }
        if (typeof height !== 'undefined') {
            chart.options.chart.height = height;
        }
        chart.getChartSize();
        const { chartWidth, chartHeight, scrollablePixelsX = 0, scrollablePixelsY = 0 } = chart;
        // Avoid expensive redrawing if the computed size didn't change
        if (chart.isDirtyBox ||
            chartWidth !== chart.oldChartWidth ||
            chartHeight !== chart.oldChartHeight) {
            // Resize the container with the global animation applied if enabled
            // (#2503)
            if (!chart.styledMode) {
                (globalAnimation ? Chart_animate : css)(chart.container, {
                    width: `${chartWidth + scrollablePixelsX}px`,
                    height: `${chartHeight + scrollablePixelsY}px`
                }, globalAnimation);
            }
            chart.setChartSize(true);
            renderer.setSize(chartWidth, chartHeight, globalAnimation);
            // Handle axes
            chart.axes.forEach(function (axis) {
                axis.isDirty = true;
                axis.setScale();
            });
            chart.isDirtyLegend = true; // Force legend redraw
            chart.isDirtyBox = true; // Force redraw of plot and chart border
            chart.layOutTitles(); // #2857
            chart.getMargins();
            chart.redraw(globalAnimation);
            chart.oldChartHeight = void 0;
            fireEvent(chart, 'resize');
            // Fire endResize and set isResizing back. If animation is disabled,
            // fire without delay, but in a new thread to avoid triggering the
            // resize observer (#19027).
            setTimeout(() => {
                if (chart) {
                    fireEvent(chart, 'endResize');
                }
            }, Chart_animObject(globalAnimation).duration);
        }
        // Handle resizing counter even if we've re-rendered or not (#20548).
        chart.isResizing -= 1;
    }
    /**
     * Set the public chart properties. This is done before and after the
     * pre-render to determine margin sizes.
     *
     * @internal
     * @function Highcharts.Chart#setChartSize
     * @emits Highcharts.Chart#event:afterSetChartSize
     */
    setChartSize(skipAxes) {
        const chart = this, { chartHeight, chartWidth, clipOffset, inverted, spacing, renderer } = chart, clipRoundFunc = Math[inverted ? 'floor' : 'round'];
        let plotLeft, plotTop, plotWidth, plotHeight;
        /**
         * The current left position of the plot area in pixels.
         *
         * @name Highcharts.Chart#plotLeft
         * @type {number}
         */
        chart.plotLeft = plotLeft = Math.round(chart.plotLeft);
        /**
         * The current top position of the plot area in pixels.
         *
         * @name Highcharts.Chart#plotTop
         * @type {number}
         */
        chart.plotTop = plotTop = Math.round(chart.plotTop);
        /**
         * The current width of the plot area in pixels.
         *
         * @name Highcharts.Chart#plotWidth
         * @type {number}
         */
        chart.plotWidth = plotWidth = Math.max(0, Math.round(chartWidth - plotLeft - (chart.marginRight ?? 0)));
        /**
         * The current height of the plot area in pixels.
         *
         * @name Highcharts.Chart#plotHeight
         * @type {number}
         */
        chart.plotHeight = plotHeight = Math.max(0, Math.round(chartHeight - plotTop - (chart.marginBottom ?? 0)));
        chart.plotSizeX = inverted ? plotHeight : plotWidth;
        chart.plotSizeY = inverted ? plotWidth : plotHeight;
        // Set boxes used for alignment
        chart.spacingBox = renderer.spacingBox = {
            x: spacing[3],
            y: spacing[0],
            width: chartWidth - spacing[3] - spacing[1],
            height: chartHeight - spacing[0] - spacing[2]
        };
        chart.plotBox = renderer.plotBox = {
            x: plotLeft,
            y: plotTop,
            width: plotWidth,
            height: plotHeight
        };
        // Compute the clipping box
        if (clipOffset) {
            chart.clipBox = {
                x: clipRoundFunc(clipOffset[inverted ? 2 : 3]),
                y: clipRoundFunc(clipOffset[inverted ? 1 : 0]),
                width: clipRoundFunc(chart.plotSizeX - clipOffset[inverted ? 0 : 1] -
                    clipOffset[inverted ? 2 : 3]),
                height: clipRoundFunc(chart.plotSizeY - clipOffset[inverted ? 1 : 0] -
                    clipOffset[inverted ? 3 : 2])
            };
        }
        if (!skipAxes) {
            for (const axis of chart.axes) {
                axis.setAxisSize();
                axis.setAxisTranslation();
            }
            renderer.alignElements();
        }
        fireEvent(chart, 'afterSetChartSize', { skipAxes: skipAxes });
    }
    /**
     * Initial margins before auto size margins are applied.
     *
     * @internal
     * @function Highcharts.Chart#resetMargins
     */
    resetMargins() {
        fireEvent(this, 'resetMargins');
        const chart = this, chartOptions = chart.options.chart, plotBorderWidth = chartOptions.plotBorderWidth || 0, halfWidth = Math.round(plotBorderWidth) / 2;
        // Create margin and spacing array
        ['margin', 'spacing'].forEach((target) => {
            const value = chartOptions[target], values = isObject(value) ? value : [value, value, value, value];
            [
                'Top',
                'Right',
                'Bottom',
                'Left'
            ].forEach((sideName, side) => {
                chart[target][side] = (chartOptions[`${target}${sideName}`] ??
                    values[side]);
            });
        });
        // Set margin names like chart.plotTop, chart.plotLeft,
        // chart.marginRight, chart.marginBottom.
        Chart_marginNames.forEach((marginName, side) => {
            chart[marginName] = chart.margin[side] ?? chart.spacing[side];
        });
        chart.axisOffset = [0, 0, 0, 0]; // Top, right, bottom, left
        chart.clipOffset = [
            halfWidth,
            halfWidth,
            halfWidth,
            halfWidth
        ];
        chart.plotBorderWidth = plotBorderWidth;
    }
    /**
     * Internal function to draw or redraw the borders and backgrounds for chart
     * and plot area.
     *
     * @internal
     * @function Highcharts.Chart#drawChartBox
     * @emits Highcharts.Chart#event:afterDrawChartBox
     */
    drawChartBox() {
        const chart = this, optionsChart = chart.options.chart, { backgroundColor, plotBackgroundColor, plotBackgroundImage, plotBorderRadius = 0, shadow } = optionsChart, { chartWidth, chartHeight, clipBox, clipOffset, clipRect, plotBGImage, plotBox, plotLeft, plotTop, plotWidth, plotHeight, renderer, styledMode } = chart;
        let chartBackground = chart.chartBackground, plotBackground = chart.plotBackground, plotBorder = chart.plotBorder, chartBorderWidth, mgn, bgAttr, verb = 'animate';
        // Chart area
        if (!chartBackground) {
            chart.chartBackground = chartBackground = renderer.rect()
                .addClass('highcharts-background')
                .add();
            verb = 'attr';
        }
        if (!styledMode) {
            // Presentational
            chartBorderWidth = optionsChart.borderWidth || 0;
            mgn = chartBorderWidth + (shadow ? 8 : 0);
            bgAttr = {
                fill: backgroundColor || 'none'
            };
            if (chartBorderWidth || chartBackground['stroke-width']) { // #980
                bgAttr.stroke = optionsChart.borderColor;
                bgAttr['stroke-width'] = chartBorderWidth;
            }
            chartBackground
                .attr(bgAttr)
                .shadow(shadow);
        }
        else {
            chartBorderWidth = mgn = chartBackground.strokeWidth();
        }
        chartBackground[verb]({
            x: mgn / 2,
            y: mgn / 2,
            width: chartWidth - mgn - chartBorderWidth % 2,
            height: chartHeight - mgn - chartBorderWidth % 2,
            r: optionsChart.borderRadius
        });
        // Plot background
        verb = 'animate';
        if (!plotBackground) {
            verb = 'attr';
            chart.plotBackground = plotBackground = renderer.rect()
                .addClass('highcharts-plot-background')
                .add();
        }
        plotBackground[verb]({
            ...plotBox,
            r: plotBorderRadius
        });
        if (!styledMode) {
            // Presentational attributes for the background
            plotBackground
                .attr({
                fill: plotBackgroundColor || 'none'
            })
                .shadow(optionsChart.plotShadow);
            // Create the background image
            if (plotBackgroundImage) {
                if (!plotBGImage) {
                    chart.plotBGImage = renderer.image(plotBackgroundImage, plotLeft, plotTop, plotWidth, plotHeight).add();
                }
                else {
                    if (plotBackgroundImage !== plotBGImage.attr('href')) {
                        plotBGImage.attr('href', plotBackgroundImage);
                    }
                    plotBGImage.animate(plotBox);
                }
            }
        }
        // Plot clip
        if (!clipRect) {
            chart.clipRect = renderer.clipRect(clipBox);
        }
        else {
            clipRect.animate({
                width: clipBox.width,
                height: clipBox.height
            });
        }
        // Plot area border
        verb = 'animate';
        if (!plotBorder) {
            verb = 'attr';
            chart.plotBorder = plotBorder = renderer.rect()
                .addClass('highcharts-plot-border')
                .attr({
                zIndex: 1.5 // Above the grid, below the axes, #24521.
            })
                .add();
        }
        // Presentational
        plotBorder.attr(styledMode ? void 0 : {
            stroke: optionsChart.plotBorderColor,
            'stroke-width': optionsChart.plotBorderWidth || 0,
            fill: 'none'
        })[verb]({ r: plotBorderRadius });
        const strokeWidth = plotBorder.strokeWidth(), plotBorderBox = merge(plotBorder.crisp(plotBox, -strokeWidth));
        // If any of the clipOffset sides are larger than half the stroke width,
        // the plotBorderBox needs to be extended so that the plot border
        // includes the full stroke of axis lines. Not for styled mode because
        // the stroke-width is (accidentally) not considered in the `clipOffset`
        // calculation.
        if (clipOffset && !styledMode) {
            const extendUp = clipOffset[0] - strokeWidth / 2, extendLeft = clipOffset[3] - strokeWidth / 2;
            plotBorderBox.x -= extendLeft;
            plotBorderBox.y -= extendUp;
            plotBorderBox.width += extendLeft + clipOffset[1] - strokeWidth / 2;
            plotBorderBox.height += extendUp + clipOffset[2] - strokeWidth / 2;
        }
        plotBorder[verb](plotBorderBox);
        // Plot border clipping along for the `plotBorderRadius` feature. A half
        // stroke width must be added for the outside clip, and subtracter for
        // the inside clip.
        ['plotClipOuter', 'plotClipInner'].forEach((prop, i) => {
            const clip = chart[prop], sw = i ? -strokeWidth : strokeWidth;
            clip?.[plotBorderRadius ? verb : 'attr']({
                x: plotBorderBox.x - sw / 2,
                y: plotBorderBox.y - sw / 2,
                width: plotBorderBox.width + sw,
                height: plotBorderBox.height + sw,
                r: plotBorderRadius ? plotBorderRadius + sw / 2 : 0
            });
            // Apply clipping only when we actually have a plot border radius
            clip?.parentGroup?.attr({
                display: plotBorderRadius ? '' : 'none'
            });
        });
        // Reset
        chart.isDirtyBox = false;
        fireEvent(this, 'afterDrawChartBox');
    }
    /**
     * Detect whether a certain chart property is needed based on inspecting its
     * options and series. This mainly applies to the chart.inverted property,
     * and in extensions to the chart.angular and chart.polar properties.
     *
     * @internal
     * @function Highcharts.Chart#propFromSeries
     */
    propFromSeries() {
        const chart = this, optionsChart = chart.options.chart, seriesOptions = chart.options.series;
        let i, klass, value;
        /**
         * The flag is set to `true` if a series of the chart is inverted.
         *
         * @name Highcharts.Chart#inverted
         * @type {boolean|undefined}
         */
        ['inverted', 'angular', 'polar'].forEach(function (key) {
            // The default series type's class
            klass = Chart_seriesTypes[optionsChart.type];
            // Get the value from available chart-wide properties
            value =
                // It is set in the options:
                optionsChart[key] ||
                    // The default series class:
                    (klass && klass.prototype[key]);
            // Requires it
            // 4. Check if any the chart's series require it
            i = seriesOptions?.length;
            while (!value && i--) {
                klass = Chart_seriesTypes[seriesOptions[i].type];
                if (klass && klass.prototype[key]) {
                    value = true;
                }
            }
            // Set the chart property
            chart[key] = value;
        });
    }
    /**
     * Internal function to link two or more series together, based on the
     * `linkedTo` option. This is done from `Chart.render`, and after
     * `Chart.addSeries` and `Series.remove`.
     *
     * @internal
     * @function Highcharts.Chart#linkSeries
     * @emits Highcharts.Chart#event:afterLinkSeries
     */
    linkSeries(isUpdating) {
        const chart = this, chartSeries = chart.series;
        // Reset links
        chartSeries.forEach(function (series) {
            series.linkedSeries.length = 0;
        });
        // Apply new links
        chartSeries.forEach(function (series) {
            const { linkedTo } = series.options, linkedParent = isString(linkedTo) && (linkedTo === ':previous' ?
                chartSeries[series.index - 1] :
                chart.get(linkedTo));
            if (linkedParent &&
                // #3341 avoid mutual linking
                linkedParent.linkedParent !== series) {
                linkedParent.linkedSeries.push(series);
                series.linkedParent = linkedParent;
                series.visible = (series.options.visible ??
                    linkedParent.options.visible ??
                    series.visible); // #3879
            }
        });
        fireEvent(this, 'afterLinkSeries', { isUpdating });
    }
    /**
     * Render series for the chart.
     *
     * @internal
     * @function Highcharts.Chart#renderSeries
     */
    renderSeries() {
        this.series.forEach(function (serie) {
            serie.translate();
            serie.render();
        });
    }
    /**
     * Render all graphics for the chart. Runs internally on initialization.
     *
     * @internal
     * @function Highcharts.Chart#render
     */
    render() {
        const chart = this, axes = chart.axes, colorAxis = chart.colorAxis, renderer = chart.renderer, { axisLayoutRuns = 2, plotBorderRadius } = chart.options.chart, renderAxes = (axes) => {
            axes.forEach((axis) => {
                if (axis.visible) {
                    axis.render();
                }
            });
        };
        let expectedSpace = 0, // Correction for X axis labels
        // If the plot area size has changed significantly, calculate tick
        // positions again
        redoHorizontal = true, redoVertical, run = 0;
        // Create the clip rectangle for plot border radius
        if (plotBorderRadius) {
            chart.plotClipOuter || (chart.plotClipOuter = renderer.clipRect());
            chart.plotClipInner || (chart.plotClipInner = renderer.clipRect());
        }
        // Title
        chart.setTitle();
        // Fire an event before the margins are computed. This is where the
        // legend is assigned.
        fireEvent(chart, 'beforeMargins');
        // Get stacks
        chart.getStacks?.();
        // Get chart margins
        chart.getMargins(true);
        chart.setChartSize();
        for (const axis of axes) {
            const { options } = axis, { labels, offset } = options;
            if (chart.hasCartesianSeries && // #20948
                axis.horiz &&
                axis.visible &&
                labels.enabled &&
                axis.series.length &&
                axis.coll !== 'colorAxis' &&
                !axis.isRadial // Gauges and polar chart (#24526)
            ) {
                expectedSpace = options.tickLength;
                axis.createGroups();
                // Calculate expected space based on dummy tick
                const mockTick = new Axis_Tick(axis, 0, '', true), label = mockTick.createLabel('x', labels);
                mockTick.destroy();
                if (label &&
                    pick(labels.reserveSpace, !isNumber(options.crossing))) {
                    expectedSpace = label.getBBox().height +
                        labels.distance +
                        Math.max(isNumber(offset) ? offset : 0, 0);
                }
                if (expectedSpace) {
                    label?.destroy();
                    break;
                }
            }
        }
        // Use Math.max to prevent negative plotHeight
        chart.plotHeight = Math.max(chart.plotHeight - expectedSpace, 0);
        while ((redoHorizontal || redoVertical || axisLayoutRuns > 1) &&
            run < axisLayoutRuns // #19794
        ) {
            const tempWidth = chart.plotWidth, tempHeight = chart.plotHeight, threshold = [1.05, 1.1];
            for (const axis of axes) {
                const horizNum = +(axis.horiz || 0);
                if (run === 0) {
                    // Get margins by pre-rendering axes
                    axis.setScale();
                    // On datetime axes, consider the tick interval match. A
                    // match close to 1 means that the current normalized tick
                    // interval is an insecure match to the requested tick
                    // interval, on the brink of landing on a higher unit. In
                    // this case, we should redo the axis to get a more
                    // appropriate tick interval (#17393).
                    const tickIntervalMatch = axis.tickPositions?.info?.match;
                    if (tickIntervalMatch) {
                        threshold[horizNum] = Math.min(tickIntervalMatch, threshold[horizNum]);
                    }
                }
                else if ((horizNum && redoHorizontal) ||
                    (!horizNum && redoVertical)) {
                    // Update to reflect the new margins
                    axis.setTickInterval(true);
                }
            }
            if (run === 0) {
                chart.getAxisMargins();
            }
            else {
                // Check again for new, rotated or moved labels
                chart.getMargins();
            }
            redoHorizontal = (tempWidth / chart.plotWidth) >
                (run ? 1 : threshold[1]);
            redoVertical = (tempHeight / chart.plotHeight) >
                (run ? 1 : threshold[0]);
            run++;
        }
        // Draw the borders and backgrounds
        chart.drawChartBox();
        // Axes
        if (chart.hasCartesianSeries) {
            renderAxes(axes);
        }
        else if (colorAxis?.length) {
            renderAxes(colorAxis);
        }
        // The series
        chart.seriesGroup || (chart.seriesGroup = renderer.g('series-group')
            .attr({ zIndex: 3 })
            .shadow(chart.options.chart.seriesGroupShadow)
            .add());
        chart.renderSeries();
        // Credits
        chart.addCredits();
        // Handle responsiveness
        if (chart.setResponsive) {
            chart.setResponsive();
        }
        // Set flag
        chart.hasRendered = true;
    }
    /**
     * Set a new credits label for the chart.
     *
     * @sample highcharts/credits/credits-update/
     *         Add and update credits
     *
     * @function Highcharts.Chart#addCredits
     *
     * @param {Highcharts.CreditsOptions} [credits]
     * A configuration object for the new credits.
     */
    addCredits(credits) {
        const chart = this, creds = merge(true, this.options.credits, credits);
        if (creds.enabled && !this.credits) {
            /**
             * The chart's credits label. The label has an `update` method that
             * allows setting new options as per the
             * [credits options set](https://api.highcharts.com/highcharts/credits).
             *
             * @name Highcharts.Chart#credits
             * @type {Highcharts.SVGElement}
             */
            this.credits = this.renderer.text(creds.text + (this.mapCredits || ''), 0, 0, creds.useHTML)
                .addClass('highcharts-credits')
                .on('click', function (e) {
                // Fire the event with browser redirect as default function
                fireEvent(chart, 'creditsClick', e, () => {
                    if (creds.href) {
                        Chart_win.location.href = creds.href;
                    }
                });
            })
                .attr({
                align: creds.position.align,
                zIndex: 8
            });
            // If creds.events?.click exists, add it as an event listener
            if (creds.events?.click) {
                addEvent(chart, 'creditsClick', creds.events.click);
            }
            if (!chart.styledMode) {
                this.credits.css(creds.style);
            }
            this.credits
                .add()
                .align(creds.position);
            // Dynamically update
            this.credits.update = function (options) {
                chart.credits = chart.credits.destroy();
                chart.addCredits(options);
            };
        }
    }
    /**
     * Remove the chart and purge memory. This method is called internally
     * before adding a second chart into the same container, as well as on
     * window unload to prevent leaks.
     *
     * @sample highcharts/members/chart-destroy/
     *         Destroy the chart from a button
     * @sample stock/members/chart-destroy/
     *         Destroy with Highcharts Stock
     *
     * @function Highcharts.Chart#destroy
     *
     * @emits Highcharts.Chart#event:destroy
     */
    destroy() {
        const chart = this, axes = chart.axes, series = chart.series, container = chart.container, parentNode = container?.parentNode;
        let i;
        // Fire the chart.destroy event
        fireEvent(chart, 'destroy');
        // Delete the chart from charts lookup array
        if (chart.renderer.forExport) {
            erase(Chart_charts, chart); // #6569
        }
        else {
            Chart_charts[chart.index] = void 0;
        }
        Core_Globals.chartCount--;
        chart.renderTo.removeAttribute('data-highcharts-chart');
        // Remove events
        removeEvent(chart);
        // ==== Destroy collections:
        // Destroy axes
        i = axes.length;
        while (i--) {
            axes[i] = axes[i].destroy();
        }
        // Destroy scroller & scroller series before destroying base series
        this.scroller?.destroy?.();
        // Destroy each series
        i = series.length;
        while (i--) {
            series[i] = series[i].destroy();
        }
        // ==== Destroy chart properties:
        [
            'title', 'subtitle', 'chartBackground', 'plotBackground',
            'plotBGImage', 'plotBorder', 'seriesGroup', 'clipRect', 'credits',
            'pointer', 'rangeSelector', 'legend', 'resetZoomButton', 'tooltip',
            'renderer'
        ].forEach((name) => {
            chart[name] = chart[name]?.destroy?.();
        });
        // Remove container and all SVG, check container as it can break in IE
        // when destroyed before finished loading
        if (container) {
            container.innerHTML = HTML_AST.emptyHTML;
            removeEvent(container);
            if (parentNode) {
                discardElement(container);
            }
        }
        // Clean it all up
        objectEach(chart, function (val, key) {
            delete chart[key];
        });
    }
    /**
     * Prepare for first rendering after all data are loaded.
     *
     * @internal
     * @function Highcharts.Chart#firstRender
     * @emits Highcharts.Chart#event:beforeRender
     */
    firstRender() {
        const chart = this, options = chart.options;
        // Create the container
        chart.getContainer();
        chart.resetMargins();
        chart.setChartSize();
        // Set the common chart properties (mainly invert) from the given series
        chart.propFromSeries();
        // Get axes
        chart.createAxes();
        // Initialize the series
        const series = isArray(options.series) ? options.series : [];
        options.series = []; // Avoid mutation
        series.forEach(
        // #9680
        function (serieOptions) {
            chart.initSeries(serieOptions);
        });
        chart.linkSeries();
        // Run an event after axes and series are initialized, but before
        // render. At this stage, the series data is indexed and cached in the
        // xData and yData arrays, so we can access those before rendering. Used
        // in Highcharts Stock.
        fireEvent(chart, 'beforeRender');
        chart.render();
        chart.pointer?.getChartPosition(); // #14973
        // Fire the load event if there are no external images
        if (!chart.renderer.asyncCounter && !chart.hasLoaded) {
            chart.onload();
        }
        // If the chart was rendered outside the top container, put it back in
        // (#3679)
        chart.temporaryDisplay(true);
    }
    /**
     * Internal function that runs on chart load, async if any images are loaded
     * in the chart. Runs the callbacks and triggers the `load` and `render`
     * events.
     *
     * @internal
     * @function Highcharts.Chart#onload
     * @emits Highcharts.Chart#event:load
     * @emits Highcharts.Chart#event:render
     */
    onload() {
        // Run callbacks, first the ones registered by modules, then user's one
        this.callbacks.concat([this.callback]).forEach(function (fn) {
            // Chart destroyed in its own callback (#3600)
            if (fn && typeof this.index !== 'undefined') {
                fn.apply(this, [this]);
            }
        }, this);
        fireEvent(this, 'load');
        fireEvent(this, 'render');
        // Set up auto resize, check for not destroyed (#6068)
        if (defined(this.index)) {
            this.setReflow();
        }
        this.warnIfA11yModuleNotLoaded();
        this.warnIfCSSNotLoaded();
        // Don't run again
        this.hasLoaded = true;
    }
    /**
     * Emit console warning if the a11y module is not loaded.
     * @internal
     */
    warnIfA11yModuleNotLoaded() {
        const { options, title } = this;
        if (options && !this.accessibility) {
            // Make chart behave as an image with the title as alt text
            this.renderer.boxWrapper.attr({
                role: 'img',
                'aria-label': (title?.element.textContent || ''
                // #17753, < is not allowed in SVG attributes
                ).replace(/</g, '&lt;')
            });
            if (!(options.accessibility && options.accessibility.enabled === false)) {
                error('Highcharts warning: Consider including the ' +
                    '"accessibility.js" module to make your chart more ' +
                    'usable for people with disabilities. Set the ' +
                    '"accessibility.enabled" option to false to remove this ' +
                    'warning. See https://www.highcharts.com/docs/accessibility/accessibility-module.', false, this);
            }
        }
    }
    /**
     * Emit console warning if the highcharts.css file is not loaded.
     * @internal
     */
    warnIfCSSNotLoaded() {
        if (this.styledMode) {
            const containerStyle = Chart_win.getComputedStyle(this.container);
            if (containerStyle.zIndex !== '0') {
                error(35, false, this);
            }
        }
    }
    /**
     * Add a series to the chart after render time. Note that this method should
     * never be used when adding data synchronously at chart render time, as it
     * adds expense to the calculations and rendering. When adding data at the
     * same time as the chart is initialized, add the series as a configuration
     * option instead. With multiple axes, the `offset` is dynamically adjusted.
     *
     * @sample highcharts/members/chart-addseries/
     *         Add a series from a button
     * @sample stock/members/chart-addseries/
     *         Add a series in Highcharts Stock
     *
     * @function Highcharts.Chart#addSeries
     *
     * @param {Highcharts.SeriesOptionsType} options
     *        The config options for the series.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart after adding.
     *
     * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
     *        Whether to apply animation, and optionally animation
     *        configuration. When `undefined`, it applies the animation that is
     *        set in the `chart.animation` option.
     *
     * @return {Highcharts.Series}
     *         The newly created series object.
     *
     * @emits Highcharts.Chart#event:addSeries
     * @emits Highcharts.Chart#event:afterAddSeries
     */
    addSeries(options, redraw, animation) {
        const chart = this;
        let series;
        if (options) { // <- not necessary
            redraw = pick(redraw, true); // Defaults to true
            fireEvent(chart, 'addSeries', { options: options }, function () {
                series = chart.initSeries(options);
                chart.isDirtyLegend = true;
                chart.linkSeries();
                fireEvent(chart, 'afterAddSeries', { series: series });
                if (redraw) {
                    chart.redraw(animation);
                }
            });
        }
        return series;
    }
    /**
     * Add an axis to the chart after render time. Note that this method should
     * never be used when adding data synchronously at chart render time, as it
     * adds expense to the calculations and rendering. When adding data at the
     * same time as the chart is initialized, add the axis as a configuration
     * option instead.
     *
     * @sample highcharts/members/chart-addaxis/
     *         Add and remove axes
     *
     * @function Highcharts.Chart#addAxis
     *
     * @param {Highcharts.AxisOptions} options
     *        The axis options.
     *
     * @param {boolean} [isX=false]
     *        Whether it is an X axis or a value axis.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart after adding.
     *
     * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
     *        Whether and how to apply animation in the redraw. When
     *        `undefined`, it applies the animation that is set in the
     *        `chart.animation` option.
     *
     * @return {Highcharts.Axis}
     *         The newly generated Axis object.
     */
    addAxis(options, isX, redraw, animation) {
        return this.createAxis(isX ? 'xAxis' : 'yAxis', { axis: options, redraw: redraw, animation: animation });
    }
    /**
     * Add a color axis to the chart after render time. Note that this method
     * should never be used when adding data synchronously at chart render time,
     * as it adds expense to the calculations and rendering. When adding data at
     * the same time as the chart is initialized, add the axis as a
     * configuration option instead.
     *
     * @sample highcharts/members/chart-addaxis/
     *         Add and remove axes
     *
     * @function Highcharts.Chart#addColorAxis
     *
     * @param {Highcharts.ColorAxisOptions} options
     *        The axis options.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart after adding.
     *
     * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
     *        Whether and how to apply animation in the redraw. When
     *        `undefined`, it applies the animation that is set in the
     *        `chart.animation` option.
     *
     * @return {Highcharts.Axis}
     *         The newly generated Axis object.
     */
    addColorAxis(options, redraw, animation) {
        return this.createAxis('colorAxis', { axis: options, redraw: redraw, animation: animation });
    }
    /**
     * Factory for creating different axis types.
     *
     * @internal
     * @function Highcharts.Chart#createAxis
     *
     * @param {string} coll
     * An axis type.
     *
     * @param {...Array<*>} options
     * All arguments for the constructor.
     *
     * @return {Highcharts.Axis}
     * The newly generated Axis object.
     */
    createAxis(coll, options) {
        const axis = new Axis_Axis(this, options.axis, coll);
        if (pick(options.redraw, true)) {
            this.redraw(options.animation);
        }
        return axis;
    }
    /**
     * Dim the chart and show a loading text or symbol. Options for the loading
     * screen are defined in {@link
     * https://api.highcharts.com/highcharts/loading|the loading options}.
     *
     * @sample highcharts/members/chart-hideloading/
     *         Show and hide loading from a button
     * @sample highcharts/members/chart-showloading/
     *         Apply different text labels
     * @sample stock/members/chart-show-hide-loading/
     *         Toggle loading in Highcharts Stock
     *
     * @function Highcharts.Chart#showLoading
     *
     * @param {string} [str]
     *        An optional text to show in the loading label instead of the
     *        default one. The default text is set in
     *        [lang.loading](https://api.highcharts.com/highcharts/lang.loading).
     */
    showLoading(str) {
        const chart = this, options = chart.options, loadingOptions = options.loading, loadingStyle = loadingOptions?.style ?? {}, setLoadingSize = function () {
            if (loadingDiv) {
                css(loadingDiv, {
                    left: loadingStyle.left ?? chart.plotLeft + 'px',
                    top: loadingStyle.top ?? chart.plotTop + 'px',
                    width: loadingStyle.width ?? chart.plotWidth + 'px',
                    height: loadingStyle.height ?? chart.plotHeight + 'px'
                });
            }
        };
        let loadingDiv = chart.loadingDiv, loadingSpan = chart.loadingSpan;
        // Create the layer at the first call
        if (!loadingDiv) {
            chart.loadingDiv = loadingDiv = createElement('div', {
                className: 'highcharts-loading highcharts-loading-hidden'
            }, null, chart.container);
        }
        if (!loadingSpan) {
            chart.loadingSpan = loadingSpan = createElement('span', { className: 'highcharts-loading-inner' }, null, loadingDiv);
            addEvent(chart, 'redraw', setLoadingSize); // #1080
        }
        loadingDiv.className = 'highcharts-loading';
        // Update text
        HTML_AST.setElementHTML(loadingSpan, pick(str, options.lang.loading, ''));
        if (!chart.styledMode) {
            // Update visuals
            css(loadingDiv, extend(loadingStyle, { zIndex: 10 }));
            css(loadingSpan, loadingOptions?.labelStyle ?? {});
            // Show it
            if (!chart.loadingShown) {
                css(loadingDiv, {
                    opacity: 0,
                    display: ''
                });
                Chart_animate(loadingDiv, {
                    opacity: loadingStyle.opacity ?? 0.5
                }, {
                    duration: loadingOptions?.showDuration ?? 0
                });
            }
        }
        chart.loadingShown = true;
        setLoadingSize();
    }
    /**
     * Hide the loading layer.
     *
     * @see Highcharts.Chart#showLoading
     *
     * @sample highcharts/members/chart-hideloading/
     *         Show and hide loading from a button
     * @sample stock/members/chart-show-hide-loading/
     *         Toggle loading in Highcharts Stock
     *
     * @function Highcharts.Chart#hideLoading
     */
    hideLoading() {
        const options = this.options, loadingDiv = this.loadingDiv;
        if (loadingDiv) {
            loadingDiv.className =
                'highcharts-loading highcharts-loading-hidden';
            if (!this.styledMode) {
                Chart_animate(loadingDiv, {
                    opacity: 0
                }, {
                    duration: options.loading?.hideDuration ?? 100,
                    complete: function () {
                        css(loadingDiv, { display: 'none' });
                    }
                });
            }
        }
        this.loadingShown = false;
    }
    /**
     * A generic function to update any element of the chart. Elements can be
     * enabled and disabled, moved, re-styled, re-formatted etc.
     *
     * A special case is configuration objects that take arrays, for example
     * [xAxis](https://api.highcharts.com/highcharts/xAxis),
     * [yAxis](https://api.highcharts.com/highcharts/yAxis) or
     * [series](https://api.highcharts.com/highcharts/series). For these
     * collections, an `id` option is used to map the new option set to an
     * existing object. If an existing object of the same id is not found, the
     * corresponding item is updated. So for example, running `chart.update`
     * with a series item without an id, will cause the existing chart's series
     * with the same index in the series array to be updated. When the
     * `oneToOne` parameter is true, `chart.update` will also take care of
     * adding and removing items from the collection. Read more under the
     * parameter description below.
     *
     * Note that when changing series data, `chart.update` may mutate the passed
     * data options.
     *
     * See also the
     * [responsive option set](https://api.highcharts.com/highcharts/responsive).
     * Switching between `responsive.rules` basically runs `chart.update` under
     * the hood.
     *
     * @sample highcharts/members/chart-update/
     *         Update chart geometry
     *
     * @function Highcharts.Chart#update
     *
     * @param {Highcharts.Options} options
     *        A configuration object for the new chart options.
     *
     * @param {boolean} [redraw=true]
     *        Whether to redraw the chart.
     *
     * @param {boolean} [oneToOne=false]
     *        When `true`, the `series`, `xAxis`, `yAxis` and `annotations`
     *        collections will be updated one to one, and items will be either
     *        added or removed to match the new updated options. For example,
     *        if the chart has two series and we call `chart.update` with a
     *        configuration containing three series, one will be added. If we
     *        call `chart.update` with one series, one will be removed. Setting
     *        an empty `series` array will remove all series, but leaving out
     *        the`series` property will leave all series untouched. If the
     *        series have id's, the new series options will be matched by id,
     *        and the remaining ones removed.
     *
     * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
     *        Whether to apply animation, and optionally animation
     *        configuration. When `undefined`, it applies the animation that is
     *        set in the `chart.animation` option.
     *
     * @emits Highcharts.Chart#event:update
     * @emits Highcharts.Chart#event:afterUpdate
     */
    update(options, redraw, oneToOne, animation) {
        const chart = this, adders = {
            credits: 'addCredits',
            title: 'setTitle',
            subtitle: 'setSubtitle',
            caption: 'setCaption'
        }, isResponsiveOptions = options.isResponsiveOptions, itemsForRemoval = [];
        let updateAllAxes, updateAllSeries, runSetSize;
        fireEvent(chart, 'update', { options: options });
        // If there are responsive rules in action, undo the responsive rules
        // before we apply the updated options and replay the responsive rules
        // on top from the chart.redraw function (#9617).
        if (!isResponsiveOptions) {
            chart.setResponsive(false, true);
        }
        options = diffObjects(options, chart.options);
        chart.userOptions = merge(chart.userOptions, options);
        // If the top-level chart option is present, some special updates are
        // required
        const optionsChart = options.chart;
        if (optionsChart) {
            merge(true, chart.options.chart, optionsChart);
            // Add support for deprecated zooming options like zoomType, #17861
            this.setZoomOptions();
            // Setter function
            if ('className' in optionsChart) {
                chart.setClassName(optionsChart.className);
            }
            if ('inverted' in optionsChart ||
                'polar' in optionsChart ||
                'type' in optionsChart) {
                // Parse options.chart.inverted and options.chart.polar together
                // with the available series.
                chart.propFromSeries();
                updateAllAxes = true;
            }
            if ('alignTicks' in optionsChart) { // #6452
                updateAllAxes = true;
            }
            if ('events' in optionsChart) {
                // Chart event handlers
                Chart_registerEventOptions(this, optionsChart);
            }
            objectEach(optionsChart, function (val, key) {
                if (chart.propsRequireUpdateSeries.indexOf('chart.' + key) !==
                    -1) {
                    updateAllSeries = true;
                }
                // Only dirty box
                if (chart.propsRequireDirtyBox.indexOf(key) !== -1) {
                    chart.isDirtyBox = true;
                }
                // Chart setSize
                if (chart.propsRequireReflow.indexOf(key) !== -1) {
                    chart.isDirtyBox = true;
                    if (!isResponsiveOptions) {
                        runSetSize = true;
                    }
                }
            });
            if (!chart.styledMode && optionsChart.style) {
                chart.renderer.setStyle(chart.options.chart.style || {});
            }
        }
        // Moved up, because tooltip needs updated plotOptions (#6218)
        if (!chart.styledMode && options.colors) {
            this.options.colors = options.colors;
        }
        // Some option structures correspond one-to-one to chart objects that
        // have update methods, for example
        // options.credits => chart.credits
        // options.legend => chart.legend
        // options.title => chart.title
        // options.tooltip => chart.tooltip
        // options.subtitle => chart.subtitle
        // options.mapNavigation => chart.mapNavigation
        // options.navigator => chart.navigator
        // options.scrollbar => chart.scrollbar
        objectEach(options, function (val, key) {
            if (chart[key] &&
                typeof chart[key].update === 'function') {
                chart[key].update(val, false);
                // If a one-to-one object does not exist, look for an adder function
            }
            else if (typeof chart[adders[key]] === 'function') {
                chart[adders[key]](val);
                // Else, just merge the options. For nodes like loading, noData,
                // plotOptions
            }
            else if (key !== 'colors' &&
                chart.collectionsWithUpdate.indexOf(key) === -1) {
                merge(true, chart.options[key], options[key]);
            }
            if (key !== 'chart' &&
                chart.propsRequireUpdateSeries.indexOf(key) !== -1) {
                updateAllSeries = true;
            }
        });
        // Setters for collections. For axes and series, each item is referred
        // by an id. If the id is not found, it defaults to the corresponding
        // item in the collection, so setting one series without an id, will
        // update the first series in the chart. Setting two series without
        // an id will update the first and the second respectively (#6019)
        // chart.update and responsive.
        this.collectionsWithUpdate.forEach((coll) => {
            if (options[coll]) {
                splat(options[coll]).forEach((newOptions, i) => {
                    if (!newOptions) {
                        return;
                    }
                    const hasId = defined(newOptions.id);
                    let item;
                    // Match by id
                    if (hasId) {
                        item = chart.get(newOptions.id);
                    }
                    // No match by id found, match by index instead
                    if (!item && chart[coll]) {
                        item = chart[coll][pick(newOptions.index, i)];
                        // Check if we grabbed an item with an existing but
                        // different id (#13541). Check that the item in this
                        // position is not internal (navigator).
                        if (item && ((hasId && defined(item.options.id)) ||
                            item.options.isInternal)) {
                            item = void 0;
                        }
                    }
                    if (item && item.coll === coll) {
                        item.update(newOptions, false);
                        if (oneToOne) {
                            item.touched = true;
                        }
                    }
                    // If oneToOne and no matching item is found, add one
                    if (!item && oneToOne && chart.collectionsWithInit[coll]) {
                        chart.collectionsWithInit[coll][0].apply(chart, 
                        // [newOptions, ...extraArguments, redraw=false]
                        [
                            newOptions
                        ].concat(
                        // Not all initializers require extra args
                        chart.collectionsWithInit[coll][1] || []).concat([
                            false
                        ])).touched = true;
                    }
                });
                // Add items for removal
                if (oneToOne) {
                    chart[coll].forEach((item) => {
                        if (!item.touched && !item.options.isInternal) {
                            itemsForRemoval.push(item);
                        }
                        else {
                            delete item.touched;
                        }
                    });
                }
            }
        });
        itemsForRemoval.forEach((item) => {
            if (item.chart && item.remove) { // #9097, avoid removing twice
                item.remove(false);
            }
        });
        if (updateAllAxes) {
            chart.axes.forEach(function (axis) {
                axis.update({}, false);
            });
        }
        // Certain options require the whole series structure to be thrown away
        // and rebuilt
        if (updateAllSeries) {
            chart.series.forEach((series) => {
                // Avoid removed navigator series
                if (series.chart) {
                    series.update({}, false);
                }
            });
        }
        // Update size. Redraw is forced.
        const newWidth = optionsChart?.width;
        const newHeight = optionsChart && (isString(optionsChart.height) ?
            relativeLength(optionsChart.height, newWidth || chart.chartWidth) :
            optionsChart.height);
        if (
        // In this case, run chart.setSize with newWidth and newHeight which
        // are undefined, only for reflowing chart elements because margin
        // or spacing has been set (#8190)
        runSetSize ||
            // In this case, the size is actually set
            (isNumber(newWidth) && newWidth !== chart.chartWidth) ||
            (isNumber(newHeight) && newHeight !== chart.chartHeight)) {
            chart.setSize(newWidth, newHeight, animation);
        }
        else if (pick(redraw, true)) {
            chart.redraw(animation);
        }
        fireEvent(chart, 'afterUpdate', {
            options: options,
            redraw: redraw,
            animation: animation
        });
    }
    /**
     * Shortcut to set the subtitle options. This can also be done from {@link
     * Chart#update} or {@link Chart#setTitle}.
     *
     * @function Highcharts.Chart#setSubtitle
     *
     * @param {Highcharts.SubtitleOptions} options
     *        New subtitle options. The subtitle text itself is set by the
     *        `options.text` property.
     */
    setSubtitle(options, redraw) {
        this.applyDescription('subtitle', options);
        this.layOutTitles(redraw);
    }
    /**
     * Set the caption options. This can also be done from {@link
     * Chart#update}.
     *
     * @function Highcharts.Chart#setCaption
     *
     * @param {Highcharts.CaptionOptions} options
     *        New caption options. The caption text itself is set by the
     *        `options.text` property.
     */
    setCaption(options, redraw) {
        this.applyDescription('caption', options);
        this.layOutTitles(redraw);
    }
    /**
     * Display the zoom button, so users can reset zoom to the default view
     * settings.
     *
     * @function Highcharts.Chart#showResetZoom
     *
     * @emits Highcharts.Chart#event:afterShowResetZoom
     * @emits Highcharts.Chart#event:beforeShowResetZoom
     */
    showResetZoom() {
        const chart = this, lang = chart.options.lang, btnOptions = chart.zooming.resetButton, theme = btnOptions.theme, alignTo = (btnOptions.relativeTo === 'chart' ||
            btnOptions.relativeTo === 'spacingBox' ?
            null :
            'plotBox');
        /** @internal */
        function zoomOut() {
            chart.zoomOut();
        }
        fireEvent(this, 'beforeShowResetZoom', null, function () {
            chart.resetZoomButton = chart.renderer
                .button(lang.resetZoom, null, null, zoomOut, theme)
                .attr({
                align: btnOptions.position.align,
                title: lang.resetZoomTitle
            })
                .addClass('highcharts-reset-zoom')
                .add()
                .align(btnOptions.position, false, alignTo);
        });
        fireEvent(this, 'afterShowResetZoom');
    }
    /**
     * Zoom the chart out after a user has zoomed in. See also
     * [Axis.setExtremes](/class-reference/Highcharts.Axis#setExtremes).
     *
     * @function Highcharts.Chart#zoomOut
     *
     * @emits Highcharts.Chart#event:selection
     */
    zoomOut() {
        fireEvent(this, 'selection', { resetSelection: true }, () => this.transform({ reset: true, trigger: 'zoom' }));
    }
    /**
     * Pan the chart by dragging the mouse across the pane. This function is
     * called on mouse move, and the distance to pan is computed from chartX
     * compared to the first chartX position in the dragging operation.
     *
     * @internal
     * @function Highcharts.Chart#pan
     * @param {Highcharts.PointerEventObject} event
     * @param {string} panning
     */
    pan(event, panning) {
        const chart = this, panningOptions = (typeof panning === 'object' ?
            panning :
            {
                enabled: panning,
                type: 'x'
            }), type = panningOptions.type, axes = type && chart[{
            x: 'xAxis',
            xy: 'axes',
            y: 'yAxis'
        }[type]]
            .filter((axis) => axis.options.panningEnabled && !axis.options.isInternal), chartOptions = chart.options.chart;
        if (chartOptions?.panning) {
            chartOptions.panning = panningOptions;
        }
        fireEvent(this, 'pan', { originalEvent: event }, () => {
            chart.transform({
                axes,
                event,
                to: {
                    x: event.chartX - (chart.mouseDownX || 0),
                    y: event.chartY - (chart.mouseDownY || 0)
                },
                trigger: 'pan'
            });
            css(chart.container, { cursor: 'move' });
        });
    }
    /**
     * Pan and scale the chart. Used internally by mouse-pan, touch-pan,
     * touch-zoom, and mousewheel zoom.
     *
     * The main positioning logic is created around two imaginary boxes. What is
     * currently within the `from` rectangle, should be transformed to fill up
     * the `to` rectangle.
     * - In a mouse zoom, the `from` rectangle is the selection, while the `to`
     *   rectangle is the full plot area.
     * - In a touch zoom, the `from` rectangle is made up of the last two-finger
     *   touch, while the `to`` rectangle is the current touch.
     * - In a mousewheel zoom, the `to` rectangle is a 10x10 px square,
     *   while the `to` rectangle reflects the scale around that.
     *
     * @internal
     * @function Highcharts.Chart#transform
     */
    transform(params) {
        const { axes = this.axes, event, from = {}, reset, selection, to = {}, trigger, allowResetButton = true } = params, { inverted, time } = this;
        // Remove active points for shared tooltip
        this.hoverPoints?.forEach((point) => point.setState());
        fireEvent(this, 'transform', params);
        let hasZoomed = params.hasZoomed || false, displayButton, isAnyAxisPanning;
        for (const axis of axes) {
            const { horiz, len, minPointOffset = 0, options, reversed } = axis, wh = horiz ? 'width' : 'height', xy = horiz ? 'x' : 'y', toLength = pick(to[wh], axis.len), fromLength = pick(from[wh], axis.len), 
            // If fingers pinched very close on this axis, treat as pan
            scale = Math.abs(toLength) < 10 ?
                1 :
                toLength / fromLength, fromCenter = (from[xy] || 0) + fromLength / 2 - axis.pos, toCenter = (to[xy] ?? axis.pos) +
                toLength / 2 - axis.pos, move = fromCenter - toCenter / scale, pointRangeDirection = (reversed && !inverted) ||
                (!reversed && inverted) ?
                -1 :
                1, minPx = move;
            // Zooming in multiple panes, zoom only in the pane that receives
            // the input
            if (!reset && (fromCenter < 0 || fromCenter > axis.len)) {
                continue;
            }
            // Adjust offset to ensure selection zoom triggers correctly
            // (#22945)
            // Set offset to 0 for ordinal axis only when zooming out, (#24545).
            const offset = (axis.chart.polar || (axis.isOrdinal && scale <= 1)) ?
                0 :
                (minPointOffset * pointRangeDirection || 0), eventMin = axis.toValue(minPx, true), eventMax = axis.toValue(minPx + len / scale, true);
            let newMin = eventMin + offset, newMax = eventMax - offset, allExtremes = axis.allExtremes;
            if (selection) {
                selection[axis.coll].push({
                    axis,
                    min: Math.min(eventMin, eventMax),
                    max: Math.max(eventMin, eventMax)
                });
            }
            if (newMin > newMax) {
                [newMin, newMax] = [newMax, newMin];
            }
            // General calculations of the full data extremes. It is calculated
            // on the first call to transform, then reused for subsequent
            // touch/pan calls. (#11315).
            if (scale === 1 &&
                !reset &&
                axis.coll === 'yAxis' &&
                !allExtremes) {
                for (const series of axis.series) {
                    const seriesExtremes = series.getExtremes(series.getProcessedData(true).modified
                        .getColumn(series.pointValKey || 'y') || [], true);
                    allExtremes ?? (allExtremes = {
                        dataMin: Number.MAX_VALUE,
                        dataMax: -Number.MAX_VALUE
                    });
                    if (isNumber(seriesExtremes.dataMin) &&
                        isNumber(seriesExtremes.dataMax)) {
                        allExtremes.dataMin = Math.min(seriesExtremes.dataMin, allExtremes.dataMin);
                        allExtremes.dataMax = Math.max(seriesExtremes.dataMax, allExtremes.dataMax);
                    }
                }
                axis.allExtremes = allExtremes;
            }
            const { dataMin, dataMax, min, max } = extend(axis.getExtremes(), allExtremes || {}), optionsMin = time.parse(options.min), optionsMax = time.parse(options.max), 
            // For boosted chart where data extremes are skipped
            safeDataMin = dataMin ?? optionsMin, safeDataMax = dataMax ?? optionsMax, range = newMax - newMin, padRange = axis.categories ? 0 : Math.min(range, safeDataMax - safeDataMin), paddedMin = safeDataMin - padRange * (defined(optionsMin) ? 0 : options.minPadding), paddedMax = safeDataMax + padRange * (defined(optionsMax) ? 0 : options.maxPadding), 
            // We're allowed to zoom outside the data extremes if we're
            // dealing with a bubble chart, if we're panning, or if we're
            // pinching or mousewheeling in.
            allowZoomOutside = axis.allowZoomOutside ||
                scale === 1 ||
                (trigger !== 'zoom' && scale > 1), 
            // Calculate the floor and the ceiling
            floor = Math.min(optionsMin ?? paddedMin, paddedMin, allowZoomOutside ? min : paddedMin), ceiling = Math.max(optionsMax ?? paddedMax, paddedMax, allowZoomOutside ? max : paddedMax);
            // It is not necessary to calculate extremes on ordinal axis,
            // because they are already calculated, so we don't want to override
            // them.
            if (!axis.isOrdinal || scale !== 1 || reset) {
                // If the new range spills over, either to the min or max,
                // adjust it.
                if (newMin < floor) {
                    newMin = floor;
                    if (scale >= 1) {
                        newMax = newMin + range;
                    }
                }
                if (newMax > ceiling) {
                    newMax = ceiling;
                    if (scale >= 1) {
                        newMin = newMax - range;
                    }
                }
                // Set new extremes if they are actually new
                if (reset || (axis.series.length &&
                    (newMin !== min || newMax !== max) &&
                    newMin >= floor &&
                    newMax <= ceiling)) {
                    if (selection) {
                        selection[axis.coll].push({
                            axis,
                            min: newMin,
                            max: newMax
                        });
                    }
                    else {
                        // Temporarily flag the axis as `isPanning` in order to
                        // disallow certain axis padding options that would make
                        // panning/zooming hard. Reset and redraw after the
                        // operation has finished.
                        axis.isPanning = trigger !== 'zoom';
                        if (axis.isPanning && trigger !== 'mousewheel') {
                            isAnyAxisPanning = true; // #21319
                        }
                        axis.setExtremes(reset ? void 0 : newMin, reset ? void 0 : newMax, false, false, { move, trigger, scale });
                        if (!reset &&
                            (newMin > floor || newMax < ceiling)) {
                            displayButton = allowResetButton;
                        }
                    }
                    hasZoomed = true;
                }
                // Show the resetZoom button for non-cartesian series.
                if (!this.hasCartesianSeries &&
                    !reset) {
                    displayButton = allowResetButton;
                }
                if (event) {
                    this[horiz ? 'mouseDownX' : 'mouseDownY'] =
                        event[horiz ? 'chartX' : 'chartY'];
                }
            }
        }
        if (hasZoomed) {
            if (selection) {
                fireEvent(this, 'selection', selection, 
                // Run transform again, this time without the selection data
                // so that the transform is applied.
                () => {
                    delete params.selection;
                    params.trigger = 'zoom';
                    this.transform(params);
                });
            }
            else {
                // Show or hide the Reset zoom button, but not while panning
                if (displayButton &&
                    !isAnyAxisPanning &&
                    !this.resetZoomButton) {
                    this.showResetZoom();
                }
                else if (!displayButton && this.resetZoomButton) {
                    this.resetZoomButton = this.resetZoomButton.destroy();
                }
                this.redraw(trigger === 'zoom' &&
                    (this.options.chart.animation ?? this.pointCount < 100));
            }
        }
        return hasZoomed;
    }
}
extend(Chart.prototype, {
    // Hook for adding callbacks in modules
    callbacks: [],
    /**
     * These collections (arrays) implement `Chart.addSomething` method used in
     * chart.update() to create new object in the collection. Equivalent for
     * deleting is resolved by simple `Something.remove()`.
     *
     * Note: We need to define these references after initializers are bound to
     * chart's prototype.
     *
     * @internal
     */
    collectionsWithInit: {
        // CollectionName: [ initializingMethod, [extraArguments] ]
        xAxis: [Chart.prototype.addAxis, [true]],
        yAxis: [Chart.prototype.addAxis, [false]],
        series: [Chart.prototype.addSeries]
    },
    /**
     * These collections (arrays) implement update() methods with support for
     * one-to-one option.
     * @internal
     */
    collectionsWithUpdate: [
        'xAxis',
        'yAxis',
        'series'
    ],
    /**
     * These properties cause isDirtyBox to be set to true when updating. Can be
     * extended from plugins.
     * @internal
     */
    propsRequireDirtyBox: [
        'backgroundColor',
        'borderColor',
        'borderWidth',
        'borderRadius',
        'plotBackgroundColor',
        'plotBackgroundImage',
        'plotBorderColor',
        'plotBorderRadius',
        'plotBorderWidth',
        'plotShadow',
        'shadow'
    ],
    /**
     * These properties require a full reflow of chart elements, best
     * implemented through running `Chart.setSize` internally (#8190).
     * @internal
     */
    propsRequireReflow: [
        'margin',
        'marginTop',
        'marginRight',
        'marginBottom',
        'marginLeft',
        'spacing',
        'spacingTop',
        'spacingRight',
        'spacingBottom',
        'spacingLeft'
    ],
    /**
     * These properties cause all series to be updated when updating. Can be
     * extended from plugins.
     * @internal
     */
    propsRequireUpdateSeries: [
        'chart.inverted',
        'chart.polar',
        'chart.ignoreHiddenSeries',
        'chart.type',
        'colors',
        'plotOptions',
        'time',
        'tooltip'
    ]
});
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Chart_Chart = (Chart);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * Callback for chart constructors.
 *
 * @callback Highcharts.ChartCallbackFunction
 *
 * @param {Highcharts.Chart} chart
 *        Created chart.
 */
/**
 * Format a number and return a string based on input settings.
 *
 * @callback Highcharts.NumberFormatterCallbackFunction
 *
 * @param {number} number
 *        The input number to format.
 *
 * @param {number} decimals
 *        The amount of decimals. A value of -1 preserves the amount in the
 *        input number.
 *
 * @param {string} [decimalPoint]
 *        The decimal point, defaults to the one given in the lang options, or
 *        a dot.
 *
 * @param {string} [thousandsSep]
 *        The thousands separator, defaults to the one given in the lang
 *        options, or a space character.
 *
 * @param {Highcharts.Chart} [ctx]
 *        Since v12.6.0, the chart context passed as an extra argument for
 *        arrow functions.
 *
 * @return {string} The formatted number.
 */
/**
 * The chart title. The title has an `update` method that allows modifying the
 * options directly or indirectly via `chart.update`.
 *
 * @interface Highcharts.TitleObject
 * @extends Highcharts.SVGElement
 */ /**
* Modify options for the title.
*
* @function Highcharts.TitleObject#update
*
* @param {Highcharts.TitleOptions} titleOptions
*        Options to modify.
*
* @param {boolean} [redraw=true]
*        Whether to redraw the chart after the title is altered. If doing more
*        operations on the chart, it is a good idea to set redraw to false and
*        call {@link Chart#redraw} after.
*/
/**
 * The chart subtitle. The subtitle has an `update` method that
 * allows modifying the options directly or indirectly via
 * `chart.update`.
 *
 * @interface Highcharts.SubtitleObject
 * @extends Highcharts.SVGElement
 */ /**
* Modify options for the subtitle.
*
* @function Highcharts.SubtitleObject#update
*
* @param {Highcharts.SubtitleOptions} subtitleOptions
*        Options to modify.
*
* @param {boolean} [redraw=true]
*        Whether to redraw the chart after the subtitle is altered. If doing
*        more operations on the chart, it is a good idea to set redraw to false
*        and call {@link Chart#redraw} after.
*/
/**
 * The chart caption. The caption has an `update` method that
 * allows modifying the options directly or indirectly via
 * `chart.update`.
 *
 * @interface Highcharts.CaptionObject
 * @extends Highcharts.SVGElement
 */ /**
* Modify options for the caption.
*
* @function Highcharts.CaptionObject#update
*
* @param {Highcharts.CaptionOptions} captionOptions
*        Options to modify.
*
* @param {boolean} [redraw=true]
*        Whether to redraw the chart after the caption is altered. If doing
*        more operations on the chart, it is a good idea to set redraw to false
*        and call {@link Chart#redraw} after.
*/
/**
 * @interface Highcharts.ChartIsInsideOptionsObject
 */ /**
* @name Highcharts.ChartIsInsideOptionsObject#axis
* @type {Highcharts.Axis|undefined}
*/ /**
* @name Highcharts.ChartIsInsideOptionsObject#ignoreX
* @type {boolean|undefined}
*/ /**
* @name Highcharts.ChartIsInsideOptionsObject#ignoreY
* @type {boolean|undefined}
*/ /**
* @name Highcharts.ChartIsInsideOptionsObject#inverted
* @type {boolean|undefined}
*/ /**
* @name Highcharts.ChartIsInsideOptionsObject#paneCoordinates
* @type {boolean|undefined}
*/ /**
* @name Highcharts.ChartIsInsideOptionsObject#series
* @type {Highcharts.Series|undefined}
*/ /**
* @name Highcharts.ChartIsInsideOptionsObject#visiblePlotOnly
* @type {boolean|undefined}
*/
''; // Keeps doclets above in JS file

;// ./code/es-modules/Extensions/ScrollablePlotArea.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 *  Highcharts feature to make the Y axis stay fixed when scrolling the chart
 *  horizontally on mobile devices. Supports left and right side axes.
 */


const { stop: ScrollablePlotArea_stop } = AnimationUtilities;

const { composed: ScrollablePlotArea_composed } = Core_Globals;


/* *
 *
 *  Functions
 *
 * */
/** @internal */
function onChartRender() {
    if ((this.scrollablePixelsX || this.scrollablePixelsY) &&
        !this.scrollablePlotArea) {
        this.scrollablePlotArea = new ScrollablePlotArea(this);
    }
    this.scrollablePlotArea?.applyFixed();
}
/** @internal */
function markDirty() {
    if (this.chart.scrollablePlotArea) {
        this.chart.scrollablePlotArea.isDirty = true;
    }
}
/** @internal */
class ScrollablePlotArea {
    static compose(AxisClass, ChartClass, SeriesClass) {
        if (pushUnique(ScrollablePlotArea_composed, 'ScrollablePlotArea')) {
            addEvent(AxisClass, 'afterInit', markDirty);
            addEvent(ChartClass, 'afterSetChartSize', (e) => this.afterSetSize(e.target, e));
            addEvent(ChartClass, 'render', onChartRender);
            addEvent(SeriesClass, 'show', markDirty);
        }
    }
    /** @internal */
    static afterSetSize(chart, e) {
        const { minWidth, minHeight } = chart.options.chart.scrollablePlotArea || {}, { clipBox, plotBox, inverted, renderer } = chart;
        let scrollablePixelsX, scrollablePixelsY, recalculateHoriz;
        // Skip for exporting
        if (renderer.forExport) {
            return;
        }
        // The amount of pixels to scroll, the difference between chart width
        // and scrollable width
        if (minWidth) {
            chart.scrollablePixelsX = scrollablePixelsX = Math.max(0, minWidth - chart.chartWidth);
            if (scrollablePixelsX) {
                chart.scrollablePlotBox = merge(chart.plotBox);
                plotBox.width = chart.plotWidth += scrollablePixelsX;
                clipBox[inverted ? 'height' : 'width'] += scrollablePixelsX;
                recalculateHoriz = true;
            }
            // Currently we can only do either X or Y
        }
        else if (minHeight) {
            chart.scrollablePixelsY = scrollablePixelsY = Math.max(0, minHeight - chart.chartHeight);
            if (defined(scrollablePixelsY)) {
                chart.scrollablePlotBox = merge(chart.plotBox);
                plotBox.height = chart.plotHeight += scrollablePixelsY;
                clipBox[inverted ? 'width' : 'height'] += scrollablePixelsY;
                recalculateHoriz = false;
            }
        }
        if (defined(recalculateHoriz)) {
            if (!e.skipAxes) {
                for (const axis of chart.axes) {
                    // Apply the corrected plot size to the axes of the other
                    // orientation than the scrolling direction
                    if (axis.horiz === recalculateHoriz ||
                        // Or parallel axes
                        (chart.hasParallelCoordinates && axis.coll === 'yAxis')) {
                        axis.setAxisSize();
                        axis.setAxisTranslation();
                    }
                }
            }
        }
        else {
            // Clear (potential) old box when a new one was not set
            delete chart.scrollablePlotBox;
        }
    }
    /* *
     *
     *  Constructors
     *
     * */
    constructor(chart) {
        const chartOptions = chart.options.chart, scrollableOptions = chartOptions.scrollablePlotArea || {}, moveFixedElements = this.moveFixedElements.bind(this), styles = {
            WebkitOverflowScrolling: 'touch',
            overflowX: 'hidden',
            overflowY: 'hidden'
        };
        if (chart.scrollablePixelsX) {
            styles.overflowX = 'auto';
        }
        if (chart.scrollablePixelsY) {
            styles.overflowY = 'auto';
        }
        this.chart = chart;
        // Insert a container with relative position that scrolling and fixed
        // container renders to (#10555)
        const parentDiv = this.parentDiv = createElement('div', {
            className: 'highcharts-scrolling-parent'
        }, {
            position: 'relative'
        }, chart.renderTo), 
        // Add the necessary divs to provide scrolling
        scrollingContainer = this.scrollingContainer = createElement('div', {
            'className': 'highcharts-scrolling'
        }, styles, parentDiv), innerContainer = this.innerContainer = createElement('div', {
            'className': 'highcharts-inner-container'
        }, void 0, scrollingContainer), fixedDiv = this.fixedDiv = createElement('div', {
            className: 'highcharts-fixed'
        }, {
            position: 'absolute',
            overflow: 'hidden',
            pointerEvents: 'none',
            zIndex: (chartOptions.style?.zIndex || 0) + 2,
            top: 0
        }, void 0, true), fixedRenderer = this.fixedRenderer = new SVG_SVGRenderer(fixedDiv, chart.chartWidth, chart.chartHeight, chartOptions.style);
        // Mask
        this.mask = fixedRenderer
            .path()
            .attr({
            fill: chartOptions.backgroundColor || '#fff',
            'fill-opacity': scrollableOptions.opacity ?? 0.85,
            zIndex: -1
        })
            .addClass('highcharts-scrollable-mask')
            .add();
        scrollingContainer.parentNode.insertBefore(fixedDiv, scrollingContainer);
        css(chart.renderTo, { overflow: 'visible' });
        addEvent(chart, 'afterShowResetZoom', moveFixedElements);
        addEvent(chart, 'afterApplyDrilldown', moveFixedElements);
        addEvent(chart, 'afterLayOutTitles', moveFixedElements);
        // On scroll, reset the chart position because it applies to the
        // scrolled container
        let lastHoverPoint;
        addEvent(scrollingContainer, 'scroll', () => {
            const { pointer, hoverPoint } = chart;
            if (pointer) {
                delete pointer.chartPosition;
                if (hoverPoint) {
                    lastHoverPoint = hoverPoint;
                }
                pointer.runPointActions(void 0, lastHoverPoint, true);
            }
        });
        // Now move the container inside
        innerContainer.appendChild(chart.container);
    }
    /* *
     *
     *  Functions
     *
     * */
    applyFixed() {
        const { chart, fixedRenderer, isDirty, scrollingContainer } = this, { axisOffset, chartWidth, chartHeight, container, plotHeight, plotLeft, plotTop, plotWidth, scrollablePixelsX = 0, scrollablePixelsY = 0 } = chart, chartOptions = chart.options.chart, scrollableOptions = chartOptions.scrollablePlotArea || {}, { scrollPositionX = 0, scrollPositionY = 0 } = scrollableOptions, scrollableWidth = chartWidth + scrollablePixelsX, scrollableHeight = chartHeight + scrollablePixelsY;
        // Set the size of the fixed renderer to the visible width
        fixedRenderer.setSize(chartWidth, chartHeight);
        if (isDirty ?? true) {
            this.isDirty = false;
            this.moveFixedElements();
        }
        // Increase the size of the scrollable renderer and background
        ScrollablePlotArea_stop(chart.container);
        css(container, {
            width: `${scrollableWidth}px`,
            height: `${scrollableHeight}px`
        });
        chart.renderer.boxWrapper.attr({
            width: scrollableWidth,
            height: scrollableHeight,
            viewBox: [0, 0, scrollableWidth, scrollableHeight].join(' ')
        });
        chart.chartBackground?.attr({
            width: scrollableWidth,
            height: scrollableHeight
        });
        css(scrollingContainer, {
            width: `${chartWidth}px`,
            height: `${chartHeight}px`
        });
        // Set scroll position the first time (this.isDirty was undefined at
        // the top of this function)
        if (!defined(isDirty)) {
            scrollingContainer.scrollLeft = scrollablePixelsX * scrollPositionX;
            scrollingContainer.scrollTop = scrollablePixelsY * scrollPositionY;
        }
        // Mask behind the left and right side
        const maskTop = plotTop - axisOffset[0] - 1, maskLeft = plotLeft - axisOffset[3] - 1, maskBottom = plotTop + plotHeight + axisOffset[2] + 1, maskRight = plotLeft + plotWidth + axisOffset[1] + 1, maskPlotRight = plotLeft + plotWidth - scrollablePixelsX, maskPlotBottom = plotTop + plotHeight - scrollablePixelsY;
        let d = [['M', 0, 0]];
        if (scrollablePixelsX) {
            d = [
                // Left side
                ['M', 0, maskTop],
                ['L', plotLeft - 1, maskTop],
                ['L', plotLeft - 1, maskBottom],
                ['L', 0, maskBottom],
                ['Z'],
                // Right side
                ['M', maskPlotRight, maskTop],
                ['L', chartWidth, maskTop],
                ['L', chartWidth, maskBottom],
                ['L', maskPlotRight, maskBottom],
                ['Z']
            ];
        }
        else if (scrollablePixelsY) {
            d = [
                // Top side
                ['M', maskLeft, 0],
                ['L', maskLeft, plotTop - 1],
                ['L', maskRight, plotTop - 1],
                ['L', maskRight, 0],
                ['Z'],
                // Bottom side
                ['M', maskLeft, maskPlotBottom],
                ['L', maskLeft, chartHeight],
                ['L', maskRight, chartHeight],
                ['L', maskRight, maskPlotBottom],
                ['Z']
            ];
        }
        if (chart.redrawTrigger !== 'adjustHeight') {
            this.mask.attr({ d });
        }
    }
    /**
     * These elements are moved over to the fixed renderer and stay fixed when
     * the user scrolls the chart.
     *
     * @internal
     */
    moveFixedElements() {
        const { container, inverted, scrollablePixelsX, scrollablePixelsY } = this.chart, fixedRenderer = this.fixedRenderer, fixedSelectors = ScrollablePlotArea.fixedSelectors.slice();
        let axisClass;
        if (scrollablePixelsX && !inverted) {
            axisClass = '.highcharts-yaxis';
        }
        else if (scrollablePixelsX && inverted) {
            axisClass = '.highcharts-xaxis';
        }
        else if (scrollablePixelsY && !inverted) {
            axisClass = '.highcharts-xaxis';
        }
        else if (scrollablePixelsY && inverted) {
            axisClass = '.highcharts-yaxis';
        }
        if (axisClass && !(this.chart.hasParallelCoordinates &&
            axisClass === '.highcharts-yaxis')) {
            // Add if not added yet
            for (const className of [
                `${axisClass}:not(.highcharts-radial-axis)`,
                `${axisClass}-labels:not(.highcharts-radial-axis-labels)`
            ]) {
                pushUnique(fixedSelectors, className);
            }
        }
        else {
            // Clear all axis related selectors
            for (const classBase of [
                '.highcharts-xaxis',
                '.highcharts-yaxis'
            ]) {
                for (const className of [
                    `${classBase}:not(.highcharts-radial-axis)`,
                    `${classBase}-labels:not(.highcharts-radial-axis-labels)`
                ]) {
                    erase(fixedSelectors, className);
                }
            }
        }
        for (const className of fixedSelectors) {
            [].forEach.call(container.querySelectorAll(className), (elem) => {
                (elem.namespaceURI === fixedRenderer.SVG_NS ?
                    fixedRenderer.box :
                    fixedRenderer.box.parentNode).appendChild(elem);
                elem.style.pointerEvents = 'auto';
            });
        }
    }
}
/* *
 *
 *  Static Properties
 *
 * */
ScrollablePlotArea.fixedSelectors = [
    '.highcharts-breadcrumbs-group',
    '.highcharts-contextbutton',
    '.highcharts-caption',
    '.highcharts-credits',
    '.highcharts-drillup-button',
    '.highcharts-legend',
    '.highcharts-legend-checkbox',
    '.highcharts-navigator-series',
    '.highcharts-navigator-xaxis',
    '.highcharts-navigator-yaxis',
    '.highcharts-navigator',
    '.highcharts-range-selector-group',
    '.highcharts-reset-zoom',
    '.highcharts-scrollbar',
    '.highcharts-subtitle',
    '.highcharts-title'
];
/* *
 *
 *  API Declarations
 *
 * */
/**
 * Options for a scrollable plot area. This feature provides a minimum size for
 * the plot area of the chart. If the size gets smaller than this, typically
 * on mobile devices, a native browser scrollbar is presented. This scrollbar
 * provides smooth scrolling for the contents of the plot area, whereas the
 * title, legend and unaffected axes are fixed.
 *
 * Since v7.1.2, a scrollable plot area can be defined for either horizontal or
 * vertical scrolling, depending on whether the `minWidth` or `minHeight`
 * option is set.
 *
 * @sample highcharts/chart/scrollable-plotarea
 *         Scrollable plot area
 * @sample highcharts/chart/scrollable-plotarea-vertical
 *         Vertically scrollable plot area
 * @sample {gantt} gantt/chart/scrollable-plotarea-vertical
 *         Gantt chart with vertically scrollable plot area
 *
 * @since     6.1.0
 * @product   highcharts gantt
 * @apioption chart.scrollablePlotArea
 */
/**
 * The minimum height for the plot area. If it gets smaller than this, the plot
 * area will become scrollable.
 *
 * @type      {number}
 * @since     7.1.2
 * @apioption chart.scrollablePlotArea.minHeight
 */
/**
 * The minimum width for the plot area. If it gets smaller than this, the plot
 * area will become scrollable.
 *
 * @type      {number}
 * @since     6.1.0
 * @apioption chart.scrollablePlotArea.minWidth
 */
/**
 * The opacity of mask applied on one of the sides of the plot
 * area.
 *
 * @sample {highcharts} highcharts/chart/scrollable-plotarea-opacity
 *         Disabled opacity for the mask
 *
 * @type        {number}
 * @default     0.85
 * @since       7.1.1
 * @apioption   chart.scrollablePlotArea.opacity
 */
/**
 * The initial scrolling position of the scrollable plot area. Ranges from 0 to
 * 1, where 0 aligns the plot area to the left and 1 aligns it to the right.
 * Typically we would use 1 if the chart has right aligned Y axes.
 *
 * @type      {number}
 * @since     6.1.0
 * @apioption chart.scrollablePlotArea.scrollPositionX
 */
/**
 * The initial scrolling position of the scrollable plot area. Ranges from 0 to
 * 1, where 0 aligns the plot area to the top and 1 aligns it to the bottom.
 *
 * @type      {number}
 * @since     7.1.2
 * @apioption chart.scrollablePlotArea.scrollPositionY
 */
(''); // Keep doclets above in transpiled file

;// ./code/es-modules/Core/Axis/Stacking/StackItem.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { format: StackItem_format } = Core_Templating;

const { series: StackItem_Series } = Series_SeriesRegistry;

/* *
 *
 *  Class
 *
 * */
/**
 * The class for stacks. Each stack, on a specific X value and either negative
 * or positive, has its own stack item.
 */
class StackItem {
    /* *
     *
     *  Constructor
     *
     * */
    /** @internal */
    constructor(axis, options, negativeValue, x, stackOption) {
        const inverted = axis.chart.inverted, reversed = axis.reversed;
        this.axis = axis;
        // The stack goes to the left either if the stack has negative value
        // or when axis is reversed. XOR operator.
        const isNegative = (this.isNegative = !!negativeValue !== !!reversed);
        // Save the options to be able to style the label
        this.options = options = options || {};
        // Save the x value to be able to position the label later
        this.x = x;
        // Initialize total value
        this.total = null;
        this.cumulative = null;
        // This will keep each points' extremes stored by series.index and point
        // index
        this.points = {};
        this.hasValidPoints = false;
        // Save the stack option on the series configuration object,
        // and whether to treat it as percent
        this.stack = stackOption;
        this.leftCliff = 0;
        this.rightCliff = 0;
        // The align options and text align varies on whether the stack is
        // negative and if the chart is inverted or not.
        // First test the user supplied value, then use the dynamic.
        this.alignOptions = {
            align: options.align ||
                (inverted ? (isNegative ? 'left' : 'right') : 'center'),
            verticalAlign: options.verticalAlign ||
                (inverted ? 'middle' : isNegative ? 'bottom' : 'top'),
            y: options.y,
            x: options.x
        };
        this.textAlign =
            options.textAlign ||
                (inverted ? (!isNegative ? 'left' : 'right') : 'center');
    }
    /* *
     *
     *  Functions
     *
     * */
    /** @internal */
    destroy() {
        destroyObjectProperties(this, this.axis);
    }
    /**
     * Renders the stack total label and adds it to the stack label group.
     * @internal
     */
    render(group) {
        const chart = this.axis.chart, options = this.options, formatOption = options.format, 
        // Format the text in the label.
        str = (formatOption ?
            StackItem_format(formatOption, this, chart) :
            options.formatter?.call(this, this)) || '';
        // Change the text to reflect the new total and set visibility to hidden
        // in case the series is hidden
        if (this.label) {
            this.label.attr({ text: str, visibility: 'hidden' });
        }
        else {
            // Create new label
            this.label = chart.renderer.label(str, null, void 0, options.shape, void 0, void 0, options.useHTML, false, 'stack-labels');
            const attr = {
                r: options.borderRadius || 0,
                text: str,
                // Set default padding to 5 as it is in dataLabels #12308
                padding: pick(options.padding, 5),
                visibility: 'hidden' // Hidden until setOffset is called
            };
            if (!chart.styledMode) {
                attr.fill = options.backgroundColor;
                attr.stroke = options.borderColor;
                attr['stroke-width'] = options.borderWidth;
                this.label.css(options.style || {});
            }
            this.label.attr(attr);
            if (!this.label.added) {
                this.label.add(group); // Add to the labels-group
            }
        }
        // Rank it higher than data labels (#8742)
        this.label.labelrank = chart.plotSizeY;
        fireEvent(this, 'afterRender');
    }
    /**
     * Sets the offset that the stack has from the x value and repositions the
     * label.
     * @internal
     */
    setOffset(xOffset, width, boxBottom, boxTop, defaultX, xAxis) {
        const { alignOptions, axis, label, options, textAlign } = this, chart = axis.chart, stackBox = this.getStackBox({
            xOffset,
            width,
            boxBottom,
            boxTop,
            defaultX,
            xAxis
        }), { verticalAlign } = alignOptions;
        if (label && stackBox) {
            const labelBox = label.getBBox(void 0, 0), padding = label.padding;
            let isJustify = pick(options.overflow, 'justify') === 'justify', visible;
            // Reset alignOptions property after justify #12337
            alignOptions.x = options.x || 0;
            alignOptions.y = options.y || 0;
            // Calculate the adjusted Stack position, to take into consideration
            // The size if the labelBox and vertical alignment as
            // well as the text alignment. It's need to be done to work with
            // default SVGLabel.align/justify methods.
            const { x, y } = this.adjustStackPosition({
                labelBox,
                verticalAlign,
                textAlign
            });
            stackBox.x -= x;
            stackBox.y -= y;
            // Align the label to the adjusted box.
            label.align(alignOptions, false, stackBox);
            // Check if label is inside the plotArea #12294
            visible = chart.isInsidePlot(label.alignAttr.x + alignOptions.x + x, label.alignAttr.y + alignOptions.y + y);
            if (!visible) {
                isJustify = false;
            }
            if (isJustify) {
                // Justify stackLabel into the alignBox
                StackItem_Series.prototype.justifyDataLabel.call(axis, label, alignOptions, label.alignAttr, labelBox, stackBox);
            }
            // Add attr to avoid the default animation of justifyDataLabel.
            // Also add correct rotation with its rotation origin. #15129
            label.attr({
                x: label.alignAttr.x,
                y: label.alignAttr.y,
                rotation: options.rotation,
                rotationOriginX: labelBox.width *
                    getAlignFactor(options.textAlign || 'center'),
                rotationOriginY: labelBox.height / 2
            });
            // Check if the dataLabel should be visible.
            if (pick(!isJustify && options.crop, true)) {
                visible =
                    isNumber(label.x) &&
                        isNumber(label.y) &&
                        chart.isInsidePlot(label.x - padding + (label.width || 0), label.y) &&
                        chart.isInsidePlot(label.x + padding, label.y);
            }
            label[visible ? 'show' : 'hide']();
        }
        fireEvent(this, 'afterSetOffset', { xOffset, width });
    }
    /**
     * Adjust the stack BBox position, to take into consideration the alignment
     * of the dataLabel. This is necessary to make the stackDataLabel work with
     * core methods like `SVGLabel.adjust` and `Series.justifyDataLabel`.
     * @internal
     */
    adjustStackPosition({ labelBox, verticalAlign, textAlign }) {
        return {
            x: labelBox.width / 2 +
                (labelBox.width / 2) * (2 * getAlignFactor(textAlign) - 1),
            y: (labelBox.height / 2) * 2 * (1 - getAlignFactor(verticalAlign))
        };
    }
    /**
     * Get the bbox of the stack.
     * @internal
     * @function Highcharts.StackItem#getStackBox
     * @return {BBoxObject}
     * The x, y, height, width of the stack.
     */
    getStackBox(stackBoxProps) {
        const stackItem = this, axis = this.axis, chart = axis.chart, { boxTop, defaultX, xOffset, width, boxBottom } = stackBoxProps, totalStackValue = axis.stacking.usePercentage ?
            100 :
            pick(boxTop, this.total, 0), y = axis.toPixels(totalStackValue), xAxis = stackBoxProps.xAxis || chart.xAxis[0], x = pick(defaultX, xAxis.translate(this.x)) + xOffset, yZero = axis.toPixels(boxBottom ||
            (isNumber(axis.min) &&
                axis.logarithmic &&
                axis.logarithmic.lin2log(axis.min)) ||
            0), height = Math.abs(y - yZero), inverted = chart.inverted, neg = stackItem.isNegative;
        return inverted ?
            {
                x: (neg ? y : y - height) - chart.plotLeft,
                y: xAxis.height - x - width + xAxis.top - chart.plotTop,
                width: height,
                height: width
            } : {
            x: x + xAxis.transB - chart.plotLeft,
            y: (neg ? y - height : y) - chart.plotTop,
            width: width,
            height: height
        };
    }
}
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Stacking_StackItem = (StackItem);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * Stack of data points
 *
 * @product highcharts
 *
 * @interface Highcharts.StackItemObject
 */ /**
* Alignment settings
* @name Highcharts.StackItemObject#alignOptions
* @type {Highcharts.AlignObject}
*/ /**
* Related axis
* @name Highcharts.StackItemObject#axis
* @type {Highcharts.Axis}
*/ /**
* Cumulative value of the stacked data points
* @name Highcharts.StackItemObject#cumulative
* @type {number}
*/ /**
* True if on the negative side
* @name Highcharts.StackItemObject#isNegative
* @type {boolean}
*/ /**
* Related SVG element
* @name Highcharts.StackItemObject#label
* @type {Highcharts.SVGElement}
*/ /**
* Related stack options
* @name Highcharts.StackItemObject#options
* @type {Highcharts.YAxisStackLabelsOptions}
*/ /**
* Total value of the stacked data points
* @name Highcharts.StackItemObject#total
* @type {number}
*/ /**
* Shared x value of the stack
* @name Highcharts.StackItemObject#x
* @type {number}
*/
''; // Keeps doclets above in JS file

;// ./code/es-modules/Core/Axis/Stacking/StackingAxis.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { getDeferredAnimation: StackingAxis_getDeferredAnimation } = AnimationUtilities;

const { series: { prototype: seriesProto } } = Series_SeriesRegistry;


/* *
 *
 *  Functions
 *
 * */
/**
 * Generate stacks for each series and calculate stacks total values
 *
 * @internal
 * @function Highcharts.Chart#getStacks
 */
function chartGetStacks() {
    const chart = this, inverted = chart.inverted;
    // Reset stacks for each axis
    chart.axes.forEach((axis) => {
        if (axis.stacking?.stacks && axis.hasVisibleSeries) {
            axis.stacking.oldStacks = axis.stacking.stacks;
        }
    });
    chart.series.forEach((series) => {
        const xAxisOptions = series.xAxis?.options || {};
        if (series.options.stacking && series.reserveSpace()) {
            series.stackKey = [
                series.type,
                pick(series.options.stack, ''),
                inverted ? xAxisOptions.top : xAxisOptions.left,
                inverted ? xAxisOptions.height : xAxisOptions.width
            ].join(',');
        }
    });
}
/** @internal */
function onAxisDestroy() {
    const stacking = this.stacking;
    if (stacking) {
        const stacks = stacking.stacks;
        // Destroy each stack total
        objectEach(stacks, (stack, stackKey) => {
            destroyObjectProperties(stack);
            delete stacks[stackKey];
        });
        stacking.stackTotalGroup?.destroy();
    }
}
/** @internal */
function onAxisInit() {
    if (!this.stacking) {
        this.stacking = new AxisAdditions(this);
    }
}
/**
 * Get stack indicator, according to it's x-value, to determine points with the
 * same x-value
 *
 * @internal
 * @function Highcharts.Series#getStackIndicator
 */
function seriesGetStackIndicator(stackIndicator, x, index, key) {
    // Update stack indicator, when:
    // first point in a stack || x changed || stack type (negative vs positive)
    // changed:
    if (!defined(stackIndicator) ||
        stackIndicator.x !== x ||
        (key && stackIndicator.stackKey !== key)) {
        stackIndicator = {
            x: x,
            index: 0,
            key,
            stackKey: key
        };
    }
    else {
        stackIndicator.index++;
    }
    stackIndicator.key = [index, x, stackIndicator.index].join(',');
    return stackIndicator;
}
/**
 * Iterate over all stacks and compute the absolute values to percent
 *
 * @internal
 * @function Highcharts.Series#modifyStacks
 */
function seriesModifyStacks() {
    const series = this, yAxis = series.yAxis, stackKey = series.stackKey || '', stacks = yAxis.stacking.stacks, processedXData = series.getColumn('x', true), stacking = series.options.stacking, stacker = series[stacking + 'Stacker'];
    let stackIndicator;
    if (stacker) { // Modifier function exists (Series.percentStacker etc.)
        [stackKey, '-' + stackKey].forEach((key) => {
            let i = processedXData.length, x, stackItem, pointExtremes;
            while (i--) {
                x = processedXData[i];
                stackIndicator = series.getStackIndicator(stackIndicator, x, series.index, key);
                stackItem = stacks[key]?.[x];
                pointExtremes = stackItem?.points[stackIndicator.key || ''];
                if (pointExtremes) {
                    stacker.call(series, pointExtremes, stackItem, i);
                }
            }
        });
    }
}
/**
 * Modifier function for percent stacks. Blows up the stack to 100%.
 *
 * @internal
 * @function Highcharts.Series#percentStacker
 */
function seriesPercentStacker(pointExtremes, stack, i) {
    const totalFactor = stack.total ? 100 / stack.total : 0;
    // Y bottom value
    pointExtremes[0] = correctFloat(pointExtremes[0] * totalFactor);
    // Y value
    pointExtremes[1] = correctFloat(pointExtremes[1] * totalFactor);
    this.stackedYData[i] = pointExtremes[1];
}
/**
 * Set grouped points in a stack-like object. When `centerInCategory` is true,
 * and `stacking` is not enabled, we need a pseudo (horizontal) stack in order
 * to handle grouping of points within the same category.
 *
 * @internal
 * @function Highcharts.Series#setGroupedPoints
 * @return {void}
 */
function seriesSetGroupedPoints(axis) {
    // Only series types supporting centerInCategory need to do this. That also
    // applies to resetting (#20221).
    if (this.is('column') || this.is('columnrange')) {
        if (this.options.centerInCategory &&
            // With only one series, we don't need to consider centerInCategory
            this.chart.series.length > 1) {
            seriesProto.setStackedPoints.call(this, axis, 'group');
            // After updating, if we now have proper stacks, we must delete the
            // group pseudo stacks (#14980)
        }
        else {
            axis.stacking.resetStacks();
        }
    }
}
/**
 * Adds series' points value to corresponding stack
 *
 * @internal
 * @function Highcharts.Series#setStackedPoints
 */
function seriesSetStackedPoints(axis, stackingParam) {
    const type = stackingParam || this.options.stacking;
    if (!type ||
        !this.reserveSpace() ||
        // Group stacks (centerInCategory) belong on the x-axis, other stacks on
        // the y-axis.
        ({ group: 'xAxis' }[type] || 'yAxis') !== axis.coll) {
        return;
    }
    const series = this, xData = series.getColumn('x', true), yData = series.getColumn(series.pointValKey || 'y', true), stackedYData = [], yDataLength = yData.length, seriesOptions = series.options, threshold = seriesOptions.threshold || 0, stackThreshold = seriesOptions.startFromThreshold ? threshold : 0, stackOption = seriesOptions.stack, stackKey = stackingParam ?
        `${series.type},${type}` : (series.stackKey || ''), negKey = '-' + stackKey, negStacks = series.negStacks, stacking = axis.stacking, stacks = stacking.stacks, oldStacks = stacking.oldStacks;
    let stackIndicator, isNegative, stack, other, key, pointKey, i;
    stacking.stacksTouched += 1;
    // Loop over the non-null y values and read them into a local array
    for (i = 0; i < yDataLength; i++) {
        const x = xData[i] || 0, y = yData[i], yNumber = isNumber(y) && y || 0;
        stackIndicator = series.getStackIndicator(stackIndicator, x, series.index);
        pointKey = stackIndicator.key || '';
        // Read stacked values into a stack based on the x value,
        // the sign of y and the stack key. Stacking is also handled for null
        // values (#739)
        isNegative = negStacks && yNumber < (stackThreshold ? 0 : threshold);
        key = isNegative ? negKey : stackKey;
        // Create empty object for this stack if it doesn't exist yet
        if (!stacks[key]) {
            stacks[key] = {};
        }
        // Initialize StackItem for this x
        if (!stacks[key][x]) {
            if (oldStacks[key]?.[x]) {
                stacks[key][x] = oldStacks[key][x];
                stacks[key][x].total = null;
            }
            else {
                stacks[key][x] = new Stacking_StackItem(axis, axis.options.stackLabels, !!isNegative, x, stackOption);
            }
        }
        // If the StackItem doesn't exist, create it first
        stack = stacks[key][x];
        if (y !== null) {
            stack.points[pointKey] = stack.points[series.index] = [
                pick(stack.cumulative, stackThreshold)
            ];
            // Record the base of the stack
            if (!defined(stack.cumulative)) {
                stack.base = pointKey;
            }
            stack.touched = stacking.stacksTouched;
            // In area charts, if there are multiple points on the same X value,
            // let the area fill the full span of those points
            if (stackIndicator.index > 0 && series.singleStacks === false) {
                stack.points[pointKey][0] = stack.points[series.index + ',' + x + ',0'][0];
            }
            // When updating to null, reset the point stack (#7493)
        }
        else {
            delete stack.points[pointKey];
            delete stack.points[series.index];
        }
        // Add value to the stack total
        let total = stack.total || 0;
        if (type === 'percent') {
            // Percent stacked column, totals are the same for the positive and
            // negative stacks
            other = isNegative ? stackKey : negKey;
            if (negStacks && stacks[other]?.[x]) {
                other = stacks[other][x];
                total = other.total = Math.max(other.total || 0, total) +
                    Math.abs(yNumber);
                // Percent stacked areas
            }
            else {
                total = correctFloat(total + Math.abs(yNumber));
            }
        }
        else if (type === 'group') {
            // In this stack, the total is the number of valid points
            if (isNumber(y)) {
                total++;
            }
        }
        else {
            total = correctFloat(total + yNumber);
        }
        if (type === 'group') {
            // This point's index within the stack, pushed to stack.points[1]
            stack.cumulative = (total || 1) - 1;
        }
        else {
            stack.cumulative = correctFloat(pick(stack.cumulative, stackThreshold) + yNumber);
        }
        stack.total = total;
        if (y !== null) {
            stack.points[pointKey].push(stack.cumulative);
            stackedYData[i] = stack.cumulative;
            stack.hasValidPoints = true;
        }
    }
    if (type === 'percent') {
        stacking.usePercentage = true;
    }
    if (type !== 'group') {
        this.stackedYData = stackedYData; // To be used in getExtremes
    }
    // Reset old stacks
    stacking.oldStacks = {};
}
/**
 * Adds stacking support to axes.
 * @internal
 * @class
 */
class AxisAdditions {
    /* *
     *
     *  Constructors
     *
     * */
    /** @internal */
    constructor(axis) {
        /** @internal */
        this.oldStacks = {};
        /** @internal */
        this.stacks = {};
        /** @internal */
        this.stacksTouched = 0;
        this.axis = axis;
    }
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Build the stacks from top down
     * @internal
     */
    buildStacks() {
        const stacking = this, axis = stacking.axis, axisSeries = axis.series, isXAxis = axis.coll === 'xAxis', reversedStacks = axis.options.reversedStacks, len = axisSeries.length;
        let actualSeries, i;
        this.resetStacks();
        stacking.usePercentage = false;
        i = len;
        while (i--) {
            actualSeries = axisSeries[reversedStacks ? i : len - i - 1];
            if (isXAxis) {
                actualSeries.setGroupedPoints(axis);
            }
            actualSeries.setStackedPoints(axis);
        }
        // Loop up again to compute percent and stream stack
        if (!isXAxis) {
            for (i = 0; i < len; i++) {
                axisSeries[i].modifyStacks();
            }
        }
        fireEvent(axis, 'afterBuildStacks');
    }
    /** @internal */
    cleanStacks() {
        if (this.oldStacks) {
            this.stacks = this.oldStacks;
            // Reset stacks
            objectEach(this.stacks, (type) => {
                objectEach(type, (stack) => {
                    stack.cumulative = stack.total;
                });
            });
        }
    }
    /**
     * Set all the stacks to initial states and destroy unused ones.
     * @internal
     */
    resetStacks() {
        objectEach(this.stacks, (type) => {
            objectEach(type, (stack, x) => {
                // Clean up memory after point deletion (#1044, #4320)
                if (isNumber(stack.touched) &&
                    stack.touched < this.stacksTouched) {
                    stack.destroy();
                    delete type[x];
                    // Reset stacks
                }
                else {
                    stack.total = null;
                    stack.cumulative = null;
                }
            });
        });
    }
    /** @internal */
    renderStackTotals() {
        const stacking = this, axis = stacking.axis, chart = axis.chart, renderer = chart.renderer, stacks = stacking.stacks, stackLabelsAnim = axis.options.stackLabels?.animation, animationConfig = StackingAxis_getDeferredAnimation(chart, stackLabelsAnim || false), stackTotalGroup = stacking.stackTotalGroup = (stacking.stackTotalGroup ||
            renderer
                .g('stack-labels')
                .attr({
                zIndex: 6,
                opacity: 0
            })
                .add());
        // The plotLeft/Top will change when y axis gets wider so we need to
        // translate the stackTotalGroup at every render call. See bug #506
        // and #516
        stackTotalGroup.translate(chart.plotLeft, chart.plotTop);
        // Render each stack total
        objectEach(stacks, (type) => {
            objectEach(type, (stack) => {
                stack.render(stackTotalGroup);
            });
        });
        stackTotalGroup.animate({
            opacity: 1
        }, animationConfig);
    }
}
/* *
 *
 *  Composition
 *
 * */
/** @internal */
var StackingAxis;
(function (StackingAxis) {
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Extends axis with stacking support.
     * @internal
     */
    function compose(AxisClass, ChartClass, SeriesClass) {
        const chartProto = ChartClass.prototype, seriesProto = SeriesClass.prototype;
        if (!chartProto.getStacks) {
            addEvent(AxisClass, 'init', onAxisInit);
            addEvent(AxisClass, 'destroy', onAxisDestroy);
            chartProto.getStacks = chartGetStacks;
            seriesProto.getStackIndicator = seriesGetStackIndicator;
            seriesProto.modifyStacks = seriesModifyStacks;
            seriesProto.percentStacker = seriesPercentStacker;
            seriesProto.setGroupedPoints = seriesSetGroupedPoints;
            seriesProto.setStackedPoints = seriesSetStackedPoints;
        }
    }
    StackingAxis.compose = compose;
})(StackingAxis || (StackingAxis = {}));
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Stacking_StackingAxis = (StackingAxis);

;// ./code/es-modules/Series/Line/LineSeries.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */




/* *
 *
 *  Class
 *
 * */
/**
 * The line series is the base type and is therefor the series base prototype.
 *
 * @internal
 */
class LineSeries extends Series_Series {
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Draw the graph. Called internally when rendering line-like series
     * types. The first time it generates the `series.graph` item and
     * optionally other series-wide items like `series.area` for area
     * charts. On subsequent calls these items are updated with new
     * positions and attributes.
     *
     * @function Highcharts.Series#drawGraph
     * @internal
     */
    drawGraph() {
        const options = this.options, graphPath = (this.gappedPath || this.getGraphPath).call(this), styledMode = this.chart.styledMode;
        // Draw the graph
        [this, ...this.zones].forEach((owner, i) => {
            let attribs, graph = owner.graph;
            const verb = graph ? 'animate' : 'attr', dashStyle = owner.dashStyle ||
                options.dashStyle;
            if (graph) {
                graph.endX = this.preventGraphAnimation ?
                    null :
                    graphPath.xMap;
                graph.animate({ d: graphPath });
            }
            else if (graphPath.length) { // #1487
                /**
                 * SVG element of line-based charts. Can be used for styling
                 * purposes. If zones are configured, this element will be
                 * hidden and replaced by multiple zone lines, accessible
                 * via `series.zones[i].graph`.
                 *
                 * @name Highcharts.Series#graph
                 * @type {Highcharts.SVGElement|undefined}
                 * @internal
                 */
                owner.graph = graph = this.chart.renderer
                    .path(graphPath)
                    .addClass('highcharts-graph' +
                    (i ? ` highcharts-zone-graph-${i - 1} ` : ' ') +
                    ((i && owner.className) || ''))
                    .attr({ zIndex: 1 }) // #1069
                    .add(this.group);
            }
            if (graph && !styledMode) {
                attribs = {
                    'stroke': ((!i && options.lineColor) || // Series only
                        owner.color ||
                        this.color ||
                        // When colorByPoint = true
                        'var(--highcharts-neutral-color-20)'),
                    'stroke-width': options.lineWidth || 0,
                    // Polygon series use filled graph
                    'fill': (this.fillGraph && this.color) || 'none'
                };
                // Apply dash style
                if (dashStyle) {
                    attribs.dashstyle = dashStyle;
                    // The reason for the `else if` is that linecaps don't mix well
                    // with dashstyle. The gaps get partially filled by the
                    // linecap.
                }
                else if (options.linecap !== 'square') {
                    attribs['stroke-linecap'] =
                        attribs['stroke-linejoin'] = 'round';
                }
                graph[verb](attribs);
                // Add shadow only to the main series (not zones)
                // If shadow is defined, use `filterUnits: 'userSpaceOnUse'`
                // to avoid known SVG filter bug (#19093)
                if (options.shadow) {
                    const isInverted = this.chart.inverted;
                    const filterUnits = { filterUnits: 'userSpaceOnUse' };
                    const shadowOptions = isObject(options.shadow) ?
                        merge(isInverted ? {} : filterUnits, options.shadow) : (isInverted ? true : filterUnits);
                    graph.shadow(shadowOptions);
                }
            }
            // Helpers for animation
            if (graph) {
                graph.startX = graphPath.xMap;
                graph.isArea = graphPath.isArea; // For arearange animation
            }
        });
    }
    // eslint-disable-next-line valid-jsdoc
    /**
     * Get the graph path.
     *
     * @internal
     */
    getGraphPath(points, nullsAsZeroes, connectCliffs) {
        const series = this, options = series.options, graphPath = [], xMap = [];
        let gap, step = options.step;
        points = points || series.points;
        // Bottom of a stack is reversed
        const reversed = points.reversed;
        if (reversed) {
            points.reverse();
        }
        // Reverse the steps (#5004)
        step = {
            right: 1,
            center: 2
        }[step] || (step && 3);
        if (step && reversed) {
            step = 4 - step;
        }
        // Remove invalid points, especially in spline (#5015)
        points = this.getValidPoints(points, false, options.nullInteraction || !(options.connectNulls &&
            !nullsAsZeroes &&
            !connectCliffs));
        // Build the line
        points.forEach(function (point, i) {
            const plotX = point.plotX, plotY = point.plotY, lastPoint = points[i - 1], isNull = point.isNull || typeof plotY !== 'number';
            // The path to this point from the previous
            let pathToPoint;
            if ((point.leftCliff || lastPoint?.rightCliff) &&
                !connectCliffs) {
                gap = true; // ... and continue
            }
            // Line series, nullsAsZeroes is not handled
            if (isNull && !defined(nullsAsZeroes) && i > 0) {
                gap = !options.connectNulls;
                // Area series, nullsAsZeroes is set
            }
            else if (isNull && !nullsAsZeroes) {
                gap = true;
            }
            else {
                if (i === 0 || gap) {
                    pathToPoint = [[
                            'M',
                            point.plotX,
                            point.plotY
                        ]];
                    // Generate the spline as defined in the SplineSeries object
                }
                else if (series.getPointSpline) {
                    pathToPoint = [series.getPointSpline(points, point, i)];
                }
                else if (step) {
                    if (step === 1) { // Right
                        pathToPoint = [[
                                'L',
                                lastPoint.plotX,
                                plotY
                            ]];
                    }
                    else if (step === 2) { // Center
                        pathToPoint = [[
                                'L',
                                (lastPoint.plotX + plotX) / 2,
                                lastPoint.plotY
                            ], [
                                'L',
                                (lastPoint.plotX + plotX) / 2,
                                plotY
                            ]];
                    }
                    else {
                        pathToPoint = [[
                                'L',
                                plotX,
                                lastPoint.plotY
                            ]];
                    }
                    pathToPoint.push([
                        'L',
                        plotX,
                        plotY
                    ]);
                }
                else {
                    // Normal line to next point
                    pathToPoint = [[
                            'L',
                            plotX,
                            plotY
                        ]];
                }
                // Prepare for animation. When step is enabled, there are
                // two path nodes for each x value.
                xMap.push(point.x);
                if (step) {
                    xMap.push(point.x);
                    if (step === 2) { // Step = center (#8073)
                        xMap.push(point.x);
                    }
                }
                graphPath.push.apply(graphPath, pathToPoint);
                gap = false;
            }
        });
        graphPath.xMap = xMap;
        series.graphPath = graphPath;
        return graphPath;
    }
}
/* *
 *
 *  Static Functions
 *
 * */
LineSeries.defaultOptions = merge(Series_Series.defaultOptions, {
    /**
     * What type of legend symbol to render for this series. Can be one
     * of `areaMarker`, `lineMarker` or `rectangle`.
     *
     * @sample {highcharts} highcharts/series/legend-symbol/
     *         Change the legend symbol
     *
     * @type      {string}
     * @default   lineMarker
     * @since     11.0.1
     * @apioption plotOptions.line.legendSymbol
     */
    legendSymbol: 'lineMarker'
});
Series_SeriesRegistry.registerSeriesType('line', LineSeries);
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
/* harmony default export */ const Line_LineSeries = ((/* unused pure expression or super */ null && (LineSeries)));
/* *
 *
 *  API Options
 *
 * */
/**
 * A line series displays information as a series of data points connected by
 * straight line segments.
 *
 * @sample {highcharts} highcharts/demo/line-chart/
 *         Line chart
 * @sample {highstock} stock/demo/basic-line/
 *         Line chart
 *
 * @extends   plotOptions.series
 * @product   highcharts highstock
 * @apioption plotOptions.line
 */
/**
 * A `line` series. If the [type](#series.line.type) option is not
 * specified, it is inherited from [chart.type](#chart.type).
 *
 * @extends   series,plotOptions.line
 * @excluding dataParser,dataURL
 * @product   highcharts highstock
 * @apioption series.line
 */
/**
 * An array of data points for the series. For the `line` series type,
 * points can be given in the following ways:
 *
 * 1. An array of numerical values. In this case, the numerical values will be
 *    interpreted as `y` options. The `x` values will be automatically
 *    calculated, either starting at 0 and incremented by 1, or from
 *    `pointStart` and `pointInterval` given in the series options. If the axis
 *    has categories, these will be used. Example:
 *    ```js
 *    data: [0, 5, 3, 5]
 *    ```
 *
 * 2. An array of arrays with 2 values. In this case, the values correspond to
 *    `x,y`. If the first value is a string, it is applied as the name of the
 *    point, and the `x` value is inferred.
 *    ```js
 *    data: [
 *        [0, 1],
 *        [1, 2],
 *        [2, 8]
 *    ]
 *    ```
 *
 * 3. An array of objects with named values. The following snippet shows only a
 *    few settings, see the complete options set below. If the total number of
 *    data points exceeds the series'
 *    [turboThreshold](#series.line.turboThreshold),
 *    this option is not available.
 *    ```js
 *    data: [{
 *        x: 1,
 *        y: 9,
 *        name: "Point2",
 *        color: "#00FF00"
 *    }, {
 *        x: 1,
 *        y: 6,
 *        name: "Point1",
 *        color: "#FF00FF"
 *    }]
 *    ```
 *
 * **Note:** In TypeScript you have to extend `PointOptionsObject` with an
 * additional declaration to allow custom data types:
 * ```ts
 * declare module `highcharts` {
 *   interface PointOptionsObject {
 *     custom: Record<string, (boolean|number|string)>;
 *   }
 * }
 * ```
 *
 * @sample {highcharts} highcharts/chart/reflow-true/
 *         Numerical values
 * @sample {highcharts} highcharts/series/data-array-of-arrays/
 *         Arrays of numeric x and y
 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
 *         Arrays of datetime x and y
 * @sample {highcharts} highcharts/series/data-array-of-name-value/
 *         Arrays of point.name and y
 * @sample {highcharts} highcharts/series/data-array-of-objects/
 *         Config objects
 *
 * @basic
 * @declare   Highcharts.PointOptionsObject
 * @type      {Array<number|Array<(number|string),(number|null)>|null|*>}
 * @apioption series.line.data
 */
/**
 * An additional, individual class name for the data point's graphic
 * representation. Changes to a point's color will also be reflected in a
 * chart's legend and tooltip.
 *
 * @sample {highcharts} highcharts/css/point-series-classname
 *         Series and point class name
 *
 * @type      {string}
 * @since     5.0.0
 * @product   highcharts highstock gantt
 * @apioption series.line.data.className
 */
/**
 * Individual color for the point. By default the color is pulled from
 * the global `colors` array.
 *
 * In styled mode, the `color` option doesn't take effect. Instead, use
 * `colorIndex`.
 *
 * @sample {highcharts} highcharts/point/color/
 *         Mark the highest point
 *
 * @type      {Highcharts.ColorType}
 * @product   highcharts highstock gantt
 * @apioption series.line.data.color
 */
/**
 * A specific color index to use for the point, so its graphic representations
 * are given the class name `highcharts-color-{n}`. In styled mode this will
 * change the color of the graphic. In non-styled mode, the color is set by the
 * `fill` attribute, so the change in class name won't have a visual effect by
 * default.
 *
 * Since v11, CSS variables on the form `--highcharts-color-{n}` make changing
 * the color scheme very convenient.
 *
 * @sample    {highcharts} highcharts/css/colorindex/
 *            Series and point color index
 *
 * @type      {number}
 * @since     5.0.0
 * @product   highcharts gantt
 * @apioption series.line.data.colorIndex
 */
/**
 * A reserved subspace to store options and values for customized functionality.
 * Here you can add additional data for your own event callbacks and formatter
 * callbacks.
 *
 * @sample {highcharts} highcharts/point/custom/
 *         Point and series with custom data
 *
 * @type      {Highcharts.Dictionary<*>}
 * @apioption series.line.data.custom
 */
/**
 * Individual data label for each point. The options are the same as
 * the ones for [plotOptions.series.dataLabels](
 * #plotOptions.series.dataLabels) with exception of `zIndex` which is applied
 * on the data label's parent group.
 *
 * @sample highcharts/point/datalabels/
 *         Show a label for the last value
 *
 * @type      {*|Array<*>}
 * @declare   Highcharts.DataLabelsOptions
 * @extends   plotOptions.line.dataLabels
 * @excluding zIndex
 * @product   highcharts highstock gantt
 * @apioption series.line.data.dataLabels
 */
/**
 * An id for the point. This can be used after render time to get a
 * pointer to the point object through `chart.get()`.
 *
 * @sample {highcharts} highcharts/point/id/
 *         Remove an id'd point
 *
 * @type      {string}
 * @since     1.2.0
 * @product   highcharts highstock gantt
 * @apioption series.line.data.id
 */
/**
 * The rank for all this point's data labels in case of collision. If two
 * data labels are about to overlap, only the one with the highest `labelrank`
 * will be drawn.
 *
 * The `labelrank` set on `series.dataLabels` takes precedence over this.
 *
 * @type      {number}
 * @apioption series.line.data.labelrank
 */
/**
 * The name of the point as shown in the legend, tooltip, dataLabels, etc.
 *
 * @see [xAxis.uniqueNames](#xAxis.uniqueNames)
 *
 * @sample {highcharts} highcharts/series/data-array-of-objects/
 *         Point names
 *
 * @type      {string}
 * @apioption series.line.data.name
 */
/**
 * Whether the data point is selected initially.
 *
 * @type      {boolean}
 * @default   false
 * @product   highcharts highstock gantt
 * @apioption series.line.data.selected
 */
/**
 * The x value of the point.
 *
 * For datetime axes, a number value is the timestamp in milliseconds since
 * 1970, while a date string is parsed according to the [current time zone]
 * (https://api.highcharts.com/highcharts/time.timezone) of the
 * chart. Date strings are supported since v12.
 *
 * @type      {number|string}
 * @product   highcharts highstock
 * @apioption series.line.data.x
 */
/**
 * The y value of the point.
 *
 * @type      {number|null}
 * @product   highcharts highstock
 * @apioption series.line.data.y
 */
/**
 * The individual point events.
 *
 * @extends   plotOptions.series.point.events
 * @product   highcharts highstock gantt
 * @apioption series.line.data.events
 */
/**
 * Options for the point markers of line-like series.
 *
 * @declare   Highcharts.PointMarkerOptionsObject
 * @extends   plotOptions.series.marker
 * @product   highcharts highstock
 * @apioption series.line.data.marker
 */
''; // Include precedent doclets in transpiled

;// ./code/es-modules/Series/Area/AreaSeriesDefaults.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */

/* *
 *
 *  API Options
 *
 * */
/**
 * The area series type.
 *
 * @sample {highcharts} highcharts/demo/area-chart/
 *         Area chart
 * @sample {highstock} stock/demo/area/
 *         Area chart
 *
 * @extends      plotOptions.line
 * @excluding    useOhlcData
 * @product      highcharts highstock
 * @optionparent plotOptions.area
 */
const AreaSeriesDefaults = {
    /**
     * @see [fillColor](#plotOptions.area.fillColor)
     * @see [fillOpacity](#plotOptions.area.fillOpacity)
     *
     * @apioption plotOptions.area.color
     */
    /**
     * Fill color or gradient for the area. When `undefined`, the series'
     * `color` is used with the series' `fillOpacity`.
     *
     * In styled mode, the fill color can be set with the `.highcharts-area`
     * class name.
     *
     * @see [color](#plotOptions.area.color)
     * @see [fillOpacity](#plotOptions.area.fillOpacity)
     *
     * @sample {highcharts} highcharts/plotoptions/area-fillcolor-default/
     *         Undefined by default
     * @sample {highcharts} highcharts/plotoptions/area-fillcolor-gradient/
     *         Gradient
     *
     * @type {Highcharts.ColorType}
     * @product   highcharts highstock
     * @apioption plotOptions.area.fillColor
     */
    /**
     * Fill opacity for the area. When you set an explicit `fillColor`,
     * the `fillOpacity` is not applied. Instead, you should define the
     * opacity in the `fillColor` with an rgba color definition. The
     * `fillOpacity` setting, also the default setting, overrides the alpha
     * component of the `color` setting.
     *
     * In styled mode, the fill opacity can be set with the
     * `.highcharts-area` class name.
     *
     * @see [color](#plotOptions.area.color)
     * @see [fillColor](#plotOptions.area.fillColor)
     *
     * @sample {highcharts} highcharts/plotoptions/area-fillopacity/
     *         Automatic fill color and fill opacity of 0.1
     *
     * @type      {number}
     * @default   {highcharts} 0.75
     * @default   {highstock} 0.75
     * @product   highcharts highstock
     * @apioption plotOptions.area.fillOpacity
     */
    /**
     * A separate color for the graph line. By default the line takes the
     * `color` of the series, but the lineColor setting allows setting a
     * separate color for the line without altering the `fillColor`.
     *
     * In styled mode, the line stroke can be set with the
     * `.highcharts-graph` class name.
     *
     * @sample {highcharts} highcharts/plotoptions/area-linecolor/
     *         Dark gray line
     *
     * @type {Highcharts.ColorType}
     * @product   highcharts highstock
     * @apioption plotOptions.area.lineColor
     */
    /**
     * A separate color for the negative part of the area. Note that `zones`
     * takes precedence over the negative fill color.
     *
     * In styled mode, a negative color is set with the
     * `.highcharts-negative` class name.
     *
     * @see [negativeColor](#plotOptions.area.negativeColor)
     *
     * @sample {highcharts} highcharts/css/series-negative-color/
     *         Negative color in styled mode
     *
     * @type {Highcharts.ColorType}
     * @since     3.0
     * @product   highcharts
     * @apioption plotOptions.area.negativeFillColor
     */
    /**
     * Whether the whole area or just the line should respond to mouseover
     * tooltips and other mouse or touch events.
     *
     * @sample {highcharts|highstock} highcharts/plotoptions/area-trackbyarea/
     *         Display the tooltip when the area is hovered
     *
     * @type      {boolean}
     * @default   false
     * @since     1.1.6
     * @product   highcharts highstock
     * @apioption plotOptions.area.trackByArea
     */
    /**
     * The Y axis value to serve as the base for the area, for
     * distinguishing between values above and below a threshold. The area
     * between the graph and the threshold is filled.
     *
     * * If a number is given, the Y axis will scale to the threshold.
     * * If `null`, the scaling behaves like a line series with fill between
     *   the graph and the Y axis minimum.
     * * If `Infinity` or `-Infinity`, the area between the graph and the
     *   corresponding Y axis extreme is filled (since v6.1.0).
     *
     * @sample {highcharts} highcharts/plotoptions/area-threshold/
     *         A threshold of 100
     * @sample {highcharts} highcharts/plotoptions/area-threshold-infinity/
     *         A threshold of Infinity
     *
     * @type    {number|null}
     * @since   2.0
     * @product highcharts highstock
     */
    threshold: 0,
    legendSymbol: 'areaMarker'
};
/**
 * A `area` series. If the [type](#series.area.type) option is not
 * specified, it is inherited from [chart.type](#chart.type).
 *
 * @extends   series,plotOptions.area
 * @excluding dataParser, dataURL, useOhlcData
 * @product   highcharts highstock
 * @apioption series.area
 */
/**
 * @see [fillColor](#series.area.fillColor)
 * @see [fillOpacity](#series.area.fillOpacity)
 *
 * @apioption series.area.color
 */
/**
 * An array of data points for the series. For the `area` series type,
 * points can be given in the following ways:
 *
 * 1. An array of numerical values. In this case, the numerical values will be
 *    interpreted as `y` options. The `x` values will be automatically
 *    calculated, either starting at 0 and incremented by 1, or from
 *    `pointStart` * and `pointInterval` given in the series options. If the
 *    axis has categories, these will be used. Example:
 *    ```js
 *    data: [0, 5, 3, 5]
 *    ```
 *
 * 2. An array of arrays with 2 values. In this case, the values correspond to
 *    `x,y`. If the first value is a string, it is applied as the name of the
 *    point, and the `x` value is inferred.
 *    ```js
 *    data: [
 *        [0, 9],
 *        [1, 7],
 *        [2, 6]
 *    ]
 *    ```
 *
 * 3. An array of objects with named values. The following snippet shows only a
 *    few settings, see the complete options set below. If the total number of
 *    data points exceeds the series'
 *    [turboThreshold](#series.area.turboThreshold), this option is not
 *    available.
 *    ```js
 *    data: [{
 *        x: 1,
 *        y: 9,
 *        name: "Point2",
 *        color: "#00FF00"
 *    }, {
 *        x: 1,
 *        y: 6,
 *        name: "Point1",
 *        color: "#FF00FF"
 *    }]
 *    ```
 *
 * @sample {highcharts} highcharts/chart/reflow-true/
 *         Numerical values
 * @sample {highcharts} highcharts/series/data-array-of-arrays/
 *         Arrays of numeric x and y
 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
 *         Arrays of datetime x and y
 * @sample {highcharts} highcharts/series/data-array-of-name-value/
 *         Arrays of point.name and y
 * @sample {highcharts} highcharts/series/data-array-of-objects/
 *         Config objects
 *
 * @basic
 * @type      {Array<number|Array<(number|string),(number|null)>|null|*>}
 * @extends   series.line.data
 * @product   highcharts highstock
 * @apioption series.area.data
 */
/**
 * @see [color](#series.area.color)
 * @see [fillOpacity](#series.area.fillOpacity)
 *
 * @apioption series.area.fillColor
 */
/**
 * @see [color](#series.area.color)
 * @see [fillColor](#series.area.fillColor)
 *
 * @default   {highcharts} 0.75
 * @default   {highstock} 0.75
 * @apioption series.area.fillOpacity
 */
''; // Adds doclets above to transpiled
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Area_AreaSeriesDefaults = (AreaSeriesDefaults);

;// ./code/es-modules/Series/Area/AreaSeries.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */



const { seriesTypes: { line: AreaSeries_LineSeries } } = Series_SeriesRegistry;

/* *
 *
 *  Class
 *
 * */
/**
 * Area series type.
 *
 * @internal
 * @class
 * @name AreaSeries
 *
 * @augments LineSeries
 */
class AreaSeries extends AreaSeries_LineSeries {
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Draw the graph and the underlying area. This method calls the Series
     * base function and adds the area. The areaPath is calculated in the
     * getSegmentPath method called from Series.prototype.drawGraph.
     * @internal
     */
    drawGraph() {
        // Define or reset areaPath
        this.areaPath = [];
        // Call the base method
        super.drawGraph.apply(this);
        // Define local variables
        const { areaPath, options } = this;
        [this, ...this.zones].forEach((owner, i) => {
            const attribs = {}, fillColor = owner.fillColor || options.fillColor;
            let area = owner.area;
            const verb = area ? 'animate' : 'attr';
            // Create or update the area
            if (area) { // Update
                area.endX = this.preventGraphAnimation ?
                    null :
                    areaPath.xMap;
                area.animate({ d: areaPath });
            }
            else { // Create
                attribs.zIndex = 0; // #1069
                /**
                 * SVG element of area-based charts. Can be used for styling
                 * purposes. If zones are configured, this element will be
                 * hidden and replaced by multiple zone areas, accessible
                 * via `series.zones[i].area`.
                 *
                 * @name Highcharts.Series#area
                 * @type {Highcharts.SVGElement|undefined}
                 */
                area = owner.area = this.chart.renderer
                    .path(areaPath)
                    .addClass('highcharts-area' +
                    (i ? ` highcharts-zone-area-${i - 1} ` : ' ') +
                    ((i && owner.className) || ''))
                    .add(this.group);
                area.isArea = true;
            }
            if (!this.chart.styledMode) {
                // If there is fillColor defined for the area, set it.
                // Otherwise, we set it to the zone/series color and add
                // fill-opacity (#18939).
                attribs.fill = fillColor || owner.color || this.color;
                attribs['fill-opacity'] = fillColor ?
                    1 : (options.fillOpacity ?? 0.75);
                // Allow clicking through the area if sticky tracking is true
                // (#18744)
                area.css({
                    pointerEvents: this.stickyTracking ? 'none' : 'auto'
                });
            }
            area[verb](attribs);
            area.startX = areaPath.xMap;
            area.shiftUnit = options.step ? 2 : 1;
        });
    }
    /**
     * @internal
     */
    getGraphPath(points) {
        const getGraphPath = AreaSeries_LineSeries.prototype.getGraphPath, options = this.options, stacking = options.stacking, yAxis = this.yAxis, bottomPoints = [], graphPoints = [], seriesIndex = this.index, stacks = yAxis.stacking.stacks[this.stackKey], threshold = options.threshold, translatedThreshold = Math.round(// #10909
        yAxis.getThreshold(options.threshold)), connectNulls = pick(// #10574
        options.connectNulls, stacking === 'percent'), 
        // To display null points in underlying stacked series, this
        // series graph must be broken, and the area also fall down to
        // fill the gap left by the null point. #2069
        addDummyPoints = function (i, otherI, side) {
            const point = points[i], otherPoint = points[otherI], stackedValues = stacking && (stacks[point.x].points[seriesIndex]), nullVal = point[side + 'Null'] || 0, cliffVal = point[side + 'Cliff'] || 0;
            let top, bottom, isNull = true;
            if (stackedValues && (cliffVal || nullVal)) {
                top = (nullVal ? stackedValues[0] : stackedValues[1]) + cliffVal;
                bottom = stackedValues[0] + cliffVal;
                isNull = !!nullVal;
            }
            else if (!stacking &&
                otherPoint &&
                (otherPoint.isNull ||
                    !defined(otherPoint.plotY))) {
                top = bottom = threshold;
            }
            // Add to the top and bottom line of the area
            if (typeof top !== 'undefined') {
                graphPoints.push({
                    plotX: plotX,
                    plotY: top === null ?
                        translatedThreshold :
                        yAxis.getThreshold(top),
                    isNull: isNull,
                    isCliff: true
                });
                bottomPoints.push({
                    plotX: plotX,
                    plotY: bottom === null ?
                        translatedThreshold :
                        yAxis.getThreshold(bottom),
                    doCurve: false // #1041, gaps in areaspline areas
                });
            }
        };
        let plotX, isNull, yBottom;
        // Find what points to use
        points = points || this.points;
        // Fill in missing points
        if (stacking) {
            points = this.getStackPoints(points);
        }
        for (let i = 0, iEnd = points.length; i < iEnd; ++i) {
            // Reset after series.update of stacking property (#12033)
            if (!stacking) {
                points[i].leftCliff = points[i].rightCliff =
                    points[i].leftNull = points[i].rightNull = void 0;
            }
            // Treat points with undefined plotY as null (e.g. non-positive
            // values on logarithmic axis, #18422)
            isNull = points[i].isNull || !defined(points[i].plotY);
            plotX = pick(points[i].rectPlotX, points[i].plotX);
            yBottom = stacking ?
                pick(points[i].yBottom, translatedThreshold) :
                translatedThreshold;
            if (!isNull || connectNulls) {
                if (!connectNulls) {
                    addDummyPoints(i, i - 1, 'left');
                }
                // Skip null point when stacking is false and connectNulls
                // true
                if (!(isNull && !stacking && connectNulls)) {
                    graphPoints.push(points[i]);
                    bottomPoints.push({
                        x: i,
                        plotX: plotX,
                        plotY: yBottom
                    });
                }
                if (!connectNulls) {
                    addDummyPoints(i, i + 1, 'right');
                }
            }
        }
        const topPath = getGraphPath.call(this, graphPoints, true, true);
        bottomPoints.reversed = true;
        const bottomPath = getGraphPath.call(this, bottomPoints, true, true);
        const firstBottomPoint = bottomPath[0];
        if (firstBottomPoint && firstBottomPoint[0] === 'M') {
            bottomPath[0] = ['L', firstBottomPoint[1], firstBottomPoint[2]];
        }
        const areaPath = topPath.concat(bottomPath);
        if (areaPath.length) {
            areaPath.push(['Z']);
        }
        // TODO: don't set leftCliff and rightCliff when connectNulls?
        const graphPath = getGraphPath
            .call(this, graphPoints, false, connectNulls);
        if (this.chart.series.length > 1 &&
            stacking &&
            graphPoints.some((point) => point.isCliff)) {
            areaPath.hasStackedCliffs = graphPath.hasStackedCliffs = true;
        }
        areaPath.xMap = topPath.xMap;
        this.areaPath = areaPath;
        return graphPath;
    }
    /**
     * Return an array of stacked points, where null and missing points are
     * replaced by dummy points in order for gaps to be drawn correctly in
     * stacks.
     * @internal
     */
    getStackPoints(points) {
        const series = this, segment = [], keys = [], xAxis = this.xAxis, yAxis = this.yAxis, stack = yAxis.stacking.stacks[this.stackKey], pointMap = {}, yAxisSeries = yAxis.series, seriesLength = yAxisSeries.length, upOrDown = yAxis.options.reversedStacks ? 1 : -1, seriesIndex = yAxisSeries.indexOf(series), translatedThreshold = yAxis.getThreshold(series.options.threshold || 0);
        points = points || this.points;
        if (this.options.stacking) {
            for (let i = 0; i < points.length; i++) {
                // Reset after point update (#7326)
                points[i].leftNull = points[i].rightNull = void 0;
                // Create a map where we can quickly look up the points by
                // their X values.
                pointMap[points[i].x] = points[i];
            }
            // Sort the keys (#1651)
            objectEach(stack, function (stackX, x) {
                // Nulled after switching between
                // grouping and not (#1651, #2336)
                if (stackX.total !== null) {
                    keys.push(x);
                }
            });
            keys.sort(function (a, b) {
                return a - b;
            });
            const visibleSeries = yAxisSeries.map((s) => s.visible);
            keys.forEach(function (x, idx) {
                let y = 0, stackPoint, stackedValues;
                if (pointMap[x] && !pointMap[x].isNull) {
                    segment.push(pointMap[x]);
                    // Find left and right cliff. -1 goes left, 1 goes
                    // right.
                    [-1, 1].forEach(function (direction) {
                        const nullName = direction === 1 ?
                            'rightNull' :
                            'leftNull', cliffName = direction === 1 ?
                            'rightCliff' :
                            'leftCliff', otherStack = stack[keys[idx + direction]];
                        let cliff = 0;
                        // If there is a stack next to this one,
                        // to the left or to the right...
                        if (otherStack) {
                            let i = seriesIndex;
                            // Can go either up or down,
                            // depending on reversedStacks
                            while (i >= 0 && i < seriesLength) {
                                const si = yAxisSeries[i].index;
                                stackPoint = otherStack.points[si];
                                if (!stackPoint) {
                                    // If the next point in this series is
                                    // missing, mark the point with
                                    // point.leftNull or point.rightNull = true.
                                    if (si === series.index) {
                                        pointMap[x][nullName] = true;
                                        // If there are missing points in the next
                                        // stack in any of the series below this
                                        // one, we need to subtract the missing
                                        // values and add a hiatus to the left or
                                        // right.
                                    }
                                    else if (visibleSeries[i]) {
                                        stackedValues = stack[x].points[si];
                                        if (stackedValues) {
                                            cliff -= (stackedValues[1] -
                                                stackedValues[0]);
                                        }
                                    }
                                }
                                // When reversedStacks is true, loop up,
                                // else loop down
                                i += upOrDown;
                            }
                        }
                        pointMap[x][cliffName] = cliff;
                    });
                    // There is no point for this X value in this series, so we
                    // insert a dummy point in order for the areas to be drawn
                    // correctly.
                }
                else {
                    // Loop down the stack to find the series below this
                    // one that has a value (#1991)
                    let i = seriesIndex;
                    while (i >= 0 && i < seriesLength) {
                        const si = yAxisSeries[i].index;
                        stackPoint = stack[x].points[si];
                        if (stackPoint) {
                            y = stackPoint[1];
                            break;
                        }
                        // When reversedStacks is true, loop up, else loop
                        // down
                        i += upOrDown;
                    }
                    y || (y = 0);
                    const plotY = yAxis.positiveValuesOnly && y <= 0 ?
                        translatedThreshold :
                        yAxis.translate(// #6272
                        y, false, true, false, true);
                    segment.push({
                        isNull: true,
                        plotX: xAxis.translate(// #6272
                        x, false, false, false, true),
                        x: x,
                        plotY,
                        yBottom: plotY
                    });
                }
            });
        }
        return segment;
    }
}
/* *
 *
 *  Static Properties
 *
 * */
AreaSeries.defaultOptions = merge(AreaSeries_LineSeries.defaultOptions, Area_AreaSeriesDefaults);
extend(AreaSeries.prototype, {
    singleStacks: false
});
Series_SeriesRegistry.registerSeriesType('area', AreaSeries);
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
/* harmony default export */ const Area_AreaSeries = ((/* unused pure expression or super */ null && (AreaSeries)));

;// ./code/es-modules/Series/Spline/SplineSeries.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { line: SplineSeries_LineSeries } = Series_SeriesRegistry.seriesTypes;

/* *
 *
 *  Class
 *
 * */
/**
 * Spline series type.
 *
 * @private
 */
class SplineSeries extends SplineSeries_LineSeries {
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Get the spline segment from a given point's previous neighbor to the
     * given point.
     *
     * @private
     * @function Highcharts.seriesTypes.spline#getPointSpline
     */
    getPointSpline(points, point, i) {
        const 
        // 1 means control points midway between points, 2 means 1/3
        // from the point, 3 is 1/4 etc
        smoothing = 1.5, denom = smoothing + 1, plotX = point.plotX || 0, plotY = point.plotY || 0, lastPoint = points[i - 1], nextPoint = points[i + 1];
        let leftContX, leftContY, rightContX, rightContY;
        /**
         * @private
         */
        function doCurve(otherPoint) {
            return otherPoint &&
                !otherPoint.isNull &&
                otherPoint.doCurve !== false &&
                // #6387, area splines next to null:
                !point.isCliff;
        }
        // Find control points
        if (doCurve(lastPoint) && doCurve(nextPoint)) {
            const lastX = lastPoint.plotX || 0, lastY = lastPoint.plotY || 0, nextX = nextPoint.plotX || 0, nextY = nextPoint.plotY || 0;
            let correction = 0;
            leftContX = (smoothing * plotX + lastX) / denom;
            leftContY = (smoothing * plotY + lastY) / denom;
            rightContX = (smoothing * plotX + nextX) / denom;
            rightContY = (smoothing * plotY + nextY) / denom;
            // Have the two control points make a straight line through main
            // point
            if (rightContX !== leftContX) { // #5016, division by zero
                correction = (((rightContY - leftContY) *
                    (rightContX - plotX)) /
                    (rightContX - leftContX) + plotY - rightContY);
            }
            leftContY += correction;
            rightContY += correction;
            // To prevent false extremes, check that control points are
            // between neighboring points' y values
            if (leftContY > lastY && leftContY > plotY) {
                leftContY = Math.max(lastY, plotY);
                // Mirror of left control point
                rightContY = 2 * plotY - leftContY;
            }
            else if (leftContY < lastY && leftContY < plotY) {
                leftContY = Math.min(lastY, plotY);
                rightContY = 2 * plotY - leftContY;
            }
            if (rightContY > nextY && rightContY > plotY) {
                rightContY = Math.max(nextY, plotY);
                leftContY = 2 * plotY - rightContY;
            }
            else if (rightContY < nextY && rightContY < plotY) {
                rightContY = Math.min(nextY, plotY);
                leftContY = 2 * plotY - rightContY;
            }
            // Record for drawing in next point
            point.rightContX = rightContX;
            point.rightContY = rightContY;
            // Visualize control points for debugging
            /*
            if (leftContX) {
                this.chart.renderer
                    .circle(
                        leftContX + this.chart.plotLeft,
                        leftContY + this.chart.plotTop,
                        2
                    )
                    .attr({
                        stroke: 'red',
                        'stroke-width': 2,
                        fill: 'none',
                        zIndex: 9
                    })
                    .add();
                this.chart.renderer
                    .path([['M', leftContX + this.chart.plotLeft,
                        leftContY + this.chart.plotTop
                    ], ['L', plotX + this.chart.plotLeft,
                        plotY + this.chart.plotTop
                    ]])
                    .attr({
                        stroke: 'red',
                        'stroke-width': 2,
                        zIndex: 9
                    })
                    .add();
            }
            if (rightContX) {
                this.chart.renderer
                    .circle(
                        rightContX + this.chart.plotLeft,
                        rightContY + this.chart.plotTop,
                        2
                    )
                    .attr({
                        stroke: 'green',
                        'stroke-width': 2,
                        fill: 'none',
                        zIndex: 9
                    })
                    .add();
                this.chart.renderer
                    .path([[
                        'M', rightContX + this.chart.plotLeft,
                        rightContY + this.chart.plotTop
                    ], [
                        'L', plotX + this.chart.plotLeft,
                        plotY + this.chart.plotTop
                    ]])
                    .attr({
                        stroke: 'green',
                        'stroke-width': 2,
                        zIndex: 9
                    })
                    .add();
            }
            // */
            point.controlPoints = {
                low: [leftContX, leftContY],
                high: [rightContX, rightContY]
            };
        }
        const ret = [
            'C',
            pick(lastPoint.rightContX, lastPoint.plotX, 0),
            pick(lastPoint.rightContY, lastPoint.plotY, 0),
            pick(leftContX, plotX, 0),
            pick(leftContY, plotY, 0),
            plotX,
            plotY
        ];
        // Reset for updating series later
        lastPoint.rightContX = lastPoint.rightContY = void 0;
        return ret;
    }
}
/* *
 *
 *  Static Properties
 *
 * */
/**
 * A spline series is a special type of line series, where the segments
 * between the data points are smoothed.
 *
 * @sample {highcharts} highcharts/demo/spline-irregular-time/
 *         Spline chart
 * @sample {highstock} stock/demo/spline/
 *         Spline chart
 *
 * @extends      plotOptions.series
 * @excluding    step, boostThreshold, boostBlending
 * @product      highcharts highstock
 * @optionparent plotOptions.spline
 */
SplineSeries.defaultOptions = merge(SplineSeries_LineSeries.defaultOptions);
Series_SeriesRegistry.registerSeriesType('spline', SplineSeries);
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Spline_SplineSeries = (SplineSeries);
/* *
 *
 *  API Options
 *
 * */
/**
 * A `spline` series. If the [type](#series.spline.type) option is
 * not specified, it is inherited from [chart.type](#chart.type).
 *
 * @extends   series,plotOptions.spline
 * @excluding dataParser, dataURL, step, boostThreshold, boostBlending
 * @product   highcharts highstock
 * @apioption series.spline
 */
/**
 * An array of data points for the series. For the `spline` series type,
 * points can be given in the following ways:
 *
 * 1. An array of numerical values. In this case, the numerical values will be
 *    interpreted as `y` options. The `x` values will be automatically
 *    calculated, either starting at 0 and incremented by 1, or from
 *    `pointStart` and `pointInterval` given in the series options. If the axis
 *    has categories, these will be used. Example:
 *    ```js
 *    data: [0, 5, 3, 5]
 *    ```
 *
 * 2. An array of arrays with 2 values. In this case, the values correspond to
 *    `x,y`. If the first value is a string, it is applied as the name of the
 *    point, and the `x` value is inferred.
 *    ```js
 *    data: [
 *        [0, 9],
 *        [1, 2],
 *        [2, 8]
 *    ]
 *    ```
 *
 * 3. An array of objects with named values. The following snippet shows only a
 *    few settings, see the complete options set below. If the total number of
 *    data points exceeds the series'
 *    [turboThreshold](#series.spline.turboThreshold),
 *    this option is not available.
 *    ```js
 *    data: [{
 *        x: 1,
 *        y: 9,
 *        name: "Point2",
 *        color: "#00FF00"
 *    }, {
 *        x: 1,
 *        y: 0,
 *        name: "Point1",
 *        color: "#FF00FF"
 *    }]
 *    ```
 *
 * @sample {highcharts} highcharts/chart/reflow-true/
 *         Numerical values
 * @sample {highcharts} highcharts/series/data-array-of-arrays/
 *         Arrays of numeric x and y
 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
 *         Arrays of datetime x and y
 * @sample {highcharts} highcharts/series/data-array-of-name-value/
 *         Arrays of point.name and y
 * @sample {highcharts} highcharts/series/data-array-of-objects/
 *         Config objects
 *
 * @basic
 * @type      {Array<number|Array<(number|string),(number|null)>|null|*>}
 * @extends   series.line.data
 * @product   highcharts highstock
 * @apioption series.spline.data
 */
''; // Adds doclets above intro transpiled

;// ./code/es-modules/Series/AreaSpline/AreaSplineSeries.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */



const { area: AreaSplineSeries_AreaSeries, area: { prototype: areaProto } } = Series_SeriesRegistry.seriesTypes;

/* *
 *
 *  Class
 *
 * */
/**
 * AreaSpline series type.
 *
 * @internal
 * @class
 * @name Highcharts.seriesTypes.areaspline
 *
 * @augments Highcharts.Series
 */
class AreaSplineSeries extends Spline_SplineSeries {
}
/* *
 *
 *  Static Properties
 *
 * */
AreaSplineSeries.defaultOptions = merge(Spline_SplineSeries.defaultOptions, AreaSplineSeries_AreaSeries.defaultOptions);
extend(AreaSplineSeries.prototype, {
    getGraphPath: areaProto.getGraphPath,
    getStackPoints: areaProto.getStackPoints,
    drawGraph: areaProto.drawGraph
});
Series_SeriesRegistry.registerSeriesType('areaspline', AreaSplineSeries);
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
/* harmony default export */ const AreaSpline_AreaSplineSeries = ((/* unused pure expression or super */ null && (AreaSplineSeries)));
/* *
 *
 *  API Options
 *
 * */
/**
 * The area spline series is an area series where the graph between the
 * points is smoothed into a spline.
 *
 * @sample {highcharts} highcharts/demo/areaspline/
 *         Area spline chart
 * @sample {highstock} stock/demo/areaspline/
 *         Area spline chart
 *
 * @extends   plotOptions.area
 * @excluding step, boostThreshold, boostBlending
 * @product   highcharts highstock
 * @apioption plotOptions.areaspline
 */
/**
 * @see [fillColor](#plotOptions.areaspline.fillColor)
 * @see [fillOpacity](#plotOptions.areaspline.fillOpacity)
 *
 * @apioption plotOptions.areaspline.color
 */
/**
 * @see [color](#plotOptions.areaspline.color)
 * @see [fillOpacity](#plotOptions.areaspline.fillOpacity)
 *
 * @apioption plotOptions.areaspline.fillColor
 */
/**
 * @see [color](#plotOptions.areaspline.color)
 * @see [fillColor](#plotOptions.areaspline.fillColor)
 *
 * @default   0.75
 * @apioption plotOptions.areaspline.fillOpacity
 */
/**
 * A `areaspline` series. If the [type](#series.areaspline.type) option
 * is not specified, it is inherited from [chart.type](#chart.type).
 *
 *
 * @extends   series,plotOptions.areaspline
 * @excluding dataParser, dataURL, step, boostThreshold, boostBlending
 * @product   highcharts highstock
 * @apioption series.areaspline
 */
/**
 * @see [fillColor](#series.areaspline.fillColor)
 * @see [fillOpacity](#series.areaspline.fillOpacity)
 *
 * @apioption series.areaspline.color
 */
/**
 * An array of data points for the series. For the `areaspline` series
 * type, points can be given in the following ways:
 *
 * 1. An array of numerical values. In this case, the numerical values will be
 *    interpreted as `y` options. The `x` values will be automatically
 *    calculated, either starting at 0 and incremented by 1, or from
 *    `pointStart` and `pointInterval` given in the series options. If the axis
 *    has categories, these will be used. Example:
 *    ```js
 *    data: [0, 5, 3, 5]
 *    ```
 *
 * 2. An array of arrays with 2 values. In this case, the values correspond to
 *    `x,y`. If the first value is a string, it is applied as the name of the
 *    point, and the `x` value is inferred.
 *    ```js
 *    data: [
 *        [0, 10],
 *        [1, 9],
 *        [2, 3]
 *    ]
 *    ```
 *
 * 3. An array of objects with named values. The following snippet shows only a
 *    few settings, see the complete options set below. If the total number of
 *    data points exceeds the series'
 *    [turboThreshold](#series.areaspline.turboThreshold), this option is not
 *    available.
 *    ```js
 *    data: [{
 *        x: 1,
 *        y: 4,
 *        name: "Point2",
 *        color: "#00FF00"
 *    }, {
 *        x: 1,
 *        y: 4,
 *        name: "Point1",
 *        color: "#FF00FF"
 *    }]
 *    ```
 *
 * @sample {highcharts} highcharts/chart/reflow-true/
 *         Numerical values
 * @sample {highcharts} highcharts/series/data-array-of-arrays/
 *         Arrays of numeric x and y
 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
 *         Arrays of datetime x and y
 * @sample {highcharts} highcharts/series/data-array-of-name-value/
 *         Arrays of point.name and y
 * @sample {highcharts} highcharts/series/data-array-of-objects/
 *         Config objects
 *
 * @basic
 * @type      {Array<number|Array<(number|string),(number|null)>|null|*>}
 * @extends   series.line.data
 * @product   highcharts highstock
 * @apioption series.areaspline.data
 */
/**
 * @see [color](#series.areaspline.color)
 * @see [fillOpacity](#series.areaspline.fillOpacity)
 *
 * @apioption series.areaspline.fillColor
 */
/**
 * @see [color](#series.areaspline.color)
 * @see [fillColor](#series.areaspline.fillColor)
 *
 * @default   0.75
 * @apioption series.areaspline.fillOpacity
 */
''; // Adds doclets above into transpiled

;// ./code/es-modules/Series/Column/ColumnSeriesDefaults.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */

/* *
 *
 *  API Options
 *
 * */
/**
 * Column series display one column per value along an X axis.
 *
 * @sample {highcharts} highcharts/demo/column-basic/
 *         Column chart
 * @sample {highstock} stock/demo/column/
 *         Column chart
 *
 * @extends      plotOptions.line
 * @excluding    connectEnds, connectNulls, gapSize, gapUnit, linecap,
 *               lineWidth, marker, step, useOhlcData
 * @product      highcharts highstock
 * @optionparent plotOptions.column
 */
const ColumnSeriesDefaults = {
    /**
     * The corner radius of the border surrounding each column or bar. A number
     * signifies pixels. A percentage string, like for example `50%`, signifies
     * a relative size. For columns this is relative to the column width, for
     * pies it is relative to the radius and the inner radius.
     *
     * @sample  {highcharts} highcharts/plotoptions/column-borderradius/
     *          Rounded columns
     * @sample  highcharts/plotoptions/series-border-radius
     *          Column and pie with rounded border
     *
     * @type    {number|string|Highcharts.BorderRadiusOptionsObject}
     * @product highcharts highstock gantt
     */
    borderRadius: 3,
    /**
     * When using automatic point colors pulled from the global
     * [colors](colors) or series-specific
     * [plotOptions.column.colors](series.colors) collections, this option
     * determines whether the chart should receive one color per series or
     * one color per point.
     *
     * In styled mode, the `colors` or `series.colors` arrays are not
     * supported, and instead this option gives the points individual color
     * class names on the form `highcharts-color-{n}`.
     *
     * @see [series colors](#plotOptions.column.colors)
     *
     * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-false/
     *         False by default
     * @sample {highcharts} highcharts/plotoptions/column-colorbypoint-true/
     *         True
     *
     * @type      {boolean}
     * @default   false
     * @since     2.0
     * @product   highcharts highstock gantt
     * @apioption plotOptions.column.colorByPoint
     */
    /**
     * A series specific or series type specific color set to apply instead
     * of the global [colors](#colors) when [colorByPoint](
     * #plotOptions.column.colorByPoint) is true.
     *
     * @type      {Array<Highcharts.ColorType>}
     * @since     3.0
     * @product   highcharts highstock gantt
     * @apioption plotOptions.column.colors
     */
    /**
     * When `true`, the columns will center in the category, ignoring null
     * or missing points. When `false`, space will be reserved for null or
     * missing points.
     *
     * @sample {highcharts} highcharts/series-column/centerincategory/
     *         Center in category
     * @sample {highcharts} highcharts/series/stack-centerincategory/
     *         Center in category, stacked and grouped
     *
     * @since   8.0.1
     * @product highcharts highstock gantt
     */
    centerInCategory: false,
    /**
     * Padding between each value groups, in x axis units.
     *
     * @sample {highcharts} highcharts/plotoptions/column-grouppadding-default/
     *         0.2 by default
     * @sample {highcharts} highcharts/plotoptions/column-grouppadding-none/
     *         No group padding - all columns are evenly spaced
     *
     * @product highcharts highstock gantt
     */
    groupPadding: 0.2,
    /**
     * Whether to group non-stacked columns or to let them render
     * independent of each other. Non-grouped columns will be laid out
     * individually and overlap each other.
     *
     * @sample {highcharts} highcharts/plotoptions/column-grouping-false/
     *         Grouping disabled
     * @sample {highstock} highcharts/plotoptions/column-grouping-false/
     *         Grouping disabled
     *
     * @type      {boolean}
     * @default   true
     * @since     2.3.0
     * @product   highcharts highstock gantt
     * @apioption plotOptions.column.grouping
     */
    /** @ignore-option */
    marker: null, // Point options are specified in the base options
    /**
     * The maximum allowed pixel width for a column, translated to the
     * height of a bar in a bar chart. This prevents the columns from
     * becoming too wide when there is a small number of points in the
     * chart.
     *
     * @see [pointWidth](#plotOptions.column.pointWidth)
     *
     * @sample {highcharts} highcharts/plotoptions/column-maxpointwidth-20/
     *         Limited to 50
     * @sample {highstock} highcharts/plotoptions/column-maxpointwidth-20/
     *         Limited to 50
     *
     * @type      {number}
     * @since     4.1.8
     * @product   highcharts highstock gantt
     * @apioption plotOptions.column.maxPointWidth
     */
    /**
     * Padding between each column or bar, in x axis units.
     *
     * @sample {highcharts} highcharts/plotoptions/column-pointpadding-default/
     *         0.1 by default
     * @sample {highcharts} highcharts/plotoptions/column-pointpadding-025/
     *          0.25
     * @sample {highcharts} highcharts/plotoptions/column-pointpadding-none/
     *         0 for tightly packed columns
     *
     * @product highcharts highstock gantt
     */
    pointPadding: 0.1,
    /**
     * A pixel value specifying a fixed width for each column or bar point.
     * When set to `undefined`, the width is calculated from the
     * `pointPadding` and `groupPadding`. The width effects the dimension
     * that is not based on the point value. For column series it is the
     * horizontal length and for bar series it is the vertical length.
     *
     * @see [maxPointWidth](#plotOptions.column.maxPointWidth)
     *
     * @sample {highcharts} highcharts/plotoptions/column-pointwidth-20/
     *         20px wide columns regardless of chart width or the amount of
     *         data points
     *
     * @type      {number}
     * @since     1.2.5
     * @product   highcharts highstock gantt
     * @apioption plotOptions.column.pointWidth
     */
    /**
     * A pixel value specifying a fixed width for the column or bar.
     * Overrides pointWidth on the series.
     *
     * @see [series.pointWidth](#plotOptions.column.pointWidth)
     *
     * @type      {number}
     * @default   undefined
     * @since     7.0.0
     * @product   highcharts highstock gantt
     * @apioption series.column.data.pointWidth
     */
    /**
     * The minimal height for a column or width for a bar. By default,
     * 0 values are not shown. To visualize a 0 (or close to zero) point,
     * set the minimal point length to a pixel value like 3\. In stacked
     * column charts, minPointLength might not be respected for tightly
     * packed values.
     *
     * @sample {highcharts} highcharts/plotoptions/column-minpointlength/
     *         Zero base value
     * @sample {highcharts} highcharts/plotoptions/column-minpointlength-pos-and-neg/
     *         Positive and negative close to zero values
     *
     * @product highcharts highstock gantt
     */
    minPointLength: 0,
    /**
     * When the series contains less points than the crop threshold, all
     * points are drawn, even if the points fall outside the visible plot
     * area at the current zoom. The advantage of drawing all points
     * (including markers and columns), is that animation is performed on
     * updates. On the other hand, when the series contains more points than
     * the crop threshold, the series data is cropped to only contain points
     * that fall within the plot area. The advantage of cropping away
     * invisible points is to increase performance on large series.
     *
     * @product highcharts highstock gantt
     */
    cropThreshold: 50,
    /**
     * The X axis range that each point is valid for. This determines the
     * width of the column. On a categorized axis, the range will be 1
     * by default (one category unit). On linear and datetime axes, the
     * range will be computed as the distance between the two closest data
     * points.
     *
     * The default `null` means it is computed automatically, but this
     * option can be used to override the automatic value.
     *
     * This option is set by default to 1 if data sorting is enabled.
     *
     * @sample {highcharts} highcharts/plotoptions/column-pointrange/
     *         Set the point range to one day on a data set with one week
     *         between the points
     *
     * @type    {number|null}
     * @since   2.3
     * @product highcharts highstock gantt
     */
    pointRange: null,
    states: {
        /**
         * Options for the hovered point. These settings override the normal
         * state options when a point is moused over or touched.
         *
         * @extends   plotOptions.series.states.hover
         * @excluding halo, lineWidth, lineWidthPlus, marker
         * @product   highcharts highstock gantt
         */
        hover: {
            /** @ignore-option */
            halo: false,
            /**
             * A specific border color for the hovered point. Defaults to
             * inherit the normal state border color.
             *
             * @type      {Highcharts.ColorType}
             * @product   highcharts gantt
             * @apioption plotOptions.column.states.hover.borderColor
             */
            /**
             * A specific color for the hovered point.
             *
             * @type      {Highcharts.ColorType}
             * @product   highcharts gantt
             * @apioption plotOptions.column.states.hover.color
             */
            /**
             * How much to brighten the point on interaction.
             *
             * In styled mode, the hover brightening is by default replaced
             * with a fill-opacity set in the `.highcharts-point:hover`
             * rule.
             *
             * @sample {highcharts} highcharts/plotoptions/column-states-hover-brightness/
             *         Brighten by 0.5
             *
             * @product highcharts highstock gantt
             */
            brightness: 0.2
        },
        /**
         * Options for the selected point. These settings override the
         * normal state options when a point is selected.
         *
         * @extends   plotOptions.series.states.select
         * @excluding halo, lineWidth, lineWidthPlus, marker
         * @product   highcharts highstock gantt
         */
        select: {
            /**
             * A specific color for the selected point.
             *
             * @type    {Highcharts.ColorType}
             * @default #cccccc
             * @product highcharts highstock gantt
             */
            color: 'var(--highcharts-neutral-color-20)',
            /**
             * A specific border color for the selected point.
             *
             * @type    {Highcharts.ColorType}
             * @default #000000
             * @product highcharts highstock gantt
             */
            borderColor: 'var(--highcharts-neutral-color-100)'
        }
    },
    dataLabels: {
        align: void 0,
        verticalAlign: void 0,
        /**
         * The y position offset of the label relative to the point in
         * pixels.
         *
         * @type {number}
         */
        y: void 0
    },
    // False doesn't work well: https://jsfiddle.net/highcharts/hz8fopan/14/
    /** @ignore-option */
    startFromThreshold: true,
    stickyTracking: false,
    tooltip: {
        distance: 6
    },
    /**
     * The Y axis value to serve as the base for the columns, for
     * distinguishing between values above and below a threshold. If `null`,
     * the columns extend from the padding Y axis minimum.
     *
     * @type    {number|null}
     * @since   2.0
     * @product highcharts
     */
    threshold: 0,
    /**
     * The width of the border surrounding each column or bar. Defaults to
     * `1` when there is room for a border, but to `0` when the columns are
     * so dense that a border would cover the next column.
     *
     * In styled mode, the stroke width can be set with the
     * `.highcharts-point` rule.
     *
     * @sample {highcharts} highcharts/plotoptions/column-borderwidth/
     *         2px black border
     *
     * @type      {number}
     * @default   undefined
     * @product   highcharts highstock gantt
     * @apioption plotOptions.column.borderWidth
     */
    /**
     * The color of the border surrounding each column or bar.
     *
     * In styled mode, the border stroke can be set with the
     * `.highcharts-point` rule.
     *
     * @sample {highcharts} highcharts/plotoptions/column-bordercolor/
     *         Dark gray border
     *
     * @type      {Highcharts.ColorType}
     * @default   #ffffff
     * @product   highcharts highstock gantt
     */
    borderColor: 'var(--highcharts-background-color)'
};
/**
 * A `column` series. If the [type](#series.column.type) option is
 * not specified, it is inherited from [chart.type](#chart.type).
 *
 * @extends   series,plotOptions.column
 * @excluding connectNulls, dataParser, dataURL, gapSize, gapUnit, linecap,
 *            lineWidth, marker, connectEnds, step
 * @product   highcharts highstock
 * @apioption series.column
 */
/**
 * An array of data points for the series. For the `column` series type,
 * points can be given in the following ways:
 *
 * 1. An array of numerical values. In this case, the numerical values will be
 *    interpreted as `y` options. The `x` values will be automatically
 *    calculated, either starting at 0 and incremented by 1, or from
 *    `pointStart` and `pointInterval` given in the series options. If the axis
 *    has categories, these will be used. Example:
 *    ```js
 *    data: [0, 5, 3, 5]
 *    ```
 *
 * 2. An array of arrays with 2 values. In this case, the values correspond to
 *    `x,y`. If the first value is a string, it is applied as the name of the
 *    point, and the `x` value is inferred.
 *    ```js
 *    data: [
 *        [0, 6],
 *        [1, 2],
 *        [2, 6]
 *    ]
 *    ```
 *
 * 3. An array of objects with named values. The following snippet shows only a
 *    few settings, see the complete options set below. If the total number of
 *    data points exceeds the series'
 *    [turboThreshold](#series.column.turboThreshold), this option is not
 *    available.
 *    ```js
 *    data: [{
 *        x: 1,
 *        y: 9,
 *        name: "Point2",
 *        color: "#00FF00"
 *    }, {
 *        x: 1,
 *        y: 6,
 *        name: "Point1",
 *        color: "#FF00FF"
 *    }]
 *    ```
 *
 * @sample {highcharts} highcharts/chart/reflow-true/
 *         Numerical values
 * @sample {highcharts} highcharts/series/data-array-of-arrays/
 *         Arrays of numeric x and y
 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
 *         Arrays of datetime x and y
 * @sample {highcharts} highcharts/series/data-array-of-name-value/
 *         Arrays of point.name and y
 * @sample {highcharts} highcharts/series/data-array-of-objects/
 *         Config objects
 *
 * @basic
 * @type      {Array<number|Array<(number|string),(number|null)>|null|*>}
 * @extends   series.line.data
 * @excluding marker
 * @product   highcharts highstock
 * @apioption series.column.data
 */
/**
 * The color of the border surrounding the column or bar.
 *
 * In styled mode, the border stroke can be set with the `.highcharts-point`
 * rule.
 *
 * @sample {highcharts} highcharts/plotoptions/column-bordercolor/
 *         Dark gray border
 *
 * @type      {Highcharts.ColorType}
 * @product   highcharts highstock
 * @apioption series.column.data.borderColor
 */
/**
 * The width of the border surrounding the column or bar.
 *
 * In styled mode, the stroke width can be set with the `.highcharts-point`
 * rule.
 *
 * @sample {highcharts} highcharts/plotoptions/column-borderwidth/
 *         2px black border
 *
 * @type      {number}
 * @product   highcharts highstock
 * @apioption series.column.data.borderWidth
 */
/**
 * A name for the dash style to use for the column or bar. Overrides
 * dashStyle on the series.
 *
 * In styled mode, the stroke dash-array can be set with the same classes as
 * listed under [data.color](#series.column.data.color).
 *
 * @see [series.pointWidth](#plotOptions.column.dashStyle)
 *
 * @type      {Highcharts.DashStyleValue}
 * @apioption series.column.data.dashStyle
 */
/**
 * A pixel value specifying a fixed width for the column or bar. Overrides
 * pointWidth on the series. The width effects the dimension that is not based
 * on the point value.
 *
 * @see [series.pointWidth](#plotOptions.column.pointWidth)
 *
 * @type      {number}
 * @apioption series.column.data.pointWidth
 */
/**
 * @excluding halo, lineWidth, lineWidthPlus, marker
 * @product   highcharts highstock
 * @apioption series.column.states.hover
 */
/**
 * @excluding halo, lineWidth, lineWidthPlus, marker
 * @product   highcharts highstock
 * @apioption series.column.states.select
 */
/**
 * @extends   series.column.states
 * @product   highcharts highstock
 * @apioption series.column.data.states
 */
''; // Keeps doclets above in JS file
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Column_ColumnSeriesDefaults = (ColumnSeriesDefaults);

;// ./code/es-modules/Series/Column/ColumnSeries.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { animObject: ColumnSeries_animObject } = AnimationUtilities;

const { parse: ColumnSeries_color } = Color_Color;


const { noop: ColumnSeries_noop } = Core_Globals;



/* *
 *
 *  Class
 *
 * */
/**
 * The column series type.
 *
 * @internal
 * @class
 * @name Highcharts.seriesTypes.column
 *
 * @augments Highcharts.Series
 */
class ColumnSeries extends Series_Series {
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Animate the column heights one by one from zero.
     *
     * @internal
     * @function Highcharts.seriesTypes.column#animate
     *
     * @param {boolean} init
     *        Whether to initialize the animation or run it
     */
    animate(init) {
        const series = this, yAxis = this.yAxis, yAxisPos = yAxis.pos, reversed = yAxis.reversed, options = series.options, { clipOffset, inverted } = this.chart, attr = {}, translateProp = inverted ?
            'translateX' :
            'translateY';
        let translateStart, translatedThreshold;
        if (init && clipOffset) {
            attr.scaleY = 0.001;
            translatedThreshold = clamp(yAxis.toPixels(options.threshold || 0), yAxisPos, yAxisPos + yAxis.len);
            if (inverted) {
                // Make sure the columns don't cover the axis line during
                // entrance animation
                translatedThreshold += reversed ?
                    -Math.floor(clipOffset[inverted ? 1 : 0]) :
                    Math.ceil(clipOffset[inverted ? 3 : 2]);
                attr.translateX = translatedThreshold - yAxis.len;
            }
            else {
                // Make sure the columns don't cover the axis line during
                // entrance animation
                translatedThreshold += reversed ?
                    Math.ceil(clipOffset[inverted ? 1 : 0]) :
                    -Math.floor(clipOffset[inverted ? 3 : 2]);
                attr.translateY = translatedThreshold;
            }
            // Apply final clipping (used in Highcharts Stock) (#7083)
            // animation is done by scaleY, so clipping is for panes
            if (series.clipBox) {
                series.setClip();
            }
            series.group.attr(attr);
        }
        else { // Run the animation
            translateStart = Number(series.group.attr(translateProp));
            series.group.animate({ scaleY: 1 }, extend(ColumnSeries_animObject(series.options.animation), {
                // Do the scale synchronously to ensure smooth
                // updating (#5030, #7228)
                step: function (val, fx) {
                    if (series.group) {
                        attr[translateProp] = translateStart +
                            fx.pos * (yAxisPos - translateStart);
                        series.group.attr(attr);
                    }
                }
            }));
        }
    }
    /**
     * Initialize the series. Extends the basic Series.init method by
     * marking other series of the same type as dirty.
     *
     * @internal
     * @function Highcharts.seriesTypes.column#init
     */
    init(chart, 
    // eslint-disable-next-line @typescript-eslint/no-unused-vars
    options) {
        super.init.apply(this, arguments);
        const series = this;
        chart = series.chart;
        // If the series is added dynamically, force redraw of other
        // series affected by a new column
        if (chart.hasRendered) {
            chart.series.forEach(function (otherSeries) {
                if (otherSeries.type === series.type) {
                    otherSeries.isDirty = true;
                }
            });
        }
    }
    /**
     * Return the width and x offset of the columns adjusted for grouping,
     * groupPadding, pointPadding, pointWidth etc.
     *
     * @internal
     * @function Highcharts.seriesTypes.column#getColumnMetrics
     */
    getColumnMetrics() {
        const series = this, options = series.options, xAxis = series.xAxis, yAxis = series.yAxis, reversedStacks = xAxis.options.reversedStacks, 
        // Keep backward compatibility: reversed xAxis had reversed
        // stacks
        reverseStacks = (xAxis.reversed && !reversedStacks) ||
            (!xAxis.reversed && reversedStacks), stackGroups = {};
        let stackKey, columnCount = 0;
        // Get the total number of column type series. This is called on
        // every series. Consider moving this logic to a chart.orderStacks()
        // function and call it on init, addSeries and removeSeries
        if (options.grouping === false) {
            columnCount = 1;
        }
        else {
            series.chart.series.forEach(function (otherSeries) {
                const otherYAxis = otherSeries.yAxis, otherOptions = otherSeries.options;
                let columnIndex;
                if (otherSeries.type === series.type &&
                    otherSeries.reserveSpace() &&
                    yAxis.len === otherYAxis.len &&
                    yAxis.pos === otherYAxis.pos) { // #642, #2086
                    if (otherOptions.stacking &&
                        otherOptions.stacking !== 'group') {
                        stackKey = otherSeries.stackKey;
                        if (typeof stackGroups[stackKey] ===
                            'undefined') {
                            stackGroups[stackKey] = columnCount++;
                        }
                        columnIndex = stackGroups[stackKey];
                    }
                    else if (otherOptions.grouping !== false) { // #1162
                        columnIndex = columnCount++;
                    }
                    otherSeries.columnIndex = columnIndex;
                }
            });
        }
        const categoryWidth = Math.min(Math.abs(xAxis.transA) * ((!xAxis.brokenAxis?.hasBreaks && xAxis.ordinal?.slope) ||
            options.pointRange ||
            xAxis.closestPointRange ||
            xAxis.tickInterval ||
            1), // #2610
        xAxis.len // #1535
        ), groupPadding = categoryWidth * options.groupPadding, groupWidth = categoryWidth - 2 * groupPadding, pointOffsetWidth = groupWidth / (columnCount || 1), pointWidth = Math.min(options.maxPointWidth || xAxis.len, pick(options.pointWidth, pointOffsetWidth * (1 - 2 * options.pointPadding))), pointPadding = (pointOffsetWidth - pointWidth) / 2, 
        // #1251, #3737
        colIndex = (series.columnIndex || 0) + (reverseStacks ? 1 : 0), pointXOffset = pointPadding +
            (groupPadding +
                colIndex * pointOffsetWidth -
                (categoryWidth / 2)) * (reverseStacks ? -1 : 1);
        // Save it for reading in linked series (Error bars particularly)
        series.columnMetrics = {
            width: pointWidth,
            offset: pointXOffset,
            paddedWidth: pointOffsetWidth,
            columnCount
        };
        return series.columnMetrics;
    }
    /**
     * Make the columns crisp. The edges are rounded to the nearest full
     * pixel.
     *
     * @internal
     * @function Highcharts.seriesTypes.column#crispCol
     */
    crispCol(x, y, width, height) {
        const borderWidth = this.borderWidth, inverted = this.chart.inverted, bottom = crisp(y + height, borderWidth, inverted);
        // Vertical
        y = crisp(y, borderWidth, inverted);
        height = bottom - y;
        // Horizontal. We need to first compute the exact right edge, then
        // round it and compute the width from there.
        if (this.options.crisp) {
            const right = crisp(x + width, borderWidth);
            x = crisp(x, borderWidth);
            width = right - x;
        }
        return { x, y, width, height };
    }
    /**
     * Adjust for missing columns, according to the `centerInCategory`
     * option. Missing columns are either single points or stacks where the
     * point or points are either missing or null.
     *
     * @internal
     * @function Highcharts.seriesTypes.column#adjustForMissingColumns
     * @param {number} x
     * The x coordinate of the column, left side
     *
     * @param {number} pointWidth
     * The pointWidth, already computed upstream
     *
     * @param {Highcharts.ColumnPoint} point
     * The point instance
     *
     * @param {Highcharts.ColumnMetricsObject} metrics
     * The series-wide column metrics
     *
     * @return {number}
     * The adjusted x position, or the original if not adjusted
     */
    adjustForMissingColumns(x, pointWidth, point, metrics) {
        if (!point.isNull && metrics.columnCount > 1) {
            const visibleSeries = this.xAxis.series
                .filter((s) => s.visible)
                .map((s) => s.index);
            let indexInCategory = 0, totalInCategory = 0;
            // Loop over all the stacks on the Y axis. When stacking is enabled,
            // these are real point stacks. When stacking is not enabled, but
            // `centerInCategory` is true, there is one stack handling the
            // grouping of points in each category. This is done in the
            // `setGroupedPoints` function.
            objectEach(this.xAxis.stacking?.stacks, (stack) => {
                const points = typeof point.x === 'number' ?
                    stack[point.x.toString()]?.points :
                    void 0, pointValues = points?.[this.index], yStackMap = {};
                // Look for the index
                if (points && isArray(pointValues)) {
                    let baseIndex = this.index;
                    // If there are multiple points with the same X then
                    // gather all series in category, and assign index
                    const seriesIndexes = Object
                        .keys(points)
                        .filter((pointKey) => 
                    // Filter out duplicate X's
                    !pointKey.match(',') &&
                        // Filter out null points
                        points[pointKey] &&
                        points[pointKey].length > 1)
                        .map(parseFloat)
                        .filter((index) => visibleSeries.indexOf(index) !== -1)
                        // When the series `stack` option is defined, assign
                        // all subsequent column of the same stack to the
                        // same index as the base column of the stack, then
                        // filter out the original series index so that
                        // `seriesIndexes` is shortened to the amount of
                        // stacks, not the amount of series (#20550).
                        .filter((index) => {
                        const otherOptions = this.chart.series[index]
                            .options, yStack = otherOptions.stacking &&
                            otherOptions.stack;
                        if (defined(yStack)) {
                            if (isNumber(yStackMap[yStack])) {
                                if (baseIndex === index) {
                                    baseIndex = yStackMap[yStack];
                                }
                                return false;
                            }
                            yStackMap[yStack] = index;
                        }
                        return true;
                    })
                        .sort((a, b) => b - a);
                    indexInCategory = seriesIndexes.indexOf(baseIndex);
                    totalInCategory = seriesIndexes.length;
                }
            });
            indexInCategory = this.xAxis.reversed ?
                totalInCategory - 1 - indexInCategory : indexInCategory;
            // Compute the adjusted x position
            const boxWidth = (totalInCategory - 1) * metrics.paddedWidth +
                pointWidth;
            x = (point.plotX || 0) + boxWidth / 2 - pointWidth -
                indexInCategory * metrics.paddedWidth;
        }
        return x;
    }
    /**
     * Translate each point to the plot area coordinate system and find
     * shape positions
     *
     * @internal
     * @function Highcharts.seriesTypes.column#translate
     */
    translate() {
        const series = this, chart = series.chart, options = series.options, 
        // For points without graphics (null points) this value is used
        // to reserve space around the point such that:
        //      - normal/null points are spaced similarly,
        //      - focus borders of null points are like those of "0" points
        // This ensures consistent dimensions between null/normal points.
        dense = series.dense =
            series.closestPointRange * series.xAxis.transA < 2, borderWidth = series.borderWidth =
            options.borderWidth ?? (dense ? 0 : 1), // #3635
        xAxis = series.xAxis, yAxis = series.yAxis, threshold = options.threshold, minPointLength = options.minPointLength ?? 5, metrics = series.getColumnMetrics(), seriesPointWidth = metrics.width, seriesXOffset = series.pointXOffset = metrics.offset, dataMin = series.dataMin, dataMax = series.dataMax, translatedThreshold = series.translatedThreshold =
            yAxis.getThreshold(threshold);
        // Postprocessed for border width
        let seriesBarW = series.barW =
            Math.max(seriesPointWidth, 1 + 2 * borderWidth);
        // When the pointPadding is 0, we want the columns to be packed
        // tightly, so we allow individual columns to have individual sizes.
        // When pointPadding is greater, we strive for equal-width columns
        // (#2694).
        if (options.pointPadding && options.crisp) {
            seriesBarW = Math.ceil(seriesBarW);
        }
        Series_Series.prototype.translate.apply(series);
        // Record the new values
        series.points.concat(series.condemnedPoints).forEach(function (point) {
            const yBottom = point.yBottom ?? translatedThreshold, safeDistance = 999 + Math.abs(yBottom), plotX = point.plotX || 0, 
            // Don't draw too far outside plot area (#1303, #2241,
            // #4264)
            plotY = clamp(point.plotY, -safeDistance, yAxis.len + safeDistance);
            let up, barY = Math.min(plotY, yBottom), barH = Math.max(plotY, yBottom) - barY, pointWidth = seriesPointWidth, barX = plotX + seriesXOffset, barW = seriesBarW;
            // Handle options.minPointLength
            if (minPointLength && Math.abs(barH) < minPointLength) {
                barH = minPointLength;
                up = (!yAxis.reversed && !point.negative) ||
                    (yAxis.reversed && point.negative);
                // Reverse zeros if there's no positive value in the series
                // in visible range (#7046)
                if (isNumber(threshold) &&
                    isNumber(dataMax) &&
                    point.y === threshold &&
                    dataMax <= threshold &&
                    // And if there's room for it (#7311)
                    (yAxis.min || 0) < threshold &&
                    // If all points are the same value (i.e zero) not draw
                    // as negative points (#10646), but only if there's room
                    // for it (#14876)
                    (dataMin !== dataMax || (yAxis.max || 0) <= threshold)) {
                    up = !up;
                    point.negative = !point.negative;
                }
                // If stacked...
                barY = (Math.abs(barY - translatedThreshold) > minPointLength ?
                    // ...keep position
                    yBottom - (up ? minPointLength : 0) :
                    // #1485, #4051
                    translatedThreshold -
                        (up ? minPointLength : 0));
            }
            // Handle point.options.pointWidth
            // @todo Handle grouping/stacking too. Calculate offset properly
            if (defined(point.options.pointWidth)) {
                pointWidth = barW =
                    Math.ceil(point.options.pointWidth);
                barX -= Math.round((pointWidth - seriesPointWidth) / 2);
            }
            // Adjust for null or missing points
            if (options.centerInCategory) {
                barX = series.adjustForMissingColumns(barX, pointWidth, point, metrics);
            }
            // Cache for access in polar
            point.barX = barX;
            point.pointWidth = pointWidth;
            // Fix the tooltip on center of grouped columns (#1216, #424,
            // #3648)
            point.tooltipPos = chart.inverted ?
                [
                    clamp(yAxis.len + yAxis.pos - chart.plotLeft - plotY, yAxis.pos - chart.plotLeft, yAxis.len + yAxis.pos - chart.plotLeft),
                    xAxis.len + xAxis.pos - chart.plotTop - barX - barW / 2,
                    barH
                ] :
                [
                    xAxis.left - chart.plotLeft + barX + barW / 2,
                    clamp(plotY + yAxis.pos -
                        chart.plotTop, yAxis.pos - chart.plotTop, yAxis.len + yAxis.pos - chart.plotTop),
                    barH
                ];
            // Register shape type and arguments to be used in drawPoints. Allow
            // `shapeType` defined on `pointClass` level.
            point.shapeType = series.pointClass.prototype.shapeType ||
                'roundedRect';
            point.shapeArgs = series.crispCol(barX, 
            // #3169, drilldown from null must have a position to work from.
            // #6585, dataLabel should be placed on xAxis, not floating in
            // the middle of the chart.
            barY, barW, point.isNull ? 0 : barH);
        });
        // Fire a specific event after column translate. We could instead apply
        // all the column logic in an `afterTranslate` event handler, but there
        // are so many other series types that use the column translation, that
        // it is more convenient to have a specific event for it.
        fireEvent(this, 'afterColumnTranslate');
    }
    /**
     * Columns have no graph
     *
     * @internal
     * @function Highcharts.seriesTypes.column#drawGraph
     */
    drawGraph() {
        this.group[this.dense ? 'addClass' : 'removeClass']('highcharts-dense-data');
    }
    /**
     * Get presentational attributes
     *
     * @internal
     * @function Highcharts.seriesTypes.column#pointAttribs
     */
    pointAttribs(point, state) {
        const options = this.options, p2o = this.pointAttrToOptions || {}, strokeOption = p2o.stroke || 'borderColor', strokeWidthOption = p2o['stroke-width'] || 'borderWidth';
        let stateOptions, zone, brightness, fill = (point && point.color) || this.color, 
        // Set to fill when borderColor null:
        stroke = ((point && point[strokeOption]) ||
            options[strokeOption] ||
            fill), dashstyle = (point && point.options.dashStyle) || options.dashStyle, strokeWidth = (point?.[strokeWidthOption]) ??
            options[strokeWidthOption] ??
            this[strokeWidthOption] ?? 1, opacity = (point?.isNull && options.nullInteraction) ?
            0 :
            (point?.opacity ?? options.opacity ?? 1);
        // Handle zone colors
        if (point && this.zones.length) {
            zone = point.getZone();
            // When zones are present, don't use point.color (#4267).
            // Changed order (#6527), added support for colorAxis (#10670)
            fill = (point.options.color ||
                (zone && (zone.color || point.nonZonedColor)) ||
                this.color);
            if (zone) {
                stroke = zone.borderColor || stroke;
                dashstyle = zone.dashStyle || dashstyle;
                strokeWidth = zone.borderWidth || strokeWidth;
            }
        }
        // Select or hover states
        if (state && point) {
            stateOptions = merge(options.states[state], 
            // #6401
            point.options.states?.[state] || {});
            brightness = stateOptions.brightness;
            fill =
                stateOptions.color || (typeof brightness !== 'undefined' &&
                    ColumnSeries_color(fill)
                        .brighten(stateOptions.brightness)
                        .get()) || fill;
            stroke = stateOptions[strokeOption] || stroke;
            strokeWidth =
                stateOptions[strokeWidthOption] || strokeWidth;
            dashstyle = stateOptions.dashStyle || dashstyle;
            opacity = pick(stateOptions.opacity, opacity);
        }
        const ret = {
            fill: fill,
            stroke: stroke,
            'stroke-width': strokeWidth,
            opacity: point?.condemned ? 0 : opacity
        };
        if (dashstyle) {
            ret.dashstyle = dashstyle;
        }
        return ret;
    }
    /**
     * Draw the columns. For bars, the series.group is rotated, so the same
     * coordinates apply for columns and bars. This method is inherited by
     * scatter series.
     *
     * @internal
     * @function Highcharts.seriesTypes.column#drawPoints
     */
    drawPoints(points) {
        points || (points = this.points.concat(this.condemnedPoints));
        const series = this, chart = this.chart, options = series.options, nullInteraction = options.nullInteraction, { styledMode, renderer } = chart, animationLimit = options.animationLimit || 250;
        let shapeArgs;
        // Draw the columns
        points.forEach(function (point) {
            const plotY = point.plotY;
            let graphic = point.graphic, shouldUpdate = !!graphic, verb = graphic && chart.pointCount < animationLimit ?
                'animate' : 'attr';
            if (isNumber(plotY) && (point.y !== null || nullInteraction)) {
                shapeArgs = point.shapeArgs;
                // When updating a series between 2d and 3d or cartesian and
                // polar, the shape type changes.
                if (graphic && point.hasNewShapeType()) {
                    graphic = graphic.destroy();
                }
                if (!graphic) {
                    let initialAttr = shapeArgs;
                    // New points fade in from old axis position
                    if (point.origin &&
                        chart.pointCount < animationLimit) {
                        initialAttr = merge(shapeArgs, point.getOrigin(point.origin, shapeArgs));
                        if (!styledMode) {
                            initialAttr.opacity = 0;
                        }
                        shouldUpdate = true;
                        verb = 'animate';
                    }
                    // Create new graphic
                    point.graphic = graphic =
                        renderer[point.shapeType](initialAttr).add(point.group || series.group);
                }
                // Update existing graphic, either because it pre-existed, or
                // because we created it in a temporary position
                if (shouldUpdate) {
                    graphic[verb](merge(shapeArgs));
                }
                // Presentational
                if (!styledMode) {
                    graphic[verb](series.pointAttribs(point, (point.selected && 'select')))
                        .shadow(point.allowShadow !== false && options.shadow);
                }
                graphic
                    .addClass(point.getClassName(), true)
                    .attr({
                    visibility: point.visible ? 'inherit' : 'hidden'
                });
            }
            else if (graphic) {
                point.graphic = graphic.destroy(); // #1269
            }
        });
    }
    /**
     * Draw the tracker for a point.
     * @internal
     */
    drawTracker(points = this.points) {
        const series = this, chart = series.chart, pointer = chart.pointer, onMouseOver = function (e) {
            pointer?.normalize(e);
            const point = pointer?.getPointFromEvent(e);
            // Undefined on graph in scatter chart
            if (pointer &&
                point &&
                series.options.enableMouseTracking &&
                (
                // Run point events only for points inside plot area, #21136
                chart.isInsidePlot(e.chartX - chart.plotLeft, e.chartY - chart.plotTop, {
                    visiblePlotOnly: true
                }) ||
                    // Allow specific series types to extend interaction
                    // outside the plot area, #24096
                    (series.allowOutsidePlotInteraction &&
                        pointer?.inClass(e.target, 'highcharts-point')) ||
                    pointer?.inClass(e.target, 'highcharts-data-label'))) {
                pointer.isDirectTouch = true;
                point.onMouseOver(e);
            }
        };
        let dataLabels;
        // Add reference to the point
        points.forEach(function (point) {
            dataLabels = (isArray(point.dataLabels) ?
                point.dataLabels :
                (point.dataLabel ? [point.dataLabel] : []));
            if (point.graphic) {
                point.graphic.element.point = point;
            }
            dataLabels.forEach(function (dataLabel) {
                (dataLabel.div || dataLabel.element).point = point;
            });
        });
        // Add the event listeners, we need to do this only once
        if (!series._hasTracking) {
            series.trackerGroups?.reduce((acc, key) => {
                if (key === 'dataLabelsGroup') {
                    acc.push(...(series.dataLabelsGroups || []));
                }
                else {
                    acc.push(series[key]);
                }
                return acc;
            }, []).forEach((group) => {
                if (!group) {
                    // Skip undefined
                    return;
                }
                group.addClass('highcharts-tracker')
                    .on('mouseover', onMouseOver)
                    .on('mouseout', function (e) {
                    pointer?.onTrackerMouseOut(e);
                })
                    .on('touchstart', onMouseOver);
                if (!chart.styledMode && series.options.cursor) {
                    group.css({ cursor: series.options.cursor });
                }
            });
            series._hasTracking = true;
        }
        fireEvent(this, 'afterDrawTracker');
    }
    /**
     * Remove this series from the chart
     *
     * @internal
     * @function Highcharts.seriesTypes.column#remove
     */
    remove() {
        const series = this, chart = series.chart;
        // Column and bar series affects other series of the same type
        // as they are either stacked or grouped
        if (chart.hasRendered) {
            chart.series.forEach(function (otherSeries) {
                if (otherSeries.type === series.type) {
                    otherSeries.isDirty = true;
                }
            });
        }
        Series_Series.prototype.remove.apply(series, arguments);
    }
}
/* *
 *
 *  Static Properties
 *
 * */
ColumnSeries.defaultOptions = merge(Series_Series.defaultOptions, Column_ColumnSeriesDefaults);
extend(ColumnSeries.prototype, {
    // When tooltip is not shared, this series (and derivatives) requires
    // direct touch/hover. KD-tree does not apply.
    directTouch: true,
    getSymbol: ColumnSeries_noop,
    // Use separate negative stacks, unlike area stacks where a negative
    // point is subtracted from previous (#1910)
    negStacks: true,
    trackerGroups: ['group', 'dataLabelsGroup']
});
Series_SeriesRegistry.registerSeriesType('column', ColumnSeries);
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Column_ColumnSeries = (ColumnSeries);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * Adjusted width and x offset of the columns for grouping.
 *
 * @internal
 * @interface Highcharts.ColumnMetricsObject
 */ /**
* Width of the columns.
* @name Highcharts.ColumnMetricsObject#width
* @type {number}
*/ /**
* Offset of the columns.
* @name Highcharts.ColumnMetricsObject#offset
* @type {number}
*/
''; // Detach doclets above

;// ./code/es-modules/Core/Series/DataLabel.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { getDeferredAnimation: DataLabel_getDeferredAnimation } = AnimationUtilities;

const { format: DataLabel_format } = Core_Templating;

/* *
 *
 *  Composition
 *
 * */
var DataLabel;
(function (DataLabel) {
    /* *
     *
     *  Declarations
     *
     * */
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Check if this series has data labels, either a series-level setting, or
     * individual. In case of individual point labels, this method is overridden
     * to always return true.
     * @internal
     */
    function hasDataLabels() {
        return mergedDataLabelOptions(this)
            .some((o) => o?.enabled);
    }
    /**
     * Align each individual data label.
     * @internal
     */
    function alignDataLabel(point, dataLabel, options, alignTo, isNew) {
        const series = this, { chart } = this, inverted = this.isCartesian && chart.inverted, { condemned, origin, plotX, plotY } = point, { crop = true, distance, overflow = 'justify', rotation = 0 } = options, alignFactor = getAlignFactor(options.align), verticalAlignFactor = getAlignFactor(options.verticalAlign), justify = rotation === 0 && !condemned && overflow === 'justify', pos = point.pos(), isInsidePlot = defined(plotX) &&
            defined(plotY) &&
            chart.isInsidePlot(plotX, Math.round(plotY), {
                inverted,
                paneCoordinates: true,
                series
            });
        // Math.round for rounding errors (#2683), alignTo to allow column
        // labels (#2700)
        let visible = this.visible &&
            point.visible &&
            defined(plotX) &&
            (series.forceDL ||
                condemned || // Allow it to fade out
                isInsidePlot ||
                (
                // If the data label is inside the align box, it is enough
                // that parts of the align box is inside the plot area
                // (#12370). When stacking, it is always inside regardless
                // of the option (#15148).
                pick(options.inside, !!this.options.stacking) &&
                    alignTo &&
                    chart.isInsidePlot(plotX, inverted ?
                        alignTo.x + 1 :
                        alignTo.y + alignTo.height - 1, {
                        inverted,
                        paneCoordinates: true,
                        series
                    })));
        if (pos) {
            const bBox = dataLabel.getBBox(), unrotatedbBox = dataLabel.getBBox(void 0, 0);
            // The alignment box is a singular point
            alignTo = extend({
                x: pos[0],
                y: Math.round(pos[1]),
                width: 0,
                height: 0
            }, alignTo || {});
            // Align to plot edges
            if (options.alignTo === 'plotEdges' && series.isCartesian) {
                alignTo[inverted ? 'x' : 'y'] = 0;
                alignTo[inverted ? 'width' : 'height'] = this.yAxis?.len || 0;
            }
            // Add the text size for alignment calculation
            extend(options, {
                width: bBox.width,
                height: bBox.height
            });
            // Apply the distance
            let distX = 0, distY = 0;
            if (isNumber(distance) && !options.inside) {
                distX = distance * (1 - 2 * alignFactor);
                distY = distance * (1 - 2 * verticalAlignFactor);
            }
            // Align the label to the adjusted box with for unrotated bBox due
            // to rotationOrigin, which is based on unrotated label
            dataLabel.align(merge(options, {
                x: (options.x || 0) + distX,
                y: (options.y || 0) + distY,
                width: unrotatedbBox.width,
                height: unrotatedbBox.height
            }), false, alignTo, false);
            // Record for later use in justifyDataLabel
            dataLabel.distX = distX;
            dataLabel.distY = distY;
            // Modify for rotation
            dataLabel.alignAttr.x += alignFactor *
                (unrotatedbBox.width - bBox.width);
            dataLabel.alignAttr.y += verticalAlignFactor *
                (unrotatedbBox.height - bBox.height);
            // The placement attributes before justifyDataLabel correction
            const x = dataLabel.alignAttr.x +
                (bBox.width - unrotatedbBox.width) / 2, y = dataLabel.alignAttr.y +
                (bBox.height - unrotatedbBox.height) / 2;
            // Handle the data label position for entrance animation. Simply
            // offset the computed position by the difference between current
            // position and the origin.
            if (origin) {
                const originPos = point.pos(false, origin.x, origin.y);
                if (originPos) {
                    const offset = [
                        originPos[0] - pos[0],
                        originPos[1] - pos[1]
                    ];
                    if (series.is('column') || point.plotHigh) {
                        offset[inverted ? 0 : 1] = 0;
                    }
                    dataLabel.attr({
                        x: x + offset[0],
                        y: y + offset[1],
                        // Start at non-zero to avoid overlapping logic treating
                        // it as hidden
                        opacity: 0.01
                    });
                    dataLabel.placed = true;
                    isNew = false;
                }
            }
            // Set the position before potential justification
            const placeAttribs = {
                'text-align': dataLabel.alignAttr['text-align'] || 'center',
                x,
                y,
                rotationOriginX: (dataLabel.width || 0) / 2,
                rotationOriginY: (dataLabel.height || 0) / 2
            };
            if (condemned || !visible) {
                placeAttribs.opacity = 0;
            }
            dataLabel[dataLabel.placed ? 'animate' : 'attr'](placeAttribs);
            // Uncomment this block to visualize the bounding boxes used for
            // determining visibility
            // chart.renderer.rect(
            //     (dataLabel.alignAttr.x || 0) + chart.plotLeft,
            //     (dataLabel.alignAttr.y || 0) + chart.plotTop,
            //     bBox.width,
            //     bBox.height
            // ).attr({
            //     stroke: 'rgba(0, 0, 0, 0.3)',
            //     'stroke-width': 1,
            //     zIndex: 20
            // }).add();
            // chart.renderer.circle(
            //     chart.plotLeft + pick(dataLabel.alignAttr.x, 0),
            //     chart.plotTop + pick(dataLabel.alignAttr.y, 0),
            //     2
            // ).attr({
            //     fill: 'red',
            //     zIndex: 20
            // }).add();
            if (justify && visible && alignTo.height >= 0) { // #8830
                this.justifyDataLabel(dataLabel, options, dataLabel.alignAttr, bBox, alignTo, isNew);
            }
            else if (crop && !condemned) {
                const { x, y } = dataLabel.alignAttr, correction = 1;
                // Check if the dataLabel should be visible.
                visible =
                    chart.isInsidePlot(x, y, {
                        paneCoordinates: true,
                        series
                    }) &&
                        chart.isInsidePlot(x + bBox.width - correction, y + bBox.height - correction, {
                            paneCoordinates: true,
                            series
                        });
            }
            // When we're using a shape, make it possible with a connector or an
            // arrow pointing to this point
            if (options.shape && !rotation) {
                dataLabel[isNew ? 'attr' : 'animate']({
                    anchorX: pos[0],
                    anchorY: pos[1]
                });
            }
        }
        // Show or hide based on the final aligned position
        dataLabel[isNew ? 'attr' : 'animate']({
            visibility: visible ? 'inherit' : 'hidden'
        });
        dataLabel.placed = visible;
    }
    /**
     * Handle the dataLabels.filter option.
     * @internal
     */
    function applyFilter(point, options) {
        const filter = options.filter;
        if (filter) {
            const op = filter.operator, prop = point[filter.property], val = filter.value;
            if ((op === '>' && prop > val) ||
                (op === '<' && prop < val) ||
                (op === '>=' && prop >= val) ||
                (op === '<=' && prop <= val) ||
                (op === '==' && prop == val) || // eslint-disable-line eqeqeq
                (op === '===' && prop === val) ||
                (op === '!=' && prop != val) || // eslint-disable-line eqeqeq
                (op === '!==' && prop !== val)) {
                return true;
            }
            return false;
        }
        return true;
    }
    /**
     * Compose the data label composition onto a series class.
     *
     * @internal
     * @function compose
     *
     * @param {Highcharts.Series} SeriesClass
     * The series class to compose onto.
     *
     * @return {void}
     */
    function compose(SeriesClass) {
        const seriesProto = SeriesClass.prototype;
        if (!seriesProto.initDataLabels) {
            seriesProto.initDataLabels = initDataLabels;
            seriesProto.initDataLabelsGroup = initDataLabelsGroup;
            seriesProto.alignDataLabel = alignDataLabel;
            seriesProto.drawDataLabels = drawDataLabels;
            seriesProto.justifyDataLabel = justifyDataLabel;
            seriesProto.mergeArrays = mergeArrays;
            seriesProto.hasDataLabels = hasDataLabels;
        }
    }
    DataLabel.compose = compose;
    /**
     * Create the SVGElement group for dataLabels.
     *
     * @internal
     * @function initDataLabelsGroup
     *
     * @param {number} index
     * The index of the data labels group.
     * @param {Highcharts.DataLabelOptions} [dataLabelsOptions]
     * Data label options for the group.
     *
     * @return {Highcharts.SVGElement}
     * The SVGElement group.
     */
    function initDataLabelsGroup(index, dataLabelsOptions) {
        fireEvent(this, 'initDataLabelsGroup', {
            index,
            zIndex: dataLabelsOptions?.zIndex
        });
        // Existing group or first time
        this.dataLabelsGroup = this.dataLabelsGroups?.[index];
        const group = this.plotGroup('dataLabelsGroup', 'data-labels', this.hasRendered ? 'inherit' : 'hidden', // #5133, #10220
        dataLabelsOptions?.zIndex ?? 6, this.dataLabelsParentGroups?.[index]);
        this.dataLabelsGroups || (this.dataLabelsGroups = []);
        this.dataLabelsGroups[index] = group;
        // Keep reference to the 1st group
        this.dataLabelsGroup = this.dataLabelsGroups[0];
        return group;
    }
    /**
     * Init the data labels with the correct animation.
     *
     * @internal
     * @function initDataLabels
     *
     * @param {number} index
     * The index of the data labels group.
     * @param {Highcharts.AnimationOptions} animationConfig
     * The animation options.
     * @param {Highcharts.DataLabelOptions} [dataLabelsOptions]
     * Data label options for the group.
     *
     * @return {Highcharts.SVGElement}
     * The SVGElement group.
     */
    function initDataLabels(index, animationConfig, dataLabelsOptions) {
        const series = this, hasRendered = !!series.hasRendered;
        // Create a separate group for the data labels to avoid rotation
        const dataLabelsGroup = this.initDataLabelsGroup(index, dataLabelsOptions)
            .attr({ opacity: +hasRendered }); // #3300
        if (!hasRendered && dataLabelsGroup) {
            if (series.visible) { // #2597, #3023, #3024
                dataLabelsGroup.show();
            }
            if (series.options.animation) {
                dataLabelsGroup.animate({ opacity: 1 }, animationConfig);
            }
            else {
                dataLabelsGroup.attr({ opacity: 1 });
            }
        }
        return dataLabelsGroup;
    }
    /**
     * Draw the data labels.
     * @internal
     */
    function drawDataLabels(points) {
        points = points || this.points;
        const series = this, chart = series.chart, seriesOptions = series.options, renderer = chart.renderer, { backgroundColor, plotBackgroundColor } = chart.options.chart, contrastColor = renderer.getContrast((isString(plotBackgroundColor) && plotBackgroundColor) ||
            (isString(backgroundColor) && backgroundColor) ||
            'var(--highcharts-background-color)'), groupByIndex = [], seriesDlOptions = mergedDataLabelOptions(series);
        // Resolve the animation
        const { animation, defer } = seriesDlOptions[0], animationConfig = defer ?
            DataLabel_getDeferredAnimation(chart, animation, series) :
            { defer: 0, duration: 0 };
        fireEvent(this, 'drawDataLabels');
        if (series.hasDataLabels?.()) {
            // Make the labels for each point
            points.concat(series.condemnedPoints).forEach((point) => {
                const dataLabels = point.dataLabels || [], pointColor = point.color || series.color, 
                // Merge in series options for the point.
                // @note
                // dataLabelAttribs (like pointAttribs) would eradicate the
                // need for dlOptions, and simplify the section below.
                pointOptions = splat(mergeArrays(seriesDlOptions, 
                // The dlOptions prop is used in treemap
                point.dlOptions || point.options?.dataLabels));
                // Handle each individual data label for this point
                pointOptions.forEach((labelOptions, i) => {
                    // Options for one dataLabel
                    const labelEnabled = (labelOptions.enabled &&
                        (point.visible || point.dataLabelOnHidden) &&
                        // #2282, #4641, #7112, #10049
                        (!point.isNull || point.dataLabelOnNull) &&
                        applyFilter(point, labelOptions)), { backgroundColor, borderColor, distance, style = {} } = labelOptions, padding = splat(labelOptions.padding || 0);
                    let formatString, labelText, rotation, attr = {}, dataLabel = dataLabels[i], isNew = !dataLabel, labelBgColor;
                    if (labelEnabled) {
                        // Create individual options structure that can be
                        // extended without affecting others
                        formatString = pick(labelOptions[point.formatPrefix + 'Format'], labelOptions.format);
                        labelText = defined(formatString) ?
                            DataLabel_format(formatString, point, chart) :
                            (labelOptions[point.formatPrefix + 'Formatter'] ||
                                labelOptions.formatter).call(point, labelOptions, point);
                        rotation = labelOptions.rotation;
                        if (!chart.styledMode) {
                            // Determine the color
                            style.color = pick(labelOptions.color, style.color, isString(series.color) ? series.color : void 0, 'var(--highcharts-neutral-color-100)');
                            // Get automated contrast color
                            if (style.color === 'contrast') {
                                if (backgroundColor !== 'none') {
                                    labelBgColor = backgroundColor;
                                }
                                point.contrastColor = renderer.getContrast((labelBgColor !== 'auto' &&
                                    labelBgColor !== 'contrast' &&
                                    isString(labelBgColor) &&
                                    labelBgColor) ||
                                    (isString(pointColor) ? pointColor : ''));
                                style.color = ((labelBgColor &&
                                    labelBgColor !== 'contrast') || // #20007
                                    labelOptions.inside ||
                                    pInt(distance || 0) < 0 ||
                                    seriesOptions.stacking) ?
                                    point.contrastColor :
                                    contrastColor;
                            }
                            else {
                                delete point.contrastColor;
                            }
                            if (seriesOptions.cursor) {
                                style.cursor = seriesOptions.cursor;
                            }
                        }
                        attr = {
                            r: labelOptions.borderRadius ?? 3,
                            rotation,
                            padding: padding[0],
                            paddingLeft: padding[3 % padding.length],
                            paddingRight: padding[1 % padding.length],
                            zIndex: 1
                        };
                        if (!chart.styledMode) {
                            attr.fill = backgroundColor === 'auto' ?
                                point.color :
                                backgroundColor;
                            attr.stroke = borderColor === 'auto' ?
                                point.color :
                                borderColor;
                            attr['stroke-width'] = labelOptions.borderWidth;
                        }
                        // Remove unused attributes (#947)
                        objectEach(attr, (val, name) => {
                            if (typeof val === 'undefined') {
                                delete attr[name];
                            }
                        });
                    }
                    // If the point is outside the plot area, or the label
                    // changes properties that we cannot change, destroy it and
                    // build a new one below. #678, #820.
                    if (dataLabel && (!labelEnabled ||
                        !defined(labelText) ||
                        // Changed useHTML value
                        !!(dataLabel.div ||
                            dataLabel.text?.foreignObject) !== !!labelOptions.useHTML ||
                        (
                        // Change from no rotation to rotation and
                        // vice versa. Don't use defined() because
                        // rotation = 0 means also rotation = undefined
                        (!dataLabel.rotation ||
                            !labelOptions.rotation) &&
                            dataLabel.rotation !== labelOptions.rotation))) {
                        dataLabel = void 0;
                        isNew = true;
                    }
                    // Individual labels are disabled if the are explicitly
                    // disabled in the point options, or if they fall outside
                    // the plot area.
                    if (labelEnabled &&
                        defined(labelText) &&
                        labelText !== '') {
                        if (!dataLabel) {
                            // Create new label element
                            dataLabel = renderer.label(labelText, 0, 0, labelOptions.shape, void 0, void 0, labelOptions.useHTML, void 0, 'data-label');
                            dataLabel.addClass(' highcharts-data-label-color-' +
                                point.colorIndex +
                                ' ' + (labelOptions.className || '') +
                                ( // #3398
                                labelOptions.useHTML ?
                                    ' highcharts-tracker' :
                                    ''));
                        }
                        else {
                            // Use old element and just update text
                            attr.text = labelText;
                        }
                        // Store data label options for later access
                        if (dataLabel) {
                            dataLabel.options = labelOptions;
                            dataLabel.attr(attr);
                            if (!chart.styledMode) {
                                // Styles must be applied before add in order to
                                // read text bounding box
                                dataLabel.css(style).shadow(labelOptions.shadow);
                            }
                            else if (style.width) {
                                // In styled mode with a width property set,
                                // the width should be applied to the
                                // dataLabel. (#20499). These properties affect
                                // layout and must be applied also in styled
                                // mode.
                                dataLabel.css({
                                    width: style.width,
                                    textOverflow: style.textOverflow,
                                    whiteSpace: style.whiteSpace
                                });
                            }
                            fireEvent(dataLabel, 'beforeAddingDataLabel', { labelOptions, point });
                            // On the first occurrence, create a dataLabelsGroup
                            // for each data labels config (#24626)
                            const dataLabelsGroup = groupByIndex[i] = (groupByIndex[i] || this.initDataLabels(i, animationConfig, labelOptions));
                            if (!dataLabel.added) {
                                dataLabel.add(dataLabelsGroup);
                            }
                            // Now the data label is created and placed at 0,0,
                            // so we need to align it
                            series.alignDataLabel(point, dataLabel, labelOptions, void 0, isNew);
                            dataLabel.isActive = true;
                            if (dataLabels[i] && dataLabels[i] !== dataLabel) {
                                dataLabels[i].destroy();
                            }
                            dataLabels[i] = dataLabel;
                        }
                    }
                });
                // Destroy and remove the inactive ones
                let j = dataLabels.length;
                while (j--) {
                    // The item can be undefined if a disabled data label is
                    // succeeded by an enabled one (#19457)
                    if (!dataLabels[j]?.isActive) {
                        dataLabels[j]?.destroy();
                        dataLabels.splice(j, 1);
                    }
                    else {
                        dataLabels[j].isActive = false;
                    }
                }
                // Write back
                point.dataLabel = dataLabels[0];
                point.dataLabels = dataLabels;
            });
        }
        fireEvent(this, 'afterDrawDataLabels');
    }
    /**
     * If data labels fall partly outside the plot area, align them back in, in
     * a way that doesn't hide the point.
     * @internal
     */
    function justifyDataLabel(dataLabel, options, alignAttr, bBox, alignTo, isNew) {
        const chart = this.chart, { align, verticalAlign } = options, { distX = 0, distY = 0 } = dataLabel, padding = dataLabel.box ? 0 : (dataLabel.padding || 0), horizontalAxis = chart.inverted ? this.yAxis : this.xAxis, horizontalAxisShift = horizontalAxis ?
            horizontalAxis.left - chart.plotLeft : 0, verticalAxis = chart.inverted ? this.xAxis : this.yAxis, verticalAxisShift = verticalAxis ?
            verticalAxis.top - chart.plotTop : 0;
        let { x = 0, y = 0 } = options, off, justifiedX, justifiedY;
        // Off left
        off = (alignAttr.x || 0) - distX + padding + horizontalAxisShift;
        if (off < 0) {
            if (align === 'right' && x >= 0) {
                options.align = 'left';
                options.inside = true;
                x -= distX;
            }
            else {
                x -= off;
            }
            justifiedX = true;
        }
        // Off right
        off = (alignAttr.x || 0) + bBox.width - distX - padding +
            horizontalAxisShift;
        if (off > chart.plotWidth) {
            if (align === 'left' && x <= 0) {
                options.align = 'right';
                options.inside = true;
                x -= distX;
            }
            else {
                x += chart.plotWidth - off;
            }
            justifiedX = true;
        }
        // Off top
        off = (alignAttr.y || 0) - distY + padding + verticalAxisShift;
        if (off < 0) {
            if (verticalAlign === 'bottom' && y >= 0) {
                options.verticalAlign = 'top';
                options.inside = true;
                y -= distY;
            }
            else {
                y -= off;
            }
            justifiedY = true;
        }
        // Off bottom
        off = (alignAttr.y || 0) + bBox.height - distY - padding +
            verticalAxisShift;
        if (off > chart.plotHeight) {
            if (verticalAlign === 'top' && y <= 0) {
                options.verticalAlign = 'bottom';
                options.inside = true;
                y -= distY;
            }
            else {
                y += chart.plotHeight - off;
            }
            justifiedY = true;
        }
        if (justifiedX || justifiedY) {
            options.x = justifiedX ? x : x + distX;
            options.y = justifiedY ? y : y + distY;
            dataLabel.placed = !isNew;
            dataLabel.align(options, void 0, alignTo);
        }
        return justifiedX || justifiedY;
    }
    /**
     * Merge two objects that can be arrays. If one of them is an array, the
     * other is merged into each element. If both are arrays, each element is
     * merged by index. If neither are arrays, we use normal merge.
     * @internal
     */
    function mergeArrays(one, two) {
        let res = [], i;
        if (isArray(one) && !isArray(two)) {
            res = one.map(function (el) {
                return merge(el, two);
            });
        }
        else if (isArray(two) && !isArray(one)) {
            res = two.map(function (el) {
                return merge(one, el);
            });
        }
        else if (!isArray(one) && !isArray(two)) {
            res = merge(one, two);
        }
        else if (isArray(one) && isArray(two)) {
            i = Math.max(one.length, two.length);
            while (i--) {
                res[i] = merge(one[i], two[i]);
            }
        }
        return res;
    }
    /**
     * Merge plotOptions and series options for dataLabels.
     * @internal
     */
    function mergedDataLabelOptions(series) {
        const plotOptions = series.chart.options.plotOptions;
        return splat(mergeArrays(mergeArrays(plotOptions?.series?.dataLabels, plotOptions?.[series.type]?.dataLabels), series.options.dataLabels));
    }
})(DataLabel || (DataLabel = {}));
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Series_DataLabel = (DataLabel);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * Callback JavaScript function to format the data label as a string. Note that
 * if a `format` is defined, the format takes precedence and the formatter is
 * ignored.
 *
 * @callback Highcharts.DataLabelsFormatterCallbackFunction
 *
 * @param {Highcharts.Point} this
 * Data label context to format
 *
 * @param {Highcharts.DataLabelsOptions} options
 * [API options](/highcharts/plotOptions.series.dataLabels) of the data label
 *
 * @return {number|string|null|undefined}
 * Formatted data label text
 */
/**
 * Values for handling data labels that flow outside the plot area.
 *
 * @typedef {"allow"|"justify"} Highcharts.DataLabelsOverflowValue
 */
''; // Keeps doclets above in JS file

;// ./code/es-modules/Series/Column/ColumnDataLabel.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */



const { composed: ColumnDataLabel_composed } = Core_Globals;

const { series: ColumnDataLabel_Series } = Series_SeriesRegistry;

/* *
 *
 *  Composition
 *
 * */
var ColumnDataLabel;
(function (ColumnDataLabel) {
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Override the basic data label alignment by adjusting for the position of
     * the column.
     * @internal
     */
    function alignDataLabel(point, dataLabel, dlOptions, alignTo, isNew) {
        const { chart, options } = this, inverted = chart.inverted, xLen = this.xAxis?.len || chart.plotSizeX || 0, yLen = this.yAxis?.len || chart.plotSizeY || 0, 
        // Data label box for alignment
        dlBox = point.dlBox || point.shapeArgs, below = point.below ?? // Range series
            (point.plotY || 0) > (this.translatedThreshold ?? yLen), 
        // Draw it inside the box?
        inside = dlOptions.inside ?? !!options.stacking;
        // Align to the column itself, or the top of it
        if (dlBox) { // Area range uses this method but not alignTo
            alignTo = merge(dlBox);
            // Check for specific overflow and crop conditions (#13240, #22617)
            if (dlOptions.overflow !== 'allow' ||
                dlOptions.crop !== false ||
                options.clip !== false) {
                if (alignTo.y < 0) {
                    alignTo.height += alignTo.y;
                    alignTo.y = 0;
                }
                // If parts of the box overshoots outside the plot area, modify
                // the box to center the label inside
                const overshoot = alignTo.y + alignTo.height - yLen;
                if (overshoot > 0 && overshoot < alignTo.height - 1) {
                    alignTo.height -= overshoot;
                }
            }
            if (inverted) {
                alignTo = {
                    x: yLen - alignTo.y - alignTo.height,
                    y: xLen - alignTo.x - alignTo.width,
                    width: alignTo.height,
                    height: alignTo.width
                };
            }
            // Compute the alignment box
            if (!inside) {
                if (inverted) {
                    alignTo.x += below ? 0 : alignTo.width;
                    alignTo.width = 0;
                }
                else {
                    alignTo.y += below ? alignTo.height : 0;
                    alignTo.height = 0;
                }
            }
        }
        // When alignment is undefined (typically columns and bars), display the
        // individual point below or above the point depending on the threshold
        dlOptions.align ?? (dlOptions.align = !inverted || inside ?
            'center' : below ? 'right' : 'left');
        dlOptions.verticalAlign ?? (dlOptions.verticalAlign = inverted || inside ?
            'middle' : below ? 'top' : 'bottom');
        // Call the parent method
        ColumnDataLabel_Series.prototype.alignDataLabel.call(this, point, dataLabel, dlOptions, alignTo, isNew);
        // If label was justified and we have contrast, set it:
        if (dlOptions.inside && point.contrastColor) {
            dataLabel.css({
                color: point.contrastColor
            });
        }
    }
    /** @internal */
    function compose(ColumnSeriesClass) {
        Series_DataLabel.compose(ColumnDataLabel_Series);
        if (pushUnique(ColumnDataLabel_composed, 'ColumnDataLabel')) {
            ColumnSeriesClass.prototype.alignDataLabel = alignDataLabel;
        }
    }
    ColumnDataLabel.compose = compose;
})(ColumnDataLabel || (ColumnDataLabel = {}));
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Column_ColumnDataLabel = (ColumnDataLabel);

;// ./code/es-modules/Series/Bar/BarSeries.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */




/* *
 *
 *  Class
 *
 * */
/**
 * Bar series type.
 *
 * @internal
 * @class
 * @name Highcharts.seriesTypes.bar
 *
 * @augments Highcharts.Series
 */
class BarSeries extends Column_ColumnSeries {
}
/* *
 *
 *  Static Properties
 *
 * */
/**
 * A bar series is a special type of column series where the columns are
 * horizontal.
 *
 * @sample highcharts/demo/bar-chart/
 *         Bar chart
 *
 * @extends      plotOptions.column
 * @product      highcharts
 * @optionparent plotOptions.bar
 */
BarSeries.defaultOptions = merge(Column_ColumnSeries.defaultOptions, {
// Nothing here yet
});
extend(BarSeries.prototype, {
    inverted: true
});
Series_SeriesRegistry.registerSeriesType('bar', BarSeries);
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
/* harmony default export */ const Bar_BarSeries = ((/* unused pure expression or super */ null && (BarSeries)));
/* *
 *
 *  API Options
 *
 * */
/**
 * A `bar` series. If the [type](#series.bar.type) option is not specified,
 * it is inherited from [chart.type](#chart.type).
 *
 * @extends   series,plotOptions.bar
 * @excluding connectNulls, dashStyle, dataParser, dataURL, gapSize, gapUnit,
 *            linecap, lineWidth, marker, connectEnds, step
 * @product   highcharts
 * @apioption series.bar
 */
/**
 * An array of data points for the series. For the `bar` series type,
 * points can be given in the following ways:
 *
 * 1. An array of numerical values. In this case, the numerical values will be
 *    interpreted as `y` options. The `x` values will be automatically
 *    calculated, either starting at 0 and incremented by 1, or from
 *    `pointStart` and `pointInterval` given in the series options. If the axis
 *    has categories, these will be used. Example:
 *    ```js
 *    data: [0, 5, 3, 5]
 *    ```
 *
 * 2. An array of arrays with 2 values. In this case, the values correspond to
 *    `x,y`. If the first value is a string, it is applied as the name of the
 *    point, and the `x` value is inferred.
 *    ```js
 *    data: [
 *        [0, 5],
 *        [1, 10],
 *        [2, 3]
 *    ]
 *    ```
 *
 * 3. An array of objects with named values. The following snippet shows only a
 *    few settings, see the complete options set below. If the total number of
 *    data points exceeds the series'
 *    [turboThreshold](#series.bar.turboThreshold), this option is not
 *    available.
 *    ```js
 *    data: [{
 *        x: 1,
 *        y: 1,
 *        name: "Point2",
 *        color: "#00FF00"
 *    }, {
 *        x: 1,
 *        y: 10,
 *        name: "Point1",
 *        color: "#FF00FF"
 *    }]
 *    ```
 *
 * @sample {highcharts} highcharts/chart/reflow-true/
 *         Numerical values
 * @sample {highcharts} highcharts/series/data-array-of-arrays/
 *         Arrays of numeric x and y
 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
 *         Arrays of datetime x and y
 * @sample {highcharts} highcharts/series/data-array-of-name-value/
 *         Arrays of point.name and y
 * @sample {highcharts} highcharts/series/data-array-of-objects/
 *         Config objects
 *
 * @basic
 * @type      {Array<number|Array<(number|string),(number|null)>|null|*>}
 * @extends   series.column.data
 * @product   highcharts
 * @apioption series.bar.data
 */
/**
 * @excluding halo,lineWidth,lineWidthPlus,marker
 * @product   highcharts highstock
 * @apioption series.bar.states.hover
 */
/**
 * @excluding halo,lineWidth,lineWidthPlus,marker
 * @product   highcharts highstock
 * @apioption series.bar.states.select
 */
''; // Gets doclets above into transpiled

;// ./code/es-modules/Series/Scatter/ScatterSeriesDefaults.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */

/* *
 *
 *  API Options
 *
 * */
/**
 * A scatter plot uses cartesian coordinates to display values for two
 * variables for a set of data.
 *
 * @sample {highcharts} highcharts/demo/scatter/
 *         Scatter plot
 *
 * @extends      plotOptions.line
 * @excluding    cropThreshold, legendSymbolColor, pointPlacement, shadow,
 *               useOhlcData
 * @product      highcharts highstock
 * @optionparent plotOptions.scatter
 */
const ScatterSeriesDefaults = {
    /**
     * The width of the line connecting the data points.
     *
     * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-none/
     *         0 by default
     * @sample {highcharts} highcharts/plotoptions/scatter-linewidth-1/
     *         1px
     *
     * @product highcharts highstock
     */
    lineWidth: 0,
    findNearestPointBy: 'xy',
    /**
     * Apply a jitter effect for the rendered markers. When plotting
     * discrete values, a little random noise may help telling the points
     * apart. The jitter setting applies a random displacement of up to `n`
     * axis units in either direction. So for example on a horizontal X
     * axis, setting the `jitter.x` to 0.24 will render the point in a
     * random position between 0.24 units to the left and 0.24 units to the
     * right of the true axis position. On a category axis, setting it to
     * 0.5 will fill up the bin and make the data appear continuous.
     *
     * When rendered on top of a box plot or a column series, a jitter value
     * of 0.24 will correspond to the underlying series' default
     * [groupPadding](
     * https://api.highcharts.com/highcharts/plotOptions.column.groupPadding)
     * and [pointPadding](
     * https://api.highcharts.com/highcharts/plotOptions.column.pointPadding)
     * settings.
     *
     * **Note:** With boost mode enabled, the jitter effect is not supported.
     *
     * @sample {highcharts} highcharts/demo/scatter-jitter
     *         Jitter on a scatter plot
     *
     * @sample {highcharts} highcharts/series-scatter/jitter-boxplot
     *         Jittered scatter plot on top of a box plot
     *
     * @product highcharts highstock
     * @since 7.0.2
     */
    jitter: {
        /**
         * The maximal X offset for the random jitter effect.
         */
        x: 0,
        /**
         * The maximal Y offset for the random jitter effect.
         */
        y: 0
    },
    marker: {
        enabled: true // Overrides auto-enabling in line series (#3647)
    },
    /**
     * Sticky tracking of mouse events. When true, the `mouseOut` event
     * on a series isn't triggered until the mouse moves over another
     * series, or out of the plot area. When false, the `mouseOut` event on
     * a series is triggered when the mouse leaves the area around the
     * series' graph or markers. This also implies the tooltip. When
     * `stickyTracking` is false and `tooltip.shared` is false, the tooltip
     * will be hidden when moving the mouse between series.
     *
     * @type      {boolean}
     * @default   false
     * @product   highcharts highstock highmaps
     * @apioption plotOptions.scatter.stickyTracking
     */
    /**
     * A configuration object for the tooltip rendering of each single
     * series. Properties are inherited from [tooltip](#tooltip).
     * Overridable properties are `headerFormat`, `pointFormat`,
     * `yDecimals`, `xDateFormat`, `yPrefix` and `ySuffix`. Unlike other
     * series, in a scatter plot the series.name by default shows in the
     * headerFormat and point.x and point.y in the pointFormat.
     *
     * @product highcharts highstock highmaps
     */
    tooltip: {
        /**
         * @product highcharts highstock
         */
        headerFormat: '<span style="color:{point.color}">\u25CF</span> ' +
            '<span style="font-size: 0.8em"> {series.name}</span><br/>',
        pointFormat: 'x: <b>{point.x}</b><br/>y: <b>{point.y}</b><br/>'
    }
};
/**
 * A `scatter` series. If the [type](#series.scatter.type) option is
 * not specified, it is inherited from [chart.type](#chart.type).
 *
 * @extends   series,plotOptions.scatter
 * @excluding cropThreshold, dataParser, dataURL, useOhlcData
 * @product   highcharts highstock
 * @apioption series.scatter
 */
/**
 * An array of data points for the series. For the `scatter` series
 * type, points can be given in the following ways:
 *
 * 1. An array of numerical values. In this case, the numerical values will be
 *    interpreted as `y` options. The `x` values will be automatically
 *    calculated, either starting at 0 and incremented by 1, or from
 *    `pointStart` and `pointInterval` given in the series options. If the axis
 *    has categories, these will be used. Example:
 *    ```js
 *    data: [0, 5, 3, 5]
 *    ```
 *
 * 2. An array of arrays with 2 values. In this case, the values correspond to
 *    `x,y`. If the first value is a string, it is applied as the name of the
 *    point, and the `x` value is inferred.
 *    ```js
 *    data: [
 *        [0, 0],
 *        [1, 8],
 *        [2, 9]
 *    ]
 *    ```
 *
 * 3. An array of objects with named values. The following snippet shows only a
 *    few settings, see the complete options set below. If the total number of
 *    data points exceeds the series'
 *    [turboThreshold](#series.scatter.turboThreshold), this option is not
 *    available.
 *    ```js
 *    data: [{
 *        x: 1,
 *        y: 2,
 *        name: "Point2",
 *        color: "#00FF00"
 *    }, {
 *        x: 1,
 *        y: 4,
 *        name: "Point1",
 *        color: "#FF00FF"
 *    }]
 *    ```
 *
 * @sample {highcharts} highcharts/chart/reflow-true/
 *         Numerical values
 * @sample {highcharts} highcharts/series/data-array-of-arrays/
 *         Arrays of numeric x and y
 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
 *         Arrays of datetime x and y
 * @sample {highcharts} highcharts/series/data-array-of-name-value/
 *         Arrays of point.name and y
 * @sample {highcharts} highcharts/series/data-array-of-objects/
 *         Config objects
 *
 * @basic
 * @type      {Array<number|Array<(number|string),(number|null)>|null|*>}
 * @extends   series.line.data
 * @product   highcharts highstock
 * @apioption series.scatter.data
 */
''; // Keeps doclets above in JS file
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Scatter_ScatterSeriesDefaults = (ScatterSeriesDefaults);

;// ./code/es-modules/Series/Scatter/ScatterSeries.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */



const { column: ScatterSeries_ColumnSeries, line: ScatterSeries_LineSeries } = Series_SeriesRegistry.seriesTypes;

/* *
 *
 *  Class
 *
 * */
/**
 * Scatter series type.
 *
 * @private
 */
class ScatterSeries extends ScatterSeries_LineSeries {
    /* *
     *
     *  Functions
     *
     * */
    /* eslint-disable valid-jsdoc */
    /**
     * Optionally add the jitter effect.
     * @private
     */
    applyJitter() {
        const series = this, jitter = this.options.jitter, len = this.points.length;
        /**
         * Return a repeatable, pseudo-random number based on an integer
         * seed.
         * @private
         */
        function unrandom(seed) {
            const rand = Math.sin(seed) * 10000;
            return rand - Math.floor(rand);
        }
        if (jitter) {
            this.points.forEach(function (point, i) {
                ['x', 'y'].forEach(function (dim, j) {
                    if (jitter[dim] && !point.isNull) {
                        const plotProp = `plot${dim.toUpperCase()}`, axis = series[`${dim}Axis`], translatedJitter = jitter[dim] *
                            axis.transA;
                        if (axis && !axis.logarithmic) {
                            // Identify the outer bounds of the jitter range
                            const min = Math.max(0, (point[plotProp] || 0) - translatedJitter), max = Math.min(axis.len, (point[plotProp] || 0) + translatedJitter);
                            // Find a random position within this range
                            point[plotProp] = min +
                                (max - min) * unrandom(i + j * len);
                            // Update clientX for the tooltip k-d-tree
                            if (dim === 'x') {
                                point.clientX = point.plotX;
                            }
                        }
                    }
                });
            });
        }
    }
    /**
     * @private
     */
    drawGraph() {
        if (this.options.lineWidth) {
            super.drawGraph();
        }
        else if (this.graph) {
            this.graph = this.graph.destroy();
        }
    }
}
/* *
 *
 *  Static Properties
 *
 * */
ScatterSeries.defaultOptions = merge(ScatterSeries_LineSeries.defaultOptions, Scatter_ScatterSeriesDefaults);
extend(ScatterSeries.prototype, {
    allowOutsidePlotInteraction: true,
    drawTracker: ScatterSeries_ColumnSeries.prototype.drawTracker,
    sorted: false,
    requireSorting: false,
    noSharedTooltip: true,
    trackerGroups: ['group', 'markerGroup', 'dataLabelsGroup']
});
/* *
 *
 *  Events
 *
 * */
/* eslint-disable no-invalid-this */
addEvent(ScatterSeries, 'afterTranslate', function () {
    this.applyJitter();
});
Series_SeriesRegistry.registerSeriesType('scatter', ScatterSeries);
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Scatter_ScatterSeries = ((/* unused pure expression or super */ null && (ScatterSeries)));

;// ./code/es-modules/Series/CenteredUtilities.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { deg2rad: CenteredUtilities_deg2rad } = Core_Globals;

/**
 * @private
 */
var CenteredUtilities;
(function (CenteredUtilities) {
    /* *
     *
     *  Declarations
     *
     * */
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Get the center of the pie based on the size and center options relative
     * to the plot area. Borrowed by the polar and gauge series types.
     *
     * @private
     * @function Highcharts.CenteredSeriesMixin.getCenter
     */
    function getCenter() {
        const options = this.options, chart = this.chart, slicingRoom = 2 * (options.slicedOffset || 0), plotWidth = chart.plotWidth - 2 * slicingRoom, plotHeight = chart.plotHeight - 2 * slicingRoom, centerOption = options.center, smallestSize = Math.min(plotWidth, plotHeight), thickness = options.thickness;
        let handleSlicingRoom, size = options.size, innerSize = options.innerSize || 0, i, value;
        if (typeof size === 'string') {
            size = parseFloat(size);
        }
        if (typeof innerSize === 'string') {
            innerSize = parseFloat(innerSize);
        }
        const positions = [
            pick(centerOption?.[0], '50%'),
            pick(centerOption?.[1], '50%'),
            // Prevent from negative values
            pick(size && size < 0 ? void 0 : options.size, '100%'),
            pick(innerSize && innerSize < 0 ? void 0 : options.innerSize || 0, '0%')
        ];
        for (i = 0; i < 4; ++i) {
            value = positions[i];
            handleSlicingRoom = i < 2 || (i === 2 && /%$/.test(value));
            // I == 0: centerX, relative to width
            // i == 1: centerY, relative to height
            // i == 2: size, relative to smallestSize
            // i == 3: innerSize, relative to size
            positions[i] = relativeLength(value, [plotWidth, plotHeight, smallestSize, positions[2]][i]) + (handleSlicingRoom ? slicingRoom : 0);
        }
        // Inner size cannot be larger than size (#3632)
        if (positions[3] > positions[2]) {
            positions[3] = positions[2];
        }
        // Thickness overrides innerSize, need to be less than pie size (#6647)
        if (isNumber(thickness) &&
            thickness * 2 < positions[2] && thickness > 0) {
            positions[3] = positions[2] - thickness * 2;
        }
        fireEvent(this, 'afterGetCenter', { positions });
        return positions;
    }
    CenteredUtilities.getCenter = getCenter;
    /**
     * GetStartAndEndRadians - Calculates start and end angles in radians.
     * Used in series types such as pie and sunburst.
     *
     * @private
     * @function Highcharts.CenteredSeriesMixin.getStartAndEndRadians
     *
     * @param {number} [start]
     *        Start angle in degrees.
     *
     * @param {number} [end]
     *        Start angle in degrees.
     *
     * @return {Highcharts.RadianAngles}
     *         Returns an object containing start and end angles as radians.
     */
    function getStartAndEndRadians(start, end) {
        const startAngle = isNumber(start) ? start : 0, // Must be a number
        endAngle = ((isNumber(end) && // Must be a number
            end > startAngle && // Must be larger than the start angle
            // difference must be less than 360 degrees
            (end - startAngle) < 360) ?
            end :
            startAngle + 360), correction = -90;
        return {
            start: CenteredUtilities_deg2rad * (startAngle + correction),
            end: CenteredUtilities_deg2rad * (endAngle + correction)
        };
    }
    CenteredUtilities.getStartAndEndRadians = getStartAndEndRadians;
})(CenteredUtilities || (CenteredUtilities = {}));
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Series_CenteredUtilities = (CenteredUtilities);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * @private
 * @interface Highcharts.RadianAngles
 */ /**
* @name Highcharts.RadianAngles#end
* @type {number}
*/ /**
* @name Highcharts.RadianAngles#start
* @type {number}
*/
''; // Keeps doclets above in JS file

;// ./code/es-modules/Series/Pie/PiePoint.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { setAnimation: PiePoint_setAnimation } = AnimationUtilities;


/* *
 *
 *  Class
 *
 * */
class PiePoint extends Series_Point {
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Extendable method for getting the path of the connector between the
     * data label and the pie slice.
     * @internal
     */
    getConnectorPath(dataLabel) {
        const labelPosition = dataLabel.dataLabelPosition, options = (dataLabel.options || {}), connectorShape = options.connectorShape, shapeFunc = (this.connectorShapes[connectorShape] || connectorShape);
        return labelPosition && shapeFunc.call(this, {
            // Pass simplified label position object for user's convenience
            ...labelPosition.computed,
            alignment: labelPosition.alignment
        }, labelPosition.connectorPosition, options) || [];
    }
    /** @internal */
    getTranslate() {
        return this.sliced && this.slicedTranslation || {
            translateX: 0,
            translateY: 0
        };
    }
    /** @internal */
    haloPath(size) {
        const shapeArgs = this.shapeArgs;
        return this.sliced || !this.visible ?
            [] :
            this.series.chart.renderer.symbols.arc(shapeArgs.x, shapeArgs.y, shapeArgs.r + size, shapeArgs.r + size, {
                // Subtract 1px to ensure the background is not bleeding
                // through between the halo and the slice (#7495).
                innerR: shapeArgs.r - 1,
                start: shapeArgs.start,
                end: shapeArgs.end,
                borderRadius: shapeArgs.borderRadius
            });
    }
    /**
     * Initialize the pie slice.
     * @internal
     */
    constructor(series, options, x) {
        super(series, options, x);
        /** @internal */
        this.half = 0;
        this.name ?? (this.name = series.chart.options.lang.pieSliceName);
        // Add event listener for select
        const toggleSlice = (e) => {
            this.slice(e.type === 'select');
        };
        addEvent(this, 'select', toggleSlice);
        addEvent(this, 'unselect', toggleSlice);
    }
    /** @internal */
    isValid() {
        // Negative points are not valid (#1530, #3623, #5322)
        return isNumber(this.y) && this.y >= 0;
    }
    /**
     * Toggle the visibility of a pie slice or other data point. Note that this
     * method is available only for some series, like pie, treemap and sunburst.
     *
     * @function Highcharts.Point#setVisible
     *
     * @param {boolean} [vis]
     * True to show the pie slice or other data point, false to hide. If
     * undefined, the visibility is toggled.
     *
     * @param {boolean} [redraw=true] Whether to redraw the chart after the point is
     * altered. If doing more operations on the chart, it is a good idea to set
     * redraw to false and call {@link Chart#redraw|chart.redraw()} after.
     *
     */
    setVisible(vis, redraw = true) {
        if (vis !== this.visible) {
            // If called without an argument, toggle visibility
            this.update({
                visible: vis ?? !this.visible
            }, redraw, void 0, false);
        }
    }
    /**
     * Set or toggle whether the slice is cut out from the pie.
     *
     * @internal
     *
     * @param {boolean} sliced
     * When undefined, the slice state is toggled.
     *
     * @param {boolean} [redraw]
     * Whether to redraw the chart. True by default.
     *
     * @param {boolean|Partial<Highcharts.AnimationOptionsObject>} [animation]
     * Animation options.
     */
    slice(sliced, redraw, animation) {
        const series = this.series, chart = series.chart;
        PiePoint_setAnimation(animation, chart);
        // Redraw is true by default
        redraw = pick(redraw, true);
        this.sliced = this.options.sliced = sliced ?? !this.sliced;
        // Update options.data
        if (series.options.data) {
            series.options.data[series.data.indexOf(this)] = this.options;
        }
        this.graphic?.animate(this.getTranslate());
    }
}
extend(PiePoint.prototype, {
    connectorShapes: {
        // Only one available before v7.0.0
        fixedOffset: function (labelPosition, connectorPosition, options) {
            const breakAt = connectorPosition.breakAt, touchingSliceAt = connectorPosition.touchingSliceAt, lineSegment = options.softConnector ? [
                'C', // Soft break
                // 1st control point (of the curve)
                labelPosition.x +
                    // 5 gives the connector a little horizontal bend
                    (labelPosition.alignment === 'left' ? -5 : 5),
                labelPosition.y, //
                2 * breakAt.x - touchingSliceAt.x, // 2nd control point
                2 * breakAt.y - touchingSliceAt.y, //
                breakAt.x, // End of the curve
                breakAt.y //
            ] : [
                'L', // Pointy break
                breakAt.x,
                breakAt.y
            ];
            // Assemble the path
            return ([
                ['M', labelPosition.x, labelPosition.y],
                lineSegment,
                ['L', touchingSliceAt.x, touchingSliceAt.y]
            ]);
        },
        straight: function (labelPosition, connectorPosition) {
            const touchingSliceAt = connectorPosition.touchingSliceAt;
            // Direct line to the slice
            return [
                ['M', labelPosition.x, labelPosition.y],
                ['L', touchingSliceAt.x, touchingSliceAt.y]
            ];
        },
        crookedLine: function (labelPosition, connectorPosition, options) {
            const { angle = this.angle || 0, breakAt, touchingSliceAt } = connectorPosition, { series } = this, [cx, cy, diameter] = series.center, r = diameter / 2, { plotLeft, plotWidth } = series.chart, leftAligned = labelPosition.alignment === 'left', { x, y } = labelPosition;
            let crookX = breakAt.x;
            if (options.crookDistance) {
                const crookDistance = relativeLength(// % to fraction
                options.crookDistance, 1);
                crookX = leftAligned ?
                    cx +
                        r +
                        (plotWidth + plotLeft - cx - r) * (1 - crookDistance) :
                    plotLeft + (cx - r) * crookDistance;
                // When the crookDistance option is undefined, make the bend in the
                // intersection between the radial line in the middle of the slice,
                // and the extension of the label position.
            }
            else {
                crookX = cx + (cy - y) * Math.tan(angle - Math.PI / 2);
            }
            const path = [['M', x, y]];
            // The crookedLine formula doesn't make sense if the path overlaps
            // the label - use straight line instead in that case
            if (leftAligned ?
                (crookX <= x && crookX >= breakAt.x) :
                (crookX >= x && crookX <= breakAt.x)) {
                path.push(['L', crookX, y]);
            }
            path.push(['L', breakAt.x, breakAt.y], ['L', touchingSliceAt.x, touchingSliceAt.y]);
            return path;
        }
    }
});
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Pie_PiePoint = (PiePoint);
/* *
 *
 *  API Options
 *
 * */
/**
 * Pie series only. Whether to display a slice offset from the center.
 *
 * @name Highcharts.Point#sliced
 * @type {boolean|undefined}
 */
''; // Keeps doclets above in JS file

;// ./code/es-modules/Series/Pie/PieSeriesDefaults.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */

/* *
 *
 *  API Options
 *
 * */
/**
 * A pie chart is a circular graphic which is divided into slices to
 * illustrate numerical proportion.
 *
 * @sample highcharts/demo/pie-chart/
 *         Pie chart
 *
 * @extends      plotOptions.line
 * @excluding    animationLimit, boostThreshold, connectEnds, connectNulls,
 *               cropThreshold, dashStyle, dataSorting, dragDrop,
 *               findNearestPointBy, getExtremesFromAll, label, lineWidth,
 *               linkedTo, marker, negativeColor, pointInterval,
 *               pointIntervalUnit, pointPlacement, pointStart,
 *               softThreshold, stacking, step, threshold, turboThreshold,
 *               zoneAxis, zones, dataSorting, boostBlending
 * @product      highcharts highmaps
 * @optionparent plotOptions.pie
 */
const PieSeriesDefaults = {
    /**
     * The corner radius of the border surrounding each slice. A number
     * signifies pixels. A percentage string, like for example `50%`, signifies
     * a size relative to the radius and the inner radius.
     *
     * @sample  highcharts/plotoptions/series-border-radius
     *          Column and pie with rounded border
     *
     * @since   11.0.0
     *
     * @type      {number|string|Highcharts.BorderRadiusOptionsObject}
     */
    borderRadius: 3,
    /**
     * @excluding legendItemClick
     * @apioption plotOptions.pie.events
     */
    /**
     * Fires when the checkbox next to the point name in the legend is
     * clicked. One parameter, event, is passed to the function. The state
     * of the checkbox is found by event.checked. The checked item is found
     * by event.item. Return false to prevent the default action which is to
     * toggle the select state of the series.
     *
     * @sample {highcharts} highcharts/plotoptions/series-events-checkboxclick/
     *         Alert checkbox status
     *
     * @type      {Function}
     * @since     1.2.0
     * @product   highcharts highmaps
     * @context   Highcharts.Point
     * @apioption plotOptions.pie.events.checkboxClick
     */
    /**
     * Fires when the legend item belonging to the pie point (slice) is
     * clicked. The `this` keyword refers to the point itself. One
     * parameter, `event`, is passed to the function, containing common
     * event information. The default action is to toggle the visibility of
     * the point. This can be prevented by calling `event.preventDefault()`.
     *
     *  **Note:** This option is deprecated in favor of
     * [legend.events.itemClick](#legend.events.itemClick).
     *
     * @deprecated 11.4.4
     * @type       {Highcharts.PointLegendItemClickCallbackFunction}
     * @since      1.2.0
     * @product    highcharts highmaps
     * @apioption  plotOptions.pie.point.events.legendItemClick
     */
    /**
     * The center of the pie chart relative to the plot area. Can be
     * percentages or pixel values. The default behavior (as of 3.0) is to
     * center the pie so that all slices and data labels are within the plot
     * area. As a consequence, the pie may actually jump around in a chart
     * with dynamic values, as the data labels move. In that case, the
     * center should be explicitly set, for example to `["50%", "50%"]`.
     *
     * @sample {highcharts} highcharts/plotoptions/pie-center/
     *         Centered at 100, 100
     *
     * @type    {Array<(number|string|null),(number|string|null)>}
     * @default [null, null]
     * @product highcharts highmaps
     */
    center: [null, null],
    /**
     * The color of the pie series. A pie series is represented as an empty
     * circle if the total sum of its values is 0. Use this property to
     * define the color of its border.
     *
     * In styled mode, the color can be defined by the
     * [colorIndex](#plotOptions.series.colorIndex) option. Also, the series
     * color can be set with the `.highcharts-series`,
     * `.highcharts-color-{n}`, `.highcharts-{type}-series` or
     * `.highcharts-series-{n}` class, or individual classes given by the
     * `className` option.
     *
     * @sample {highcharts} highcharts/plotoptions/pie-emptyseries/
     *         Empty pie series
     *
     * @type      {Highcharts.ColorType}
     * @default   var(--highcharts-neutral-color-20)
     * @apioption plotOptions.pie.color
     */
    /**
     * @product highcharts
     */
    clip: false,
    /**
     * @ignore-option
     *
     * @internal
     */
    colorByPoint: true, // Always true for pies
    /**
     * A series specific or series type specific color set to use instead
     * of the global [colors](#colors).
     *
     * @sample {highcharts} highcharts/demo/pie-monochrome/
     *         Set default colors for all pies
     *
     * @type      {Array<Highcharts.ColorType>}
     * @since     3.0
     * @product   highcharts highmaps
     * @apioption plotOptions.pie.colors
     */
    /**
     * @declare   Highcharts.SeriesPieDataLabelsOptionsObject
     * @extends   plotOptions.series.dataLabels
     * @excluding align, allowOverlap, inside, staggerLines, step
     */
    dataLabels: {
        /**
         * Alignment method for data labels. Possible values are:
         *
         * - `plotEdges`: Each label touches the nearest vertical edge of
         *   the plot area.
         *
         * - `connectors`: Connectors have the same x position and the
         *   widest label of each half (left & right) touches the nearest
         *   vertical edge of the plot area.
         *
         * @sample {highcharts} highcharts/plotoptions/pie-datalabels-alignto-connectors/
         *         alignTo: connectors
         * @sample {highcharts} highcharts/plotoptions/pie-datalabels-alignto-plotedges/
         *         alignTo: plotEdges
         *
         * @type      {string}
         * @since     7.0.0
         * @product   highcharts highmaps
         * @apioption plotOptions.pie.dataLabels.alignTo
         */
        /**
         * The color of the line connecting the data label to the pie slice.
         * The default color is the same as the point's color.
         *
         * In styled mode, the connector stroke is given in the
         * `.highcharts-data-label-connector` class.
         *
         * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorcolor/
         *         Blue connectors
         * @sample {highcharts} highcharts/css/pie-point/
         *         Styled connectors
         *
         * @type      {Highcharts.ColorType}
         * @since     2.1
         * @product   highcharts highmaps
         * @apioption plotOptions.pie.dataLabels.connectorColor
         */
        /**
         * The distance from the data label to the connector. Note that
         * data labels also have a default `padding`, so in order for the
         * connector to touch the text, the `padding` must also be 0.
         *
         * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorpadding/
         *         No padding
         *
         * @since   2.1
         * @product highcharts highmaps
         */
        connectorPadding: 5,
        /**
         * Specifies the method that is used to generate the connector path.
         * Highcharts provides 3 built-in connector shapes: `'crookedLine'`
         * (default since v11), `'fixedOffset'` and `'straight'`.
         *
         * Users can provide their own method by passing a function instead of a
         * string. Three arguments are passed to the callback:
         *
         * - An object that holds the information about the coordinates of the
         *   label (`x` & `y` properties) and how the label is located in
         *   relation to the pie (`alignment` property). `alignment` can by one
         *   of the following: `'left'` (pie on the left side of the data
         *   label), `'right'` (pie on the right side of the data label) or
         *   `'center'` (data label overlaps the pie).
         *
         * - An object that holds the information about the position of the
         *   connector. Its `touchingSliceAt` property tells the position of
         *   the place where the connector touches the slice.
         *
         * - Data label options
         *
         * The function has to return an SVG path definition in array form (see
         * the example).
         *
         * @sample {highcharts}
         *         highcharts/plotoptions/pie-datalabels-connectorshape-string/
         *         connectorShape is a String
         * @sample {highcharts}
         *         highcharts/plotoptions/pie-datalabels-connectorshape-function/
         *         connectorShape is a function
         *
         * @type    {string|Function}
         * @since   7.0.0
         * @product highcharts highmaps
         */
        connectorShape: 'crookedLine',
        /**
         * The width of the line connecting the data label to the pie slice.
         *
         * In styled mode, the connector stroke width is given in the
         * `.highcharts-data-label-connector` class.
         *
         * @sample {highcharts} highcharts/plotoptions/pie-datalabels-connectorwidth-disabled/
         *         Disable the connector
         * @sample {highcharts} highcharts/css/pie-point/
         *         Styled connectors
         *
         * @type      {number}
         * @default   1
         * @since     2.1
         * @product   highcharts highmaps
         * @apioption plotOptions.pie.dataLabels.connectorWidth
         */
        /**
         * Works only if `connectorShape` is `'crookedLine'`. It defines how
         * far from the vertical plot edge the connector path should be
         * crooked. With the default, `undefined`, the crook is placed so that
         * the horizontal line from the label intersects with the radial line
         * extending through the center of the pie slice.
         *
         * @sample {highcharts} highcharts/plotoptions/pie-datalabels-crookdistance/
         *         crookDistance set to 90%
         *
         * @since   7.0.0
         * @product highcharts highmaps
         */
        crookDistance: void 0,
        /**
         * The distance of the data label from the pie's edge. Negative
         * numbers put the data label on top of the pie slices. Can also be
         * defined as a percentage of pie's radius. Connectors are only
         * shown for data labels outside the pie.
         *
         * @sample {highcharts} highcharts/plotoptions/pie-datalabels-distance/
         *         Data labels on top of the pie
         *
         * @type    {number|string}
         * @since   2.1
         * @product highcharts highmaps
         */
        distance: 30,
        enabled: true,
        /**
         * A
         * [format string](https://www.highcharts.com/docs/chart-concepts/labels-and-string-formatting)
         * for the data label. Available variables are the same as for
         * `formatter`.
         *
         * @sample {highcharts} highcharts/plotoptions/series-datalabels-format/
         *         Add a unit
         *
         * @type      {string}
         * @default   undefined
         * @since     3.0
         * @apioption plotOptions.pie.dataLabels.format
         */
        // eslint-disable-next-line valid-jsdoc
        /**
         * Callback JavaScript function to format the data label. Note that
         * if a `format` is defined, the format takes precedence and the
         * formatter is ignored.
         *
         * @type {Highcharts.DataLabelsFormatterCallbackFunction}
         * @default function () { return this.point.isNull ? void 0 : this.point.name; }
         */
        formatter: function () {
            return this.isNull ? void 0 : this.name;
        },
        /**
         * Whether to render the connector as a soft arc or a line with a sharp
         * break. Works only if `connectorShape` equals to `fixedOffset`.
         *
         * @sample {highcharts}
         *         highcharts/plotoptions/pie-datalabels-softconnector-true/
         *         Soft
         * @sample {highcharts}
         *         highcharts/plotoptions/pie-datalabels-softconnector-false/
         *         Non soft
         *
         * @since   2.1.7
         * @product highcharts highmaps
         */
        softConnector: true,
        /**
         * @sample {highcharts} highcharts/plotoptions/pie-datalabels-overflow
         *         Long labels truncated with an ellipsis
         * @sample {highcharts} highcharts/plotoptions/pie-datalabels-overflow-wrap
         *         Long labels are wrapped
         *
         * @type      {Highcharts.CSSObject}
         * @apioption plotOptions.pie.dataLabels.style
         */
        x: 0
    },
    /**
     * If the total sum of the pie's values is 0, the series is represented
     * as an empty circle . The `fillColor` option defines the color of that
     * circle. Use [pie.borderWidth](#plotOptions.pie.borderWidth) to set
     * the border thickness.
     *
     * @sample {highcharts} highcharts/plotoptions/pie-emptyseries/
     *         Empty pie series
     *
     * @type {Highcharts.ColorType}
     */
    fillColor: void 0,
    /**
     * The end angle of the pie in degrees where 0 is top and 90 is right.
     * Defaults to `startAngle` plus 360.
     *
     * @sample {highcharts} highcharts/demo/pie-semi-circle/
     *         Semi-circle donut
     *
     * @type      {number}
     * @since     1.3.6
     * @product   highcharts highmaps
     * @apioption plotOptions.pie.endAngle
     */
    /**
     * Thickness describing the ring size for a donut type chart,
     * overriding [innerSize](#plotOptions.pie.innerSize).
     *
     * @type      {number}
     * @default   undefined
     * @product   highcharts
     * @since 10.1.0
     * @apioption plotOptions.pie.thickness
     */
    /**
     * Equivalent to [chart.ignoreHiddenSeries](#chart.ignoreHiddenSeries),
     * this option tells whether the series shall be redrawn as if the
     * hidden point were `null`.
     *
     * The default value changed from `false` to `true` with Highcharts
     * 3.0.
     *
     * @sample {highcharts} highcharts/plotoptions/pie-ignorehiddenpoint/
     *         True, the hidden point is ignored
     *
     * @since   2.3.0
     * @product highcharts highmaps
     */
    ignoreHiddenPoint: true,
    /**
     * @default true
     * @extends plotOptions.series.inactiveOtherPoints
     */
    inactiveOtherPoints: true,
    /**
     * The size of the inner diameter for the pie. A size greater than 0
     * renders a donut chart. Can be a percentage or pixel value.
     * Percentages are relative to the pie size. Pixel values are given as
     * integers. Setting overridden by thickness.
     *
     *
     * Note: in Highcharts < 4.1.2, the percentage was relative to the plot
     * area, not the pie size.
     *
     * @sample {highcharts} highcharts/plotoptions/pie-innersize-80px/
     *         80px inner size
     * @sample {highcharts} highcharts/plotoptions/pie-innersize-50percent/
     *         50% of the plot area
     * @sample {highcharts} highcharts/demo/3d-pie-donut/
     *         3D donut
     *
     * @type      {number|string}
     * @default   0
     * @since     2.0
     * @product   highcharts highmaps
     * @apioption plotOptions.pie.innerSize
     */
    /**
     * @ignore-option
     *
     * @internal
     */
    legendType: 'point',
    /**
     * @ignore-option
     *
     * @internal
     */
    marker: null, // Point options are specified in the base options
    /**
     * The minimum size for a pie in response to auto margins. The pie will
     * try to shrink to make room for data labels in side the plot area,
     *  but only to this size.
     *
     * @type      {number|string}
     * @default   80
     * @since     3.0
     * @product   highcharts highmaps
     * @apioption plotOptions.pie.minSize
     */
    /**
     * The diameter of the pie relative to the plot area. Can be a
     * percentage or pixel value. Pixel values are given as integers. The
     * default behavior (as of 3.0) is to scale to the plot area and give
     * room for data labels within the plot area.
     * [slicedOffset](#plotOptions.pie.slicedOffset) is also included in the
     * default size calculation. As a consequence, the size of the pie may
     * vary when points are updated and data labels more around. In that
     * case it is best to set a fixed value, for example `"75%"`.
     *
     * @sample {highcharts} highcharts/plotoptions/pie-size/
     *         Smaller pie
     *
     * @type    {number|string|null}
     * @product highcharts highmaps
     */
    size: null,
    /**
     * Whether to display this particular series or series type in the
     * legend. Since 2.1, pies are not shown in the legend by default.
     *
     * @sample {highcharts} highcharts/plotoptions/series-showinlegend/
     *         One series in the legend, one hidden
     *
     * @product highcharts highmaps
     */
    showInLegend: false,
    /**
     * If a point is sliced, moved out from the center, how many pixels
     * should it be moved?.
     *
     * @sample {highcharts} highcharts/plotoptions/pie-slicedoffset-20/
     *         20px offset
     *
     * @product highcharts highmaps
     */
    slicedOffset: 10,
    /**
     * The start angle of the pie slices in degrees where 0 is top and 90
     * right.
     *
     * @sample {highcharts} highcharts/plotoptions/pie-startangle-90/
     *         Start from right
     *
     * @type      {number}
     * @default   0
     * @since     2.3.4
     * @product   highcharts highmaps
     * @apioption plotOptions.pie.startAngle
     */
    /**
     * Sticky tracking of mouse events. When true, the `mouseOut` event
     * on a series isn't triggered until the mouse moves over another
     * series, or out of the plot area. When false, the `mouseOut` event on
     * a series is triggered when the mouse leaves the area around the
     * series'  graph or markers. This also implies the tooltip. When
     * `stickyTracking` is false and `tooltip.shared` is false, the tooltip
     * will be hidden when moving the mouse between series.
     *
     * @product highcharts highmaps
     */
    stickyTracking: false,
    tooltip: {
        followPointer: true
    },
    /**
     * The color of the border surrounding each slice. When `null`, the
     * border takes the same color as the slice fill. This can be used
     * together with a `borderWidth` to fill drawing gaps created by
     * antialiasing artifacts in border-less pies.
     *
     * In styled mode, the border stroke is given in the `.highcharts-point`
     * class.
     *
     * @sample {highcharts} highcharts/plotoptions/pie-bordercolor-black/
     *         Black border
     *
     * @type    {Highcharts.ColorType}
     * @product highcharts highmaps
     */
    borderColor: 'var(--highcharts-background-color)',
    /**
     * The width of the border surrounding each slice.
     *
     * When setting the border width to 0, there may be small gaps between
     * the slices due to SVG antialiasing artifacts. To work around this,
     * keep the border width at 0.5 or 1, but set the `borderColor` to
     * `null` instead.
     *
     * In styled mode, the border stroke width is given in the
     * `.highcharts-point` class.
     *
     * @sample {highcharts} highcharts/plotoptions/pie-borderwidth/
     *         3px border
     *
     * @product highcharts highmaps
     */
    borderWidth: 1,
    /**
     * @ignore-option
     * @internal
     */
    lineWidth: void 0, // #12222
    states: {
        /**
         * @extends   plotOptions.series.states.hover
         * @excluding marker, lineWidth, lineWidthPlus
         * @product   highcharts highmaps
         */
        hover: {
            /**
             * How much to brighten the point on interaction.
             *
             * In styled mode, the hover brightness is by default replaced
             * by a fill-opacity given in the `.highcharts-point-hover`
             * class.
             *
             * @sample {highcharts} highcharts/plotoptions/pie-states-hover-brightness/
             *         Brightened by 0.5
             *
             * @product highcharts highmaps
             */
            brightness: 0.1
        }
    }
};
/**
 * A `pie` series. If the [type](#series.pie.type) option is not specified,
 * it is inherited from [chart.type](#chart.type).
 *
 * @extends   series,plotOptions.pie
 * @excluding cropThreshold, dataParser, dataURL, linkedTo, stack, xAxis, yAxis,
 *            dataSorting, step, boostThreshold, boostBlending
 * @product   highcharts highmaps
 * @apioption series.pie
 */
/**
 * An array of data points for the series. For the `pie` series type,
 * points can be given in the following ways:
 *
 * 1. An array of numerical values. In this case, the numerical values will be
 *    interpreted as `y` options. Example:
 *    ```js
 *    data: [0, 5, 3, 5]
 *    ```
 *
 * 2. An array of objects with named values. The following snippet shows only a
 *    few settings, see the complete options set below. If the total number of
 *    data points exceeds the series'
 *    [turboThreshold](#series.pie.turboThreshold),
 *    this option is not available.
 *    ```js
 *    data: [{
 *        y: 1,
 *        name: "Point2",
 *        color: "#00FF00"
 *    }, {
 *        y: 7,
 *        name: "Point1",
 *        color: "#FF00FF"
 *    }]
 *    ```
 *
 * @sample {highcharts} highcharts/chart/reflow-true/
 *         Numerical values
 * @sample {highcharts} highcharts/series/data-array-of-arrays/
 *         Arrays of numeric x and y
 * @sample {highcharts} highcharts/series/data-array-of-arrays-datetime/
 *         Arrays of datetime x and y
 * @sample {highcharts} highcharts/series/data-array-of-name-value/
 *         Arrays of point.name and y
 * @sample {highcharts} highcharts/series/data-array-of-objects/
 *         Config objects
 *
 * @basic
 * @type      {Array<number|Array<string,(number|null)>|null|*>}
 * @extends   series.line.data
 * @excluding marker, x
 * @product   highcharts highmaps
 * @apioption series.pie.data
 */
/**
 * @type      {Highcharts.SeriesPieDataLabelsOptionsObject}
 * @product   highcharts highmaps
 * @apioption series.pie.data.dataLabels
 */
/**
 * The sequential index of the data point in the legend.
 *
 * @type      {number}
 * @product   highcharts highmaps
 * @apioption series.pie.data.legendIndex
 */
/**
 * Whether to display a slice offset from the center.
 *
 * @sample {highcharts} highcharts/point/sliced/
 *         One sliced point
 *
 * @type      {boolean}
 * @product   highcharts highmaps
 * @apioption series.pie.data.sliced
 */
/**
 * @extends plotOptions.pie.dataLabels
 * @excluding align, allowOverlap, inside, staggerLines, step
 * @product   highcharts highmaps
 * @apioption series.pie.dataLabels
 */
/**
 * @excluding legendItemClick
 * @product   highcharts highmaps
 * @apioption series.pie.events
 */
''; // Placeholder for transpiled doclets above
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Pie_PieSeriesDefaults = (PieSeriesDefaults);

;// ./code/es-modules/Series/Pie/PieSeries.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { getStartAndEndRadians } = Series_CenteredUtilities;


const { noop: PieSeries_noop } = Core_Globals;






/* *
 *
 *  Class
 *
 * */
/**
 * Pie series type.
 *
 * @internal
 * @class
 * @name Highcharts.seriesTypes.pie
 *
 * @augments Highcharts.Series
 */
class PieSeries extends Series_Series {
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Animates the pies in.
     * @internal
     */
    animate(init) {
        const series = this, points = series.points, startAngleRad = series.startAngleRad;
        if (!init) {
            points.forEach(function (point) {
                const graphic = point.graphic, args = point.shapeArgs;
                if (graphic && args) {
                    // Start values
                    graphic.attr({
                        // Animate from inner radius (#779)
                        r: pick(point.startR, (series.center && series.center[3] / 2)),
                        start: startAngleRad,
                        end: startAngleRad
                    });
                    // Animate
                    graphic.animate({
                        r: args.r,
                        start: args.start,
                        end: args.end
                    }, series.options.animation);
                }
            });
        }
    }
    /**
     * Called internally to draw auxiliary graph in pie-like series in
     * situation when the default graph is not sufficient enough to present
     * the data well. Auxiliary graph is saved in the same object as
     * regular graph.
     * @internal
     */
    drawEmpty() {
        const start = this.startAngleRad, end = this.endAngleRad, options = this.options;
        let centerX, centerY;
        // Draw auxiliary graph if there're no visible points.
        if (this.total === 0 && this.center) {
            centerX = this.center[0];
            centerY = this.center[1];
            if (!this.graph) {
                this.graph = this.chart.renderer
                    .arc(centerX, centerY, this.center[1] / 2, 0, start, end)
                    .addClass('highcharts-empty-series')
                    .add(this.group);
            }
            this.graph.attr({
                d: SVG_Symbols.arc(centerX, centerY, this.center[2] / 2, 0, {
                    start,
                    end,
                    innerR: this.center[3] / 2
                })
            });
            if (!this.chart.styledMode) {
                this.graph.attr({
                    'stroke-width': options.borderWidth,
                    fill: options.fillColor || 'none',
                    stroke: options.color ||
                        'var(--highcharts-neutral-color-20)'
                });
            }
        }
        else if (this.graph) { // Destroy the graph object.
            this.graph = this.graph.destroy();
        }
    }
    /**
     * Slices in pie chart are initialized in DOM, but it's shapes and
     * animations are normally run in `drawPoints()`.
     * @internal
     */
    drawPoints() {
        const renderer = this.chart.renderer;
        this.points.forEach(function (point) {
            // When updating a series between 2d and 3d or cartesian and
            // polar, the shape type changes.
            if (point.graphic && point.hasNewShapeType()) {
                point.graphic = point.graphic.destroy();
            }
            if (!point.graphic) {
                point.graphic = renderer[point.shapeType](point.shapeArgs)
                    .add(point.series.group);
                point.delayedRendering = true;
            }
        });
    }
    /**
     * Extend the generatePoints method by adding total and percentage
     * properties to each point
     * @internal
     */
    generatePoints() {
        super.generatePoints();
        this.updateTotals();
    }
    /**
     * Utility for getting the x value from a given y, used for anti-collision
     * logic in data labels.
     * @internal
     */
    getXPos(y, left, point, dataLabel) {
        const center = this.center, 
        // Variable pie has individual radius
        radius = this.radii ?
            this.radii[point.index] || 0 :
            center[2] / 2, labelPosition = dataLabel.dataLabelPosition, distance = labelPosition?.distance || 0;
        const angle = Math.asin(clamp((y - center[1]) / (radius + distance), -1, 1));
        const x = center[0] +
            (left ? -1 : 1) *
                (Math.cos(angle) * (radius + distance)) +
            (distance > 0 ?
                // 5 is the horizontal part pointing out of the label. It
                // used to be the `padding` setting, but that doesn't make
                // sense
                (left ? -5 : 5) :
                0);
        return x;
    }
    /**
     * Define hasData function for non-cartesian series. Returns true if the
     * series has at least one visible point (#23235)
     * @internal
     */
    hasData() {
        return this.points.some((point) => point.visible);
    }
    /**
     * Draw the data points
     * @internal
     */
    redrawPoints() {
        const series = this, chart = series.chart;
        let groupTranslation, graphic, pointAttr, shapeArgs;
        this.drawEmpty();
        // Apply the drop-shadow to the group because otherwise each element
        // would cast a shadow on others
        if (series.group && !chart.styledMode) {
            series.group.shadow(series.options.shadow);
        }
        // Draw the slices
        series.points.forEach(function (point) {
            const animateTo = {};
            graphic = point.graphic;
            if (!point.isNull && graphic) {
                shapeArgs = point.shapeArgs;
                // If the point is sliced, use special translation, else use
                // plot area translation
                groupTranslation = point.getTranslate();
                if (!chart.styledMode) {
                    pointAttr = series.pointAttribs(point, (point.selected && 'select'));
                }
                // Draw the slice
                if (!point.delayedRendering) {
                    graphic
                        .setRadialReference(series.center);
                    if (!chart.styledMode) {
                        merge(true, animateTo, pointAttr);
                    }
                    merge(true, animateTo, shapeArgs, groupTranslation);
                    graphic.animate(animateTo);
                }
                else {
                    graphic
                        .setRadialReference(series.center)
                        .attr(shapeArgs)
                        .attr(groupTranslation);
                    if (!chart.styledMode) {
                        graphic
                            .attr(pointAttr)
                            .attr({ 'stroke-linejoin': 'round' });
                    }
                    point.delayedRendering = false;
                }
                graphic
                    .attr({
                    visibility: point.visible ? 'inherit' : 'hidden'
                });
                graphic.addClass(point.getClassName(), true);
            }
            else if (graphic) {
                point.graphic = graphic.destroy();
            }
        });
    }
    /**
     * Utility for sorting data labels.
     * @internal
     */
    sortByAngle(points, sign) {
        points.sort(function (a, b) {
            return ((typeof a.angle !== 'undefined') &&
                (b.angle - a.angle) * sign);
        });
    }
    /**
     * Do translation for pie slices
     * @internal
     */
    translate(positions) {
        fireEvent(this, 'translate');
        this.generatePoints();
        const series = this, options = series.options, slicedOffset = options.slicedOffset, radians = getStartAndEndRadians(options.startAngle, options.endAngle), startAngleRad = series.startAngleRad = radians.start, endAngleRad = series.endAngleRad = radians.end, circ = endAngleRad - startAngleRad, // 2 * Math.PI,
        points = series.points, ignoreHiddenPoint = options.ignoreHiddenPoint, len = points.length;
        let start, end, angle, 
        // The x component of the radius vector for a given point
        radiusX, radiusY, i, point, cumulative = 0;
        // Get positions - either an integer or a percentage string must be
        // given. If positions are passed as a parameter, we're in a
        // recursive loop for adjusting space for data labels.
        if (!positions) {
            /**
             * The series center position, read only. This applies only to
             * circular chart types like pie and sunburst. It is an array of
             * `[centerX, centerY, diameter, innerDiameter]`.
             *
             * @name Highcharts.Series#center
             * @type {Array<number>}
             */
            series.center = positions = series.getCenter();
        }
        // Calculate the geometry for each point
        for (i = 0; i < len; i++) {
            point = points[i];
            // Set start and end angle
            start = startAngleRad + (cumulative * circ);
            if (point.isValid() &&
                (!ignoreHiddenPoint || point.visible)) {
                cumulative += point.percentage / 100;
            }
            end = startAngleRad + (cumulative * circ);
            // Set the shape
            const shapeArgs = {
                x: positions[0],
                y: positions[1],
                r: positions[2] / 2,
                innerR: positions[3] / 2,
                start,
                end
            };
            point.shapeType = 'arc';
            point.shapeArgs = shapeArgs;
            // The angle must stay within -90 and 270 (#2645)
            angle = (end + start) / 2;
            if (angle > 1.5 * Math.PI) {
                angle -= 2 * Math.PI;
            }
            else if (angle < -Math.PI / 2) {
                angle += 2 * Math.PI;
            }
            // Center for the sliced out slice
            point.slicedTranslation = {
                translateX: Math.round(Math.cos(angle) * slicedOffset),
                translateY: Math.round(Math.sin(angle) * slicedOffset)
            };
            // Set the anchor point for tooltips
            radiusX = Math.cos(angle) * positions[2] / 2;
            radiusY = Math.sin(angle) * positions[2] / 2;
            point.tooltipPos = [
                positions[0] + radiusX * 0.7,
                positions[1] + radiusY * 0.7
            ];
            point.half = angle < -Math.PI / 2 || angle > Math.PI / 2 ?
                1 :
                0;
            point.angle = angle;
        }
        fireEvent(series, 'afterTranslate');
    }
    /**
     * Recompute total chart sum and update percentages of points.
     * @internal
     */
    updateTotals() {
        const points = this.points, len = points.length, ignoreHiddenPoint = this.options.ignoreHiddenPoint;
        let i, point, total = 0;
        // Get the total sum
        for (i = 0; i < len; i++) {
            point = points[i];
            if (point.isValid() &&
                (!ignoreHiddenPoint || point.visible)) {
                total += point.y;
            }
        }
        this.total = total;
        // Set each point's properties
        for (i = 0; i < len; i++) {
            point = points[i];
            point.percentage =
                (total > 0 && (point.visible || !ignoreHiddenPoint)) ?
                    point.y / total * 100 :
                    0;
            point.total = total;
        }
    }
}
/* *
 *
 *  Static Properties
 *
 * */
PieSeries.defaultOptions = merge(Series_Series.defaultOptions, Pie_PieSeriesDefaults);
extend(PieSeries.prototype, {
    axisTypes: [],
    directTouch: true,
    drawGraph: void 0,
    drawTracker: Column_ColumnSeries.prototype.drawTracker,
    getCenter: Series_CenteredUtilities.getCenter,
    getSymbol: PieSeries_noop,
    invertible: false,
    isCartesian: false,
    noSharedTooltip: true,
    pointAttribs: Column_ColumnSeries.prototype.pointAttribs,
    pointClass: Pie_PiePoint,
    requireSorting: false,
    searchPoint: PieSeries_noop,
    trackerGroups: ['group', 'dataLabelsGroup']
});
Series_SeriesRegistry.registerSeriesType('pie', PieSeries);
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
/* harmony default export */ const Pie_PieSeries = ((/* unused pure expression or super */ null && (PieSeries)));

;// ./code/es-modules/Series/Pie/PieDataLabel.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */



const { composed: PieDataLabel_composed, noop: PieDataLabel_noop } = Core_Globals;

const { distribute: PieDataLabel_distribute } = Renderer_RendererUtilities;

const { series: PieDataLabel_Series } = Series_SeriesRegistry;

/* *
 *
 *  Composition
 *
 * */
/** @internal */
var PieDataLabel_ColumnDataLabel;
(function (ColumnDataLabel) {
    /* *
     *
     *  Constants
     *
     * */
    const dataLabelPositioners = {
        // Based on the value computed in Highcharts' distribute algorithm.
        radialDistributionY: function (point, dataLabel) {
            return (dataLabel.dataLabelPosition?.top || 0) +
                point.distributeBox.pos;
        },
        // Get the x - use the natural x position for labels near the top and
        // bottom, to prevent the top and bottom slice connectors from touching
        // each other on either side. Based on the value computed in Highcharts'
        // distribute algorithm.
        radialDistributionX: function (series, point, y, naturalY, dataLabel) {
            const pos = dataLabel.dataLabelPosition;
            return series.getXPos(y < (pos?.top || 0) + 2 || y > (pos?.bottom || 0) - 2 ?
                naturalY :
                y, point.half, point, dataLabel);
        },
        // The dataLabels.distance determines the x position of the label
        justify: function (point, dataLabel, radius, seriesCenter) {
            return seriesCenter[0] + (point.half ? -1 : 1) *
                (radius + (dataLabel.dataLabelPosition?.distance || 0));
        },
        // Left edges of the left-half labels touch the left edge of the plot
        // area. Right edges of the right-half labels touch the right edge of
        // the plot area.
        alignToPlotEdges: function (dataLabel, half, plotWidth, plotLeft) {
            const dataLabelWidth = dataLabel.getBBox().width;
            return half ? dataLabelWidth + plotLeft :
                plotWidth - dataLabelWidth - plotLeft;
        },
        // Connectors of each side end in the same x position. Labels are
        // aligned to them. Left edge of the widest left-half label touches the
        // left edge of the plot area. Right edge of the widest right-half label
        // touches the right edge of the plot area.
        alignToConnectors: function (points, half, plotWidth, plotLeft) {
            let maxDataLabelWidth = 0, dataLabelWidth;
            // Find widest data label
            points.forEach(function (point) {
                dataLabelWidth = point.dataLabel.getBBox().width;
                if (dataLabelWidth > maxDataLabelWidth) {
                    maxDataLabelWidth = dataLabelWidth;
                }
            });
            return half ? maxDataLabelWidth + plotLeft :
                plotWidth - maxDataLabelWidth - plotLeft;
        }
    };
    /* *
     *
     *  Functions
     *
     * */
    /** @internal */
    function compose(PieSeriesClass) {
        Series_DataLabel.compose(PieDataLabel_Series);
        if (pushUnique(PieDataLabel_composed, 'PieDataLabel')) {
            const pieProto = PieSeriesClass.prototype;
            pieProto.dataLabelPositioners = dataLabelPositioners;
            pieProto.alignDataLabel = PieDataLabel_noop;
            pieProto.drawDataLabels = drawDataLabels;
            pieProto.getDataLabelPosition = getDataLabelPosition;
            pieProto.placeDataLabels = placeDataLabels;
            pieProto.verifyDataLabelOverflow = verifyDataLabelOverflow;
        }
    }
    ColumnDataLabel.compose = compose;
    /** @internal */
    function getDataLabelPosition(point, distance) {
        const halfPI = Math.PI / 2, { start = 0, end = 0 } = point.shapeArgs || {};
        let angle = point.angle || 0;
        // If a large slice is crossing the lowest point, prefer rendering it 45
        // degrees out at either lower right or lower left. That's where there's
        // most likely to be space available and avoid text being truncated
        // (#22100). Technically this logic should also apply to the top point,
        // but that is more of an edge case since the default start angle is at
        // the top.
        if (distance > 0 &&
            // Crossing the bottom
            start < halfPI && end > halfPI &&
            // Angle within the bottom quadrant
            angle > halfPI / 2 && angle < halfPI * 1.5) {
            angle = angle <= halfPI ?
                Math.max(halfPI / 2, (start + halfPI) / 2) :
                Math.min(halfPI * 1.5, (halfPI + end) / 2);
        }
        const { center, options } = this, r = center[2] / 2, cosAngle = Math.cos(angle), sinAngle = Math.sin(angle), x = center[0] + cosAngle * r, y = center[1] + sinAngle * r, finalConnectorOffset = Math.min((options.slicedOffset || 0) + (options.borderWidth || 0), distance / 5); // #1678
        return {
            natural: {
                // Initial position of the data label - it's utilized for
                // finding the final position for the label
                x: x + cosAngle * distance,
                y: y + sinAngle * distance
            },
            computed: {
            // Used for generating connector path - initialized later in
            // drawDataLabels function x: undefined, y: undefined
            },
            // Left - pie on the left side of the data label
            // Right - pie on the right side of the data label
            // Center - data label overlaps the pie
            alignment: distance < 0 ? 'center' : point.half ? 'right' : 'left',
            connectorPosition: {
                angle,
                breakAt: {
                    x: x + cosAngle * finalConnectorOffset,
                    y: y + sinAngle * finalConnectorOffset
                },
                touchingSliceAt: {
                    x,
                    y
                }
            },
            distance
        };
    }
    /**
     * Override the base drawDataLabels method by pie specific functionality.
     * @internal
     */
    function drawDataLabels() {
        const series = this, points = series.points, chart = series.chart, plotWidth = chart.plotWidth, plotHeight = chart.plotHeight, plotLeft = chart.plotLeft, maxWidth = Math.round(chart.chartWidth / 3), seriesCenter = series.center, radius = seriesCenter[2] / 2, centerY = seriesCenter[1], halves = [
            [], // Right
            [] // Left
        ], overflow = [0, 0, 0, 0], // Top, right, bottom, left
        dataLabelPositioners = series.dataLabelPositioners;
        let connector, dataLabelWidth, labelHeight, maxLabelDistance = 0;
        // Get out if not enabled
        if (!series.visible || !series.hasDataLabels?.()) {
            return;
        }
        // Reset all labels that have been shortened
        points.forEach((point) => {
            (point.dataLabels || []).forEach((dataLabel) => {
                if (dataLabel.shortened) {
                    dataLabel
                        .attr({
                        width: 'auto'
                    }).css({
                        width: 'auto',
                        textOverflow: 'clip'
                    });
                    dataLabel.shortened = false;
                }
            });
        });
        // Run parent method
        PieDataLabel_Series.prototype.drawDataLabels.apply(series);
        points.forEach((point) => {
            (point.dataLabels || []).forEach((dataLabel, i) => {
                const r = seriesCenter[2] / 2, dataLabelOptions = dataLabel.options, distance = relativeLength(dataLabelOptions?.distance || 0, r);
                // Arrange points for collision detection
                if (i === 0) {
                    halves[point.half].push(point);
                }
                // Avoid long labels squeezing the pie size too far down
                if (!defined(dataLabelOptions?.style?.width)) {
                    if (dataLabel.getBBox().width > maxWidth) {
                        dataLabel.css({
                            // Use a fraction of the maxWidth to avoid wrapping
                            // close to the end of the string.
                            width: Math.round(maxWidth * 0.7) + 'px'
                        });
                        dataLabel.shortened = true;
                    }
                }
                dataLabel.dataLabelPosition = this.getDataLabelPosition(point, distance);
                maxLabelDistance = Math.max(maxLabelDistance, distance);
            });
        });
        /* Loop over the points in each half, starting from the top and bottom
         * of the pie to detect overlapping labels.
         */
        halves.forEach((points, halfIdx) => {
            const length = points.length, positions = [];
            let top, bottom, size = 0, distributionLength;
            if (!length) {
                return;
            }
            // Sort by angle
            series.sortByAngle(points, halfIdx - 0.5);
            // Only do anti-collision when we have dataLabels outside the pie
            // and have connectors. (#856)
            if (maxLabelDistance > 0) {
                top = Math.max(0, centerY - radius - maxLabelDistance);
                bottom = Math.min(centerY + radius + maxLabelDistance, chart.plotHeight);
                points.forEach((point) => {
                    // Check if specific points' label is outside the pie
                    (point.dataLabels || []).forEach((dataLabel) => {
                        const labelPosition = dataLabel.dataLabelPosition;
                        if (labelPosition &&
                            labelPosition.distance > 0) {
                            // The point.top depends on point.labelDistance
                            // value. Used for calculation of y value in getX
                            // method
                            labelPosition.top = Math.max(0, centerY - radius - labelPosition.distance);
                            labelPosition.bottom = Math.min(centerY + radius + labelPosition.distance, chart.plotHeight);
                            size = dataLabel.getBBox().height || 21;
                            dataLabel.lineHeight = chart.renderer.fontMetrics(dataLabel.text || dataLabel).h + 2 * dataLabel.padding;
                            point.distributeBox = {
                                target: ((dataLabel.dataLabelPosition
                                    ?.natural.y || 0) -
                                    labelPosition.top +
                                    dataLabel.lineHeight / 2),
                                size,
                                rank: point.y
                            };
                            positions.push(point.distributeBox);
                        }
                    });
                });
                distributionLength = bottom + size - top;
                PieDataLabel_distribute(positions, distributionLength, distributionLength / 5);
                // Uncomment this to visualize the boxes
                /*
                points.forEach((point): void => {
                    const box = point.distributeBox;
                    point.dlBox?.destroy();
                    if (box?.pos) {
                        point.dlBox = chart.renderer.rect(
                            chart.plotLeft + this.center[0] + (
                                halfIdx ?
                                    -this.center[2] / 2 - 100 :
                                    this.center[2] / 2
                            ),
                            chart.plotTop + box.pos,
                            100,
                            box.size
                        )
                            .attr({
                                stroke: 'silver',
                                'stroke-width': 1
                            })
                            .add();
                    }
                });
                // */
            }
            // Now the used slots are sorted, fill them up sequentially
            points.forEach((point) => {
                (point.dataLabels || []).forEach((dataLabel) => {
                    const dataLabelOptions = (dataLabel.options || {}), distributeBox = point.distributeBox, labelPosition = dataLabel.dataLabelPosition, naturalY = labelPosition?.natural.y || 0, connectorPadding = dataLabelOptions
                        .connectorPadding || 0, lineHeight = dataLabel.lineHeight || 21, bBox = dataLabel.getBBox(), topOffset = (lineHeight - bBox.height) / 2;
                    let x = 0, y = naturalY, visibility = 'inherit';
                    if (labelPosition) {
                        if (positions &&
                            defined(distributeBox) &&
                            labelPosition.distance > 0) {
                            if (typeof distributeBox.pos === 'undefined') {
                                visibility = 'hidden';
                            }
                            else {
                                labelHeight = distributeBox.size;
                                // Find label's y position
                                y = dataLabelPositioners
                                    .radialDistributionY(point, dataLabel);
                            }
                        }
                        // Find label's x position. The justify option is
                        // undocumented in the API - preserve support for it
                        if (dataLabelOptions.justify) {
                            x = dataLabelPositioners.justify(point, dataLabel, radius, seriesCenter);
                        }
                        else {
                            switch (dataLabelOptions.alignTo) {
                                case 'connectors':
                                    x = dataLabelPositioners.alignToConnectors(points, halfIdx, plotWidth, plotLeft);
                                    break;
                                case 'plotEdges':
                                    x = dataLabelPositioners.alignToPlotEdges(dataLabel, halfIdx, plotWidth, plotLeft);
                                    break;
                                default:
                                    x = dataLabelPositioners.radialDistributionX(series, point, y - topOffset, naturalY, dataLabel);
                            }
                        }
                        // Record the placement and visibility
                        labelPosition.attribs = {
                            visibility,
                            align: labelPosition.alignment
                        };
                        labelPosition.posAttribs = {
                            x: x +
                                (dataLabelOptions.x || 0) + // (#12985)
                                ({
                                    left: connectorPadding,
                                    right: -connectorPadding
                                }[labelPosition.alignment] || 0),
                            y: y +
                                (dataLabelOptions.y || 0) - // (#12985)
                                // Vertically center
                                lineHeight / 2
                        };
                        labelPosition.computed.x = x;
                        labelPosition.computed.y = y - topOffset;
                        // Detect overflowing data labels
                        if (pick(dataLabelOptions.crop, true)) {
                            dataLabelWidth = dataLabel.getBBox().width;
                            let sideOverflow;
                            // Overflow left
                            if (x - dataLabelWidth < connectorPadding &&
                                halfIdx === 1 // Left half
                            ) {
                                sideOverflow = Math.round(dataLabelWidth - x + connectorPadding);
                                overflow[3] = Math.max(sideOverflow, overflow[3]);
                                // Overflow right
                            }
                            else if (x + dataLabelWidth >
                                plotWidth - connectorPadding &&
                                halfIdx === 0 // Right half
                            ) {
                                sideOverflow = Math.round(x +
                                    dataLabelWidth -
                                    plotWidth +
                                    connectorPadding);
                                overflow[1] = Math.max(sideOverflow, overflow[1]);
                            }
                            // Overflow top
                            if (y - labelHeight / 2 < 0) {
                                overflow[0] = Math.max(Math.round(-y + labelHeight / 2), overflow[0]);
                                // Overflow left
                            }
                            else if (y + labelHeight / 2 > plotHeight) {
                                overflow[2] = Math.max(Math.round(y + labelHeight / 2 - plotHeight), overflow[2]);
                            }
                            labelPosition.sideOverflow = sideOverflow;
                        }
                    }
                }); // For each data label of the point
            }); // For each point
        }); // For each half
        // Do not apply the final placement and draw the connectors until we
        // have verified that labels are not spilling over.
        if (arrayMax(overflow) === 0 ||
            this.verifyDataLabelOverflow(overflow)) {
            // Place the labels in the final position
            this.placeDataLabels();
            this.points.forEach((point) => {
                point.dataLabels?.forEach((dataLabel, i) => {
                    // #8864: every connector can have individual options
                    const { connectorColor, connectorWidth = 1 } = (dataLabel.options || {}), labelPosition = dataLabel.dataLabelPosition;
                    // Draw the connector
                    if (isNumber(connectorWidth)) {
                        let isNew;
                        connector = dataLabel.connector;
                        if (labelPosition && labelPosition.distance > 0) {
                            isNew = !connector;
                            if (!connector) {
                                dataLabel.connector = connector = chart.renderer
                                    .path()
                                    .addClass('highcharts-data-label-connector ' +
                                    ' highcharts-color-' +
                                    point.colorIndex +
                                    (point.className ?
                                        ' ' + point.className :
                                        ''))
                                    .add(series.dataLabelsGroups?.[i]);
                            }
                            if (!chart.styledMode) {
                                connector.attr({
                                    'stroke-width': connectorWidth,
                                    'stroke': (connectorColor ||
                                        point.color ||
                                        'var(--highcharts-neutral-color-60)')
                                });
                            }
                            connector[isNew ? 'attr' : 'animate']({
                                d: point.getConnectorPath(dataLabel)
                            });
                            connector.attr({
                                visibility: labelPosition.attribs?.visibility
                            });
                        }
                        else if (connector) {
                            dataLabel.connector = connector.destroy();
                        }
                    }
                });
            });
        }
    }
    /**
     * Perform the final placement of the data labels after we have verified
     * that they fall within the plot area.
     * @internal
     */
    function placeDataLabels() {
        this.points.forEach((point) => {
            (point.dataLabels || []).forEach((dataLabel) => {
                const labelPosition = dataLabel.dataLabelPosition;
                if (labelPosition) {
                    // Shorten data labels with ellipsis if they still overflow
                    // after the pie has reached minSize (#223).
                    if (labelPosition.sideOverflow) {
                        dataLabel.css({
                            width: (Math.max(dataLabel.getBBox().width -
                                labelPosition.sideOverflow, 0)) + 'px',
                            textOverflow: (dataLabel.options?.style?.textOverflow ||
                                'ellipsis')
                        });
                        dataLabel.shortened = true;
                    }
                    dataLabel.attr(labelPosition.attribs);
                    dataLabel[dataLabel.moved ? 'animate' : 'attr'](labelPosition.posAttribs);
                    dataLabel.moved = true;
                }
                else if (dataLabel) {
                    dataLabel.attr({ y: -9999 });
                }
            });
            // Clear for update
            delete point.distributeBox;
        }, this);
    }
    /**
     * Verify whether the data labels are allowed to draw, or we should run more
     * translation and data label positioning to keep them inside the plot area.
     * Returns true when data labels are ready to draw.
     * @internal
     */
    function verifyDataLabelOverflow(overflow) {
        const center = this.center, options = this.options, centerOption = options.center, minSize = options.minSize || 80;
        let newSize = minSize, 
        // If a size is set, return true and don't try to shrink the pie
        // to fit the labels.
        ret = options.size !== null;
        if (!ret) {
            // Handle horizontal size and center
            if (centerOption[0] !== null) { // Fixed center
                newSize = Math.max(center[2] -
                    Math.max(overflow[1], overflow[3]), minSize);
            }
            else { // Auto center
                newSize = Math.max(
                // Horizontal overflow
                center[2] - overflow[1] - overflow[3], minSize);
                // Horizontal center
                center[0] += (overflow[3] - overflow[1]) / 2;
            }
            // Handle vertical size and center
            if (centerOption[1] !== null) { // Fixed center
                newSize = clamp(newSize, minSize, center[2] - Math.max(overflow[0], overflow[2]));
            }
            else { // Auto center
                newSize = clamp(newSize, minSize, 
                // Vertical overflow
                center[2] - overflow[0] - overflow[2]);
                // Vertical center
                center[1] += (overflow[0] - overflow[2]) / 2;
            }
            // If the size must be decreased, we need to run translate and
            // drawDataLabels again
            if (newSize < center[2]) {
                center[2] = newSize;
                center[3] = Math.min(// #3632
                options.thickness ?
                    Math.max(0, newSize - options.thickness * 2) :
                    Math.max(0, relativeLength(options.innerSize || 0, newSize)), newSize); // #6647
                this.translate(center);
                if (this.drawDataLabels) {
                    this.drawDataLabels();
                }
                // Else, return true to indicate that the pie and its labels is
                // within the plot area
            }
            else {
                ret = true;
            }
        }
        return ret;
    }
})(PieDataLabel_ColumnDataLabel || (PieDataLabel_ColumnDataLabel = {}));
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
/* harmony default export */ const PieDataLabel = (PieDataLabel_ColumnDataLabel);

;// ./code/es-modules/Core/Geometry/GeometryUtilities.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */

/* *
 *
 *  Namespace
 *
 * */
/** @internal */
var GeometryUtilities;
(function (GeometryUtilities) {
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Calculates the center between a list of points.
     *
     * @internal
     *
     * @param {Array<Highcharts.PositionObject>} points
     * A list of points to calculate the center of.
     *
     * @return {Highcharts.PositionObject}
     * Calculated center
     */
    function getCenterOfPoints(points) {
        const sum = points.reduce((sum, point) => {
            sum.x += point.x;
            sum.y += point.y;
            return sum;
        }, { x: 0, y: 0 });
        return {
            x: sum.x / points.length,
            y: sum.y / points.length
        };
    }
    GeometryUtilities.getCenterOfPoints = getCenterOfPoints;
    /**
     * Calculates the distance between two points based on their x and y
     * coordinates.
     *
     * @internal
     *
     * @param {Highcharts.PositionObject} p1
     * The x and y coordinates of the first point.
     *
     * @param {Highcharts.PositionObject} p2
     * The x and y coordinates of the second point.
     *
     * @return {number}
     * Returns the distance between the points.
     */
    function getDistanceBetweenPoints(p1, p2) {
        return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
    }
    GeometryUtilities.getDistanceBetweenPoints = getDistanceBetweenPoints;
    /**
     * Calculates the angle between two points.
     * @todo add unit tests.
     * @internal
     * @param {Highcharts.PositionObject} p1 The first point.
     * @param {Highcharts.PositionObject} p2 The second point.
     * @return {number} Returns the angle in radians.
     */
    function getAngleBetweenPoints(p1, p2) {
        return Math.atan2(p2.x - p1.x, p2.y - p1.y);
    }
    GeometryUtilities.getAngleBetweenPoints = getAngleBetweenPoints;
    /**
     * Test for point in polygon. Polygon defined as array of [x,y] points.
     * @internal
     * @param {PositionObject} point The point potentially within a polygon.
     * @param {Array<Array<number>>} polygon The polygon potentially containing the point.
     */
    function pointInPolygon({ x, y }, polygon) {
        const len = polygon.length;
        let i, j, inside = false;
        for (i = 0, j = len - 1; i < len; j = i++) {
            const [x1, y1] = polygon[i], [x2, y2] = polygon[j];
            if (y1 > y !== y2 > y &&
                (x < (x2 - x1) *
                    (y - y1) /
                    (y2 - y1) +
                    x1)) {
                inside = !inside;
            }
        }
        return inside;
    }
    GeometryUtilities.pointInPolygon = pointInPolygon;
})(GeometryUtilities || (GeometryUtilities = {}));
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
/* harmony default export */ const Geometry_GeometryUtilities = (GeometryUtilities);

;// ./code/es-modules/Core/Series/OverlappingDataLabels.js
/* *
 *
 *  Highcharts module to hide overlapping data labels.
 *  This module is included in Highcharts.
 *
 *  (c) 2009-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { pointInPolygon } = Geometry_GeometryUtilities;

/* *
 *
 *  Functions
 *
 * */
/**
 * Hide overlapping labels. Labels are moved and faded in and out on zoom to
 * provide a smooth visual impression.
 *
 * @internal
 * @function Highcharts.Chart#hideOverlappingLabels
 * @param {Array<Highcharts.SVGElement>} labels
 *        Rendered data labels
 */
function chartHideOverlappingLabels(labels) {
    const chart = this, len = labels.length, isIntersectRect = (box1, box2) => !(box2.x >= box1.x + box1.width ||
        box2.x + box2.width <= box1.x ||
        box2.y >= box1.y + box1.height ||
        box2.y + box2.height <= box1.y), isPolygonOverlap = (box1Poly, box2Poly) => {
        for (const p of box1Poly) {
            if (pointInPolygon({ x: p[0], y: p[1] }, box2Poly)) {
                return true;
            }
        }
        return false;
    };
    /**
     * Get the box with its position inside the chart, as opposed to getBBox
     * that only reports the position relative to the parent.
     */
    function getAbsoluteBox(label) {
        if (label && (!label.alignAttr || label.placed)) {
            const padding = label.box ? 0 : (label.padding || 0), pos = label.dataLabelPosition?.posAttribs || // #21725
                label.alignAttr ||
                {
                    x: label.attr('x'),
                    y: label.attr('y')
                }, { height, polygon, width } = label.getBBox(), alignOffset = getAlignFactor(label.alignValue) * width;
            label.width = width;
            label.height = height;
            return {
                x: pos.x + (label.parentGroup?.translateX || 0) + padding - alignOffset,
                y: pos.y + (label.parentGroup?.translateY || 0) + padding,
                width: width - 2 * padding,
                height: height - 2 * padding,
                polygon
            };
        }
    }
    let label, label1, label2, box1, box2, isLabelAffected = false;
    for (let i = 0; i < len; i++) {
        label = labels[i];
        if (label) {
            // Mark with initial opacity
            label.oldOpacity = label.opacity;
            label.newOpacity = 1;
            label.absoluteBox = getAbsoluteBox(label);
        }
    }
    // Prevent a situation in a gradually rising slope, that each label will
    // hide the previous one because the previous one always has lower rank.
    labels.sort((a, b) => (b?.labelrank || 0) - (a?.labelrank || 0));
    // Detect overlapping labels
    for (let i = 0; i < len; ++i) {
        label1 = labels[i];
        box1 = label1 && label1.absoluteBox;
        const box1Poly = box1?.polygon;
        for (let j = i + 1; j < len; ++j) {
            label2 = labels[j];
            box2 = label2 && label2.absoluteBox;
            let toHide = false;
            if (box1 &&
                box2 &&
                label1 !== label2 && // #6465, polar chart with connectEnds
                label1?.newOpacity !== 0 &&
                label2?.newOpacity !== 0 &&
                // #15863 dataLabels are no longer hidden by translation
                label1?.visibility !== 'hidden' &&
                label2?.visibility !== 'hidden') {
                const box2Poly = box2.polygon;
                // If labels have polygons, only evaluate
                // based on polygons
                if (box1Poly &&
                    box2Poly &&
                    box1Poly !== box2Poly) {
                    if (isPolygonOverlap(box1Poly, box2Poly)) {
                        toHide = true;
                    }
                    // If there are no polygons, evaluate rectangles colliding
                }
                else if (isIntersectRect(box1, box2)) {
                    toHide = true;
                }
                if (toHide) {
                    const overlappingLabel = (label1?.labelrank < label2?.labelrank ?
                        label1 :
                        label2), labelText = overlappingLabel?.text;
                    if (overlappingLabel) {
                        overlappingLabel.newOpacity = 0;
                    }
                    if (labelText?.element.querySelector('textPath')) {
                        labelText.hide();
                    }
                }
            }
        }
    }
    // Hide or show
    for (const label of labels) {
        if (label && hideOrShow(label, chart)) {
            isLabelAffected = true;
        }
    }
    if (isLabelAffected) {
        fireEvent(chart, 'afterHideAllOverlappingLabels');
    }
}
/** @internal */
function composeOverlappingDataLabels(ChartClass) {
    const chartProto = ChartClass.prototype;
    if (!chartProto.hideOverlappingLabels) {
        chartProto.hideOverlappingLabels = chartHideOverlappingLabels;
        addEvent(ChartClass, 'render', OverlappingDataLabels_onChartRender);
    }
}
/**
 * Hide or show labels based on opacity.
 *
 * @internal
 * @function hideOrShow
 * @param {Highcharts.SVGElement} label
 * The label.
 * @param {Highcharts.Chart} chart
 * The chart that contains the label.
 * @return {boolean}
 * Whether label is affected
 */
function hideOrShow(label, chart) {
    let complete, newOpacity, isLabelAffected = false;
    if (label) {
        newOpacity = label.newOpacity;
        if (label.oldOpacity !== newOpacity) {
            // Toggle data labels
            if (label.hasClass('highcharts-data-label')) {
                // Make sure the label is completely hidden to avoid catching
                // clicks (#4362)
                label[newOpacity ? 'removeClass' : 'addClass']('highcharts-data-label-hidden');
                complete = function () {
                    if (!chart.styledMode) {
                        label.css({
                            pointerEvents: newOpacity ? 'auto' : 'none'
                        });
                    }
                };
                isLabelAffected = true;
                // Animate or set the opacity
                label[label.isOld || label.placed ? 'animate' : 'attr']({ opacity: newOpacity }, void 0, complete);
                fireEvent(chart, 'afterHideOverlappingLabel');
                // Toggle other labels, tick labels
            }
            else {
                label.attr({
                    opacity: newOpacity
                });
            }
        }
        label.isOld = true;
    }
    return isLabelAffected;
}
/**
 * Collect potential overlapping data labels. Stack labels probably don't need
 * to be considered because they are usually accompanied by data labels that lie
 * inside the columns.
 * @internal
 */
function OverlappingDataLabels_onChartRender() {
    const chart = this;
    let labels = [];
    // Consider external label collectors
    for (const collector of (chart.labelCollectors || [])) {
        labels = labels.concat(collector());
    }
    for (const yAxis of (chart.yAxis || [])) {
        if (yAxis.stacking &&
            yAxis.options.stackLabels &&
            !yAxis.options.stackLabels.allowOverlap) {
            objectEach(yAxis.stacking.stacks, (stack) => {
                objectEach(stack, (stackItem) => {
                    if (stackItem.label) {
                        labels.push(stackItem.label);
                    }
                });
            });
        }
    }
    for (const series of (chart.series || [])) {
        if (series.visible && series.hasDataLabels?.()) { // #3866
            const push = (points) => {
                for (const point of points) {
                    if (point.visible) {
                        (point.dataLabels || []).forEach((label) => {
                            const options = label.options || {};
                            label.labelrank = pick(options.labelrank, point.labelrank, point.shapeArgs?.height); // #4118
                            // #21725: Sync target positions for generic overlap
                            // checking. During animations (e.g., toggling a
                            // point), DOM positions may overlap. We force
                            // alignAttr to the final target coordinates so
                            // getAbsoluteBox() evaluates the final resting
                            // positions.
                            /*
                            // Commented out because it caused initial overlap
                            // in the highcharts/demo/pie-semi-circle sample.
                            const pos = label.dataLabelPosition?.posAttribs;
                            if (pos) {
                                label.alignAttr = label.alignAttr || {};
                                label.alignAttr.x = pos.x;
                                label.alignAttr.y = pos.y;
                            }
                            */
                            // Allow overlap if the option is explicitly true
                            if (
                            // #13449
                            options.allowOverlap ??
                                // Pie labels outside have a separate placement
                                // logic, skip the overlap logic
                                (series.is('pie') &&
                                    Number(options.distance) > 0)) {
                                label.oldOpacity = label.opacity;
                                label.newOpacity = 1;
                                hideOrShow(label, chart);
                                // Do not allow overlap
                            }
                            else {
                                labels.push(label);
                            }
                        });
                    }
                }
            };
            push(series.nodes || []);
            push(series.points);
        }
    }
    this.hideOverlappingLabels(labels);
}

;// ./code/es-modules/Extensions/BorderRadius.js
/* *
 *
 *  Highcharts Border Radius module
 *
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { defaultOptions: BorderRadius_defaultOptions } = Defaults;

const { noop: BorderRadius_noop } = Core_Globals;

/* *
 *
 *  Constants
 *
 * */
const defaultBorderRadiusOptions = {
    radius: 0,
    scope: 'stack',
    where: void 0
};
/* *
 *
 *  Variables
 *
 * */
let oldArc = BorderRadius_noop;
let oldRoundedRect = BorderRadius_noop;
/* *
 *
 *  Functions
 *
 * */
/** @internal */
function applyBorderRadius(path, i, r) {
    const a = path[i];
    let b = path[i + 1];
    if (b[0] === 'Z') {
        b = path[0];
    }
    let line, arc, fromLineToArc;
    // From straight line to arc
    if ((a[0] === 'M' || a[0] === 'L') && b[0] === 'A') {
        line = a;
        arc = b;
        fromLineToArc = true;
        // From arc to straight line
    }
    else if (a[0] === 'A' && (b[0] === 'M' || b[0] === 'L')) {
        line = b;
        arc = a;
    }
    if (line && arc && arc.params) {
        const bigR = arc[1], 
        // In our use cases, outer pie slice arcs are clockwise and inner
        // arcs (donut/sunburst etc) are anti-clockwise
        clockwise = arc[5], params = arc.params, { start, end, cx, cy } = params;
        // Some geometric constants
        const relativeR = clockwise ? (bigR - r) : (bigR + r), 
        // The angle, on the big arc, that the border radius arc takes up
        angleOfBorderRadius = relativeR ? Math.asin(r / relativeR) : 0, angleOffset = clockwise ?
            angleOfBorderRadius :
            -angleOfBorderRadius, 
        // The distance along the radius of the big arc to the starting
        // point of the small border radius arc
        distanceBigCenterToStartArc = (Math.cos(angleOfBorderRadius) *
            relativeR);
        // From line to arc
        if (fromLineToArc) {
            // Update the cache
            params.start = start + angleOffset;
            // First move to the start position at the radial line. We want to
            // start one borderRadius closer to the center.
            line[1] = cx + distanceBigCenterToStartArc * Math.cos(start);
            line[2] = cy + distanceBigCenterToStartArc * Math.sin(start);
            // Now draw an arc towards the point where the small circle touches
            // the great circle.
            path.splice(i + 1, 0, [
                'A',
                r,
                r,
                0, // Slanting,
                0, // Long arc
                1, // Clockwise
                cx + bigR * Math.cos(params.start),
                cy + bigR * Math.sin(params.start)
            ]);
            // From arc to line
        }
        else {
            // Update the cache
            params.end = end - angleOffset;
            // End the big arc a bit earlier
            arc[6] = cx + bigR * Math.cos(params.end);
            arc[7] = cy + bigR * Math.sin(params.end);
            // Draw a small arc towards a point on the end angle, but one
            // borderRadius closer to the center relative to the perimeter.
            path.splice(i + 1, 0, [
                'A',
                r,
                r,
                0,
                0,
                1,
                cx + distanceBigCenterToStartArc * Math.cos(end),
                cy + distanceBigCenterToStartArc * Math.sin(end)
            ]);
        }
        // Long or short arc must be reconsidered because we have modified the
        // start and end points
        arc[4] = Math.abs(params.end - params.start) < Math.PI ? 0 : 1;
    }
}
/**
 * Extend arc with borderRadius.
 * @internal
 */
function BorderRadius_arc(x, y, w, h, options = {}) {
    const path = oldArc(x, y, w, h, options), { brStart = true, brEnd = true, innerR = 0, r = w, start = 0, end = 0 } = options;
    if (options.open || !options.borderRadius) {
        return path;
    }
    const alpha = end - start, sinHalfAlpha = Math.sin(alpha / 2), borderRadius = Math.max(Math.min(relativeLength(options.borderRadius || 0, r - innerR), 
    // Cap to half the sector radius
    (r - innerR) / 2, 
    // For smaller pie slices, cap to the largest small circle that
    // can be fitted within the sector
    (r * sinHalfAlpha) / (1 + sinHalfAlpha)), 0), 
    // For the inner radius, we need an extra cap because the inner arc
    // is shorter than the outer arc
    innerBorderRadius = Math.min(borderRadius, 2 * (alpha / Math.PI) * innerR);
    // Apply turn-by-turn border radius. Start at the end since we're
    // splicing in arc segments.
    let i = path.length - 1;
    while (i--) {
        if ((!brStart && (i === 0 || i === 3)) ||
            (!brEnd && (i === 1 || i === 2))) {
            continue;
        }
        applyBorderRadius(path, i, i > 1 ? innerBorderRadius : borderRadius);
    }
    return path;
}
/** @internal */
function seriesOnAfterColumnTranslate() {
    if (this.options.borderRadius &&
        !(this.chart.is3d && this.chart.is3d())) {
        const { options, yAxis } = this, percent = options.stacking === 'percent', seriesDefault = BorderRadius_defaultOptions.plotOptions?.[this.type]
            ?.borderRadius, borderRadius = optionsToObject(options.borderRadius, isObject(seriesDefault) ? seriesDefault : {}), reversed = yAxis.options.reversed;
        for (const point of this.points) {
            const { shapeArgs } = point;
            if (point.shapeType === 'roundedRect' && shapeArgs) {
                const { width = 0, height = 0, y = 0 } = shapeArgs;
                let brBoxY = y, brBoxHeight = height;
                // It would be nice to refactor StackItem.getStackBox/
                // setOffset so that we could get a reliable box out of
                // it. Currently it is close if we remove the label
                // offset, but we still need to run crispCol and also
                // flip it if inverted, so atm it is simpler to do it
                // like the below.
                if (borderRadius.scope === 'stack' &&
                    point.stackTotal) {
                    const stackEnd = yAxis.translate(percent ? 100 : point.stackTotal, false, true, false, true), stackThreshold = yAxis.translate(options.threshold || 0, false, true, false, true), box = this.crispCol(0, Math.min(stackEnd, stackThreshold), 0, Math.abs(stackEnd - stackThreshold));
                    brBoxY = box.y;
                    brBoxHeight = box.height;
                }
                const flip = (point.negative ? -1 : 1) *
                    (reversed ? -1 : 1) === -1;
                // Handle the where option
                let where = borderRadius.where;
                // Waterfall, hanging columns should have rounding on
                // all sides
                if (!where &&
                    this.is('waterfall') &&
                    Math.abs((point.yBottom || 0) -
                        (this.translatedThreshold || 0)) > this.borderWidth) {
                    where = 'all';
                }
                if (!where) {
                    where = 'end';
                }
                // Get the radius
                const r = Math.min(relativeLength(borderRadius.radius, width), width / 2, 
                // Cap to the height, but not if where is `end`
                where === 'all' ? brBoxHeight / 2 : Infinity) || 0;
                // If the `where` option is 'end', cut off the
                // rectangles by making the border-radius box one r
                // greater, so that the imaginary radius falls outside
                // the rectangle.
                if (where === 'end') {
                    if (flip) {
                        brBoxY -= r;
                        brBoxHeight += r;
                    }
                    else {
                        brBoxHeight += r;
                    }
                }
                extend(shapeArgs, { brBoxHeight, brBoxY, r });
            }
        }
    }
}
/** @internal */
function composeBorderRadius(SeriesClass, SVGElementClass, SVGRendererClass) {
    const PieSeriesClass = SeriesClass.types.pie;
    if (!SVGElementClass.symbolCustomAttribs.includes('borderRadius')) {
        const symbols = SVGRendererClass.prototype.symbols;
        addEvent(SeriesClass, 'afterColumnTranslate', seriesOnAfterColumnTranslate, {
            // After columnrange and polar column modifications
            order: 9
        });
        addEvent(PieSeriesClass, 'afterTranslate', pieSeriesOnAfterTranslate);
        SVGElementClass.symbolCustomAttribs.push('borderRadius', 'brBoxHeight', 'brBoxY', 'brEnd', 'brStart');
        oldArc = symbols.arc;
        oldRoundedRect = symbols.roundedRect;
        symbols.arc = BorderRadius_arc;
        symbols.roundedRect = BorderRadius_roundedRect;
    }
}
/** @internal */
function optionsToObject(options, seriesBROptions) {
    if (!isObject(options)) {
        options = { radius: options || 0 };
    }
    return merge(defaultBorderRadiusOptions, seriesBROptions, options);
}
/** @internal */
function pieSeriesOnAfterTranslate() {
    const borderRadius = optionsToObject(this.options.borderRadius);
    for (const point of this.points) {
        const shapeArgs = point.shapeArgs;
        if (shapeArgs) {
            shapeArgs.borderRadius = relativeLength(borderRadius.radius, (shapeArgs.r || 0) - ((shapeArgs.innerR) || 0));
        }
    }
}
/**
 * Extend roundedRect with individual cutting through rOffset.
 * @internal
 */
function BorderRadius_roundedRect(x, y, width, height, options = {}) {
    const path = oldRoundedRect(x, y, width, height, options), { r = 0, brBoxHeight = height, brBoxY = y } = options, brOffsetTop = y - brBoxY, brOffsetBtm = (brBoxY + brBoxHeight) - (y + height), 
    // When the distance to the border-radius box is greater than the r
    // itself, it means no border radius. The -0.1 accounts for float
    // rounding errors.
    rTop = (brOffsetTop - r) > -0.1 ? 0 : r, rBtm = (brOffsetBtm - r) > -0.1 ? 0 : r, cutTop = Math.max(rTop && brOffsetTop, 0), cutBtm = Math.max(rBtm && brOffsetBtm, 0);
    /*

    The naming of control points:

      / a -------- b \
     /                \
    h                  c
    |                  |
    |                  |
    |                  |
    g                  d
     \                /
      \ f -------- e /

    */
    const a = [x + rTop, y], b = [x + width - rTop, y], c = [x + width, y + rTop], d = [
        x + width, y + height - rBtm
    ], e = [
        x + width - rBtm,
        y + height
    ], f = [x + rBtm, y + height], g = [x, y + height - rBtm], h = [x, y + rTop];
    const applyPythagoras = (r, altitude) => Math.sqrt(Math.pow(r, 2) - Math.pow(altitude, 2));
    // Inside stacks, cut off part of the top
    if (cutTop) {
        const base = applyPythagoras(rTop, rTop - cutTop);
        a[0] -= base;
        b[0] += base;
        c[1] = h[1] = y + rTop - cutTop;
    }
    // Column is lower than the radius. Cut off bottom inside the top
    // radius.
    if (height < rTop - cutTop) {
        const base = applyPythagoras(rTop, rTop - cutTop - height);
        c[0] = d[0] = x + width - rTop + base;
        e[0] = Math.min(c[0], e[0]);
        f[0] = Math.max(d[0], f[0]);
        g[0] = h[0] = x + rTop - base;
        c[1] = h[1] = y + height;
    }
    // Inside stacks, cut off part of the bottom
    if (cutBtm) {
        const base = applyPythagoras(rBtm, rBtm - cutBtm);
        e[0] += base;
        f[0] -= base;
        d[1] = g[1] = y + height - rBtm + cutBtm;
    }
    // Cut off top inside the bottom radius
    if (height < rBtm - cutBtm) {
        const base = applyPythagoras(rBtm, rBtm - cutBtm - height);
        c[0] = d[0] = x + width - rBtm + base;
        b[0] = Math.min(c[0], b[0]);
        a[0] = Math.max(d[0], a[0]);
        g[0] = h[0] = x + rBtm - base;
        d[1] = g[1] = y;
    }
    // Preserve the box for data labels
    path.length = 0;
    path.push(['M', ...a], 
    // Top side
    ['L', ...b], 
    // Top right corner
    ['A', rTop, rTop, 0, 0, 1, ...c], 
    // Right side
    ['L', ...d], 
    // Bottom right corner
    ['A', rBtm, rBtm, 0, 0, 1, ...e], 
    // Bottom side
    ['L', ...f], 
    // Bottom left corner
    ['A', rBtm, rBtm, 0, 0, 1, ...g], 
    // Left side
    ['L', ...h], 
    // Top left corner
    ['A', rTop, rTop, 0, 0, 1, ...a], ['Z']);
    return path;
}
/* *
 *
 *  API Declarations
 *
 * */
/**
 * Detailed options for border radius.
 *
 * @sample  {highcharts} highcharts/plotoptions/column-borderradius/
 *          Rounded columns
 * @sample  highcharts/plotoptions/series-border-radius
 *          Column and pie with rounded border
 *
 * @interface Highcharts.BorderRadiusOptionsObject
 */ /**
* The border radius. A number signifies pixels. A percentage string, like for
* example `50%`, signifies a relative size. For columns this is relative to the
* column width, for pies it is relative to the radius and the inner radius.
*
* @sample  {highcharts} highcharts/plotoptions/column-borderradius/
*          Rounded columns
* @sample  highcharts/plotoptions/series-border-radius
*          Column and pie with rounded border
*
* @name Highcharts.BorderRadiusOptionsObject#radius
* @type {string|number}
*/ /**
* The scope of the rounding for column charts or plot bands. In a stacked
* column chart, the value `point` means each single point will get rounded
* corners. The value `stack` means the rounding will apply to the full
* stack, so that only points close to the top or bottom will receive
* rounding.
*
* Similarly, for plot bands, the `individual` value means each plot band
* will get rounded corners.
*
* @sample  {highcharts} highcharts/plotoptions/column-borderradius/
*          Rounded columns
*
* @name Highcharts.BorderRadiusOptionsObject#scope
* @validvalue ["individual", "point", "stack"]
* @type {string}
*/ /**
* For column charts, where in the point or stack to apply rounding. The `end`
* value means only those corners at the point value will be rounded, leaving
* the corners at the base or threshold unrounded. This is the most intuitive
* behavior. The `all` value means also the base will be rounded.
*
* @sample  {highcharts} highcharts/plotoptions/column-borderradius-where-all
*          Rounding on all corners
*
* @name Highcharts.BorderRadiusOptionsObject#where
* @validvalue ["all", "end"]
* @type {string}
* @default end
*/
(''); // Keeps doclets above in JS file

;// ./code/es-modules/Core/Responsive.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */



/* *
 *
 *  Composition
 *
 * */
var Responsive;
(function (Responsive) {
    /* *
     *
     *  Declarations
     *
     * */
    /* *
     *
     *  Functions
     *
     * */
    /** @internal */
    function compose(ChartClass) {
        const chartProto = ChartClass.prototype;
        if (!chartProto.matchResponsiveRule) {
            extend(chartProto, {
                matchResponsiveRule,
                setResponsive
            });
        }
        return ChartClass;
    }
    Responsive.compose = compose;
    /**
     * Handle a single responsiveness rule.
     *
     * @internal
     * @function Highcharts.Chart#matchResponsiveRule
     * @param {Highcharts.ResponsiveRulesOptions} rule
     * @param {Array<string>} matches
     */
    function matchResponsiveRule(rule, matches) {
        const condition = rule.condition, fn = condition.callback || function () {
            return (this.chartWidth <= pick(condition.maxWidth, Number.MAX_VALUE) &&
                this.chartHeight <= pick(condition.maxHeight, Number.MAX_VALUE) &&
                this.chartWidth >= pick(condition.minWidth, 0) &&
                this.chartHeight >= pick(condition.minHeight, 0));
        };
        if (fn.call(this, this)) {
            matches.push(rule._id);
        }
    }
    /**
     * Update the chart based on the current chart/document size and options
     * for responsiveness.
     *
     * @internal
     * @function Highcharts.Chart#setResponsive
     * @param  {boolean} [redraw=true]
     * @param  {boolean} [reset=false]
     * Reset by un-applying all rules. Chart.update resets all rules before
     * applying updated options.
     */
    function setResponsive(redraw, reset) {
        const options = this.options.responsive, currentResponsive = this.currentResponsive;
        let ruleIds = [], undoOptions;
        if (!reset && options && options.rules) {
            options.rules.forEach((rule) => {
                if (typeof rule._id === 'undefined') {
                    rule._id = uniqueKey();
                }
                this.matchResponsiveRule(rule, ruleIds /* , redraw */);
            }, this);
        }
        // Merge matching rules
        const mergedOptions = merge(...ruleIds
            .map((ruleId) => find(options?.rules || [], (rule) => (rule._id === ruleId)))
            .map((rule) => rule?.chartOptions));
        mergedOptions.isResponsiveOptions = true;
        // Stringified key for the rules that currently apply.
        ruleIds = (ruleIds.toString() || void 0);
        const currentRuleIds = currentResponsive?.ruleIds;
        // Changes in what rules apply
        if (ruleIds !== currentRuleIds) {
            // Undo previous rules. Before we apply a new set of rules, we
            // need to roll back completely to base options (#6291).
            if (currentResponsive) {
                this.currentResponsive = void 0;
                this.updatingResponsive = true;
                this.update(currentResponsive.undoOptions, redraw, true);
                this.updatingResponsive = false;
            }
            if (ruleIds) {
                // Get undo-options for matching rules. The `undoOptions``
                // hold the current values before they are changed by the
                // `mergedOptions`.
                undoOptions = diffObjects(mergedOptions, this.options, true, this.collectionsWithUpdate);
                undoOptions.isResponsiveOptions = true;
                this.currentResponsive = {
                    ruleIds: ruleIds,
                    mergedOptions: mergedOptions,
                    undoOptions: undoOptions
                };
                if (!this.updatingResponsive) {
                    this.update(mergedOptions, redraw, true);
                }
            }
            else {
                this.currentResponsive = void 0;
            }
        }
    }
})(Responsive || (Responsive = {}));
/* *
 *
 *  Default Export
 *
 * */
/* harmony default export */ const Core_Responsive = (Responsive);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * A callback function to gain complete control on when the responsive rule
 * applies.
 *
 * @callback Highcharts.ResponsiveCallbackFunction
 *
 * @param {Highcharts.Chart} this
 * Chart context.
 *
 * @param {Highcharts.Chart} [ctx]
 * Since v12.6.0, the chart context passed as an extra argument for arrow
 * functions.
 *
 * @return {boolean}
 * Return `true` if it applies.
 */
(''); // Keeps doclets above in JS file
/* *
 *
 *  API Options
 *
 * */
/**
 * Allows setting a set of rules to apply for different screen or chart
 * sizes. Each rule specifies additional chart options.
 *
 * @sample {highstock} stock/demo/responsive/
 *         Stock chart
 * @sample highcharts/responsive/axis/
 *         Axis
 * @sample highcharts/responsive/legend/
 *         Legend
 * @sample highcharts/responsive/classname/
 *         Class name
 *
 * @since     5.0.0
 * @apioption responsive
 */
/**
 * A set of rules for responsive settings. The rules are executed from
 * the top down.
 *
 * @sample {highcharts} highcharts/responsive/axis/
 *         Axis changes
 * @sample {highstock} highcharts/responsive/axis/
 *         Axis changes
 * @sample {highmaps} highcharts/responsive/axis/
 *         Axis changes
 *
 * @type      {Array<*>}
 * @since     5.0.0
 * @apioption responsive.rules
 */
/**
 * A full set of chart options to apply as overrides to the general
 * chart options. The chart options are applied when the given rule
 * is active.
 *
 * A special case is configuration objects that take arrays, for example
 * [xAxis](#xAxis), [yAxis](#yAxis) or [series](#series). For these
 * collections, an `id` option is used to map the new option set to
 * an existing object. If an existing object of the same id is not found,
 * the item of the same index updated. So for example, setting `chartOptions`
 * with two series items without an `id`, will cause the existing chart's
 * two series to be updated with respective options.
 *
 * @sample {highstock} stock/demo/responsive/
 *         Stock chart
 * @sample highcharts/responsive/axis/
 *         Axis
 * @sample highcharts/responsive/legend/
 *         Legend
 * @sample highcharts/responsive/classname/
 *         Class name
 *
 * @type      {Highcharts.Options}
 * @since     5.0.0
 * @apioption responsive.rules.chartOptions
 */
/**
 * Under which conditions the rule applies.
 *
 * @since     5.0.0
 * @apioption responsive.rules.condition
 */
/**
 * A callback function to gain complete control on when the responsive
 * rule applies. Return `true` if it applies. This opens for checking
 * against other metrics than the chart size, for example the document
 * size or other elements. Since v12.6.0, the callback also receives `ctx`
 * as the first argument, so that arrow functions can access the same
 * context as regular functions using `this`.
 *
 * @type      {Highcharts.ResponsiveCallbackFunction}
 * @since     5.0.0
 * @context   Highcharts.Chart
 * @apioption responsive.rules.condition.callback
 */
/**
 * The responsive rule applies if the chart height is less than this.
 *
 * @type      {number}
 * @since     5.0.0
 * @apioption responsive.rules.condition.maxHeight
 */
/**
 * The responsive rule applies if the chart width is less than this.
 *
 * @sample highcharts/responsive/axis/
 *         Max width is 500
 *
 * @type      {number}
 * @since     5.0.0
 * @apioption responsive.rules.condition.maxWidth
 */
/**
 * The responsive rule applies if the chart height is greater than this.
 *
 * @type      {number}
 * @default   0
 * @since     5.0.0
 * @apioption responsive.rules.condition.minHeight
 */
/**
 * The responsive rule applies if the chart width is greater than this.
 *
 * @type      {number}
 * @default   0
 * @since     5.0.0
 * @apioption responsive.rules.condition.minWidth
 */
(''); // Keeps doclets above in JS file

;// ./code/es-modules/masters/highcharts.src.js
// SPDX-License-Identifier: LicenseRef-Highcharts
/**
 * @license Highcharts JS v13.0.0 (2026-06-11)
 * @module highcharts/highcharts
 *
 * (c) 2009-2026 Highsoft AS
 *
 * A commercial license may be required depending on use,
 * see www.highcharts.com/license
 */














































const G = Core_Globals;
// Classes
G.AST = HTML_AST;
G.Axis = Axis_Axis;
G.Chart = Chart_Chart;
G.Color = Color_Color;
G.DataLabel = Series_DataLabel;
G.DataTable = Data_DataTableCore;
G.DataTableCore = Data_DataTableCore;
G.Fx = Animation_Fx;
G.HTMLElement = HTML_HTMLElement;
G.Legend = Legend_Legend;
G.LegendSymbol = Legend_LegendSymbol;
G.PlotLineOrBand = PlotLineOrBand_PlotLineOrBand;
G.Point = Series_Point;
G.Pointer = Core_Pointer;
G.Series = Series_Series;
G.SeriesRegistry = Series_SeriesRegistry;
G.StackItem = Stacking_StackItem;
G.SVGElement = SVG_SVGElement;
G.SVGRenderer = SVG_SVGRenderer;
G.Templating = Core_Templating;
G.Tick = Axis_Tick;
G.Time = Core_Time;
G.Tooltip = Core_Tooltip;
// Utilities
G.addEvent = addEvent;
G.animObject = AnimationUtilities.animObject;
G.animate = AnimationUtilities.animate;
G.arrayMax = arrayMax;
G.arrayMin = arrayMin;
G.attr = attr;
G.chart = Chart_Chart.chart;
G.clamp = clamp;
G.color = Color_Color.parse;
G.correctFloat = correctFloat;
G.createElement = createElement;
G.css = css;
G.crisp = crisp;
G.dateFormat = Core_Templating.dateFormat;
G.defaultOptions = Defaults.defaultOptions;
G.defined = defined;
G.destroyObjectProperties = destroyObjectProperties;
G.diffObjects = diffObjects;
G.discardElement = discardElement;
G.distribute = Renderer_RendererUtilities.distribute;
G.erase = erase;
G.error = error;
G.extend = extend;
G.extendClass = extendClass;
G.find = find;
G.fireEvent = fireEvent;
G.format = Core_Templating.format;
G.getAlignFactor = getAlignFactor;
G.getClosestDistance = getClosestDistance;
G.getDeferredAnimation = AnimationUtilities.getDeferredAnimation;
G.getMagnitude = getMagnitude;
G.getNestedProperty = getNestedProperty;
G.getOptions = Defaults.getOptions;
G.getStyle = getStyle;
G.insertItem = insertItem;
G.isArray = isArray;
G.isClass = isClass;
G.isDOMElement = isDOMElement;
G.isFunction = isFunction;
G.isNumber = isNumber;
G.isObject = isObject;
G.isString = isString;
G.internalClearTimeout = internalClearTimeout;
G.merge = merge;
G.normalizeTickInterval = normalizeTickInterval;
G.numberFormat = Core_Templating.numberFormat;
G.objectEach = objectEach;
G.offset = offset;
G.pad = pad;
G.pick = pick;
G.pushUnique = pushUnique;
G.pInt = pInt;
G.relativeLength = relativeLength;
G.removeEvent = removeEvent;
G.replaceNested = replaceNested;
G.seriesType = Series_SeriesRegistry.seriesType;
G.setAnimation = AnimationUtilities.setAnimation;
G.setOptions = Defaults.setOptions;
G.splat = splat;
G.stableSort = stableSort;
G.stop = AnimationUtilities.stop;
G.syncTimeout = syncTimeout;
G.time = Defaults.defaultTime;
G.ucfirst = ucfirst;
G.timers = Animation_Fx.timers;
G.timeUnits = timeUnits;
G.uniqueKey = uniqueKey;
G.useSerialIds = useSerialIds;
G.wrap = wrap;
// Compositions
composeBorderRadius(G.Series, G.SVGElement, G.SVGRenderer);
Column_ColumnDataLabel.compose(G.Series.types.column);
Series_DataLabel.compose(G.Series);
Axis_DateTimeAxis.compose(G.Axis);
HTML_HTMLElement.compose(G.SVGRenderer);
Legend_Legend.compose(G.Chart);
Axis_LogarithmicAxis.compose(G.Axis);
composeOverlappingDataLabels(G.Chart);
PieDataLabel.compose(G.Series.types.pie);
PlotLineOrBand_PlotLineOrBand.compose(G.Chart, G.Axis);
Core_Pointer.compose(G.Chart);
Core_Responsive.compose(G.Chart);
ScrollablePlotArea.compose(G.Axis, G.Chart, G.Series);
Stacking_StackingAxis.compose(G.Axis, G.Chart, G.Series);
Core_Tooltip.compose(G.Pointer);
// Default Export
/* harmony default export */ const highcharts_src = (G);

;// ./code/es-modules/Stock/Navigator/ChartNavigatorComposition.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { isTouchDevice: ChartNavigatorComposition_isTouchDevice } = Core_Globals;

/* *
 *
 *  Constants
 *
 * */
const composedMembers = [];
/* *
 *
 *  Variables
 *
 * */
let NavigatorConstructor;
/* *
 *
 *  Functions
 *
 * */
/** @internal */
function compose(ChartClass, NavigatorClass) {
    if (pushUnique(composedMembers, ChartClass)) {
        const chartProto = ChartClass.prototype;
        NavigatorConstructor = NavigatorClass;
        chartProto.callbacks.push(onChartCallback);
        addEvent(ChartClass, 'afterAddSeries', onChartAfterAddSeries);
        addEvent(ChartClass, 'afterSetChartSize', onChartAfterSetChartSize);
        addEvent(ChartClass, 'afterUpdate', onChartAfterUpdate);
        addEvent(ChartClass, 'beforeRender', onChartBeforeRender);
        addEvent(ChartClass, 'beforeShowResetZoom', onChartBeforeShowResetZoom);
        addEvent(ChartClass, 'update', onChartUpdate);
    }
}
/**
 * Handle adding new series.
 * @internal
 */
function onChartAfterAddSeries() {
    if (this.navigator) {
        // Recompute which series should be shown in navigator, and add them
        this.navigator.setBaseSeries(null, false);
    }
}
/**
 * For stock charts, extend the Chart.setChartSize method so that we can set the
 * final top position of the navigator once the height of the chart, including
 * the legend, is determined. #367. We can't use Chart.getMargins, because
 * labels offsets are not calculated yet.
 * @internal
 */
function onChartAfterSetChartSize() {
    const legend = this.legend, navigator = this.navigator;
    let legendOptions, xAxis, yAxis;
    if (navigator) {
        legendOptions = legend && legend.options;
        xAxis = navigator.xAxis;
        yAxis = navigator.yAxis;
        const { scrollbarHeight, scrollButtonSize } = navigator;
        // Compute the top position
        if (this.inverted) {
            navigator.left = navigator.opposite ?
                this.chartWidth - scrollbarHeight -
                    navigator.height :
                this.spacing[3] + scrollbarHeight;
            navigator.top = this.plotTop + scrollButtonSize;
        }
        else {
            navigator.left = pick(xAxis.left, this.plotLeft + scrollButtonSize);
            navigator.top = navigator.navigatorOptions.top ||
                this.chartHeight -
                    navigator.height -
                    scrollbarHeight -
                    (this.scrollbar?.options.margin || 0) -
                    this.spacing[2] -
                    (this.rangeSelector && this.extraBottomMargin ?
                        this.rangeSelector.getHeight() :
                        0) -
                    ((legendOptions &&
                        legendOptions.verticalAlign === 'bottom' &&
                        legendOptions.layout !== 'proximate' && // #13392
                        legendOptions.enabled &&
                        !legendOptions.floating) ?
                        legend.legendHeight +
                            pick(legendOptions.margin, 10) :
                        0) -
                    (this.titleOffset ? this.titleOffset[2] : 0);
        }
        if (xAxis && yAxis) { // False if navigator is disabled (#904)
            if (this.inverted) {
                xAxis.options.left = yAxis.options.left = navigator.left;
            }
            else {
                xAxis.options.top = yAxis.options.top = navigator.top;
            }
            xAxis.setAxisSize();
            yAxis.setAxisSize();
        }
    }
}
/**
 * Initialize navigator, if no scrolling exists yet.
 * @internal
 */
function onChartAfterUpdate(event) {
    if (!this.navigator && !this.scroller &&
        (this.options.navigator.enabled ||
            this.options.scrollbar.enabled)) {
        this.scroller = this.navigator = new NavigatorConstructor(this);
        if (pick(event.redraw, true)) {
            this.redraw(event.animation); // #7067
        }
    }
}
/**
 * Initialize navigator for stock charts
 * @internal
 */
function onChartBeforeRender() {
    const options = this.options;
    if (options.navigator.enabled ||
        options.scrollbar.enabled) {
        this.scroller = this.navigator = new NavigatorConstructor(this);
    }
}
/**
 * For Stock charts. For x only zooming, do not to create the zoom button
 * because X axis zooming is already allowed by the Navigator and Range
 * selector. (#9285)
 * @internal
 */
function onChartBeforeShowResetZoom() {
    const chartOptions = this.options, navigator = chartOptions.navigator, rangeSelector = chartOptions.rangeSelector;
    if (((navigator && navigator.enabled) ||
        (rangeSelector && rangeSelector.enabled)) &&
        ((!ChartNavigatorComposition_isTouchDevice &&
            this.zooming.type === 'x') ||
            (ChartNavigatorComposition_isTouchDevice && this.zooming.pinchType === 'x'))) {
        return false;
    }
}
/** @internal */
function onChartCallback(chart) {
    const navigator = chart.navigator;
    // Initialize the navigator
    if (navigator && chart.xAxis[0]) {
        const extremes = chart.xAxis[0].getExtremes();
        navigator.render(extremes.min, extremes.max);
    }
}
/**
 * Merge options, if no scrolling exists yet
 * @internal
 */
function onChartUpdate(e) {
    const navigatorOptions = (e.options.navigator || {}), scrollbarOptions = (e.options.scrollbar || {});
    if (!this.navigator && !this.scroller &&
        (navigatorOptions.enabled || scrollbarOptions.enabled)) {
        merge(true, this.options.navigator, navigatorOptions);
        merge(true, this.options.scrollbar, scrollbarOptions);
        delete e.options.navigator;
        delete e.options.scrollbar;
    }
}
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
const ChartNavigatorComposition = {
    compose
};
/** @internal */
/* harmony default export */ const Navigator_ChartNavigatorComposition = (ChartNavigatorComposition);

;// ./code/es-modules/Core/Axis/NavigatorAxisComposition.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { isTouchDevice: NavigatorAxisComposition_isTouchDevice } = Core_Globals;

/* *
 *
 *  Functions
 *
 * */
/** @internal */
function NavigatorAxisComposition_onAxisInit() {
    const axis = this;
    if (!axis.navigatorAxis) {
        axis.navigatorAxis = new NavigatorAxisAdditions(axis);
    }
}
/**
 * For Stock charts, override selection zooming with some special features
 * because X axis zooming is already allowed by the Navigator and Range
 * selector.
 * @internal
 */
function onAxisSetExtremes(e) {
    const axis = this, chart = axis.chart, chartOptions = chart.options, navigator = chartOptions.navigator, navigatorAxis = axis.navigatorAxis, pinchType = chart.zooming.pinchType, rangeSelector = chartOptions.rangeSelector, zoomType = chart.zooming.type;
    let zoomed;
    if (axis.isXAxis &&
        (navigator?.enabled || rangeSelector?.enabled)) {
        // For y only zooming, ignore the X axis completely
        if (zoomType === 'y' && e.trigger === 'zoom') {
            zoomed = false;
            // For xy zooming, record the state of the zoom before zoom selection,
            // then when the reset button is pressed, revert to this state. This
            // should apply only if the chart is initialized with a range (#6612),
            // otherwise zoom all the way out.
        }
        else if (((e.trigger === 'zoom' && zoomType === 'xy') ||
            (NavigatorAxisComposition_isTouchDevice && pinchType === 'xy')) &&
            axis.options.range) {
            const previousZoom = navigatorAxis.previousZoom;
            // Minimum defined, zooming in
            if (defined(e.min)) {
                navigatorAxis.previousZoom = [axis.min, axis.max];
                // Minimum undefined, resetting zoom
            }
            else if (previousZoom) {
                e.min = previousZoom[0];
                e.max = previousZoom[1];
                navigatorAxis.previousZoom = void 0;
            }
        }
    }
    if (typeof zoomed !== 'undefined') {
        e.preventDefault();
    }
}
/* *
 *
 *  Class
 *
 * */
/** @internal */
class NavigatorAxisAdditions {
    /* *
     *
     *  Static Functions
     *
     * */
    static compose(AxisClass) {
        if (!AxisClass.keepProps.includes('navigatorAxis')) {
            AxisClass.keepProps.push('navigatorAxis');
            addEvent(AxisClass, 'init', NavigatorAxisComposition_onAxisInit);
            addEvent(AxisClass, 'setExtremes', onAxisSetExtremes);
        }
    }
    /* *
     *
     *  Constructors
     *
     * */
    constructor(axis) {
        this.axis = axis;
    }
    /* *
     *
     *  Functions
     *
     * */
    destroy() {
        this.axis = void 0;
    }
    /**
     * Add logic to normalize the zoomed range in order to preserve the pressed
     * state of range selector buttons
     *
     * @internal
     * @function Highcharts.Axis#toFixedRange
     */
    toFixedRange(pxMin, pxMax, fixedMin, fixedMax) {
        const axis = this.axis, halfPointRange = (axis.pointRange || 0) / 2;
        let newMin = pick(fixedMin, axis.translate(pxMin, true, !axis.horiz)), newMax = pick(fixedMax, axis.translate(pxMax, true, !axis.horiz));
        // Add/remove half point range to/from the extremes (#1172)
        if (!defined(fixedMin)) {
            newMin = correctFloat(newMin + halfPointRange);
        }
        if (!defined(fixedMax)) {
            newMax = correctFloat(newMax - halfPointRange);
        }
        if (!isNumber(newMin) || !isNumber(newMax)) { // #1195, #7411
            newMin = newMax = void 0;
        }
        return {
            min: newMin,
            max: newMax
        };
    }
}
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
/* harmony default export */ const NavigatorAxisComposition = (NavigatorAxisAdditions);

;// ./code/es-modules/Stock/Navigator/NavigatorDefaults.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { seriesTypes: NavigatorDefaults_seriesTypes } = Series_SeriesRegistry;
/* *
 *
 *  Constants
 *
 * */
/**
 * The navigator is a small series below the main series, displaying
 * a view of the entire data set. It provides tools to zoom in and
 * out on parts of the data as well as panning across the dataset.
 *
 * @product      highstock gantt
 * @optionparent navigator
 */
const NavigatorDefaults = {
    /**
     * Whether the navigator and scrollbar should adapt to updated data
     * in the base X axis. When loading data async, as in the demo below,
     * this should be `false`. Otherwise new data will trigger navigator
     * redraw, which will cause unwanted looping. In the demo below, the
     * data in the navigator is set only once. On navigating, only the main
     * chart content is updated.
     *
     * @sample {highstock} stock/demo/lazy-loading/
     *         Set to false with async data loading
     *
     * @type      {boolean}
     * @default   true
     * @apioption navigator.adaptToUpdatedData
     */
    /**
     * An integer identifying the index to use for the base series, or a
     * string representing the id of the series.
     *
     * **Note**: As of Highcharts 5.0, this is now a deprecated option.
     * Prefer [series.showInNavigator](#plotOptions.series.showInNavigator).
     *
     * @see [series.showInNavigator](#plotOptions.series.showInNavigator)
     *
     * @deprecated 5.0.0
     * @type      {number|string}
     * @default   0
     * @apioption navigator.baseSeries
     */
    /**
     * Enable or disable the navigator.
     *
     * @sample {highstock} stock/navigator/enabled/
     *         Disable the navigator
     *
     * @type      {boolean}
     * @default   true
     * @apioption navigator.enabled
     */
    /**
     * When the chart is inverted, whether to draw the navigator on the
     * opposite side.
     *
     * @type      {boolean}
     * @default   false
     * @since     5.0.8
     * @apioption navigator.opposite
     */
    /**
     * The height of the navigator.
     *
     * @sample {highstock} stock/navigator/height/
     *         A higher navigator
     */
    height: 40,
    /**
     * The distance from the nearest element, the X axis or X axis labels.
     *
     * @sample {highstock} stock/navigator/margin/
     *         A margin of 2 draws the navigator closer to the X axis labels
     */
    margin: 22,
    /**
     * Whether the mask should be inside the range marking the zoomed
     * range, or outside. In Highcharts Stock 1.x it was always `false`.
     *
     * @sample {highstock} stock/demo/maskinside-false/
     *         False, mask outside
     *
     * @since   2.0
     */
    maskInside: true,
    /**
     * Options for the handles for dragging the zoomed area.
     *
     * @sample {highstock} stock/navigator/handles/
     *         Colored handles
     */
    handles: {
        /**
         * Width for handles.
         *
         * @sample {highstock} stock/navigator/styled-handles/
         *         Styled handles
         *
         * @since   6.0.0
         */
        width: 7,
        /**
         * Border radius of the handles.
         *
         * @sample {highstock} stock/navigator/handles-border-radius/
         *      Border radius on the navigator handles.
         *
         * @since 11.4.2
         */
        borderRadius: 0,
        /**
         * Height for handles.
         *
         * @sample {highstock} stock/navigator/styled-handles/
         *         Styled handles
         *
         * @since   6.0.0
         */
        height: 15,
        /**
         * Array to define shapes of handles. 0-index for left, 1-index for
         * right.
         *
         * Additionally, the URL to a graphic can be given on this form:
         * `url(graphic.png)`. Note that for the image to be applied to
         * exported charts, its URL needs to be accessible by the export
         * server.
         *
         * Custom callbacks for symbol path generation can also be added to
         * `Highcharts.SVGRenderer.prototype.symbols`. The callback is then
         * used by its method name, as shown in the demo.
         *
         * @sample {highstock} stock/navigator/styled-handles/
         *         Styled handles
         *
         * @type    {Array<string>}
         * @default ["navigator-handle", "navigator-handle"]
         * @since   6.0.0
         */
        symbols: ['navigator-handle', 'navigator-handle'],
        /**
         * Allows to enable/disable handles.
         *
         * @since   6.0.0
         */
        enabled: true,
        /**
         * The width for the handle border and the stripes inside.
         *
         * @sample {highstock} stock/navigator/styled-handles/
         *         Styled handles
         *
         * @since     6.0.0
         * @apioption navigator.handles.lineWidth
         */
        lineWidth: 1,
        /**
         * The fill for the handle.
         *
         * @type {Highcharts.ColorType}
         */
        backgroundColor: 'var(--highcharts-neutral-color-5)',
        /**
         * The stroke for the handle border and the stripes inside.
         *
         * @type {Highcharts.ColorType}
         */
        borderColor: 'var(--highcharts-neutral-color-40)'
    },
    /**
     * The color of the mask covering the areas of the navigator series
     * that are currently not visible in the main series. The default
     * color is bluish with an opacity of 0.3 to see the series below.
     *
     * @see In styled mode, the mask is styled with the
     *      `.highcharts-navigator-mask` and
     *      `.highcharts-navigator-mask-inside` classes.
     *
     * @sample {highstock} stock/navigator/maskfill/
     *         Blue, semi transparent mask
     *
     * @type    {Highcharts.ColorType}
     */
    maskFill: 'color-mix(var(--highcharts-highlight-color-60) 30%, transparent)',
    /**
     * The color of the line marking the currently zoomed area in the
     * navigator.
     *
     * @sample {highstock} stock/navigator/outline/
     *         2px blue outline
     *
     * @type {Highcharts.ColorType}
     */
    outlineColor: 'var(--highcharts-neutral-color-40)',
    /**
     * The width of the line marking the currently zoomed area in the
     * navigator.
     *
     * @see In styled mode, the outline stroke width is set with the
     *      `.highcharts-navigator-outline` class.
     *
     * @sample {highstock} stock/navigator/outline/
     *         2px blue outline
     *
     * @type    {number}
     */
    outlineWidth: 1,
    /**
     * Options for the navigator series. Available options are the same
     * as any series, documented at [plotOptions](#plotOptions.series)
     * and [series](#series).
     *
     * Unless data is explicitly defined on navigator.series, the data
     * is borrowed from the first series in the chart.
     *
     * Default series options for the navigator series are:
     * ```js
     * series: {
     *     type: 'areaspline',
     *     fillOpacity: 0.05,
     *     dataGrouping: {
     *         smoothed: true
     *     },
     *     lineWidth: 1,
     *     marker: {
     *         enabled: false
     *     }
     * }
     * ```
     *
     * @see In styled mode, the navigator series is styled with the
     *      `.highcharts-navigator-series` class.
     *
     * @sample {highstock} stock/navigator/series-data/
     *         Using a separate data set for the navigator
     * @sample {highstock} stock/navigator/series/
     *         A green navigator series
     *
     * @type {*|Array<*>|Highcharts.SeriesOptionsType|Array<Highcharts.SeriesOptionsType>}
     */
    series: {
        /**
         * The type of the navigator series.
         *
         * Heads up:
         * In column-type navigator, zooming is limited to at least one
         * point with its `pointRange`.
         *
         * @sample {highstock} stock/navigator/column/
         *         Column type navigator
         *
         * @type    {string}
         * @default {highstock} `areaspline` if defined, otherwise `line`
         * @default {gantt} gantt
         */
        type: (typeof NavigatorDefaults_seriesTypes.areaspline === 'undefined' ?
            'line' :
            'areaspline'),
        /**
         * The fill opacity of the navigator series.
         */
        fillOpacity: 0.05,
        /**
         * The pixel line width of the navigator series.
         */
        lineWidth: 1,
        /**
         * @ignore-option
         */
        compare: null,
        /**
         * @ignore-option
         */
        sonification: {
            enabled: false
        },
        /**
         * Unless data is explicitly defined, the data is borrowed from the
         * first series in the chart.
         *
         * @type      {Array<number|Array<number|string|null>|object|null>}
         * @product   highstock
         * @apioption navigator.series.data
         */
        /**
         * Data grouping options for the navigator series.
         *
         * @extends plotOptions.series.dataGrouping
         */
        dataGrouping: {
            approximation: 'average',
            enabled: true,
            groupPixelWidth: 2,
            // Replace smoothed property by anchors, #12455.
            firstAnchor: 'firstPoint',
            anchor: 'middle',
            lastAnchor: 'lastPoint',
            // Day and week differs from plotOptions.series.dataGrouping
            units: [
                ['millisecond', [1, 2, 5, 10, 20, 25, 50, 100, 200, 500]],
                ['second', [1, 2, 5, 10, 15, 30]],
                ['minute', [1, 2, 5, 10, 15, 30]],
                ['hour', [1, 2, 3, 4, 6, 8, 12]],
                ['day', [1, 2, 3, 4]],
                ['week', [1, 2, 3]],
                ['month', [1, 3, 6]],
                ['year', null]
            ]
        },
        /**
         * Data label options for the navigator series. Data labels are
         * disabled by default on the navigator series.
         *
         * @extends plotOptions.series.dataLabels
         */
        dataLabels: {
            enabled: false,
            zIndex: 2 // #1839
        },
        id: 'highcharts-navigator-series',
        className: 'highcharts-navigator-series',
        /**
         * Sets the fill color of the navigator series.
         *
         * @type      {Highcharts.ColorType}
         * @apioption navigator.series.color
         */
        /**
         * Line color for the navigator series. Allows setting the color
         * while disallowing the default candlestick setting.
         *
         * @type {Highcharts.ColorString|null}
         */
        lineColor: null, // #4602
        marker: {
            enabled: false
        },
        /**
         * Since Highcharts Stock v8, default value is the same as default
         * `pointRange` defined for a specific type (e.g. `null` for
         * column type).
         *
         * In Highcharts Stock version < 8, defaults to 0.
         *
         * @type {number|null}
         * @apioption navigator.series.pointRange
         */
        /**
         * The threshold option. Setting it to 0 will make the default
         * navigator area series draw its area from the 0 value and up.
         *
         * @type {number|null}
         */
        threshold: null
    },
    /**
     * Enable or disable navigator sticking to right, while adding new
     * points. If `undefined`, the navigator sticks to the axis maximum only
     * if it was already at the maximum prior to adding points.
     *
     * @type      {boolean}
     * @default   undefined
     * @since 10.2.1
     * @sample {highstock} stock/navigator/sticktomax-false/
     * stickToMax set to false
     * @apioption navigator.stickToMax
     */
    /**
     * Options for the navigator X axis. Default series options for the
     * navigator xAxis are:
     * ```js
     * xAxis: {
     *     tickWidth: 0,
     *     lineWidth: 0,
     *     gridLineWidth: 1,
     *     tickPixelInterval: 200,
     *     labels: {
     *            align: 'left',
     *         style: {
     *             color: '#888'
     *         },
     *         x: 3,
     *         y: -4
     *     }
     * }
     * ```
     *
     * @extends   xAxis
     * @excluding linkedTo, maxZoom, minRange, opposite, range, scrollbar,
     *            showEmpty, maxRange
     */
    xAxis: {
        /**
         * Additional range on the right side of the xAxis. Works similar to
         * `xAxis.maxPadding`, but the value is set in terms of axis values,
         * percentage or pixels.
         *
         * If it's a number, it is interpreted as axis values, which in a
         * datetime axis equals milliseconds.
         *
         * If it's a percentage string, is interpreted as percentages of the
         * axis length. An overscroll of 50% will make a 100px axis 50px longer.
         *
         * If it's a pixel string, it is interpreted as a fixed pixel value, but
         * limited to 90% of the axis length.
         *
         * If it's undefined, the value is inherited from `xAxis.overscroll`.
         *
         * Can be set for both, main xAxis and navigator's xAxis.
         *
         * @type    {number | string | undefined}
         * @since   6.0.0
         * @apioption navigator.xAxis.overscroll
         */
        className: 'highcharts-navigator-xaxis',
        tickLength: 0,
        lineWidth: 0,
        gridLineColor: 'var(--highcharts-neutral-color-10)',
        id: 'navigator-x-axis',
        gridLineWidth: 1,
        tickPixelInterval: 200,
        labels: {
            align: 'left',
            /**
             * @type {Highcharts.CSSObject}
             */
            style: {
                /** @ignore */
                color: 'var(--highcharts-neutral-color-100)',
                /** @ignore */
                fontSize: '0.7em',
                /** @ignore */
                opacity: 0.6,
                /** @ignore */
                textOutline: '2px contrast'
            },
            x: 3,
            y: -4
        },
        crosshair: false
    },
    /**
     * Options for the navigator Y axis. Default series options for the
     * navigator yAxis are:
     * ```js
     * yAxis: {
     *     gridLineWidth: 0,
     *     startOnTick: false,
     *     endOnTick: false,
     *     minPadding: 0.1,
     *     maxPadding: 0.1,
     *     labels: {
     *         enabled: false
     *     },
     *     title: {
     *         text: null
     *     },
     *     tickWidth: 0
     * }
     * ```
     *
     * @extends   yAxis
     * @excluding height, linkedTo, maxZoom, minRange, ordinal, range,
     *            showEmpty, scrollbar, top, units, maxRange, minLength,
     *            maxLength, resize
     */
    yAxis: {
        className: 'highcharts-navigator-yaxis',
        gridLineWidth: 0,
        startOnTick: false,
        endOnTick: false,
        minPadding: 0.1,
        id: 'navigator-y-axis',
        maxPadding: 0.1,
        labels: {
            enabled: false
        },
        crosshair: false,
        title: {
            text: void 0
        },
        tickLength: 0,
        tickWidth: 0
    }
};
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
/* harmony default export */ const Navigator_NavigatorDefaults = (NavigatorDefaults);
/* *
 *
 *  API Options
 *
 * */
/**
 * Maximum range which can be set using the navigator's handles.
 * Opposite of [xAxis.minRange](#xAxis.minRange).
 *
 * @sample {highstock} stock/navigator/maxrange/
 *         Defined max and min range
 *
 * @type      {number}
 * @since     6.0.0
 * @product   highstock gantt
 * @apioption xAxis.maxRange
 */
(''); // Keeps doclets above in JS file

;// ./code/es-modules/Stock/Navigator/NavigatorSymbols.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */



/* *
 *
 *  Constants
 *
 * */
/**
 * Draw one of the handles on the side of the zoomed range in the navigator.
 * @internal
 */
function navigatorHandle(_x, _y, width, height, options = {}) {
    const halfWidth = options.width ? options.width / 2 : width, markerPosition = 1.5, r = relativeLength(options.borderRadius || 0, Math.min(halfWidth * 2, height));
    height = options.height || height;
    return [
        ['M', -markerPosition, height / 2 - 3.5],
        ['L', -markerPosition, height / 2 + 4.5],
        ['M', markerPosition - 1, height / 2 - 3.5],
        ['L', markerPosition - 1, height / 2 + 4.5],
        ...SVG_Symbols.rect(-halfWidth - 1, 0.5, halfWidth * 2 + 1, height, { r })
    ];
}
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
const NavigatorSymbols = {
    'navigator-handle': navigatorHandle
};
/** @internal */
/* harmony default export */ const Navigator_NavigatorSymbols = (NavigatorSymbols);

;// ./code/es-modules/Stock/Utilities/StockUtilities.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


/* *
 *
 *  Functions
 *
 * */
/**
 * Sets the chart.fixedRange to the specified value. If the value is larger
 * than actual range, sets it to the maximum possible range. (#20327)
 *
 * @internal
 * @function Highcharts.StockChart#setFixedRange
 * @param {number|undefined} range
 *        Range to set in axis units.
 */
function setFixedRange(range) {
    const xAxis = this.xAxis[0];
    if (defined(xAxis.dataMax) &&
        defined(xAxis.dataMin) &&
        range) {
        this.fixedRange = Math.min(range, xAxis.dataMax - xAxis.dataMin);
    }
    else {
        this.fixedRange = range;
    }
}
/** @internal */
const StockUtilities = {
    setFixedRange
};
/** @internal */
/* harmony default export */ const Utilities_StockUtilities = (StockUtilities);

;// ./code/es-modules/Stock/Navigator/NavigatorComposition.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { defaultOptions: NavigatorComposition_defaultOptions } = Defaults;

const { composed: NavigatorComposition_composed } = Core_Globals;





const { setFixedRange: NavigatorComposition_setFixedRange } = Utilities_StockUtilities;

/* *
 *
 *  Variables
 *
 * */
/* *
 *
 *  Functions
 *
 * */
/** @internal */
function NavigatorComposition_compose(ChartClass, AxisClass, SeriesClass) {
    NavigatorAxisComposition.compose(AxisClass);
    if (pushUnique(NavigatorComposition_composed, 'Navigator')) {
        ChartClass.prototype.setFixedRange = NavigatorComposition_setFixedRange;
        extend(SVG_SVGRenderer.prototype.symbols, Navigator_NavigatorSymbols);
        extend(NavigatorComposition_defaultOptions, { navigator: Navigator_NavigatorDefaults });
        addEvent(SeriesClass, 'afterUpdate', onSeriesAfterUpdate);
    }
}
/**
 * Handle updating series
 * @internal
 */
function onSeriesAfterUpdate() {
    if (this.chart.navigator && !this.options.isInternal) {
        this.chart.navigator.setBaseSeries(null, false);
    }
}
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
const NavigatorComposition = {
    compose: NavigatorComposition_compose
};
/** @internal */
/* harmony default export */ const Navigator_NavigatorComposition = (NavigatorComposition);

;// ./code/es-modules/Core/Axis/ScrollbarAxis.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { composed: ScrollbarAxis_composed } = Core_Globals;

/* *
 *
 *  Composition
 *
 * */
/** @internal */
var ScrollbarAxis;
(function (ScrollbarAxis) {
    /* *
     *
     *  Variables
     *
     * */
    let Scrollbar;
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Attaches to axis events to create scrollbars if enabled.
     *
     * @internal
     *
     * @param {Highcharts.Axis} AxisClass
     * Axis class to extend.
     *
     * @param {Highcharts.Scrollbar} ScrollbarClass
     * Scrollbar class to use.
     */
    function compose(AxisClass, ScrollbarClass) {
        if (pushUnique(ScrollbarAxis_composed, 'Axis.Scrollbar')) {
            Scrollbar = ScrollbarClass;
            addEvent(AxisClass, 'afterGetOffset', onAxisAfterGetOffset);
            addEvent(AxisClass, 'afterInit', onAxisAfterInit);
            addEvent(AxisClass, 'afterRender', onAxisAfterRender);
        }
    }
    ScrollbarAxis.compose = compose;
    /** @internal */
    function getExtremes(axis) {
        const axisMin = pick(axis.options?.min, axis.min);
        const axisMax = pick(axis.options?.max, axis.max);
        return {
            axisMin,
            axisMax,
            scrollMin: defined(axis.dataMin) ?
                Math.min(axisMin, axis.min ?? Infinity, axis.dataMin, axis.threshold ?? Infinity) : axisMin,
            scrollMax: axis.treeGrid?.adjustedMax ?? (defined(axis.dataMax) ?
                Math.max(axisMax, axis.max ?? -Infinity, axis.dataMax, axis.threshold ?? -Infinity) :
                axisMax)
        };
    }
    /**
     * Make space for a scrollbar.
     * @internal
     */
    function onAxisAfterGetOffset() {
        const axis = this, scrollbar = axis.scrollbar, opposite = scrollbar && !scrollbar.options.opposite, index = axis.horiz ? 2 : opposite ? 3 : 1;
        if (scrollbar) {
            // Reset scrollbars offsets
            axis.chart.scrollbarsOffsets = [0, 0];
            axis.chart.axisOffset[index] +=
                scrollbar.size + (scrollbar.options.margin || 0);
        }
    }
    /**
     * Wrap axis initialization and create scrollbar if enabled.
     * @internal
     */
    function onAxisAfterInit() {
        const axis = this;
        if (axis.options?.scrollbar?.enabled) {
            // Predefined options:
            axis.options.scrollbar.vertical = !axis.horiz;
            axis.options.startOnTick = axis.options.endOnTick = false;
            axis.scrollbar = new Scrollbar(axis.chart.renderer, axis.options.scrollbar, axis.chart);
            addEvent(axis.scrollbar, 'changed', function (e) {
                const { axisMin, axisMax, scrollMin: unitedMin, scrollMax: unitedMax } = getExtremes(axis), minPX = axis.toPixels(unitedMin), maxPX = axis.toPixels(unitedMax), rangePX = maxPX - minPX;
                let to, from;
                // #12834, scroll when show/hide series, wrong extremes
                if (!defined(axisMin) || !defined(axisMax)) {
                    return;
                }
                if ((axis.horiz && !axis.reversed) ||
                    (!axis.horiz && axis.reversed)) {
                    to = Math.min(unitedMax, axis.toValue(minPX + rangePX * this.to));
                    from = Math.max(unitedMin, axis.toValue(minPX + rangePX * this.from));
                }
                else {
                    // Y-values in browser are reversed, but this also
                    // applies for reversed horizontal axis:
                    to = Math.min(unitedMax, axis.toValue(minPX + rangePX * (1 - this.from)));
                    from = Math.max(unitedMin, axis.toValue(minPX + rangePX * (1 - this.to)));
                }
                if (this.shouldUpdateExtremes(e.DOMType)) {
                    // #17977, set animation to undefined instead of true
                    const animate = e.DOMType === 'mousemove' ||
                        e.DOMType === 'touchmove' ? false : void 0;
                    axis.setExtremes(correctFloat(from), correctFloat(to), true, animate, e);
                }
                else {
                    // When live redraw is disabled, don't change extremes
                    // Only change the position of the scrollbar thumb
                    this.setRange(this.from, this.to);
                }
            });
        }
    }
    /**
     * Wrap rendering axis, and update scrollbar if one is created.
     * @internal
     */
    function onAxisAfterRender() {
        const axis = this, { scrollMin, scrollMax } = getExtremes(axis), scrollbar = axis.scrollbar, offset = (axis.axisTitleMargin || 0) + (axis.titleOffset || 0), scrollbarsOffsets = axis.chart.scrollbarsOffsets, axisMargin = axis.options.margin || 0;
        let offsetsIndex, from, to;
        if (scrollbar && scrollbarsOffsets) {
            if (axis.horiz) {
                // Reserve space for labels/title
                if (!axis.opposite) {
                    scrollbarsOffsets[1] += offset;
                }
                scrollbar.position(axis.left, (axis.top +
                    axis.height +
                    2 +
                    scrollbarsOffsets[1] -
                    (axis.opposite ? axisMargin : 0)), axis.width, axis.height);
                // Next scrollbar should reserve space for margin (if set)
                if (!axis.opposite) {
                    scrollbarsOffsets[1] += axisMargin;
                }
                offsetsIndex = 1;
            }
            else {
                // Reserve space for labels/title
                if (axis.opposite) {
                    scrollbarsOffsets[0] += offset;
                }
                let xPosition;
                if (!scrollbar.options.opposite) {
                    xPosition = axis.opposite ? 0 : axisMargin;
                }
                else {
                    xPosition = axis.left +
                        axis.width +
                        2 +
                        scrollbarsOffsets[0] -
                        (axis.opposite ? 0 : axisMargin);
                }
                scrollbar.position(xPosition, axis.top, axis.width, axis.height);
                // Next scrollbar should reserve space for margin (if set)
                if (axis.opposite) {
                    scrollbarsOffsets[0] += axisMargin;
                }
                offsetsIndex = 0;
            }
            scrollbarsOffsets[offsetsIndex] += scrollbar.size +
                (scrollbar.options.margin || 0);
            if (isNaN(scrollMin) ||
                isNaN(scrollMax) ||
                !defined(axis.min) ||
                !defined(axis.max) ||
                (defined(axis.dataMin) && // #23335
                    axis.dataMin === axis.dataMax // #10733
                )) {
                // Default action: when data extremes are the same or there is
                // not extremes on the axis, but scrollbar exists, make it
                // full size
                scrollbar.setRange(0, 1);
            }
            else if (axis.min === axis.max) { // #20359
                // When the extremes are the same, set the scrollbar to a point
                // within the extremes range. Utilize pointRange to perform the
                // calculations. (#20359)
                const interval = axis.pointRange / (axis.dataMax +
                    1);
                from = interval * axis.min;
                to = interval * (axis.max + 1);
                scrollbar.setRange(from, to);
            }
            else {
                from = (axis.toPixels(axis.min) - axis.toPixels(scrollMin)) /
                    (axis.toPixels(scrollMax) - axis.toPixels(scrollMin));
                to = (axis.toPixels(axis.max) - axis.toPixels(scrollMin)) /
                    (axis.toPixels(scrollMax) - axis.toPixels(scrollMin));
                if ((axis.horiz && !axis.reversed) ||
                    (!axis.horiz && axis.reversed)) {
                    scrollbar.setRange(from, to);
                }
                else {
                    // Inverse vertical axis
                    scrollbar.setRange(1 - to, 1 - from);
                }
            }
        }
    }
})(ScrollbarAxis || (ScrollbarAxis = {}));
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
/* harmony default export */ const Axis_ScrollbarAxis = (ScrollbarAxis);

;// ./code/es-modules/Stock/Scrollbar/ScrollbarDefaults.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */

/* *
 *
 *  Constant
 *
 * */
/**
 *
 * The scrollbar is a means of panning over the X axis of a stock chart.
 * Scrollbars can also be applied to other types of axes.
 *
 * Another approach to scrollable charts is the [chart.scrollablePlotArea](
 * https://api.highcharts.com/highcharts/chart.scrollablePlotArea) option that
 * is especially suitable for simpler cartesian charts on mobile.
 *
 * In styled mode, all the presentational options for the
 * scrollbar are replaced by the classes `.highcharts-scrollbar-thumb`,
 * `.highcharts-scrollbar-arrow`, `.highcharts-scrollbar-button`,
 * `.highcharts-scrollbar-rifles` and `.highcharts-scrollbar-track`.
 *
 * @sample stock/yaxis/inverted-bar-scrollbar/
 *         A scrollbar on a simple bar chart
 *
 * @product highstock gantt
 * @optionparent scrollbar
 */
const ScrollbarDefaults = {
    /**
     * The height of the scrollbar. If `buttonsEnabled` is true , the height
     * also applies to the width of the scroll arrows so that they are always
     * squares.
     *
     * @sample stock/scrollbar/style/
     *         Non-default height
     *
     * @type    {number}
     */
    height: 10,
    /**
     * The border rounding radius of the bar.
     *
     * @sample stock/scrollbar/style/
     *         Scrollbar styling
     */
    barBorderRadius: 5,
    /**
     * The corner radius of the scrollbar buttons.
     *
     * @sample stock/scrollbar/style/
     *         Scrollbar styling
     */
    buttonBorderRadius: 0,
    /**
     * Enable or disable the buttons at the end of the scrollbar.
     *
     * @since 11.0.0
     */
    buttonsEnabled: false,
    /**
     * Enable or disable the scrollbar.
     *
     * @sample stock/scrollbar/enabled/
     *         Disable the scrollbar, only use navigator
     *
     * @type      {boolean}
     * @default   true
     * @apioption scrollbar.enabled
     */
    /**
     * Whether to redraw the main chart as the scrollbar or the navigator
     * zoomed window is moved. Defaults to `true` for modern browsers and
     * `false` for legacy IE browsers as well as mobile devices. This option
     * works regardless of whether the scrollbar is enabled or not.
     *
     * @sample stock/scrollbar/liveredraw
     *         Setting live redraw to false
     *
     * @type  {boolean}
     * @since 1.3
     */
    liveRedraw: void 0,
    /**
     * The margin between the scrollbar and its axis when the scrollbar is
     * applied directly to an axis, or the navigator in case that is enabled.
     * Defaults to 10 for axis, 3 for navigator.
     *
     * @type {number|undefined}
     */
    margin: void 0,
    /**
     * The minimum width of the scrollbar.
     *
     * @since 1.2.5
     */
    minWidth: 6,
    /** @ignore-option */
    opposite: true,
    /**
     * Whether to show or hide the scrollbar when the scrolled content is
     * zoomed out to it full extent.
     *
     * @type      {boolean}
     * @default   true
     * @apioption scrollbar.showFull
     */
    step: 0.2,
    /**
     * The z index of the scrollbar group.
     */
    zIndex: 3,
    /**
     * The background color of the scrollbar itself.
     *
     * @sample stock/scrollbar/style/
     *         Scrollbar styling
     *
     * @type {Highcharts.ColorType}
     */
    barBackgroundColor: 'var(--highcharts-neutral-color-20)',
    /**
     * The width of the bar's border.
     *
     * @sample stock/scrollbar/style/
     *         Scrollbar styling
     */
    barBorderWidth: 0,
    /**
     * The color of the scrollbar's border.
     *
     * @type {Highcharts.ColorType}
     */
    barBorderColor: 'var(--highcharts-neutral-color-20)',
    /**
     * The color of the small arrow inside the scrollbar buttons.
     *
     * @sample stock/scrollbar/style/
     *         Scrollbar styling
     *
     * @type {Highcharts.ColorType}
     */
    buttonArrowColor: 'var(--highcharts-neutral-color-80)',
    /**
     * The color of scrollbar buttons.
     *
     * @sample stock/scrollbar/style/
     *         Scrollbar styling
     *
     * @type {Highcharts.ColorType}
     */
    buttonBackgroundColor: 'var(--highcharts-neutral-color-10)',
    /**
     * The color of the border of the scrollbar buttons.
     *
     * @sample stock/scrollbar/style/
     *         Scrollbar styling
     *
     * @type {Highcharts.ColorType}
     */
    buttonBorderColor: 'var(--highcharts-neutral-color-20)',
    /**
     * The border width of the scrollbar buttons.
     *
     * @sample stock/scrollbar/style/
     *         Scrollbar styling
     */
    buttonBorderWidth: 1,
    /**
     * The color of the small rifles in the middle of the scrollbar.
     *
     * @type {Highcharts.ColorType}
     */
    rifleColor: 'none',
    /**
     * The color of the track background.
     *
     * @sample stock/scrollbar/style/
     *         Scrollbar styling
     *
     * @type {Highcharts.ColorType}
     */
    trackBackgroundColor: 'rgba(255, 255, 255, 0.001)', // #18922
    /**
     * The color of the border of the scrollbar track.
     *
     * @sample stock/scrollbar/style/
     *         Scrollbar styling
     *
     * @type {Highcharts.ColorType}
     */
    trackBorderColor: 'var(--highcharts-neutral-color-20)',
    /**
     * The corner radius of the border of the scrollbar track.
     *
     * @sample stock/scrollbar/style/
     *         Scrollbar styling
     */
    trackBorderRadius: 5,
    /**
     * The width of the border of the scrollbar track.
     *
     * @sample stock/scrollbar/style/
     *         Scrollbar styling
     */
    trackBorderWidth: 1
};
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
/* harmony default export */ const Scrollbar_ScrollbarDefaults = (ScrollbarDefaults);

;// ./code/es-modules/Stock/Scrollbar/Scrollbar.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */


const { defaultOptions: Scrollbar_defaultOptions } = Defaults;

const { composed: Scrollbar_composed } = Core_Globals;



/* *
 *
 *  Constants
 *
 * */
/* eslint-disable no-invalid-this, valid-jsdoc */
/**
 * A reusable scrollbar, internally used in Highcharts Stock's
 * navigator and optionally on individual axes.
 *
 * @internal
 * @class
 * @name Highcharts.Scrollbar
 * @param {Highcharts.SVGRenderer} renderer
 * @param {Highcharts.ScrollbarOptions} options
 * @param {Highcharts.Chart} chart
 */
class Scrollbar {
    /* *
     *
     *  Static Functions
     *
     * */
    static compose(AxisClass) {
        Axis_ScrollbarAxis.compose(AxisClass, Scrollbar);
        if (pushUnique(Scrollbar_composed, 'Scrollbar')) {
            extend(Scrollbar_defaultOptions, { scrollbar: Scrollbar_ScrollbarDefaults });
        }
    }
    /**
     * When we have vertical scrollbar, rifles and arrow in buttons should be
     * rotated. The same method is used in Navigator's handles, to rotate them.
     *
     * @function Highcharts.swapXY
     *
     * @param {Highcharts.SVGPathArray} path
     * Path to be rotated.
     *
     * @param {boolean} [vertical]
     * If vertical scrollbar, swap x-y values.
     *
     * @return {Highcharts.SVGPathArray}
     * Rotated path.
     *
     * @requires modules/stock
     */
    static swapXY(path, vertical) {
        if (vertical) {
            path.forEach((seg) => {
                const len = seg.length;
                let temp;
                for (let i = 0; i < len; i += 2) {
                    temp = seg[i + 1];
                    if (typeof temp === 'number') {
                        seg[i + 1] = seg[i + 2];
                        seg[i + 2] = temp;
                    }
                }
            });
        }
        return path;
    }
    /* *
     *
     *  Constructors
     *
     * */
    constructor(renderer, options, chart) {
        /* *
         *
         *  Properties
         *
         * */
        this._events = [];
        this.chartX = 0;
        this.chartY = 0;
        this.from = 0;
        this.scrollbarButtons = [];
        this.scrollbarLeft = 0;
        this.scrollbarStrokeWidth = 1;
        this.scrollbarTop = 0;
        this.size = 0;
        this.to = 0;
        this.trackBorderWidth = 1;
        this.x = 0;
        this.y = 0;
        this.init(renderer, options, chart);
    }
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Set up the mouse and touch events for the Scrollbar
     *
     * @internal
     * @function Highcharts.Scrollbar#addEvents
     */
    addEvents() {
        const buttonsOrder = this.options.inverted ? [1, 0] : [0, 1], buttons = this.scrollbarButtons, bar = this.scrollbarGroup.element, track = this.track.element, mouseDownHandler = this.mouseDownHandler.bind(this), mouseMoveHandler = this.mouseMoveHandler.bind(this), mouseUpHandler = this.mouseUpHandler.bind(this);
        const _events = [
            // Mouse events
            [
                buttons[buttonsOrder[0]].element,
                'click',
                this.buttonToMinClick.bind(this)
            ],
            [
                buttons[buttonsOrder[1]].element,
                'click',
                this.buttonToMaxClick.bind(this)
            ],
            [track, 'click', this.trackClick.bind(this)],
            [bar, 'mousedown', mouseDownHandler],
            [bar.ownerDocument, 'mousemove', mouseMoveHandler],
            [bar.ownerDocument, 'mouseup', mouseUpHandler],
            // Touch events
            [bar, 'touchstart', mouseDownHandler],
            [bar.ownerDocument, 'touchmove', mouseMoveHandler],
            [bar.ownerDocument, 'touchend', mouseUpHandler]
        ];
        // Add them all
        _events.forEach(function (args) {
            addEvent.apply(null, args);
        });
        this._events = _events;
    }
    buttonToMaxClick(e) {
        const scroller = this;
        const range = ((scroller.to - scroller.from) *
            pick(scroller.options.step, 0.2));
        scroller.updatePosition(scroller.from + range, scroller.to + range);
        fireEvent(scroller, 'changed', {
            from: scroller.from,
            to: scroller.to,
            trigger: 'scrollbar',
            DOMEvent: e
        });
    }
    buttonToMinClick(e) {
        const scroller = this;
        const range = correctFloat(scroller.to - scroller.from) *
            pick(scroller.options.step, 0.2);
        scroller.updatePosition(correctFloat(scroller.from - range), correctFloat(scroller.to - range));
        fireEvent(scroller, 'changed', {
            from: scroller.from,
            to: scroller.to,
            trigger: 'scrollbar',
            DOMEvent: e
        });
    }
    /**
     * Get normalized (0-1) cursor position over the scrollbar
     *
     * @internal
     * @function Highcharts.Scrollbar#cursorToScrollbarPosition
     *
     * @param  {*} normalizedEvent
     *         normalized event, with chartX and chartY values
     *
     * @return {Highcharts.Dictionary<number>}
     *         Local position {chartX, chartY}
     */
    cursorToScrollbarPosition(normalizedEvent) {
        const scroller = this, options = scroller.options, minWidthDifference = options.minWidth > scroller.calculatedWidth ?
            options.minWidth :
            0; // `minWidth` distorts translation
        return {
            chartX: (normalizedEvent.chartX - scroller.x -
                scroller.xOffset) /
                (scroller.barWidth - minWidthDifference),
            chartY: (normalizedEvent.chartY - scroller.y -
                scroller.yOffset) /
                (scroller.barWidth - minWidthDifference)
        };
    }
    /**
     * Destroys allocated elements.
     *
     * @internal
     * @function Highcharts.Scrollbar#destroy
     */
    destroy() {
        const scroller = this, navigator = scroller.chart.scroller;
        // Disconnect events added in addEvents
        scroller.removeEvents();
        // Destroy properties
        [
            'track',
            'scrollbarRifles',
            'scrollbar',
            'scrollbarGroup',
            'group'
        ].forEach(function (prop) {
            if (scroller[prop] && scroller[prop].destroy) {
                scroller[prop] = scroller[prop].destroy();
            }
        });
        // #6421, chart may have more scrollbars
        if (navigator && scroller === navigator.scrollbar) {
            navigator.scrollbar = null;
            // Destroy elements in collection
            destroyObjectProperties(navigator.scrollbarButtons);
        }
    }
    /**
     * Draw the scrollbar buttons with arrows
     *
     * @internal
     * @function Highcharts.Scrollbar#drawScrollbarButton
     * @param {number} index
     *        0 is left, 1 is right
     */
    drawScrollbarButton(index) {
        const scroller = this, renderer = scroller.renderer, scrollbarButtons = scroller.scrollbarButtons, options = scroller.options, size = scroller.size, group = renderer.g().add(scroller.group);
        scrollbarButtons.push(group);
        if (options.buttonsEnabled) {
            // Create a rectangle for the scrollbar button
            const rect = renderer.rect()
                .addClass('highcharts-scrollbar-button')
                .add(group);
            // Presentational attributes
            if (!scroller.chart.styledMode) {
                rect.attr({
                    stroke: options.buttonBorderColor,
                    'stroke-width': options.buttonBorderWidth,
                    fill: options.buttonBackgroundColor
                });
            }
            // Place the rectangle based on the rendered stroke width
            rect.attr(rect.crisp({
                x: -0.5,
                y: -0.5,
                width: size,
                height: size,
                r: options.buttonBorderRadius
            }, rect.strokeWidth()));
            // Button arrow
            const arrow = renderer
                .path(Scrollbar.swapXY([[
                    'M',
                    size / 2 + (index ? -1 : 1),
                    size / 2 - 3
                ], [
                    'L',
                    size / 2 + (index ? -1 : 1),
                    size / 2 + 3
                ], [
                    'L',
                    size / 2 + (index ? 2 : -2),
                    size / 2
                ]], options.vertical))
                .addClass('highcharts-scrollbar-arrow')
                .add(scrollbarButtons[index]);
            if (!scroller.chart.styledMode) {
                arrow.attr({
                    fill: options.buttonArrowColor
                });
            }
        }
    }
    /**
     * @internal
     * @function Highcharts.Scrollbar#init
     * @param {Highcharts.SVGRenderer} renderer
     * @param {Highcharts.ScrollbarOptions} options
     * @param {Highcharts.Chart} chart
     */
    init(renderer, options, chart) {
        const scroller = this;
        scroller.scrollbarButtons = [];
        scroller.renderer = renderer;
        scroller.userOptions = options;
        scroller.options = merge(Scrollbar_ScrollbarDefaults, Scrollbar_defaultOptions.scrollbar, options);
        scroller.options.margin = pick(scroller.options.margin, 10);
        scroller.chart = chart;
        // Backward compatibility
        scroller.size = pick(scroller.options.size, scroller.options.height);
        // Init
        if (options.enabled) {
            scroller.render();
            scroller.addEvents();
        }
    }
    mouseDownHandler(e) {
        const scroller = this, normalizedEvent = scroller.chart.pointer?.normalize(e) || e, mousePosition = scroller.cursorToScrollbarPosition(normalizedEvent);
        scroller.chartX = mousePosition.chartX;
        scroller.chartY = mousePosition.chartY;
        scroller.initPositions = [scroller.from, scroller.to];
        scroller.grabbedCenter = true;
    }
    /**
     * Event handler for the mouse move event.
     * @internal
     */
    mouseMoveHandler(e) {
        const scroller = this, normalizedEvent = scroller.chart.pointer?.normalize(e) || e, options = scroller.options, direction = options.vertical ?
            'chartY' : 'chartX', initPositions = scroller.initPositions || [];
        let scrollPosition, chartPosition, change;
        // In iOS, a mousemove event with e.pageX === 0 is fired when
        // holding the finger down in the center of the scrollbar. This
        // should be ignored.
        if (scroller.grabbedCenter &&
            // #4696, scrollbar failed on Android
            (!e.touches || e.touches[0][direction] !== 0)) {
            chartPosition = scroller.cursorToScrollbarPosition(normalizedEvent)[direction];
            scrollPosition = scroller[direction];
            change = chartPosition - scrollPosition;
            scroller.hasDragged = true;
            scroller.updatePosition(initPositions[0] + change, initPositions[1] + change);
            if (scroller.hasDragged) {
                fireEvent(scroller, 'changed', {
                    from: scroller.from,
                    to: scroller.to,
                    trigger: 'scrollbar',
                    DOMType: e.type,
                    DOMEvent: e
                });
            }
        }
    }
    /**
     * Event handler for the mouse up event.
     * @internal
     */
    mouseUpHandler(e) {
        const scroller = this;
        if (scroller.hasDragged) {
            fireEvent(scroller, 'changed', {
                from: scroller.from,
                to: scroller.to,
                trigger: 'scrollbar',
                DOMType: e.type,
                DOMEvent: e
            });
        }
        scroller.grabbedCenter =
            scroller.hasDragged =
                scroller.chartX =
                    scroller.chartY = null;
    }
    /**
     * Position the scrollbar, method called from a parent with defined
     * dimensions.
     *
     * @internal
     * @function Highcharts.Scrollbar#position
     * @param {number} x
     *        x-position on the chart
     * @param {number} y
     *        y-position on the chart
     * @param {number} width
     *        width of the scrollbar
     * @param {number} height
     *        height of the scrollbar
     */
    position(x, y, width, height) {
        const scroller = this, options = scroller.options, { buttonsEnabled, margin = 0, vertical } = options, method = scroller.rendered ? 'animate' : 'attr';
        let xOffset = height, yOffset = 0;
        // Make the scrollbar visible when it is repositioned, #15763.
        scroller.group.show();
        scroller.x = x;
        scroller.y = y + this.trackBorderWidth;
        scroller.width = width; // Width with buttons
        scroller.height = height;
        scroller.xOffset = xOffset;
        scroller.yOffset = yOffset;
        // If Scrollbar is a vertical type, swap options:
        if (vertical) {
            scroller.width = scroller.yOffset = width = yOffset = scroller.size;
            scroller.xOffset = xOffset = 0;
            scroller.yOffset = yOffset = buttonsEnabled ? scroller.size : 0;
            // Width without buttons
            scroller.barWidth = height - (buttonsEnabled ? width * 2 : 0);
            scroller.x = x = x + margin;
        }
        else {
            scroller.height = height = scroller.size;
            scroller.xOffset = xOffset = buttonsEnabled ? scroller.size : 0;
            // Width without buttons
            scroller.barWidth = width - (buttonsEnabled ? height * 2 : 0);
            scroller.y = scroller.y + margin;
        }
        // Set general position for a group:
        scroller.group[method]({
            translateX: x,
            translateY: scroller.y
        });
        // Resize background/track:
        scroller.track[method]({
            width: width,
            height: height
        });
        // Move right/bottom button to its place:
        scroller.scrollbarButtons[1][method]({
            translateX: vertical ? 0 : width - xOffset,
            translateY: vertical ? height - yOffset : 0
        });
    }
    /**
     * Removes the event handlers attached previously with addEvents.
     *
     * @internal
     * @function Highcharts.Scrollbar#removeEvents
     */
    removeEvents() {
        this._events.forEach(function (args) {
            removeEvent.apply(null, args);
        });
        this._events.length = 0;
    }
    /**
     * Render scrollbar with all required items.
     *
     * @internal
     * @function Highcharts.Scrollbar#render
     */
    render() {
        const scroller = this, renderer = scroller.renderer, options = scroller.options, size = scroller.size, styledMode = scroller.chart.styledMode, group = renderer.g('scrollbar')
            .attr({
            zIndex: options.zIndex
        })
            .hide() // Initially hide the scrollbar #15863
            .add();
        // Draw the scrollbar group
        scroller.group = group;
        // Draw the scrollbar track:
        scroller.track = renderer.rect()
            .addClass('highcharts-scrollbar-track')
            .attr({
            r: options.trackBorderRadius || 0,
            height: size,
            width: size
        }).add(group);
        if (!styledMode) {
            scroller.track.attr({
                fill: options.trackBackgroundColor,
                stroke: options.trackBorderColor,
                'stroke-width': options.trackBorderWidth
            });
        }
        const trackBorderWidth = scroller.trackBorderWidth =
            scroller.track.strokeWidth();
        scroller.track.attr({
            x: -crisp(0, trackBorderWidth),
            y: -crisp(0, trackBorderWidth)
        });
        // Draw the scrollbar itself
        scroller.scrollbarGroup = renderer.g().add(group);
        scroller.scrollbar = renderer.rect()
            .addClass('highcharts-scrollbar-thumb')
            .attr({
            height: size - trackBorderWidth,
            width: size - trackBorderWidth,
            r: options.barBorderRadius || 0
        }).add(scroller.scrollbarGroup);
        scroller.scrollbarRifles = renderer
            .path(Scrollbar.swapXY([
            ['M', -3, size / 4],
            ['L', -3, 2 * size / 3],
            ['M', 0, size / 4],
            ['L', 0, 2 * size / 3],
            ['M', 3, size / 4],
            ['L', 3, 2 * size / 3]
        ], options.vertical))
            .addClass('highcharts-scrollbar-rifles')
            .add(scroller.scrollbarGroup);
        if (!styledMode) {
            scroller.scrollbar.attr({
                fill: options.barBackgroundColor,
                stroke: options.barBorderColor,
                'stroke-width': options.barBorderWidth
            });
            scroller.scrollbarRifles.attr({
                stroke: options.rifleColor,
                'stroke-width': 1
            });
        }
        scroller.scrollbarStrokeWidth = scroller.scrollbar.strokeWidth();
        scroller.scrollbarGroup.translate(-crisp(0, scroller.scrollbarStrokeWidth), -crisp(0, scroller.scrollbarStrokeWidth));
        // Draw the buttons:
        scroller.drawScrollbarButton(0);
        scroller.drawScrollbarButton(1);
    }
    /**
     * Set scrollbar size, with a given scale.
     *
     * @internal
     * @function Highcharts.Scrollbar#setRange
     * @param {number} from
     *        scale (0-1) where bar should start
     * @param {number} to
     *        scale (0-1) where bar should end
     */
    setRange(from, to) {
        const scroller = this, options = scroller.options, vertical = options.vertical, minWidth = options.minWidth, fullWidth = scroller.barWidth, method = (this.rendered &&
            !this.hasDragged &&
            !(this.chart.navigator && this.chart.navigator.hasDragged)) ? 'animate' : 'attr';
        if (!defined(fullWidth)) {
            return;
        }
        const toPX = fullWidth * Math.min(to, 1);
        let fromPX, newSize;
        from = Math.max(from, 0);
        fromPX = Math.ceil(fullWidth * from);
        scroller.calculatedWidth = newSize = correctFloat(toPX - fromPX);
        // We need to recalculate position, if minWidth is used
        if (newSize < minWidth) {
            fromPX = (fullWidth - minWidth + newSize) * from;
            newSize = minWidth;
        }
        const newPos = Math.floor(fromPX + scroller.xOffset + scroller.yOffset);
        const newRiflesPos = newSize / 2 - 0.5; // -0.5 -> rifle line width / 2
        // Store current position:
        scroller.from = from;
        scroller.to = to;
        if (!vertical) {
            scroller.scrollbarGroup[method]({
                translateX: newPos
            });
            scroller.scrollbar[method]({
                width: newSize
            });
            scroller.scrollbarRifles[method]({
                translateX: newRiflesPos
            });
            scroller.scrollbarLeft = newPos;
            scroller.scrollbarTop = 0;
        }
        else {
            scroller.scrollbarGroup[method]({
                translateY: newPos
            });
            scroller.scrollbar[method]({
                height: newSize
            });
            scroller.scrollbarRifles[method]({
                translateY: newRiflesPos
            });
            scroller.scrollbarTop = newPos;
            scroller.scrollbarLeft = 0;
        }
        if (newSize <= 12) {
            scroller.scrollbarRifles.hide();
        }
        else {
            scroller.scrollbarRifles.show();
        }
        // Show or hide the scrollbar based on the showFull setting
        if (options.showFull === false) {
            if (from <= 0 && to >= 1) {
                scroller.group.hide();
            }
            else {
                scroller.group.show();
            }
        }
        scroller.rendered = true;
    }
    /**
     * Checks if the extremes should be updated in response to a scrollbar
     * change event.
     *
     * @internal
     * @function Highcharts.Scrollbar#shouldUpdateExtremes
     */
    shouldUpdateExtremes(eventType) {
        return (pick(this.options.liveRedraw, Core_Globals.svg &&
            !Core_Globals.isTouchDevice &&
            !this.chart.boosted) ||
            // Mouseup always should change extremes
            eventType === 'mouseup' ||
            eventType === 'touchend' ||
            // Internal events
            !defined(eventType));
    }
    trackClick(e) {
        const scroller = this;
        const normalizedEvent = scroller.chart.pointer?.normalize(e) || e, range = scroller.to - scroller.from, top = scroller.y + scroller.scrollbarTop, left = scroller.x + scroller.scrollbarLeft;
        if ((scroller.options.vertical && normalizedEvent.chartY > top) ||
            (!scroller.options.vertical && normalizedEvent.chartX > left)) {
            // On the top or on the left side of the track:
            scroller.updatePosition(scroller.from + range, scroller.to + range);
        }
        else {
            // On the bottom or the right side of the track:
            scroller.updatePosition(scroller.from - range, scroller.to - range);
        }
        fireEvent(scroller, 'changed', {
            from: scroller.from,
            to: scroller.to,
            trigger: 'scrollbar',
            DOMEvent: e
        });
    }
    /**
     * Update the scrollbar with new options
     *
     * @internal
     * @function Highcharts.Scrollbar#update
     * @param  {Highcharts.ScrollbarOptions} options
     */
    update(options) {
        this.destroy();
        this.init(this.chart.renderer, merge(true, this.options, options), this.chart);
    }
    /**
     * Update position option in the Scrollbar, with normalized 0-1 scale
     *
     * @internal
     * @function Highcharts.Scrollbar#updatePosition
     * @param  {number} from
     * @param  {number} to
     */
    updatePosition(from, to) {
        if (to > 1) {
            from = correctFloat(1 - correctFloat(to - from));
            to = 1;
        }
        if (from < 0) {
            to = correctFloat(to - from);
            from = 0;
        }
        this.from = from;
        this.to = to;
    }
}
/* *
 *
 *  Static Properties
 *
 * */
Scrollbar.defaultOptions = Scrollbar_ScrollbarDefaults;
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
/* harmony default export */ const Scrollbar_Scrollbar = (Scrollbar);

;// ./code/es-modules/Stock/Navigator/Navigator.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Torstein Hønsi
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 * */




const { defaultOptions: Navigator_defaultOptions } = Defaults;

const { isTouchDevice: Navigator_isTouchDevice } = Core_Globals;




/* *
 *
 *  Functions
 *
 * */
/**
 * Finding the min or max of a set of variables where we don't know if they are
 * defined, is a pattern that is repeated several places in Highcharts. Consider
 * making this a global utility method.
 * @internal
 */
function numExt(extreme, ...args) {
    const numbers = [].filter.call(args, isNumber);
    if (numbers.length) {
        return Math[extreme].apply(0, numbers);
    }
}
/* *
 *
 *  Class
 *
 * */
/**
 * The Navigator class
 *
 * @internal
 * @class
 * @name Highcharts.Navigator
 *
 * @param {Highcharts.Chart} chart
 *        Chart object
 */
class Navigator {
    /* *
     *
     *  Static Properties
     *
     * */
    static compose(ChartClass, AxisClass, SeriesClass) {
        Navigator_ChartNavigatorComposition.compose(ChartClass, Navigator);
        Navigator_NavigatorComposition.compose(ChartClass, AxisClass, SeriesClass);
    }
    /* *
     *
     *  Constructor
     *
     * */
    constructor(chart) {
        this.isDirty = false;
        this.scrollbarHeight = 0;
        this.init(chart);
    }
    /* *
     *
     *  Functions
     *
     * */
    /**
     * Draw one of the handles on the side of the zoomed range in the navigator.
     *
     * @internal
     * @function Highcharts.Navigator#drawHandle
     *
     * @param {number} x
     *        The x center for the handle
     *
     * @param {number} index
     *        0 for left and 1 for right
     *
     * @param {boolean|undefined} inverted
     *        Flag for chart.inverted
     *
     * @param {string} verb
     *        Use 'animate' or 'attr'
     */
    drawHandle(x, index, inverted, verb) {
        const navigator = this, height = navigator.navigatorOptions.handles.height;
        // Place it
        navigator.handles[index][verb](inverted ? {
            translateX: Math.round(navigator.left + navigator.height / 2),
            translateY: Math.round(navigator.top + parseInt(x, 10) + 0.5 - height)
        } : {
            translateX: Math.round(navigator.left + parseInt(x, 10)),
            translateY: Math.round(navigator.top + navigator.height / 2 - height / 2 - 1)
        });
    }
    /**
     * Render outline around the zoomed range
     *
     * @internal
     * @function Highcharts.Navigator#drawOutline
     *
     * @param {number} zoomedMin
     *        in pixels position where zoomed range starts
     *
     * @param {number} zoomedMax
     *        in pixels position where zoomed range ends
     *
     * @param {boolean|undefined} inverted
     *        flag if chart is inverted
     *
     * @param {string} verb
     *        use 'animate' or 'attr'
     */
    drawOutline(zoomedMin, zoomedMax, inverted, verb) {
        const navigator = this, maskInside = navigator.navigatorOptions.maskInside, outlineWidth = navigator.outline.strokeWidth(), halfOutline = outlineWidth / 2, outlineCorrection = (outlineWidth % 2) / 2, // #5800
        scrollButtonSize = navigator.scrollButtonSize, navigatorSize = navigator.size, navigatorTop = navigator.top, height = navigator.height, lineTop = navigatorTop - halfOutline, lineBtm = navigatorTop + height;
        let left = navigator.left, verticalMin, path;
        if (inverted) {
            verticalMin = navigatorTop + zoomedMax + outlineCorrection;
            zoomedMax = navigatorTop + zoomedMin + outlineCorrection;
            path = [
                [
                    'M',
                    left + height,
                    navigatorTop - scrollButtonSize - outlineCorrection
                ],
                // Top right of zoomed range
                ['L', left + height, verticalMin],
                ['L', left, verticalMin], // Top left of z.r.
                ['M', left, zoomedMax], // Bottom left of z.r.
                ['L', left + height, zoomedMax], // Bottom right of z.r.
                [
                    'L',
                    left + height,
                    navigatorTop + navigatorSize + scrollButtonSize
                ]
            ];
            if (maskInside) {
                path.push(
                // Upper left of zoomed range
                ['M', left + height, verticalMin - halfOutline], 
                // Upper right of z.r.
                [
                    'L',
                    left + height,
                    zoomedMax + halfOutline
                ]);
            }
        }
        else {
            left -= scrollButtonSize;
            zoomedMin += left + scrollButtonSize - outlineCorrection;
            zoomedMax += left + scrollButtonSize - outlineCorrection;
            path = [
                // Left
                ['M', left, lineTop],
                // Upper left of zoomed range
                ['L', zoomedMin, lineTop],
                // Lower left of z.r.
                ['L', zoomedMin, lineBtm],
                // Lower right of z.r.
                ['M', zoomedMax, lineBtm],
                // Upper right of z.r.
                ['L', zoomedMax, lineTop],
                // Right
                [
                    'L',
                    left + navigatorSize + scrollButtonSize * 2,
                    lineTop
                ]
            ];
            if (maskInside) {
                path.push(
                // Upper left of zoomed range
                ['M', zoomedMin - halfOutline, lineTop], 
                // Upper right of z.r.
                ['L', zoomedMax + halfOutline, lineTop]);
            }
        }
        navigator.outline[verb]({
            d: path
        });
    }
    /**
     * Render outline around the zoomed range
     *
     * @internal
     * @function Highcharts.Navigator#drawMasks
     *
     * @param {number} zoomedMin
     *        in pixels position where zoomed range starts
     *
     * @param {number} zoomedMax
     *        in pixels position where zoomed range ends
     *
     * @param {boolean|undefined} inverted
     *        flag if chart is inverted
     *
     * @param {string} verb
     *        use 'animate' or 'attr'
     */
    drawMasks(zoomedMin, zoomedMax, inverted, verb) {
        const navigator = this, left = navigator.left, top = navigator.top, navigatorHeight = navigator.height;
        let height, width, x, y;
        // Determine rectangle position & size
        // According to (non)inverted position:
        if (inverted) {
            x = [left, left, left];
            y = [top, top + zoomedMin, top + zoomedMax];
            width = [navigatorHeight, navigatorHeight, navigatorHeight];
            height = [
                zoomedMin,
                zoomedMax - zoomedMin,
                navigator.size - zoomedMax
            ];
        }
        else {
            x = [left, left + zoomedMin, left + zoomedMax];
            y = [top, top, top];
            width = [
                zoomedMin,
                zoomedMax - zoomedMin,
                navigator.size - zoomedMax
            ];
            height = [navigatorHeight, navigatorHeight, navigatorHeight];
        }
        navigator.shades.forEach((shade, i) => {
            shade[verb]({
                x: x[i],
                y: y[i],
                width: width[i],
                height: height[i]
            });
        });
    }
    /**
     * Generate and update DOM elements for a navigator:
     *
     * - main navigator group
     *
     * - all shades
     *
     * - outline
     *
     * - handles
     *
     * @internal
     * @function Highcharts.Navigator#renderElements
     */
    renderElements() {
        const navigator = this, navigatorOptions = navigator.navigatorOptions, maskInside = navigatorOptions.maskInside, chart = navigator.chart, inverted = chart.inverted, renderer = chart.renderer, mouseCursor = {
            cursor: inverted ? 'ns-resize' : 'ew-resize'
        }, 
        // Create the main navigator group
        navigatorGroup = navigator.navigatorGroup ??
            (navigator.navigatorGroup = renderer
                .g('navigator')
                .attr({
                zIndex: 8,
                visibility: 'hidden'
            })
                .add());
        // Create masks, each mask will get events and fill:
        [
            !maskInside,
            maskInside,
            !maskInside
        ].forEach((hasMask, index) => {
            const shade = navigator.shades[index] ??
                (navigator.shades[index] = renderer.rect()
                    .addClass('highcharts-navigator-mask' +
                    (index === 1 ? '-inside' : '-outside'))
                    .add(navigatorGroup));
            if (!chart.styledMode) {
                shade.attr({
                    fill: hasMask ? navigatorOptions.maskFill : 'rgba(0,0,0,0)'
                });
                if (index === 1) {
                    shade.css(mouseCursor);
                }
            }
        });
        // Create the outline:
        if (!navigator.outline) {
            navigator.outline = renderer.path()
                .addClass('highcharts-navigator-outline')
                .add(navigatorGroup);
        }
        if (!chart.styledMode) {
            navigator.outline.attr({
                'stroke-width': navigatorOptions.outlineWidth,
                stroke: navigatorOptions.outlineColor
            });
        }
        // Create the handles:
        if (navigatorOptions.handles?.enabled) {
            let redrawHandles = false;
            const handlesOptions = navigatorOptions.handles, { height, width } = handlesOptions;
            [0, 1].forEach((index) => {
                const newSymbolName = handlesOptions.symbols[index];
                redrawHandles = redrawHandles ||
                    (navigator.handles[index]?.symbolName !== newSymbolName);
                // First render of handles or update of handle symbol
                if (redrawHandles) {
                    navigator.handles[index]?.destroy();
                    navigator.handles[index] = renderer.symbol(newSymbolName, -width / 2 - 1, 0, width, height, handlesOptions);
                    // Z index is 6 for right handle, 7 for left. Can't be 10,
                    // because of the tooltip in inverted chart (#2908).
                    navigator.handles[index].attr({ zIndex: 7 - index })
                        .addClass('highcharts-navigator-handle ' +
                        'highcharts-navigator-handle-' +
                        ['left', 'right'][index]).add(navigatorGroup);
                }
                if (chart.inverted) {
                    navigator.handles[index].attr({
                        rotation: 90,
                        rotationOriginX: Math.floor(-width / 2),
                        rotationOriginY: (height + width) / 2
                    });
                }
                if (!chart.styledMode) {
                    navigator.handles[index]
                        .attr({
                        fill: handlesOptions.backgroundColor,
                        stroke: handlesOptions.borderColor,
                        'stroke-width': handlesOptions.lineWidth,
                        width: handlesOptions.width,
                        height: handlesOptions.height,
                        x: -width / 2 - 1,
                        y: 0
                    })
                        .css(mouseCursor);
                }
            });
            if (redrawHandles) {
                navigator.partsEventsToUnbind?.forEach((unbind) => {
                    unbind();
                });
                navigator.partsEventsToUnbind = [
                    ...navigator.getPartsEvents('mousedown'),
                    ...navigator.getPartsEvents('touchstart')
                ];
            }
        }
    }
    /**
     * Update navigator
     *
     * @internal
     * @function Highcharts.Navigator#update
     *
     * @param {Highcharts.NavigatorOptions} options
     *        Options to merge in when updating navigator
     */
    update(options, redraw = false) {
        const chart = this.chart, invertedUpdate = chart.options.chart.inverted !==
            chart.scrollbar?.options.vertical;
        merge(true, chart.options.navigator, options);
        this.navigatorOptions = chart.options.navigator || {};
        this.setOpposite();
        // Revert to destroy/init for navigator/scrollbar enabled toggle
        if (defined(options.enabled) || invertedUpdate) {
            this.destroy();
            this.navigatorEnabled = options.enabled || this.navigatorEnabled;
            return this.init(chart);
        }
        if (this.navigatorEnabled) {
            this.isDirty = true;
            if (options.adaptToUpdatedData === false) {
                this.baseSeries.forEach((series) => {
                    removeEvent(series, 'updatedData', this.updatedDataHandler);
                }, this);
            }
            if (options.adaptToUpdatedData) {
                this.baseSeries.forEach((series) => {
                    series.eventsToUnbind.push(addEvent(series, 'updatedData', this.updatedDataHandler));
                }, this);
            }
            // Update navigator series
            if (options.series || options.baseSeries) {
                this.setBaseSeries(void 0, false);
            }
            // Update navigator axis
            if (options.height || options.xAxis || options.yAxis) {
                this.height = options.height ?? this.height;
                const offsets = this.getXAxisOffsets();
                this.xAxis.update({
                    ...options.xAxis,
                    offsets,
                    [chart.inverted ? 'width' : 'height']: this.height,
                    [chart.inverted ? 'height' : 'width']: void 0
                }, false);
                this.yAxis.update({
                    ...options.yAxis,
                    [chart.inverted ? 'width' : 'height']: this.height
                }, false);
            }
        }
        if (redraw) {
            chart.redraw();
        }
    }
    /**
     * Render the navigator
     *
     * @internal
     * @function Highcharts.Navigator#render
     * @param {number} min
     *        X axis value minimum
     * @param {number} max
     *        X axis value maximum
     * @param {number} [pxMin]
     *        Pixel value minimum
     * @param {number} [pxMax]
     *        Pixel value maximum
     */
    render(min, max, pxMin, pxMax) {
        const navigator = this, chart = navigator.chart, xAxis = navigator.xAxis, pointRange = xAxis.pointRange || 0, scrollbarXAxis = xAxis.navigatorAxis.fake ? chart.xAxis[0] : xAxis, navigatorEnabled = navigator.navigatorEnabled, rendered = navigator.rendered, inverted = chart.inverted, minRange = chart.xAxis[0].minRange, maxRange = chart.xAxis[0].options.maxRange, scrollButtonSize = navigator.scrollButtonSize;
        let navigatorWidth, scrollbarLeft, scrollbarTop, scrollbarHeight = navigator.scrollbarHeight, navigatorSize, verb;
        // Don't redraw while moving the handles (#4703).
        if (this.hasDragged && !defined(pxMin)) {
            return;
        }
        if (this.isDirty) {
            // Update DOM navigator elements
            this.renderElements();
        }
        min = correctFloat(min - pointRange / 2);
        max = correctFloat(max + pointRange / 2);
        // Don't render the navigator until we have data (#486, #4202, #5172).
        if (!isNumber(min) || !isNumber(max)) {
            // However, if navigator was already rendered, we may need to resize
            // it. For example hidden series, but visible navigator (#6022).
            if (rendered) {
                pxMin = 0;
                pxMax = pick(xAxis.width, scrollbarXAxis.width);
            }
            else {
                return;
            }
        }
        navigator.left = pick(xAxis.left, 
        // In case of scrollbar only, without navigator
        chart.plotLeft + scrollButtonSize +
            (inverted ? chart.plotWidth : 0));
        let zoomedMax = navigator.size = navigatorSize = pick(xAxis.len, (inverted ? chart.plotHeight : chart.plotWidth) -
            2 * scrollButtonSize);
        if (inverted) {
            navigatorWidth = scrollbarHeight;
        }
        else {
            navigatorWidth = navigatorSize + 2 * scrollButtonSize;
        }
        // Get the pixel position of the handles
        pxMin = pick(pxMin, xAxis.toPixels(min, true));
        pxMax = pick(pxMax, xAxis.toPixels(max, true));
        // Verify (#1851, #2238)
        if (!isNumber(pxMin) || Math.abs(pxMin) === Infinity) {
            pxMin = 0;
            pxMax = navigatorWidth;
        }
        // Are we below the minRange? (#2618, #6191)
        const newMin = xAxis.toValue(pxMin, true), newMax = xAxis.toValue(pxMax, true), currentRange = Math.abs(correctFloat(newMax - newMin));
        if (defined(minRange) && currentRange < minRange) {
            if (this.grabbedLeft) {
                pxMin = xAxis.toPixels(newMax - minRange - pointRange, true);
            }
            else if (this.grabbedRight) {
                pxMax = xAxis.toPixels(newMin + minRange + pointRange, true);
            }
        }
        else if (defined(maxRange) &&
            correctFloat(currentRange - pointRange) > maxRange) {
            if (this.grabbedLeft) {
                pxMin = xAxis.toPixels(newMax - maxRange - pointRange, true);
            }
            else if (this.grabbedRight) {
                pxMax = xAxis.toPixels(newMin + maxRange + pointRange, true);
            }
        }
        // Handles are allowed to cross, but never exceed the plot area
        navigator.zoomedMax = clamp(Math.max(pxMin, pxMax), 0, zoomedMax);
        navigator.zoomedMin = clamp(navigator.fixedWidth ?
            navigator.zoomedMax - navigator.fixedWidth :
            Math.min(pxMin, pxMax), 0, zoomedMax);
        navigator.range = navigator.zoomedMax - navigator.zoomedMin;
        zoomedMax = Math.round(navigator.zoomedMax);
        const zoomedMin = Math.round(navigator.zoomedMin);
        if (navigatorEnabled) {
            navigator.navigatorGroup.attr({
                visibility: 'inherit'
            });
            // Place elements
            verb = rendered && !navigator.hasDragged ? 'animate' : 'attr';
            navigator.drawMasks(zoomedMin, zoomedMax, inverted, verb);
            navigator.drawOutline(zoomedMin, zoomedMax, inverted, verb);
            if (navigator.navigatorOptions.handles?.enabled) {
                navigator.drawHandle(zoomedMin, 0, inverted, verb);
                navigator.drawHandle(zoomedMax, 1, inverted, verb);
            }
        }
        if (navigator.scrollbar) {
            if (inverted) {
                scrollbarTop = navigator.top - scrollButtonSize;
                scrollbarLeft = navigator.left - scrollbarHeight +
                    (navigatorEnabled || !scrollbarXAxis.opposite ? 0 :
                        // Multiple axes has offsets:
                        (scrollbarXAxis.titleOffset || 0) +
                            // Self margin from the axis.title
                            scrollbarXAxis.axisTitleMargin);
                scrollbarHeight = navigatorSize + 2 * scrollButtonSize;
            }
            else {
                scrollbarTop = navigator.top + (navigatorEnabled ?
                    navigator.height :
                    -scrollbarHeight);
                scrollbarLeft = navigator.left - scrollButtonSize;
            }
            // Reposition scrollbar
            navigator.scrollbar.position(scrollbarLeft, scrollbarTop, navigatorWidth, scrollbarHeight);
            // Keep scale 0-1
            navigator.scrollbar.setRange(
            // Use real value, not rounded because range can be very small
            // (#1716)
            navigator.zoomedMin / (navigatorSize || 1), navigator.zoomedMax / (navigatorSize || 1));
        }
        navigator.rendered = true;
        this.isDirty = false;
        fireEvent(this, 'afterRender');
    }
    /**
     * Set up the mouse and touch events for the navigator. Shades and handles
     * events are added inside the `renderElements` method by calling the
     * `getPartsEvents` method.
     *
     * @internal
     * @function Highcharts.Navigator#addMouseEvents
     */
    addMouseEvents() {
        const navigator = this, chart = navigator.chart, container = chart.container;
        const eventsToUnbind = [];
        let mouseMoveHandler, mouseUpHandler;
        /**
         * Create mouse events' handlers.
         * Make them as separate functions to enable wrapping them:
         */
        navigator.mouseMoveHandler = mouseMoveHandler = function (e) {
            navigator.onMouseMove(e);
        };
        navigator.mouseUpHandler = mouseUpHandler = function (e) {
            navigator.onMouseUp(e);
        };
        eventsToUnbind.push(
        // Add mouse move and mouseup events. These are bind to doc/div,
        // because Navigator.grabbedSomething flags are stored in mousedown
        // events
        addEvent(chart.renderTo, 'mousemove', mouseMoveHandler), addEvent(container.ownerDocument, 'mouseup', mouseUpHandler), 
        // Touch events
        addEvent(chart.renderTo, 'touchmove', mouseMoveHandler), addEvent(container.ownerDocument, 'touchend', mouseUpHandler));
        navigator.eventsToUnbind = eventsToUnbind;
        // Data events
        if (navigator.series && navigator.series[0]) {
            eventsToUnbind.push(addEvent(navigator.series[0].xAxis, 'foundExtremes', function () {
                chart.navigator?.modifyNavigatorAxisExtremes();
            }));
        }
    }
    /**
     * Generate events for handles and masks
     *
     * @internal
     * @function Highcharts.Navigator#getPartsEvents
     *
     * @param {string} eventName
     *        Event name handler, 'mousedown' or 'touchstart'
     *
     * @return {Array<Function>}
     *         An array of functions to remove navigator functions from the
     *         events again.
     */
    getPartsEvents(eventName) {
        const navigator = this, events = [];
        ['shades', 'handles'].forEach((name) => {
            navigator[name].forEach(function (navigatorItem, index) {
                events.push(addEvent(navigatorItem.element, eventName, function (e) {
                    navigator[`${name}Mousedown`](e, index);
                }));
            });
        });
        return events;
    }
    /**
     * Mousedown on a shaded mask, either:
     *
     * - will be stored for future drag&drop
     *
     * - will directly shift to a new range
     *
     * @internal
     * @function Highcharts.Navigator#shadesMousedown
     *
     * @param {Highcharts.PointerEventObject} e
     *        Mouse event
     *
     * @param {number} index
     *        Index of a mask in Navigator.shades array
     */
    shadesMousedown(e, index) {
        e = this.chart.pointer?.normalize(e) || e;
        const navigator = this, chart = navigator.chart, xAxis = navigator.xAxis, zoomedMin = navigator.zoomedMin, navigatorSize = navigator.size, range = navigator.range;
        let navigatorPosition = navigator.left, chartX = e.chartX, fixedMax, fixedMin, ext, left;
        // For inverted chart, swap some options:
        if (chart.inverted) {
            chartX = e.chartY;
            navigatorPosition = navigator.top;
        }
        if (index === 1) {
            // Store information for drag&drop
            navigator.grabbedCenter = chartX;
            navigator.fixedWidth = range;
            navigator.dragOffset = chartX - zoomedMin;
        }
        else {
            // Shift the range by clicking on shaded areas
            left = chartX - navigatorPosition - range / 2;
            if (index === 0) {
                left = Math.max(0, left);
            }
            else if (index === 2 && left + range >= navigatorSize) {
                left = navigatorSize - range;
                if (navigator.reversedExtremes) {
                    // #7713
                    left -= range;
                    fixedMin = navigator.getUnionExtremes()?.dataMin;
                }
                else {
                    // #2293, #3543
                    fixedMax = navigator.getUnionExtremes()?.dataMax;
                }
            }
            if (left !== zoomedMin) { // It has actually moved
                navigator.fixedWidth = range; // #1370
                ext = xAxis.navigatorAxis.toFixedRange(left, left + range, fixedMin, fixedMax);
                if (defined(ext.min)) { // #7411
                    fireEvent(this, 'setRange', {
                        min: Math.min(ext.min, ext.max),
                        max: Math.max(ext.min, ext.max),
                        redraw: true,
                        eventArguments: {
                            trigger: 'navigator'
                        }
                    });
                }
            }
        }
    }
    /**
     * Mousedown on a handle mask.
     * Will store necessary information for drag&drop.
     *
     * @internal
     * @function Highcharts.Navigator#handlesMousedown
     * @param {Highcharts.PointerEventObject} e
     *        Mouse event
     * @param {number} index
     *        Index of a handle in Navigator.handles array
     */
    handlesMousedown(e, index) {
        e = this.chart.pointer?.normalize(e) || e;
        const navigator = this, chart = navigator.chart, baseXAxis = chart.xAxis[0], 
        // For reversed axes, min and max are changed,
        // so the other extreme should be stored
        reverse = navigator.reversedExtremes;
        if (index === 0) {
            // Grab the left handle
            navigator.grabbedLeft = true;
            navigator.otherHandlePos = navigator.zoomedMax;
            navigator.fixedExtreme = reverse ? baseXAxis.min : baseXAxis.max;
        }
        else {
            // Grab the right handle
            navigator.grabbedRight = true;
            navigator.otherHandlePos = navigator.zoomedMin;
            navigator.fixedExtreme = reverse ? baseXAxis.max : baseXAxis.min;
        }
        chart.setFixedRange(void 0);
    }
    /**
     * Mouse move event based on x/y mouse position.
     *
     * @internal
     * @function Highcharts.Navigator#onMouseMove
     *
     * @param {Highcharts.PointerEventObject} e
     *        Mouse event
     */
    onMouseMove(e) {
        const navigator = this, chart = navigator.chart, navigatorSize = navigator.navigatorSize, range = navigator.range, dragOffset = navigator.dragOffset, inverted = chart.inverted;
        let left = navigator.left, chartX;
        // In iOS, a mousemove event with e.pageX === 0 is fired when holding
        // the finger down in the center of the scrollbar. This should be
        // ignored.
        if (!e.touches || e.touches[0].pageX !== 0) { // #4696
            e = chart.pointer?.normalize(e) || e;
            chartX = e.chartX;
            // Swap some options for inverted chart
            if (inverted) {
                left = navigator.top;
                chartX = e.chartY;
            }
            // Drag left handle or top handle
            if (navigator.grabbedLeft) {
                navigator.hasDragged = true;
                navigator.render(0, 0, chartX - left, navigator.otherHandlePos);
                // Drag right handle or bottom handle
            }
            else if (navigator.grabbedRight) {
                navigator.hasDragged = true;
                navigator.render(0, 0, navigator.otherHandlePos, chartX - left);
                // Drag scrollbar or open area in navigator
            }
            else if (navigator.grabbedCenter && dragOffset) {
                navigator.hasDragged = true;
                if (chartX < dragOffset) { // Outside left
                    chartX = dragOffset;
                    // Outside right
                }
                else if (chartX >
                    navigatorSize + dragOffset - range) {
                    chartX = navigatorSize + dragOffset - range;
                }
                navigator.render(0, 0, chartX - dragOffset, chartX - dragOffset + range);
            }
            if (navigator.hasDragged &&
                pick(navigator.scrollbarOptions?.liveRedraw, 
                // By default, don't run live redraw on touch
                // devices or if the chart is in boost.
                !Navigator_isTouchDevice &&
                    !this.chart.boosted)) {
                e.DOMType = e.type;
                setTimeout(function () {
                    navigator.onMouseUp(e);
                }, 0);
            }
        }
    }
    /**
     * Mouse up event based on x/y mouse position.
     *
     * @internal
     * @function Highcharts.Navigator#onMouseUp
     * @param {Highcharts.PointerEventObject} e
     *        Mouse event
     */
    onMouseUp(e) {
        const navigator = this, chart = navigator.chart, xAxis = navigator.xAxis, scrollbar = navigator.scrollbar, DOMEvent = e.DOMEvent || e, inverted = chart.inverted, verb = navigator.rendered && !navigator.hasDragged ?
            'animate' : 'attr';
        let zoomedMax, zoomedMin, unionExtremes, fixedMin, fixedMax, ext;
        if (
        // MouseUp is called for both, navigator and scrollbar (that order),
        // which causes calling afterSetExtremes twice. Prevent first call
        // by checking if scrollbar is going to set new extremes (#6334)
        (navigator.hasDragged && (!scrollbar || !scrollbar.hasDragged)) ||
            e.trigger === 'scrollbar') {
            unionExtremes = navigator.getUnionExtremes();
            // When dragging one handle, make sure the other one doesn't change
            if (navigator.zoomedMin === navigator.otherHandlePos) {
                fixedMin = navigator.fixedExtreme;
            }
            else if (navigator.zoomedMax === navigator.otherHandlePos) {
                fixedMax = navigator.fixedExtreme;
            }
            // Snap to right edge (#4076)
            if (navigator.zoomedMax === navigator.size) {
                fixedMax = navigator.reversedExtremes ?
                    unionExtremes.dataMin :
                    unionExtremes.dataMax;
            }
            // Snap to left edge (#7576)
            if (navigator.zoomedMin === 0) {
                fixedMin = navigator.reversedExtremes ?
                    unionExtremes.dataMax :
                    unionExtremes.dataMin;
            }
            ext = xAxis.navigatorAxis.toFixedRange(navigator.zoomedMin, navigator.zoomedMax, fixedMin, fixedMax);
            if (defined(ext.min)) {
                fireEvent(this, 'setRange', {
                    min: Math.min(ext.min, ext.max),
                    max: Math.max(ext.min, ext.max),
                    redraw: true,
                    animation: navigator.hasDragged ? false : null,
                    eventArguments: {
                        trigger: 'navigator',
                        triggerOp: 'navigator-drag',
                        DOMEvent: DOMEvent // #1838
                    }
                });
            }
        }
        if (e.DOMType !== 'mousemove' &&
            e.DOMType !== 'touchmove') {
            navigator.grabbedLeft = navigator.grabbedRight =
                navigator.grabbedCenter = navigator.fixedWidth =
                    navigator.fixedExtreme = navigator.otherHandlePos =
                        navigator.hasDragged = navigator.dragOffset = null;
        }
        // Update position of navigator shades, outline and handles (#12573)
        if (navigator.navigatorEnabled &&
            isNumber(navigator.zoomedMin) &&
            isNumber(navigator.zoomedMax)) {
            zoomedMin = Math.round(navigator.zoomedMin);
            zoomedMax = Math.round(navigator.zoomedMax);
            if (navigator.shades) {
                navigator.drawMasks(zoomedMin, zoomedMax, inverted, verb);
            }
            if (navigator.outline) {
                navigator.drawOutline(zoomedMin, zoomedMax, inverted, verb);
            }
            if (navigator.navigatorOptions.handles?.enabled &&
                Object.keys(navigator.handles).length ===
                    navigator.handles.length) {
                navigator.drawHandle(zoomedMin, 0, inverted, verb);
                navigator.drawHandle(zoomedMax, 1, inverted, verb);
            }
        }
    }
    /**
     * Removes the event handlers attached previously with addEvents.
     *
     * @internal
     * @function Highcharts.Navigator#removeEvents
     */
    removeEvents() {
        this.eventsToUnbind?.forEach((unbind) => {
            unbind();
        });
        this.eventsToUnbind = void 0;
        this.removeBaseSeriesEvents();
    }
    /**
     * Remove data events.
     *
     * @internal
     * @function Highcharts.Navigator#removeBaseSeriesEvents
     */
    removeBaseSeriesEvents() {
        const baseSeries = this.baseSeries || [];
        if (this.navigatorEnabled && baseSeries[0]) {
            if (this.navigatorOptions.adaptToUpdatedData !== false) {
                baseSeries.forEach(function (series) {
                    removeEvent(series, 'updatedData', this.updatedDataHandler);
                }, this);
            }
            // We only listen for extremes-events on the first baseSeries
            if (baseSeries[0].xAxis) {
                removeEvent(baseSeries[0].xAxis, 'foundExtremes', this.modifyBaseAxisExtremes);
            }
        }
    }
    /**
     * Calculate the navigator xAxis offsets
     *
     * @internal
     */
    getXAxisOffsets() {
        return (this.chart.inverted ?
            [this.scrollButtonSize, 0, -this.scrollButtonSize, 0] :
            [0, -this.scrollButtonSize, 0, this.scrollButtonSize]);
    }
    /**
     * Initialize the Navigator object
     *
     * @internal
     * @function Highcharts.Navigator#init
     */
    init(chart) {
        const chartOptions = chart.options, navigatorOptions = chartOptions.navigator || {}, navigatorEnabled = navigatorOptions.enabled, scrollbarOptions = chartOptions.scrollbar || {}, scrollbarEnabled = scrollbarOptions.enabled, height = navigatorEnabled && navigatorOptions.height || 0, scrollbarHeight = scrollbarEnabled && scrollbarOptions.height || 0, scrollButtonSize = scrollbarOptions.buttonsEnabled && scrollbarHeight || 0;
        this.handles = [];
        this.shades = [];
        this.chart = chart;
        this.setBaseSeries();
        this.height = height;
        this.scrollbarHeight = scrollbarHeight;
        this.scrollButtonSize = scrollButtonSize;
        this.scrollbarEnabled = scrollbarEnabled;
        this.navigatorEnabled = !!navigatorEnabled;
        this.navigatorOptions = navigatorOptions;
        this.scrollbarOptions = scrollbarOptions;
        this.setOpposite();
        const navigator = this, baseSeries = navigator.baseSeries, xAxisIndex = chart.xAxis.length, yAxisIndex = chart.yAxis.length, baseXaxis = baseSeries && baseSeries[0] && baseSeries[0].xAxis ||
            chart.xAxis[0] || { options: {} };
        chart.isDirtyBox = true;
        if (navigator.navigatorEnabled) {
            const offsets = this.getXAxisOffsets();
            // An x axis is required for scrollbar also
            navigator.xAxis = new Axis_Axis(chart, merge({
                // Inherit base xAxis' break, ordinal options and overscroll
                breaks: baseXaxis.options.breaks,
                ordinal: baseXaxis.options.ordinal,
                overscroll: baseXaxis.options.overscroll
            }, navigatorOptions.xAxis, {
                type: 'datetime',
                yAxis: navigatorOptions.yAxis?.id,
                index: xAxisIndex,
                isInternal: true,
                offset: 0,
                keepOrdinalPadding: true, // #2436
                startOnTick: false,
                endOnTick: false,
                // Inherit base xAxis' padding when ordinal is false (#16915).
                minPadding: baseXaxis.options.ordinal ? 0 :
                    baseXaxis.options.minPadding,
                maxPadding: baseXaxis.options.ordinal ? 0 :
                    baseXaxis.options.maxPadding,
                zoomEnabled: false
            }, chart.inverted ? {
                offsets,
                width: height
            } : {
                offsets,
                height
            }), 'xAxis');
            navigator.yAxis = new Axis_Axis(chart, merge(navigatorOptions.yAxis, {
                alignTicks: false,
                offset: 0,
                index: yAxisIndex,
                isInternal: true,
                reversed: pick((navigatorOptions.yAxis &&
                    navigatorOptions.yAxis.reversed), (chart.yAxis[0] && chart.yAxis[0].reversed), false), // #14060
                zoomEnabled: false
            }, chart.inverted ? {
                width: height
            } : {
                height: height
            }), 'yAxis');
            navigator.xAxis.clippable = false;
            navigator.yAxis.clippable = false;
            // If we have a base series, initialize the navigator series
            if (baseSeries ||
                navigatorOptions.series?.data ||
                navigatorOptions.series?.dataTable) {
                navigator.updateNavigatorSeries(false);
                // If not, set up an event to listen for added series
            }
            else if (chart.series.length === 0) {
                navigator.unbindRedraw = addEvent(chart, 'beforeRedraw', function () {
                    // We've got one, now add it as base
                    if (chart.series.length > 0 && !navigator.series) {
                        navigator.setBaseSeries();
                        navigator.unbindRedraw?.(); // Reset
                    }
                });
            }
            navigator.reversedExtremes = (chart.inverted && !navigator.xAxis.reversed) || (!chart.inverted && navigator.xAxis.reversed);
            // Render items, so we can bind events to them:
            navigator.renderElements();
            // Add mouse events
            navigator.addMouseEvents();
            // In case of scrollbar only, fake an x axis to get translation
        }
        else {
            navigator.xAxis = {
                chart,
                navigatorAxis: {
                    fake: true
                },
                translate: function (value, reverse) {
                    const axis = chart.xAxis[0], ext = axis.getExtremes(), scrollTrackWidth = axis.len - 2 * scrollButtonSize, min = numExt('min', axis.options.min, ext.dataMin), valueRange = numExt('max', axis.options.max, ext.dataMax) - min;
                    return reverse ?
                        // From pixel to value
                        (value * valueRange / scrollTrackWidth) + min :
                        // From value to pixel
                        scrollTrackWidth * (value - min) / valueRange;
                },
                toPixels: function (value) {
                    return this.translate(value);
                },
                toValue: function (value) {
                    return this.translate(value, true);
                }
            };
            navigator.xAxis.navigatorAxis.axis = navigator.xAxis;
            navigator.xAxis.navigatorAxis.toFixedRange = (NavigatorAxisComposition.prototype.toFixedRange.bind(navigator.xAxis.navigatorAxis));
        }
        // Initialize the scrollbar
        if (chart.options.scrollbar?.enabled) {
            const options = merge(chart.options.scrollbar, { vertical: chart.inverted });
            if (!isNumber(options.margin)) {
                options.margin = chart.inverted ? -3 : 3;
            }
            chart.scrollbar = navigator.scrollbar = new Scrollbar_Scrollbar(chart.renderer, options, chart);
            addEvent(navigator.scrollbar, 'changed', function (e) {
                const range = navigator.size, to = range * this.to, from = range * this.from;
                navigator.hasDragged = navigator.scrollbar?.hasDragged;
                navigator.render(0, 0, from, to);
                if (this.shouldUpdateExtremes(e.DOMType)) {
                    setTimeout(function () {
                        navigator.onMouseUp(e);
                    });
                }
            });
        }
        // Add data events
        navigator.addBaseSeriesEvents();
        // Add redraw events
        navigator.addChartEvents();
    }
    /**
     * Set the opposite property on navigator
     *
     * @internal
     */
    setOpposite() {
        const navigatorOptions = this.navigatorOptions, navigatorEnabled = this.navigatorEnabled, chart = this.chart;
        this.opposite = pick(navigatorOptions.opposite, Boolean(!navigatorEnabled && chart.inverted)); // #6262
    }
    /**
     * Get the union data extremes of the chart - the outer data extremes of the
     * base X axis and the navigator axis.
     *
     * @internal
     * @function Highcharts.Navigator#getUnionExtremes
     */
    getUnionExtremes(returnFalseOnNoBaseSeries) {
        const baseAxis = this.chart.xAxis[0], time = this.chart.time, navAxis = this.xAxis, navAxisOptions = navAxis.options, baseAxisOptions = baseAxis.options;
        let ret;
        if (!returnFalseOnNoBaseSeries || baseAxis.dataMin !== null) {
            ret = {
                dataMin: pick(// #4053
                time.parse(navAxisOptions?.min), numExt('min', time.parse(baseAxisOptions.min), baseAxis.dataMin, navAxis.dataMin, navAxis.min)),
                dataMax: pick(time.parse(navAxisOptions?.max), numExt('max', time.parse(baseAxisOptions.max), baseAxis.dataMax, navAxis.dataMax, navAxis.max))
            };
        }
        return ret;
    }
    /**
     * Set the base series and update the navigator series from this. With a bit
     * of modification we should be able to make this an API method to be called
     * from the outside
     *
     * @internal
     * @function Highcharts.Navigator#setBaseSeries
     * @param {Highcharts.SeriesOptionsType} [baseSeriesOptions]
     *        Additional series options for a navigator
     * @param {boolean} [redraw]
     *        Whether to redraw after update.
     */
    setBaseSeries(baseSeriesOptions, redraw) {
        const chart = this.chart, baseSeries = this.baseSeries = [];
        baseSeriesOptions = (baseSeriesOptions ||
            chart.options.navigator?.baseSeries ||
            (chart.series.length ?
                // Find the first non-navigator series (#8430)
                find(chart.series, (s) => (!s.options.isInternal)).index :
                0));
        // Iterate through series and add the ones that should be shown in
        // navigator.
        (chart.series || []).forEach((series, i) => {
            if (
            // Don't include existing nav series
            !series.options.isInternal &&
                (series.options.showInNavigator ||
                    (i === baseSeriesOptions ||
                        series.options.id === baseSeriesOptions) &&
                        series.options.showInNavigator !== false)) {
                baseSeries.push(series);
            }
        });
        // When run after render, this.xAxis already exists
        if (this.xAxis && !this.xAxis.navigatorAxis.fake) {
            this.updateNavigatorSeries(true, redraw);
        }
    }
    /**
     * Update series in the navigator from baseSeries, adding new if does not
     * exist.
     *
     * @internal
     * @function Highcharts.Navigator.updateNavigatorSeries
     */
    updateNavigatorSeries(addEvents, redraw) {
        const navigator = this, chart = navigator.chart, baseSeries = navigator.baseSeries, navSeriesMixin = {
            enableMouseTracking: false,
            index: null, // #6162
            linkedTo: null, // #6734
            group: 'nav', // For columns
            padXAxis: false,
            xAxis: this.navigatorOptions.xAxis?.id,
            yAxis: this.navigatorOptions.yAxis?.id,
            showInLegend: false,
            stacking: void 0, // #4823
            isInternal: true,
            states: {
                inactive: {
                    opacity: 1
                }
            }
        }, 
        // Remove navigator series that are no longer in the baseSeries
        navigatorSeries = navigator.series =
            (navigator.series || []).filter((navSeries) => {
                const base = navSeries.baseSeries;
                if (base && baseSeries.indexOf(base) < 0) { // Not in array
                    // If there is still a base series connected to this
                    // series, remove event handler and reference.
                    if (base) {
                        removeEvent(base, 'updatedData', navigator.updatedDataHandler);
                        delete base.navigatorSeries;
                    }
                    // Kill the nav series. It may already have been
                    // destroyed (#8715).
                    if (navSeries.chart) {
                        navSeries.destroy();
                    }
                    return false;
                }
                return true;
            });
        let baseOptions, mergedNavSeriesOptions, chartNavigatorSeriesOptions = navigator.navigatorOptions.series, baseNavigatorOptions;
        // Go through each base series and merge the options to create new
        // series
        if (baseSeries && baseSeries.length) {
            baseSeries.forEach((base) => {
                const linkedNavSeries = base.navigatorSeries, userNavOptions = extend(
                // Grab color and visibility from base as default
                {
                    color: base.color,
                    visible: base.visible
                }, !isArray(chartNavigatorSeriesOptions) ?
                    chartNavigatorSeriesOptions :
                    Navigator_defaultOptions.navigator.series);
                // Don't update if the series exists in nav and we have disabled
                // adaptToUpdatedData.
                if (linkedNavSeries &&
                    navigator.navigatorOptions.adaptToUpdatedData === false) {
                    return;
                }
                navSeriesMixin.name = 'Navigator ' + baseSeries.length;
                baseOptions = base.options || {};
                baseNavigatorOptions = baseOptions.navigatorOptions || {};
                // The dataLabels options are not merged correctly
                // if the settings are an array, #13847.
                userNavOptions.dataLabels = splat(userNavOptions.dataLabels);
                mergedNavSeriesOptions = merge(baseOptions, navSeriesMixin, userNavOptions, baseNavigatorOptions);
                // Once nav series type is resolved, pick correct pointRange
                mergedNavSeriesOptions.pointRange = pick(
                // Strictly set pointRange in options
                userNavOptions.pointRange, baseNavigatorOptions.pointRange, 
                // Fallback to default values, e.g. `null` for column
                Navigator_defaultOptions.plotOptions[mergedNavSeriesOptions.type || 'line']?.pointRange);
                // Merge data separately. Do a slice to avoid mutating the
                // navigator options from base series (#4923).
                const navigatorSeriesData = baseNavigatorOptions.data || userNavOptions.data, navigatorSeriesDataTable = baseNavigatorOptions.dataTable ||
                    userNavOptions.dataTable;
                navigator.hasNavigatorData =
                    navigator.hasNavigatorData ||
                        !!navigatorSeriesData ||
                        !!navigatorSeriesDataTable;
                mergedNavSeriesOptions.data =
                    navigatorSeriesData ||
                        baseOptions.data?.slice(0);
                mergedNavSeriesOptions.dataTable =
                    navigatorSeriesDataTable ||
                        baseOptions.dataTable;
                // Update or add the series
                if (linkedNavSeries && linkedNavSeries.options) {
                    linkedNavSeries.update(mergedNavSeriesOptions, redraw);
                }
                else {
                    base.navigatorSeries = chart.initSeries(mergedNavSeriesOptions);
                    // Trigger setSortedData with dataSorting enabled (#20318)
                    fireEvent(base.navigatorSeries, 'afterUpdate');
                    base.navigatorSeries.baseSeries = base; // Store ref
                    navigatorSeries.push(base.navigatorSeries);
                }
            });
        }
        // If user has defined data (and no base series) or explicitly defined
        // navigator.series as an array, we create these series on top of any
        // base series.
        if ((chartNavigatorSeriesOptions?.data ||
            chartNavigatorSeriesOptions?.dataTable) &&
            !(baseSeries && baseSeries.length) ||
            isArray(chartNavigatorSeriesOptions)) {
            const colors = chart.options.colors || [];
            navigator.hasNavigatorData = false;
            // Allow navigator.series to be an array
            chartNavigatorSeriesOptions =
                splat(chartNavigatorSeriesOptions);
            chartNavigatorSeriesOptions.forEach((userSeriesOptions, i) => {
                navSeriesMixin.name =
                    'Navigator ' + (navigatorSeries.length + 1);
                mergedNavSeriesOptions = merge(Navigator_defaultOptions.navigator?.series, {
                    // Since we don't have a base series to pull color from,
                    // try to fake it by using color from series with same
                    // index. Otherwise pull from the colors array. We need
                    // an explicit color as otherwise updates will increment
                    // color counter and we'll get a new color for each
                    // update of the nav series.
                    color: (chart.series[i] &&
                        !chart.series[i].options.isInternal &&
                        chart.series[i].color) ||
                        colors[i] ||
                        colors[0]
                }, navSeriesMixin, userSeriesOptions);
                mergedNavSeriesOptions.data = userSeriesOptions.data;
                mergedNavSeriesOptions.dataTable = userSeriesOptions.dataTable;
                if (mergedNavSeriesOptions.data ||
                    mergedNavSeriesOptions.dataTable) {
                    navigator.hasNavigatorData = true;
                    navigatorSeries.push(chart.initSeries(mergedNavSeriesOptions));
                }
            });
        }
        if (addEvents) {
            this.addBaseSeriesEvents();
        }
    }
    /**
     * Add data events.
     * For example when main series is updated we need to recalculate extremes
     *
     * @internal
     * @function Highcharts.Navigator#addBaseSeriesEvent
     */
    addBaseSeriesEvents() {
        const navigator = this, baseSeries = navigator.baseSeries || [];
        // Bind modified extremes event to first base's xAxis only.
        // In event of > 1 base-xAxes, the navigator will ignore those.
        // Adding this multiple times to the same axis is no problem, as
        // duplicates should be discarded by the browser.
        if (baseSeries[0] && baseSeries[0].xAxis) {
            baseSeries[0].eventsToUnbind.push(addEvent(baseSeries[0].xAxis, 'foundExtremes', this.modifyBaseAxisExtremes));
        }
        baseSeries.forEach((base) => {
            // Link base series show/hide to navigator series visibility
            base.eventsToUnbind.push(addEvent(base, 'show', function () {
                if (this.navigatorSeries) {
                    this.navigatorSeries.setVisible(true, false);
                }
            }));
            base.eventsToUnbind.push(addEvent(base, 'hide', function () {
                if (this.navigatorSeries) {
                    this.navigatorSeries.setVisible(false, false);
                }
            }));
            // Respond to updated data in the base series, unless explicitly
            // not adapting to data changes.
            if (this.navigatorOptions.adaptToUpdatedData !== false) {
                if (base.xAxis) {
                    base.eventsToUnbind.push(addEvent(base, 'updatedData', this.updatedDataHandler));
                }
            }
            // Handle series removal
            base.eventsToUnbind.push(addEvent(base, 'remove', function () {
                if (baseSeries) {
                    erase(baseSeries, base); // #21043
                }
                if (this.navigatorSeries && navigator.series) {
                    erase(navigator.series, this.navigatorSeries);
                    if (defined(this.navigatorSeries.options)) {
                        this.navigatorSeries.remove(false);
                    }
                    delete this.navigatorSeries;
                }
            }));
        });
    }
    /**
     * Get minimum from all base series connected to the navigator
     * @internal
     * @param {number} currentSeriesMin
     *        Minium from the current series
     * @return {number}
     *         Minimum from all series
     */
    getBaseSeriesMin(currentSeriesMin) {
        return this.baseSeries.reduce(function (min, series) {
            // #10193
            return Math.min(min, series.getColumn('x')[0] ?? min);
        }, currentSeriesMin);
    }
    /**
     * Set the navigator x axis extremes to reflect the total. The navigator
     * extremes should always be the extremes of the union of all series in the
     * chart as well as the navigator series.
     *
     * @internal
     * @function Highcharts.Navigator#modifyNavigatorAxisExtremes
     */
    modifyNavigatorAxisExtremes() {
        const xAxis = this.xAxis;
        if (typeof xAxis.getExtremes !== 'undefined') {
            const unionExtremes = this.getUnionExtremes(true);
            if (unionExtremes &&
                (unionExtremes.dataMin !== xAxis.min ||
                    unionExtremes.dataMax !== xAxis.max)) {
                xAxis.min = unionExtremes.dataMin;
                xAxis.max = unionExtremes.dataMax;
            }
        }
    }
    /**
     * Hook to modify the base axis extremes with information from the Navigator
     *
     * @internal
     * @function Highcharts.Navigator#modifyBaseAxisExtremes
     */
    modifyBaseAxisExtremes() {
        const baseXAxis = this, navigator = baseXAxis.chart.navigator, baseExtremes = baseXAxis.getExtremes(), baseMin = baseExtremes.min, baseMax = baseExtremes.max, baseDataMin = baseExtremes.dataMin, baseDataMax = baseExtremes.dataMax, range = baseMax - baseMin, stickToMin = navigator?.stickToMin, stickToMax = navigator?.stickToMax, overscroll = pick(baseXAxis.ordinal?.convertOverscroll(baseXAxis.options.overscroll), 0), navigatorSeries = navigator.series && navigator.series[0], hasSetExtremes = !!baseXAxis.setExtremes, 
        // When the extremes have been set by range selector button, don't
        // stick to min or max. The range selector buttons will handle the
        // extremes. (#5489)
        unmutable = baseXAxis.eventArgs &&
            baseXAxis.eventArgs.trigger === 'rangeSelectorButton';
        let newMax, newMin;
        if (!unmutable) {
            // If the zoomed range is already at the min, move it to the right
            // as new data comes in
            if (stickToMin) {
                newMin = baseDataMin;
                newMax = newMin + range;
            }
            // If the zoomed range is already at the max, move it to the right
            // as new data comes in
            if (stickToMax) {
                newMax = baseDataMax + overscroll;
                // If stickToMin is true, the new min value is set above
                if (!stickToMin) {
                    newMin = Math.max(baseDataMin, // Don't go below data extremes (#13184)
                    newMax - range, navigator.getBaseSeriesMin(navigatorSeries && navigatorSeries.xData ?
                        navigatorSeries.xData[0] :
                        -Number.MAX_VALUE));
                }
            }
            // Update the extremes
            if (hasSetExtremes && (stickToMin || stickToMax)) {
                if (isNumber(newMin)) {
                    baseXAxis.min = baseXAxis.userMin = newMin;
                    baseXAxis.max = baseXAxis.userMax = newMax;
                }
            }
        }
        // Reset
        navigator.stickToMin =
            navigator.stickToMax = null;
    }
    /**
     * Handler for updated data on the base series. When data is modified, the
     * navigator series must reflect it. This is called from the Chart.redraw
     * function before axis and series extremes are computed.
     *
     * @internal
     * @function Highcharts.Navigator#updateDataHandler
     */
    updatedDataHandler() {
        const navigator = this.chart.navigator, baseSeries = this, navigatorSeries = this.navigatorSeries, shouldStickToMax = navigator.reversedExtremes ?
            Math.round(navigator.zoomedMin) === 0 :
            Math.round(navigator.zoomedMax) >= Math.round(navigator.size);
        // If the scrollbar is scrolled all the way to the right, keep right as
        // new data comes in, unless user set navigator.stickToMax to false.
        navigator.stickToMax = pick(this.chart.options.navigator &&
            this.chart.options.navigator.stickToMax, shouldStickToMax);
        navigator.stickToMin = navigator.shouldStickToMin(baseSeries, navigator);
        // Set the navigator series data to the new data of the base series
        if (navigatorSeries && !navigator.hasNavigatorData) {
            navigatorSeries.options.pointStart = baseSeries.getColumn('x')[0];
            navigatorSeries.setData(baseSeries.options.data || baseSeries.options.dataTable, false, void 0, false); // #5414
        }
    }
    /**
     * Detect if the zoomed area should stick to the minimum, #14742.
     *
     * @internal
     * @function Highcharts.Navigator#shouldStickToMin
     */
    shouldStickToMin(baseSeries, navigator) {
        const xDataMin = navigator.getBaseSeriesMin(baseSeries.getColumn('x')[0]), xAxis = baseSeries.xAxis, max = xAxis.max, min = xAxis.min, range = xAxis.options.range;
        let stickToMin = true;
        if (isNumber(max) && isNumber(min)) {
            // If range declared, stick to the minimum only if the range
            // is smaller than the data set range.
            if (range && max - xDataMin > 0) {
                stickToMin = max - xDataMin < range;
            }
            else {
                // If the current axis minimum falls outside the new
                // updated dataset, we must adjust.
                stickToMin = min <= xDataMin;
            }
        }
        else {
            stickToMin = false; // #15864
        }
        return stickToMin;
    }
    /**
     * Add chart events, like redrawing navigator, when chart requires that.
     *
     * @internal
     * @function Highcharts.Navigator#addChartEvents
     */
    addChartEvents() {
        if (!this.eventsToUnbind) {
            this.eventsToUnbind = [];
        }
        this.eventsToUnbind.push(
        // Move the scrollbar after redraw, like after data updata even if
        // axes don't redraw
        addEvent(this.chart, 'redraw', function () {
            const navigator = this.navigator, xAxis = navigator && (navigator.baseSeries &&
                navigator.baseSeries[0] &&
                navigator.baseSeries[0].xAxis ||
                this.xAxis[0]); // #5709, #13114
            if (xAxis) {
                navigator.render(xAxis.min, xAxis.max);
            }
        }), 
        // Make room for the navigator, can be placed around the chart:
        addEvent(this.chart, 'getMargins', function () {
            const chart = this, navigator = chart.navigator;
            let marginName = navigator.opposite ?
                'plotTop' : 'marginBottom';
            if (chart.inverted) {
                marginName = navigator.opposite ?
                    'marginRight' : 'plotLeft';
            }
            chart[marginName] = (chart[marginName] || 0) + (navigator.navigatorEnabled || !chart.inverted ?
                navigator.height +
                    (this.scrollbar?.options.margin || 0) +
                    navigator.scrollbarHeight : 0) + (navigator.navigatorOptions.margin || 0);
        }), addEvent(this, 'setRange', function (e) {
            this.chart.xAxis[0].setExtremes(e.min, e.max, e.redraw, e.animation, e.eventArguments);
        }));
    }
    /**
     * Destroys allocated elements.
     *
     * @internal
     * @function Highcharts.Navigator#destroy
     */
    destroy() {
        // Disconnect events added in addEvents
        this.removeEvents();
        if (this.xAxis) {
            erase(this.chart.xAxis, this.xAxis);
            erase(this.chart.axes, this.xAxis);
        }
        if (this.yAxis) {
            erase(this.chart.yAxis, this.yAxis);
            erase(this.chart.axes, this.yAxis);
        }
        // Destroy series
        (this.series || []).forEach((s) => {
            if (s.destroy) {
                s.destroy();
            }
        });
        // Destroy properties
        [
            'series', 'xAxis', 'yAxis', 'shades', 'outline', 'scrollbarTrack',
            'scrollbarRifles', 'scrollbarGroup', 'scrollbar', 'navigatorGroup',
            'rendered'
        ].forEach((prop) => {
            if (this[prop] && this[prop].destroy) {
                this[prop].destroy();
            }
            this[prop] = null;
        });
        // Destroy elements in collection
        [this.handles].forEach((coll) => {
            destroyObjectProperties(coll);
        });
        // Clean up linked series
        this.baseSeries.forEach((s) => {
            s.navigatorSeries = void 0;
        });
        this.navigatorEnabled = false;
    }
}
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
/* harmony default export */ const Navigator_Navigator = (Navigator);

;// ./code/es-modules/Stock/Navigator/StandaloneNavigatorDefaults.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Mateusz Bernacik
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */

/* *
 *
 *  Constants
 *
 * */
/** @internal */
const standaloneNavigatorDefaults = {
    chart: {
        height: 70,
        margin: [0, 5, 0, 5]
    },
    exporting: {
        enabled: false
    },
    legend: {
        enabled: false
    },
    navigator: {
        enabled: false
    },
    plotOptions: {
        series: {
            states: {
                hover: {
                    enabled: false
                }
            },
            marker: {
                enabled: false
            }
        }
    },
    scrollbar: {
        enabled: false
    },
    title: {
        text: ''
    },
    tooltip: {
        enabled: false
    },
    xAxis: {
        visible: false
    },
    yAxis: {
        height: 0,
        visible: false
    }
};
/* *
 *
 *  Default Export
 *
 * */
/** @internal */
/* harmony default export */ const StandaloneNavigatorDefaults = (standaloneNavigatorDefaults);

;// ./code/es-modules/Stock/Navigator/StandaloneNavigator.js
/* *
 *
 *  (c) 2010-2026 Highsoft AS
 *  Author: Mateusz Bernacik
 *
 *  Integration of this software requires a license.
 *  - For commercial use, see www.highcharts.com/license
 *  - For non-commercial, see www.highcharts.com/license-eula
 *
 *
 * */







/* *
 *
 *  Class
 *
 * */
/**
 * The StandaloneNavigator class. The StandaloneNavigator class allows for
 * creating a standalone navigator component that synchronizes the extremes
 * across multiple bound charts.
 *
 * @internal
 * @class
 * @name Highcharts.StandaloneNavigator
 *
 * @param {string|Highcharts.HTMLDOMElement} [renderTo]
 * The DOM element to render to, or its id.
 *
 * @param {StandaloneNavigatorOptions} userOptions
 * The standalone navigator options.
 */
class StandaloneNavigator {
    /* *
     *
     *  Static Functions
     *
     * */
    /**
     * Factory function for standalone navigator.
     *
     * @function Highcharts.navigator
     *
     * @param {string|Highcharts.HTMLDOMElement} [renderTo]
     * The DOM element to render to, or its id.
     *
     * @param {StandaloneNavigatorOptions} options
     * The standalone navigator options with chart-like structure.
     *
     * Returns the navigator object.
     */
    static navigator(renderTo, options) {
        const nav = new StandaloneNavigator(renderTo, options);
        if (!Core_Globals.navigators) {
            Core_Globals.navigators = [nav];
        }
        else {
            Core_Globals.navigators.push(nav);
        }
        return nav;
    }
    /* *
     *
     *  Constructor
     *
     * */
    constructor(element, userOptions) {
        this.boundAxes = [];
        this.userOptions = userOptions;
        this.chartOptions = merge(Core_Globals.getOptions(), StandaloneNavigatorDefaults, userOptions.chart, { navigator: userOptions });
        if (this.chartOptions.chart && userOptions.height) {
            this.chartOptions.chart.height = userOptions.height;
        }
        const chart = new Chart_Chart(element, this.chartOptions);
        chart.options = merge(chart.options, { navigator: { enabled: true }, scrollbar: { enabled: true } });
        if (this.chartOptions.navigator && this.chartOptions.scrollbar) {
            this.chartOptions.navigator.enabled = true;
            this.chartOptions.scrollbar.enabled = true;
        }
        this.navigator = new Navigator_Navigator(chart);
        chart.navigator = this.navigator;
        this.initNavigator();
    }
    /**
     * Binds an axis to the standalone navigator,
     * allowing the navigator to control the axis' range.
     *
     * @sample stock/standalone-navigator/bind/
     *         Bind chart with a button
     *
     * @function Highcharts.StandaloneNavigator#bind
     *
     * @param {Axis | Chart} axisOrChart
     *        The Axis or Chart to bind to the navigator.
     *
     * @param {boolean} [twoWay=true]
     *        Enables two-way binding between the navigator and the axis/chart.
     *        When true, changes in the navigator's range will update the axis
     *        and vice versa. When false, changes in the navigator's range will
     *        be reflected in the axis, but changes in the axis ranges won't be
     *        reflected on the navigator.
     */
    bind(axisOrChart, twoWay = true) {
        const nav = this;
        // If the chart is passed, bind the first xAxis
        const axis = (axisOrChart instanceof Chart_Chart) ?
            axisOrChart.xAxis[0] :
            axisOrChart;
        if (!(axis instanceof Axis_Axis)) {
            return;
        }
        const { min, max } = this.navigator.xAxis, removeEventCallbacks = [];
        if (twoWay) {
            const removeSetExtremesEvent = addEvent(axis, 'setExtremes', (e) => {
                if (e.trigger === 'pan' ||
                    e.trigger === 'zoom' ||
                    e.trigger === 'mousewheel' ||
                    e.trigger === 'rangeSelectorButton' ||
                    e.trigger === 'rangeSelectorInput') {
                    nav.setRange(e.min, e.max, true, e.trigger !== 'pan' && e.trigger !== 'mousewheel', { trigger: axis });
                }
            });
            removeEventCallbacks.push(removeSetExtremesEvent);
        }
        const removeSetRangeEvent = addEvent(this.navigator, 'setRange', (e) => {
            axis.setExtremes(e.min, e.max, e.redraw, e.animation);
        });
        removeEventCallbacks.push(removeSetRangeEvent);
        let boundAxis = this.boundAxes.filter(function (boundAxis) {
            return boundAxis.axis === axis;
        })[0];
        if (!boundAxis) {
            boundAxis = { axis, callbacks: [] };
            this.boundAxes.push(boundAxis);
        }
        boundAxis.callbacks = removeEventCallbacks;
        // Show axis' series in navigator based on showInNavigator property
        axis.series.forEach((series) => {
            if (series.options.showInNavigator) {
                nav.addSeries(series.options);
            }
        });
        // Set extremes to match the navigator's extremes
        axis.setExtremes(min, max);
        // Unbind the axis before it's destroyed
        addEvent(axis, 'destroy', (e) => {
            if (!e.keepEvents) {
                this.unbind(axis);
            }
        });
    }
    /**
     * Unbinds a single axis or all bound axes from the standalone navigator.
     *
     * @sample stock/standalone-navigator/unbind/
     *         Unbind chart with a button
     *
     * @function Highcharts.StandaloneNavigator#unbind
     *
     * @param {Chart | Axis | undefined} axisOrChart
     *        Passing a Chart object unbinds the first X axis of the chart,
     *        an Axis object unbinds that specific axis,
     *        and undefined unbinds all axes bound to the navigator.
     */
    unbind(axisOrChart) {
        // If no axis or chart is provided, unbind all bound axes
        if (!axisOrChart) {
            this.boundAxes.forEach(({ callbacks }) => {
                callbacks.forEach((removeCallback) => removeCallback());
            });
            this.boundAxes.length = 0;
            return;
        }
        const axis = (axisOrChart instanceof Axis_Axis) ?
            axisOrChart :
            axisOrChart.xAxis[0];
        for (let i = this.boundAxes.length - 1; i >= 0; i--) {
            if (this.boundAxes[i].axis === axis) {
                this.boundAxes[i].callbacks.forEach((callback) => callback());
                this.boundAxes.splice(i, 1);
            }
        }
    }
    /**
     * Destroys allocated standalone navigator elements.
     *
     * @function Highcharts.StandaloneNavigator#destroy
     */
    destroy() {
        // Disconnect events
        this.boundAxes.forEach(({ callbacks }) => {
            callbacks.forEach((removeCallback) => removeCallback());
        });
        this.boundAxes.length = 0;
        this.navigator.destroy();
        this.navigator.chart.destroy();
    }
    /**
     * Updates the standalone navigator's options with a new set of user
     * options.
     *
     * @sample stock/standalone-navigator/update/
     *         Bind chart with a button
     *
     * @function Highcharts.StandaloneNavigator#update
     *
     * @param  {StandaloneNavigatorOptions} newOptions
     *         Updates the standalone navigator's options with new user options.
     *
     * @param  {boolean | undefined} redraw
     *         Whether to redraw the standalone navigator. By default, if not
     *         specified, the standalone navigator will be redrawn.
     */
    update(newOptions, redraw) {
        this.chartOptions = merge(this.chartOptions, newOptions.height && { chart: { height: newOptions.height } }, newOptions.chart, { navigator: newOptions });
        this.navigator.chart.update(this.chartOptions, redraw);
    }
    /**
     * Redraws the standalone navigator.
     *
     * @function Highcharts.StandaloneNavigator#redraw
     */
    redraw() {
        this.navigator.chart.redraw();
    }
    /**
     * Adds a series to the standalone navigator.
     *
     * @internal
     *
     * @param {SeriesOptions} seriesOptions
     *        Options for the series to be added to the navigator.
     */
    addSeries(seriesOptions) {
        this.navigator.chart.addSeries(merge(seriesOptions, { showInNavigator: pick(seriesOptions.showInNavigator, true) }));
        this.navigator.setBaseSeries();
    }
    /**
     * Initialize the standalone navigator.
     *
     * @internal
     */
    initNavigator() {
        const nav = this.navigator;
        nav.top = 1;
        nav.xAxis.setScale();
        nav.yAxis.setScale();
        nav.xAxis.render();
        nav.yAxis.render();
        nav.series?.forEach((s) => {
            s.translate();
            s.render();
            s.redraw();
        });
        const { min, max } = this.getInitialExtremes();
        nav.chart.xAxis[0].userMin = min;
        nav.chart.xAxis[0].userMax = max;
        nav.render(min, max);
    }
    /**
     * Get the current range of the standalone navigator.
     *
     * @sample stock/standalone-navigator/getrange/
     *         Report the standalone navigator's range by clicking on a button
     *
     * @function Highcharts.StandaloneNavigator#getRange
     *
     * @return {Highcharts.ExtremesObject}
     *         The current range of the standalone navigator.
     */
    getRange() {
        const { min, max } = this.navigator.chart.xAxis[0].getExtremes(), { userMin, userMax, min: dataMin, max: dataMax } = this.navigator.xAxis.getExtremes();
        return {
            min: pick(min, dataMin),
            max: pick(max, dataMax),
            dataMin,
            dataMax,
            userMin,
            userMax
        };
    }
    /**
     * Set the range of the standalone navigator.
     *
     * @sample stock/standalone-navigator/setrange/
     *         Set range from a button
     *
     * @function Highcharts.StandaloneNavigator#setRange
     *
     * @param {number | undefined} min
     *        The new minimum value.
     *
     * @param {number | undefined} max
     *        The new maximum value.
     *
     * @emits Highcharts.StandaloneNavigator#event:setRange
     */
    setRange(min, max, redraw, animation, eventArguments) {
        fireEvent(this.navigator, 'setRange', {
            min,
            max,
            redraw,
            animation,
            eventArguments: merge(eventArguments, { trigger: 'navigator' })
        });
    }
    /**
     * Get the initial, options based extremes for the standalone navigator.
     *
     * @internal
     *
     * @return {{ min: number, max: number }}
     *         The initial minimum and maximum extremes values.
     */
    getInitialExtremes() {
        const { min, max } = this.navigator.xAxis.getExtremes();
        return {
            min: min,
            max: max
        };
    }
}
/** @internal */
/* harmony default export */ const Navigator_StandaloneNavigator = (StandaloneNavigator);
/* *
 *
 *  API Declarations
 *
 * */
/**
 * Standalone Navigator options.
 *
 * @interface Highcharts.StandaloneNavigatorOptions
 */ /**
*/
''; // Detach doclets above

;// ./code/es-modules/masters/modules/navigator.src.js
// SPDX-License-Identifier: LicenseRef-Highcharts
/**
 * @license Highcharts JS v13.0.0 (2026-06-11)
 * @module highcharts/modules/navigator
 * @requires highcharts
 *
 * Standalone navigator module
 *
 * (c) 2009-2026 Highsoft AS
 * Author: Mateusz Bernacik
 *
 * A commercial license may be required depending on use,
 * see www.highcharts.com/license
 */




const navigator_src_G = Core_Globals;
navigator_src_G.StandaloneNavigator = navigator_src_G.StandaloneNavigator || Navigator_StandaloneNavigator;
navigator_src_G.navigator = navigator_src_G.StandaloneNavigator.navigator;
Navigator_NavigatorComposition.compose(navigator_src_G.Chart, navigator_src_G.Axis, navigator_src_G.Series);
/* harmony default export */ const navigator_src = ((/* unused pure expression or super */ null && (Highcharts)));

;// ./code/es-modules/masters/standalone-navigator.src.js




/* harmony default export */ const standalone_navigator_src = (highcharts_src);

export { standalone_navigator_src as default };
