UNPKG

highcharts

Version:
1,323 lines (1,320 loc) 2.07 MB
/** * @license Highcharts JS v11.3.0 (2024-01-10) * * (c) 2009-2024 Torstein Honsi * * License: www.highcharts.com/license */ (function (root, factory) { if (typeof module === 'object' && module.exports) { factory['default'] = factory; module.exports = (root && root.document) ? factory(root) : factory; } else if (typeof define === 'function' && define.amd) { define('highcharts/highcharts', function () { return factory(root); }); } else { if (root.Highcharts) { root.Highcharts.error(16, true); } root.Highcharts = factory(root); } }(typeof window !== 'undefined' ? window : this, function (window) { 'use strict'; var _modules = {}; function _registerModule(obj, path, args, fn) { if (!obj.hasOwnProperty(path)) { obj[path] = fn.apply(null, args); if (typeof CustomEvent === 'function') { window.dispatchEvent(new CustomEvent( 'HighchartsModuleLoaded', { detail: { path: path, module: obj[path] } } )); } } } _registerModule(_modules, 'Core/Globals.js', [], function () { /* * * * (c) 2010-2024 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ /* * * * Namespace * * */ /** * Shared Highcharts properties. * @private */ var Globals; (function (Globals) { /* * * * Constants * * */ Globals.SVG_NS = 'http://www.w3.org/2000/svg', Globals.product = 'Highcharts', Globals.version = '11.3.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 && Globals.doc.createElementNS && !!Globals.doc.createElementNS(Globals.SVG_NS, 'svg').createSVGRect), Globals.userAgent = (Globals.win.navigator && Globals.win.navigator.userAgent) || '', Globals.isChrome = Globals.userAgent.indexOf('Chrome') !== -1, 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.hasBidiBug = (Globals.isFirefox && parseInt(Globals.userAgent.split('Firefox/')[1], 10) < 4 // issue #38 ), Globals.hasTouch = !!Globals.win.TouchEvent, 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. * @private */ 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. * * @sample highcharts/global/dateformats/ * Adding support for week number * * @name Highcharts.dateFormats * @type {Record<string, Highcharts.TimeFormatCallbackFunction>} */ Globals.dateFormats = {}; /** * @private * @deprecated * @todo Use only `Core/Series/SeriesRegistry.seriesTypes` */ Globals.seriesTypes = {}; /** * @private */ Globals.symbolSizes = {}; /* * * * Properties * * */ // eslint-disable-next-line prefer-const Globals.chartCount = 0; })(Globals || (Globals = {})); /* * * * Default Export * * */ /* * * * 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 * @name Highcharts.theme * @type {Highcharts.Options} */ (''); // keeps doclets above in JS file return Globals; }); _registerModule(_modules, 'Core/Utilities.js', [_modules['Core/Globals.js']], function (H) { /* * * * (c) 2010-2024 Torstein Honsi * * License: www.highcharts.com/license * * !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!! * * */ const { charts, doc, win } = H; /* * * * 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 (win.console && error.messages.indexOf(message) === -1 // prevent console flooting ) { 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(H, 'displayError', { chart, code, message, params }, defaultHandler); error.messages.push(message); } (function (error) { error.messages = []; })(error || (error = {})); /* eslint-disable valid-jsdoc */ /** * 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 {boolean} extend * Whether to extend the left-side object (a) or return a whole new * object. * * @param {T|undefined} a * The first object to extend. When only this is given, the function * returns a deep copy. * * @param {...Array<object|undefined>} [n] * An object 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. */ /** * Utility function to deep merge two or more objects and return a third 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 {T|undefined} a * The first object to extend. When only this is given, the function * returns a deep copy. * * @param {...Array<object|undefined>} [n] * An object 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() { /* eslint-enable valid-jsdoc */ let i, args = arguments, 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 (args[0] === 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; } /** * Constrain a value to within a lower and upper threshold. * * @private * @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; } // 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. * @private */ 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))) { ret[key] = keeper[key]; } }); } diff(newer, older, ret, 0); return ret; } /** * Shortcut for parseInt * * @private * @function Highcharts.pInt * * @param {*} s * any * * @param {number} [mag] * Magnitude * * @return {number} * number */ function pInt(s, mag) { return parseInt(s, mag || 10); } /** * 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 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 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 && obj.constructor; return !!(isObject(obj, true) && !isDOMElement(obj) && (c && 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; } /** * Remove the last occurence 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; } } } /** * 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. * * @private * @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; } /** * Adds an item to an array, if it is not present in the array. * * @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); } /** * 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; } /** * 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; } /** * 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]; } /** * 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 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). * * @function Highcharts.clearTimeout * * @param {number|undefined} id * Id of a timeout. */ function internalClearTimeout(id) { if (defined(id)) { clearTimeout(id); } } /* eslint-disable valid-jsdoc */ /** * 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) { /* eslint-enable valid-jsdoc */ let n; if (!a) { a = {}; } for (n in b) { // eslint-disable-line guard-for-in a[n] = b[n]; } return a; } /* eslint-disable valid-jsdoc */ /** * 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; } } } /** * 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) { if (H.isMS && !H.svg) { // #2686 if (styles && defined(styles.opacity)) { styles.filter = `alpha(opacity=${styles.opacity * 100})`; } } extend(el.style, styles); } /** * 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; } // eslint-disable-next-line valid-jsdoc /** * Extend a prototyped class by new members. * * @deprecated * @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; } /** * 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; } /** * 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); } /** * 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))); }; } /** * 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)); } /** * Take an interval and normalize it to multiples of round numbers. * * @deprecated * @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; } /** * Sort an object array and keep the order of equal items. The ECMAScript * standard does not specify the behaviour 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 } } /** * 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; } /** * 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) { objectEach(obj, function (val, n) { // If the object is non-null and destroy is defined if (val && val !== except && val.destroy) { // Invoke the destroy val.destroy(); } // Delete the property from the object. delete obj[n]; }); } /** * Discard a HTML element * * @function Highcharts.discardElement * * @param {Highcharts.HTMLDOMElement} element * The HTML node to discard. */ function discardElement(element) { if (element && element.parentElement) { element.parentElement.removeChild(element); } } /** * 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)); } /** * 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 * * @private * @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); }; /** * Find the closest distance between two values of a two-dimensional array * @private * @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; } /** * Returns the value of a property path on a given object. * * @private * @function getNestedProperty * * @param {string} path * Path to the property, for example `custom.myValue`. * * @param {unknown} obj * 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]; // 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 && 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; } /** * Search for an item in an array. * * @function Highcharts.inArray * * @deprecated * * @param {*} item * The item to search for. * * @param {Array<*>} arr * The array or node collection to search in. * * @param {number} [fromIndex=0] * The index to start searching from. * * @return {number} * The index within the array, or -1 if not found. */ function inArray(item, arr, fromIndex) { error(32, false, void 0, { 'Highcharts.inArray': 'use Array.indexOf' }); return arr.indexOf(item, fromIndex);