@highcharts/dashboards
Version:
Highcharts Dashboards framework
24,864 lines • 794 kB
JavaScript
// SPDX-License-Identifier: LicenseRef-Highcharts
/**
* @license Highcharts Dashboards v4.2.1 (2026-08-06)
* @module dashboards/dashboards
*
* (c) 2009-2026 Highsoft AS
*
* A commercial license may be required depending on use,
* see www.highcharts.com/license
*/
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
(root["_Dashboards"] = factory(),module.exports = root["_Dashboards"]);
else if(typeof define === 'function' && define.amd)
define("dashboards/dashboards", [], factory);
else if(typeof exports === 'object')
(root["_Dashboards"] = factory(),exports["dashboards/dashboards"] = root["_Dashboards"]);
else
((root["Dashboards"] && root["Dashboards"].error(16, true)), root["Dashboards"] = factory());
})(typeof window === 'undefined' ? this : window, () => {
return /******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ // The require scope
/******/ const __webpack_require__ = {};
/******/
/************************************************************************/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter/value functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ if(Array.isArray(definition)) {
/******/ var i = 0;
/******/ while(i < definition.length) {
/******/ var key = definition[i++];
/******/ var binding = definition[i++];
/******/ if(!__webpack_require__.o(exports, key)) {
/******/ if(binding === 0) {
/******/ Object.defineProperty(exports, key, { enumerable: true, value: definition[i++] });
/******/ } else {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: binding });
/******/ }
/******/ } else if(binding === 0) { i++; }
/******/ }
/******/ } else {
/******/ 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))
/******/ })();
/******/
/************************************************************************/
let __webpack_exports__ = {};
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
"default": () => (/* binding */ dashboards_src)
});
;// ./code/dashboards/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 = '4.2.1', 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/dashboards/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/dashboards/es-modules/Data/Converters/DataConverterUtils.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:
* - Kamil Kubik
*
* */
/* *
*
* Functions
*
* */
/**
* Converts a value to a Date.
*
* @param {DataConverterType} value
* Value to convert.
*
* @return {globalThis.Date}
* Converted value as a Date.
*/
function asDate(value, converter) {
let timestamp;
if (typeof value === 'string') {
timestamp = converter.parseDate(value);
}
else if (typeof value === 'number') {
timestamp = value;
}
else if (value instanceof Date) {
return value;
}
else {
timestamp = converter.parseDate(asString(value));
}
return new Date(timestamp);
}
/**
* Converts a value to a number.
*
* @param {DataConverterType} value
* Value to convert.
*
* @return {number}
* Converted value as a number.
*/
function asNumber(value, decimalRegExp) {
if (typeof value === 'number') {
return value;
}
if (typeof value === 'boolean') {
return value ? 1 : 0;
}
if (typeof value === 'string') {
const decimalRegex = decimalRegExp;
if (value.indexOf(' ') > -1) {
value = value.replace(/\s+/g, '');
}
if (decimalRegex) {
if (!decimalRegex.test(value)) {
return NaN;
}
value = value.replace(decimalRegex, '$1.$2');
}
return parseFloat(value);
}
if (value instanceof Date) {
return value.getDate();
}
if (value) {
return value.getRowCount();
}
return NaN;
}
/**
* Converts a value to a string.
*
* @param {DataConverterType} value
* Value to convert.
*
* @return {string}
* Converted value as a string.
*/
function asString(value) {
return '' + value;
}
/**
* Converts a value to a boolean.
*
* @param {DataConverterType} value
* Value to convert.
*
* @return {boolean}
* Converted value as a boolean.
*/
function asBoolean(value) {
if (typeof value === 'boolean') {
return value;
}
if (typeof value === 'string') {
return value !== '' && value !== '0' && value !== 'false';
}
return !!asNumber(value);
}
/**
* Guesses the potential type of a string value for parsing CSV etc.
*
* @param {*} value
* The value to examine.
*
* @return {'number' | 'string' | 'Date'}
* Type string, either `string`, `Date`, or `number`.
*/
function guessType(value, converter) {
let result = 'string';
if (typeof value === 'string') {
const trimedValue = trim(`${value}`), decimalRegExp = converter.decimalRegExp;
let innerTrimedValue = trim(trimedValue, true);
if (decimalRegExp) {
innerTrimedValue = (decimalRegExp.test(innerTrimedValue) ?
innerTrimedValue.replace(decimalRegExp, '$1.$2') :
'');
}
const floatValue = parseFloat(innerTrimedValue);
if (+innerTrimedValue === floatValue) {
// String is numeric
value = floatValue;
}
else {
// Determine if a date string
const dateValue = converter.parseDate(value);
result = isNumber(dateValue) ? 'Date' : 'string';
}
}
if (typeof value === 'number') {
// Greater than milliseconds in a year assumed timestamp
result = value > 365 * 24 * 3600 * 1000 ? 'Date' : 'number';
}
return result;
}
/**
* Trim a string from whitespaces.
*
* @param {string} str
* String to trim.
*
* @param {boolean} [inside=false]
* Remove all spaces between numbers.
*
* @return {string}
* Trimmed string
*/
function trim(str, inside) {
if (typeof str === 'string') {
str = str.replace(/^\s+|\s+$/g, '');
// Clear white space inside the string, like thousands separators
if (inside && /^[\d\s]+$/.test(str)) {
str = str.replace(/\s/g, '');
}
}
return str;
}
/**
* Parses an array of columns to a column collection. If more headers are
* provided, the corresponding, empty columns are added.
*
* @param {DataTableColumn[]} [columnsArray]
* Array of columns.
*
* @param {string[]} [headers]
* Column ids to use.
*
* @return {DataTableColumnCollection}
* Parsed columns.
*/
function getColumnsCollection(columnsArray = [], headers) {
const columns = {};
for (let i = 0, iEnd = Math.max(headers.length, columnsArray.length); i < iEnd; ++i) {
const columnId = headers[i] || `${i}`;
columns[columnId] = columnsArray[i] ? columnsArray[i].slice() : [];
}
return columns;
}
/* *
*
* Default Export
*
* */
const DataConverterUtils = {
asBoolean,
asDate,
asNumber,
asString,
getColumnsCollection,
guessType,
trim
};
/* harmony default export */ const Converters_DataConverterUtils = (DataConverterUtils);
;// ./code/dashboards/es-modules/Data/Converters/DataConverter.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
* - Sebastian Bochan
* - Gøran Slettemark
* - Torstein Hønsi
* - Wojciech Chmiel
* - Jomar Hønsi
* - Kamil Kubik
*
* */
/* *
*
* Class
*
* */
/**
* Base class providing an interface and basic methods for a DataConverter
*
* @private
*/
class DataConverter {
/**
* Adds a converter class to the registry.
*
* @private
*
* @param {string} key
* Registry key of the converter class.
*
* @param {DataConverterTypes} DataConverterClass
* Connector class (aka class constructor) to register.
*
* @return {boolean}
* Returns true, if the registration was successful. False is returned, if
* their is already a converter registered with this key.
*/
static registerType(key, DataConverterClass) {
return (!!key &&
!DataConverter.types[key] &&
!!(DataConverter.types[key] = DataConverterClass));
}
/* *
*
* Constructor
*
* */
/**
* Constructs an instance of the DataConverter.
*
* @param {UserOptions} [options]
* Options for the DataConverter.
*/
constructor(options) {
/* *
*
* Properties
*
* */
/**
* A collection of available date formats.
*/
this.dateFormats = {
'YYYY/mm/dd': {
regex: /^(\d{4})([\-\.\/])(\d{1,2})\2(\d{1,2})$/,
parser: function (match) {
return (match ?
Date.UTC(+match[1], +match[3] - 1, +match[4]) : NaN);
}
},
'dd/mm/YYYY': {
regex: /^(\d{1,2})([\-\.\/])(\d{1,2})\2(\d{4})$/,
parser: function (match) {
return (match ?
Date.UTC(+match[4], +match[3] - 1, +match[1]) : NaN);
},
alternative: 'mm/dd/YYYY' // Different format with the same regex
},
'mm/dd/YYYY': {
regex: /^(\d{1,2})([\-\.\/])(\d{1,2})\2(\d{4})$/,
parser: function (match) {
return (match ?
Date.UTC(+match[4], +match[1] - 1, +match[3]) : NaN);
}
},
'dd/mm/YY': {
regex: /^(\d{1,2})([\-\.\/])(\d{1,2})\2(\d{2})$/,
parser: function (match) {
const d = new Date();
if (!match) {
return NaN;
}
let year = +match[4];
if (year > (d.getFullYear() - 2000)) {
year += 1900;
}
else {
year += 2000;
}
return Date.UTC(year, +match[3] - 1, +match[1]);
},
alternative: 'mm/dd/YY' // Different format with the same regex
},
'mm/dd/YY': {
regex: /^(\d{1,2})([\-\.\/])(\d{1,2})\2(\d{2})$/,
parser: function (match) {
return (match ?
Date.UTC(+match[4] + 2000, +match[1] - 1, +match[3]) :
NaN);
}
}
};
const mergedOptions = merge(DataConverter.defaultOptions, options);
let regExpPoint = mergedOptions.decimalPoint;
if (regExpPoint === '.' || regExpPoint === ',') {
regExpPoint = regExpPoint === '.' ? '\\.' : ',';
this.decimalRegExp =
new RegExp('^(-?[0-9]+)' + regExpPoint + '([0-9]+)$');
}
this.options = mergedOptions;
}
/* *
*
* Functions
*
* */
/**
* Converts a string value based on its guessed type.
*
* @param {*} value
* The value to examine.
*
* @return {number | string | Date}
* The converted value.
*/
convertByType(value) {
const converter = this, typeMap = {
'number': (value) => Converters_DataConverterUtils.asNumber(value, converter.decimalRegExp),
'Date': (value) => Converters_DataConverterUtils.asDate(value, converter),
'string': Converters_DataConverterUtils.asString
};
return typeMap[Converters_DataConverterUtils.guessType(value, converter)]
.call(converter, value);
}
/**
* Tries to guess the date format
* - Check if either month candidate exceeds 12
* - Check if year is missing (use current year)
* - Check if a shortened year format is used (e.g. 1/1/99)
* - If no guess can be made, the user must be prompted
* data is the data to deduce a format based on
* @private
*
* @param {string[]} data
* Data to check the format.
*
* @param {number} limit
* Max data to check the format.
*
* @param {boolean} save
* Whether to save the date format in the converter options.
*/
deduceDateFormat(data, limit, save) {
const parser = this, stable = [], max = [];
let format = 'YYYY/mm/dd', thing, guessedFormat = [], i = 0, madeDeduction = false, elem, j;
if (!limit || limit > data.length) {
limit = data.length;
}
for (; i < limit; i++) {
if (typeof data[i] !== 'undefined' &&
data[i] && data[i].length) {
thing = data[i]
.trim()
.replace(/[\-\.\/]/g, ' ')
.split(' ');
guessedFormat = [
'',
'',
''
];
for (j = 0; j < thing.length; j++) {
if (j < guessedFormat.length) {
elem = parseInt(thing[j], 10);
if (elem) {
max[j] = (!max[j] || max[j] < elem) ? elem : max[j];
if (typeof stable[j] !== 'undefined') {
if (stable[j] !== elem) {
stable[j] = false;
}
}
else {
stable[j] = elem;
}
if (elem > 31) {
if (elem < 100) {
guessedFormat[j] = 'YY';
}
else {
guessedFormat[j] = 'YYYY';
}
}
else if (elem > 12 &&
elem <= 31) {
guessedFormat[j] = 'dd';
madeDeduction = true;
}
else if (!guessedFormat[j].length) {
guessedFormat[j] = 'mm';
}
}
}
}
}
}
if (madeDeduction) {
// This handles a few edge cases with hard to guess dates
for (j = 0; j < stable.length; j++) {
if (stable[j] !== false) {
if (max[j] > 12 &&
guessedFormat[j] !== 'YY' &&
guessedFormat[j] !== 'YYYY') {
guessedFormat[j] = 'YY';
}
}
else if (max[j] > 12 && guessedFormat[j] === 'mm') {
guessedFormat[j] = 'dd';
}
}
// If the middle one is dd, and the last one is dd,
// the last should likely be year.
if (guessedFormat.length === 3 &&
guessedFormat[1] === 'dd' &&
guessedFormat[2] === 'dd') {
guessedFormat[2] = 'YY';
}
format = guessedFormat.join('/');
// If the calculated format is not valid, we need to present an
// error.
}
// Save the deduced format in the converter options.
if (save) {
parser.options.dateFormat = format;
}
return format;
}
/**
* Emits an event on the DataConverter instance.
*
* @param {Event} [e]
* Event object containing additional event data
*/
emit(e) {
fireEvent(this, e.type, e);
}
/**
* Registers a callback for a specific event.
*
* @param {string} type
* Event type as a string.
*
* @param {DataEventCallback} callback
* Function to register for an modifier callback.
*
* @return {Function}
* Function to unregister callback from the modifier event.
*/
on(type, callback) {
return addEvent(this, type, callback);
}
/**
* Parse a date and return it as a number.
*
* @param {string} value
* Value to parse.
*
* @param {string} dateFormatProp
* Which of the predefined date formats
* to use to parse date values.
*/
parseDate(value, dateFormatProp) {
const converter = this, options = converter.options;
let dateFormat = dateFormatProp || options.dateFormat, result = NaN, key, match = null;
if (options.parseDate) {
result = options.parseDate(value);
}
else {
const dateFormats = converter.dateFormats;
// Auto-detect the date format the first time
if (!dateFormat) {
for (key in dateFormats) { // eslint-disable-line guard-for-in
const format = dateFormats[key];
match = value.match(format.regex);
if (match) {
dateFormat = key;
result = format.parser(match);
break;
}
}
// Next time, use the one previously found
}
else {
let format = dateFormats[dateFormat];
if (!format) {
// The selected format is invalid
format = dateFormats['YYYY/mm/dd'];
}
match = value.match(format.regex);
if (match) {
result = format.parser(match);
}
}
// Fall back to Date.parse
if (!match) {
const parsed = Date.parse(value);
if (!isNaN(parsed)) {
result =
parsed - new Date(parsed).getTimezoneOffset() * 60000;
// Reset dates without year in Chrome
if (!value.includes('2001') &&
new Date(result).getFullYear() === 2001) {
result = NaN;
}
}
}
}
return result;
}
}
/* *
*
* Static Properties
*
* */
/**
* Default options
*/
DataConverter.defaultOptions = {
dateFormat: '',
firstRowAsNames: true
};
/**
* Registry as a record object with converter names and their class.
*/
DataConverter.types = {};
/* *
*
* Default Export
*
* */
/* harmony default export */ const Converters_DataConverter = (DataConverter);
;// ./code/dashboards/es-modules/Data/Converters/CSVConverter.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:
* - Torstein Hønsi
* - Christer Vasseng
* - Gøran Slettemark
* - Sophie Bremer
* - Kamil Kubik
*
* */
/* *
*
* Class
*
* */
/**
* Handles parsing and transforming CSV to a table.
*
* @private
*/
class CSVConverter extends Converters_DataConverter {
/* *
*
* Constructor
*
* */
/**
* Constructs an instance of the CSV parser.
*
* @param {Partial<CSVConverterOptions>} [options]
* Options for the CSV parser.
*/
constructor(options) {
const mergedOptions = merge(CSVConverter.defaultOptions, options);
super(mergedOptions);
/* *
*
* Properties
*
* */
this.headers = [];
this.dataTypes = [];
this.options = mergedOptions;
}
/* *
*
* Functions
*
* */
/**
* Parses the CSV string into a DataTable column collection.
* Handles line and item delimiters, optional header row, and
* applies pre-processing if a beforeParse callback is provided.
*
* @param {Partial<CSVConverterOptions>} [options]
* Options for the parser.
* @param {DataEventDetail} [eventDetail]
* Custom information for pending events.
* @return {DataTableColumnCollection}
* The parsed column collection.
*
* @emits CSVDataParser#parse
* @emits CSVDataParser#afterParse
*/
parse(options, eventDetail) {
const converter = this, dataTypes = converter.dataTypes, parserOptions = merge(this.options, options), { beforeParse, lineDelimiter, firstRowAsNames, itemDelimiter } = parserOptions;
let lines, rowIt = 0, { csv, startRow, endRow } = parserOptions, column;
const columnsArray = [];
converter.emit({
type: 'parse',
columns: columnsArray,
detail: eventDetail,
headers: converter.headers
});
if (csv && beforeParse) {
csv = beforeParse(csv);
}
if (csv) {
lines = csv
.replace(/\r\n|\r/g, '\n') // Windows | Mac
.split(lineDelimiter || '\n');
if (!startRow || startRow < 0) {
startRow = 0;
}
if (!endRow || endRow >= lines.length) {
endRow = lines.length - 1;
}
if (!itemDelimiter) {
converter.guessedItemDelimiter =
converter.guessDelimiter(lines);
}
// If the first row contain names, add them to the
// headers array and skip the row.
if (firstRowAsNames) {
const headers = lines[0].split(itemDelimiter || converter.guessedItemDelimiter || ',');
// Remove ""s from the headers
for (let i = 0; i < headers.length; i++) {
headers[i] = headers[i].trim().replace(/^["']|["']$/g, '');
}
converter.headers = headers;
startRow++;
}
let offset = 0;
for (rowIt = startRow; rowIt <= endRow; rowIt++) {
if (lines[rowIt][0] === '#') {
offset++;
}
else {
converter.parseCSVRow(columnsArray, lines[rowIt], rowIt - startRow - offset);
}
}
if (dataTypes.length &&
dataTypes[0].length &&
dataTypes[0][1] === 'date' && // Format is a string date
!converter.options.dateFormat) {
converter.deduceDateFormat(columnsArray[0], null, true);
}
// Guess types.
for (let i = 0, iEnd = columnsArray.length; i < iEnd; ++i) {
column = columnsArray[i];
for (let j = 0, jEnd = column.length; j < jEnd; ++j) {
if (column[j] && typeof column[j] === 'string') {
let cellValue = converter.convertByType(column[j]);
if (cellValue instanceof Date) {
cellValue = cellValue.getTime();
}
columnsArray[i][j] = cellValue;
}
}
}
}
// Normalize columns to same length to avoid truncation.
columnsArray.forEach((col) => {
col.length = Math.max(...columnsArray.map((c) => c.length));
});
converter.emit({
type: 'afterParse',
columns: columnsArray,
detail: eventDetail,
headers: converter.headers
});
return Converters_DataConverterUtils.getColumnsCollection(columnsArray, converter.headers);
}
/**
* Parses a single CSV row string into columns, handling delimiters,
* quoted values, data type inference, and column range selection.
*/
parseCSVRow(columns, columnStr, rowNumber) {
const converter = this, dataTypes = converter.dataTypes, { startColumn, endColumn } = converter.options, itemDelimiter = (converter.options.itemDelimiter ||
converter.guessedItemDelimiter);
let { decimalPoint } = converter.options;
if (!decimalPoint || decimalPoint === itemDelimiter) {
decimalPoint = converter.guessedDecimalPoint || '.';
}
let i = 0, c = '', token = '', actualColumn = 0, column = 0;
const read = (j) => {
c = columnStr[j];
};
const pushType = (type) => {
if (dataTypes.length < column + 1) {
dataTypes.push([type]);
}
if (dataTypes[column][dataTypes[column].length - 1] !== type) {
dataTypes[column].push(type);
}
};
const push = () => {
if (startColumn > actualColumn || actualColumn > endColumn) {
// Skip this column, but increment the column count (#7272)
++actualColumn;
token = '';
return;
}
// Save the type of the token.
if (typeof token === 'string') {
const parsedNumber = parseFloat(token);
if (!isNaN(parsedNumber) && isFinite(Number(token))) {
token = parsedNumber;
pushType('number');
}
else if (!isNaN(Date.parse(token))) {
token = token.replace(/\//g, '-');
pushType('date');
}
else {
pushType('string');
}
}
else {
pushType('number');
}
if (columns.length < column + 1) {
columns.push([]);
}
// Try to apply the decimal point, and check if the token then is a
// number. If not, reapply the initial value
if (typeof token !== 'number' &&
Converters_DataConverterUtils.guessType(token, converter) !== 'number' &&
decimalPoint) {
const initialValue = token;
token = token.replace(decimalPoint, '.');
if (Converters_DataConverterUtils.guessType(token, converter) !== 'number') {
token = initialValue;
}
}
columns[column][rowNumber] = token;
token = '';
++column;
++actualColumn;
};
if (!columnStr.trim().length) {
return;
}
if (columnStr.trim()[0] === '#') {
return;
}
for (; i < columnStr.length; i++) {
read(i);
if (c === '#') {
// If there are hexvalues remaining (#13283)
if (!/^#[A-F\d]{3,3}|[A-F\d]{6,6}/i.test(columnStr.substring(i))) {
// The rest of the row is a comment
push();
return;
}
}
// Quoted string
if (c === '"') {
read(++i);
while (i < columnStr.length) {
if (c === '"') {
break;
}
token += c;
read(++i);
}
}
else if (c === itemDelimiter) {
push();
// Actual column data
}
else {
token += c;
}
}
push();
}
/**
* Internal method that guesses the delimiter from the first
* 13 lines of the CSV
* @param {string[]} lines
* The CSV, split into lines
*/
guessDelimiter(lines) {
let points = 0, commas = 0, guessed;
const potDelimiters = {
',': 0,
';': 0,
'\t': 0
}, linesCount = lines.length;
for (let i = 0; i < linesCount; i++) {
let inStr = false, c, cn, cl, token = '';
// We should be able to detect dateFormats within 13 rows
if (i > 13) {
break;
}
const columnStr = lines[i];
for (let j = 0; j < columnStr.length; j++) {
c = columnStr[j];
cn = columnStr[j + 1];
cl = columnStr[j - 1];
if (c === '#') {
// Skip the rest of the line - it's a comment
break;
}
if (c === '"') {
if (inStr) {
if (cl !== '"' && cn !== '"') {
while (cn === ' ' && j < columnStr.length) {
cn = columnStr[++j];
}
// After parsing a string, the next non-blank
// should be a delimiter if the CSV is properly
// formed.
if (typeof potDelimiters[cn] !== 'undefined') {
potDelimiters[cn]++;
}
inStr = false;
}
}
else {
inStr = true;
}
}
else if (typeof potDelimiters[c] !== 'undefined') {
token = token.trim();
if (!isNaN(Date.parse(token))) {
potDelimiters[c]++;
}
else if (isNaN(Number(token)) ||
!isFinite(Number(token))) {
potDelimiters[c]++;
}
token = '';
}
else {
token += c;
}
if (c === ',') {
commas++;
}
if (c === '.') {
points++;
}
}
}
// Count the potential delimiters.
// This could be improved by checking if the number of delimiters
// equals the number of columns - 1
if (potDelimiters[';'] > potDelimiters[',']) {
guessed = ';';
}
else if (potDelimiters[','] > potDelimiters[';']) {
guessed = ',';
}
else {
// No good guess could be made..
guessed = ',';
}
// Try to deduce the decimal point if it's not explicitly set.
// If both commas or points is > 0 there is likely an issue
if (points > commas) {
this.guessedDecimalPoint = '.';
}
else {
this.guessedDecimalPoint = ',';
}
return guessed;
}
}
/* *
*
* Static Properties
*
* */
/**
* Default options
*/
CSVConverter.defaultOptions = {
...Converters_DataConverter.defaultOptions,
lineDelimiter: '\n',
startColumn: 0,
endColumn: Number.MAX_VALUE,
startRow: 0,
endRow: Number.MAX_VALUE
};
Converters_DataConverter.registerType('CSV', CSVConverter);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Converters_CSVConverter = (CSVConverter);
;// ./code/dashboards/es-modules/Data/Modifiers/DataModifier.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
* - Dawid Draguła
*
* */
/* *
*
* Class
*
* */
/**
* Abstract class to provide an interface for modifying a table.
*/
class DataModifier {
/**
* Adds a modifier class to the registry. The modifier class has to provide
* the `DataModifier.options` property and the `DataModifier.modifyTable`
* method to modify the table.
*
* @private
*
* @param {string} key
* Registry key of the modifier class.
*
* @param {DataModifierType} DataModifierClass
* Modifier class (aka class constructor) to register.
*
* @return {boolean}
* Returns true, if the registration was successful. False is returned, if
* their is already a modifier registered with this key.
*/
static registerType(key, DataModifierClass) {
return (!!key &&
!DataModifier.types[key] &&
!!(DataModifier.types[key] = DataModifierClass));
}
/* *
*
* Functions
*
* */
/**
* Runs a timed execution of the modifier on the given datatable.
* Can be configured to run multiple times.
*
* @param {DataTable} dataTable
* The datatable to execute
*
* @param {BenchmarkOptions} options
* Options. Currently supports `iterations` for number of iterations.
*
* @return {Array<number>}
* An array of times in milliseconds
*
*/
benchmark(dataTable, options) {
const results = [];
const modifier = this;
const execute = () => {
modifier.modifyTable(dataTable);
modifier.emit({
type: 'afterBenchmarkIteration'
});
};
const defaultOptions = {
iterations: 1
};
const { iterations } = merge(defaultOptions, options);
modifier.on('afterBenchmarkIteration', () => {
if (results.length === iterations) {
modifier.emit({
type: 'afterBenchmark',
results
});
return;
}
// Run again
execute();
});
const times = {
startTime: 0,
endTime: 0
};
// Add timers
modifier.on('modify', () => {
times.startTime = window.performance.now();
});
modifier.on('afterModify', () => {
times.endTime = window.performance.now();
results.push(times.endTime - times.startTime);
});
// Initial run
execute();
return results;
}
/**
* Emits an event on the modifier to all registered callbacks of this event.
*
* @param {DataModifierEvent} [e]
* Event object containing additional event information.
*/
emit(e) {
fireEvent(this, e.type, e);
}
/**
* Modifies the given table and sets its `modified` property as a reference
* to the modified table. If `modified` property does not exist on the
* original table, it's always created.
*
* @param {Highcharts.DataTable} table
* Table to modify.
*
* @param {DataEventDetail} [eventDetail]
* Custom information for pending events.
*
* @return {Promise<Highcharts.DataTable>}
* Table with `modified` property as a reference.
*/
modify(table, eventDetail) {
const modifier = this;
return new Promise((resolve, reject) => {
if (!table.modified) {
table.modified = table.clone(false, eventDetail);
}
try {
resolve(modifier.modifyTable(table, eventDetail));
}
catch (e) {
modifier.emit({
type: 'error',
detail: eventDetail,
table
});
reject(e instanceof Error ? e : new Error('' + e));
}
});
}
/**
* Registers a callback for a specific modifier event.
*
* @param {string} type
* Event type as a string.
*
* @param {DataEventCallback} callback
* Function to register for an modifier callback.
*
* @return {Function}
* Function to unregister callback from the modifier event.
*/
on(type, callback) {
return addEvent(this, type, callback);
}
}
/* *
*
* Static Properties
*
* */
/**
* Registry as a record object with modifier names and their class
* constructor.
*/
DataModifier.types = {};
/* *
*
* Default Export
*
* */
/* harmony default export */ const Modifiers_DataModifier = (DataModifier);
;// ./code/dashboards/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/dashboards/es-modules/Core/Utilities.js
/* unused harmony import specifier */ var Utilities_isNumber;
/* unused harmony import specifier */ var Utilities_pick;
/* *
*
* (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
(Utilities_isNumber(indexOption) &&
indexOption < Utilities_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 = Utilities_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/dashboards/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#getColumn
*
* @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/dashboards/es-modules/Data/DataTable.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
* - Jomar Hønsi
* - Dawid Draguła
*
* */
const { splice: DataTable_splice, setLength: DataTable_setLength } = 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.
*
* @class
* @name Highcharts.DataTable
*
* @param {Highcharts.DataTableOptionsObject} [options]
* Options to initialize the new DataTable instance.
*/
class DataTable extends Data_DataTableCore {
/* *
*
* Constructor
*
* */
constructor(options = {}) {
super(options);
this.metadata = options.metadata;
}
/* *
*
* Functions
*
* */
/**
* Returns a clone of this table. The cloned table is completely independent
* of the original, and any changes made to the clone will not affect
* the original table.
*
* @function Highcharts.DataTable#clone
*
* @param {boolean} [skipColumns]
* Whether to clone columns or not.
*
* @param {Highcharts.DataTableEventDetail} [eventDetail]
* Custom information for pending events.
*
* @return {Highcharts.DataTable}
* Clone of this data table.
*
* @emits #cloneTable
* @emits #afterCloneTable
*/
clone(skipColumns, eventDetail) {
const table = this, tableOptions = {};
table.emit({ type: 'cloneTable', detail: eventDetail });
if (!skipColumns) {
tableOptions.columns = table.columns;
}
if (!table.autoId) {
tableOptions.id = table.id;
}
const tableClone = new DataTable(tableOptions);
if (!skipColumns) {
tableClone.versionTag = table.versionTag;
tableClone.originalRowIndexes = table.originalRowIndexes;
tableClone.localRowIndexes = table.localRowIndexes;
}
tableClone.metadata = { ...table.metadata };
table.emit({
type: 'afterCloneTable',
detail: eventDetail,
tableClone
});
return tableClone;
}
/**
* Deletes columns from the table.
*
* @function Highcharts.DataTable#deleteColumns
*
* @param {Array<string>} [columnIds]
* Names of columns to delete. If no array is provided, all
* columns will be deleted.
*
* @param {Highcharts.DataTableEventDetail} [eventDetail]
* Custom information for pending events.
*
* @return {Highcharts.DataTableColumnCollection|undefined}
* Returns the deleted columns, if found.
*
* @emits #deleteColumns
* @emits #afterDeleteColumns
*/
deleteColumns(columnIds, eventDetail) {
const table = this, columns = table.columns, deletedColumns = {}, modifiedColumns = {}, modifier = table.modifier, rowCount = table.rowCount;
columnIds = (columnIds || Object.keys(columns));
if (columnIds.length) {
table.emit({
type: 'deleteColumns',
columnIds,
detail: eventDetail
});
for (let i = 0, iEnd = columnIds.length, column, columnId; i < iEnd; ++i) {
columnId = columnIds[i];
column = columns[columnId];
if (column) {
deletedColumns[columnId] = column;
modifiedColumns[columnId] = new Array(rowCount);
}
delete columns[columnId];
}
if (!Object.keys(columns).length) {
table.rowCount = 0;
this.deleteRowIndexReferences();
}
if (modifier) {
modifier.modifyTable(table);
}
table.emit({
type: 'afterDeleteColumns',
columns: deletedColumns,
columnIds,
detail: eventDetail
});
return deletedColumns;
}
}
/**
* Deletes the row index references. This is useful when the original table
* is deleted, and the references are no longer needed. This table is
* then considered an original table or a table that has the same rows
* order as the original table.
*/
deleteRowIndexReferences() {
delete this.originalRowIndexes;
delete this.localRowIndexes;
}
/**
* Deletes rows in this table.
*
* @function Highcharts.DataTable#deleteRows
*
* @param {number | number[]} [rowIndex]
* Index of the row where deletion should start, or an array of indices for
* deleting multiple rows. If not specified, all rows will be deleted.
*
* @param {number} [rowCount]
* Number of rows to delete.
*
* @param {Highcharts.DataTableEventDetail} [eventDetail]
* Custom information for pending events.
*
* @return {Array<Highcharts.DataTableRow>}
* Returns the deleted rows, if found.
*
* @emits #deleteRows
* @emits #afterDeleteRows
*/
deleteRows(rowIndex, rowCount = 1, eventDetail) {
const { columns, modifier } = this;
const deletedRows = [];
let indices;
let actualRowCount;
if (!defined(rowIndex)) {
// No index provided - delete all rows.
indices = [0];
actualRowCount = this.rowCount;
}
else if (Array.isArray(rowIndex)) {
// Array of indices provided - delete the specified rows.
indices = rowIndex
// Remove negative indices, and indices beyond the row count,
// and remove duplicates.
.filter((index, i, arr) => (index >= 0 &&
index < this.rowCount &&
arr.indexOf(index) === i))
// Sort indices in descending order.
.sort((a, b) => b - a);
actualRowCount = indices.length;
}
else {
// Single index provided - delete the specified range of rows.
indices = [rowIndex];
actualRowCount = rowCount;
}
this.emit({
type: 'deleteRows',
detail: eventDetail,
rowCount: actualRowCount,
rowIndex: rowIndex ?? 0
});
if (actualRowCount > 0) {
const columnIds = Object.keys(columns);
for (let i = 0; i < columnIds.length; ++i) {
const columnId = columnIds[i];
const column = columns[columnId];
let deletedCells;
// Perform a range splice.
if (indices.length === 1 && actualRowCount > 1) {
const result = DataTable_splice(column, indices[0], actualRowCount);
deletedCells = result.removed;
columns[columnId] = result.array;
}
else {
// Perform a index splice for each index in the array.
deletedCells = [];
for (const index of indices) {
deletedCells.push(column[index]);
DataTable_splice(column, index, 1);
}
// Reverse the deleted cells to maintain the correct order.
deletedCells.reverse();
}
if (!i) {
this.rowCount = column.length;
}
for (let j = 0, jEnd = deletedCells.length; j < jEnd; ++j) {
deletedRows[j] = deletedRows[j] || [];
deletedRows[j][i] = deletedCells[j];
}
}
}
if (modifier) {
modifier.modifyTable(this);
}
this.emit({
type: 'afterDeleteRows',
detail: eventDetail,
rowCount: actualRowCount,
rowIndex: rowIndex ?? 0,
rows: deletedRows
});
return deletedRows;
}
/**
* Emits an event on this table to all registered callbacks of the given
* event.
* @private
*
* @param {Event} e
* Event object with event information.
*/
emit(e) {
if ([
'afterDeleteColumns',
'afterDeleteRows',
'afterSetCell',
'afterSetColumns',
'afterSetRows'
].includes(e.type)) {
this.versionTag = uniqueKey();
}
fireEvent(this, e.type, e);
}
/**
* Fetches a single cell value.
*
* @function Highcharts.DataTable#getCell
*
* @param {string} columnId
* Column name of the cell to retrieve.
*
* @param {number} rowIndex
* Row index of the cell to retrieve.
*
* @return {Highcharts.DataTableCellType|undefined}
* Returns the cell value or `undefined`.
*/
getCell(columnId, rowIndex) {
const table = this;
const column = table.columns[columnId];
if (column) {
return column[rowIndex];
}
}
/**
* Fetches the given column by the canonical column name.
* This function is a simplified wrap of {@link getColumns}.
*
* @function Highcharts.DataTable#getColumn
*
* @param {string} columnId
* Name of the column to get.
*
* @param {boolean} [asReference]
* Whether to return the column as a readonly reference.
*
* @return {Highcharts.DataTableColumn|undefined}
* A copy of the column, or `undefined` if not found.
*/
getColumn(columnId, asReference) {
return this.getColumns([columnId], asReference)[columnId];
}
/**
* Fetches all column IDs.
*
* @function Highcharts.DataTable#getColumnIds
*
* @return {Array<string>}
* Returns all column IDs.
*/
getColumnIds() {
return Object.keys(this.columns);
}
/**
* Retrieves all or the given columns.
*
* @function Highcharts.DataTable#getColumns
*
* @param {Array<string>} [columnIds]
* Column names to retrieve.
*
* @param {boolean} [asReference]
* Whether to return columns as a readonly reference.
*
* @param {boolean} [asBasicColumns]
* Whether to transform all typed array columns to normal arrays.
*
* @return {Highcharts.DataTableColumnCollection}
* Collection of columns. If a requested column was not found, it is
* `undefined`.
*/
getColumns(columnIds, asReference, asBasicColumns) {
const table = this, tableColumns = table.columns, columns = {};
columnIds = (columnIds || Object.keys(tableColumns));
for (let i = 0, iEnd = columnIds.length, column, columnId; i < iEnd; ++i) {
columnId = columnIds[i];
column = tableColumns[columnId];
if (column) {
if (asReference) {
columns[columnId] = column;
}
else if (asBasicColumns && !Array.isArray(column)) {
columns[columnId] = Array.from(column);
}
else {
columns[columnId] = column.slice();
}
}
}
return columns;
}
/**
* Takes the original row index and returns the local row index in the
* modified table for which this function is called.
*
* @param {number} originalRowIndex
* Original row index to get the local row index for.
*
* @return {number|undefined}
* Returns the local row index or `undefined` if not found.
*/
getLocalRowIndex(originalRowIndex) {
const { localRowIndexes } = this;
if (localRowIndexes) {
return localRowIndexes[originalRowIndex];
}
return originalRowIndex;
}
/**
* Returns the modifier associated with this table, if any.
*
* @return {Highcharts.DataModifier|undefined}
* Returns the modifier or `undefined`.
*
* @private
*/
getModifier() {
return this.modifier;
}
/**
* Takes the local row index and returns the index of the corresponding row
* in the original table.
*
* @param {number} rowIndex
* Local row index to get the original row index for.
*
* @return {number|undefined}
* Returns the original row index or `undefined` if not found.
*/
getOriginalRowIndex(rowIndex) {
const { originalRowIndexes } = this;
if (originalRowIndexes) {
return originalRowIndexes[rowIndex];
}
return rowIndex;
}
/**
* Retrieves the row at a given index. This function is a simplified wrap of
* {@link getRows}.
*
* @function Highcharts.DataTable#getRow
*
* @param {number} rowIndex
* Row index to retrieve. First row has index 0.
*
* @param {Array<string>} [columnIds]
* Column names in order to retrieve.
*
* @return {Highcharts.DataTableRow}
* Returns the row values, or `undefined` if not found.
*/
getRow(rowIndex, columnIds) {
return this.getRows(rowIndex, 1, columnIds)[0];
}
/**
* Returns the number of rows in this table.
*
* @function Highcharts.DataTable#getRowCount
*
* @return {number}
* Number of rows in this table.
*/
getRowCount() {
// @todo Implement via property getter `.length` browsers supported
return this.rowCount;
}
/**
* Retrieves the index of the first row matching a specific cell value.
*
* @function Highcharts.DataTable#getRowIndexBy
*
* @param {string} columnId
* Column to search in.
*
* @param {Highcharts.DataTableCellType} cellValue
* Cell value to search for. `NaN` and `undefined` are not supported.
*
* @param {number} [rowIndexOffset]
* Index offset to start searching.
*
* @return {number|undefined}
* Index of the first row matching the cell value.
*/
getRowIndexBy(columnId, cellValue, rowIndexOffset) {
const table = this;
const column = table.columns[columnId];
if (column) {
let rowIndex = -1;
if (Array.isArray(column)) {
// Normal array
rowIndex = column.indexOf(cellValue, rowIndexOffset);
}
else if (isNumber(cellValue)) {
// Typed array
rowIndex = column.indexOf(cellValue, rowIndexOffset);
}
if (rowIndex !== -1) {
return rowIndex;
}
}
}
/**
* Retrieves the row at a given index. This function is a simplified wrap of
* {@link getRowObjects}.
*
* @function Highcharts.DataTable#getRowObject
*
* @param {number} rowIndex
* Row index.
*
* @param {Array<string>} [columnIds]
* Column names and their order to retrieve.
*
* @return {Highcharts.DataTableRowObject}
* Returns the row values, or `undefined` if not found.
*/
getRowObject(rowIndex, columnIds) {
return this.getRowObjects(rowIndex, 1, columnIds)[0];
}
/**
* Fetches all or a number of rows as an object.
*
* @function Highcharts.DataTable#getRowObjects
*
* @param {number} [rowIndex]
* Index of the first row to fetch. Defaults to first row at index `0`.
*
* @param {number} [rowCount]
* Number of rows to fetch. Defaults to maximal number of rows.
*
* @param {Array<string>} [columnIds]
* Column names and their order to retrieve.
*
* @return {Highcharts.DataTableRowObject}
* Returns retrieved rows.
*/
getRowObjects(rowIndex = 0, rowCount = (this.rowCount - rowIndex), columnIds) {
const table = this, columns = table.columns, rows = new Array(rowCount);
columnIds = (columnIds || Object.keys(columns));
for (let i = rowIndex, i2 = 0, iEnd = Math.min(table.rowCount, (rowIndex + rowCount)), column, row; i < iEnd; ++i, ++i2) {
row = rows[i2] = {};
for (const columnId of columnIds) {
column = columns[columnId];
row[columnId] = (column ? column[i] : void 0);
}
}
return rows;
}
/**
* Fetches all or a number of rows as an array.
*
* @function Highcharts.DataTable#getRows
*
* @param {number} [rowIndex]
* Index of the first row to fetch. Defaults to first row at index `0`.
*
* @param {number} [rowCount]
* Number of rows to fetch. Defaults to maximal number of rows.
*
* @param {Array<string>} [columnIds]
* Column names and their order to retrieve.
*
* @return {Highcharts.DataTableRow}
* Returns retrieved rows.
*/
getRows(rowIndex = 0, rowCount = (this.rowCount - rowIndex), columnIds) {
const table = this, columns = table.columns, rows = new Array(rowCount);
columnIds = (columnIds || Object.keys(columns));
for (let i = rowIndex, i2 = 0, iEnd = Math.min(table.rowCount, (rowIndex + rowCount)), column, row; i < iEnd; ++i, ++i2) {
row = rows[i2] = [];
for (const columnId of columnIds) {
column = columns[columnId];
row.push(column ? column[i] : void 0);
}
}
return rows;
}
/**
* Returns the unique version tag of the current state of the table.
*
* @function Highcharts.DataTable#getVersionTag
*
* @return {string}
* Unique version tag.
*/
getVersionTag() {
return this.versionTag;
}
/**
* Determines whether all specified column names exist in the table.
*
* @function Highcharts.DataTable#hasColumns
*
* @param {Array<string>} columnIds
* Column names to check.
*
* @return {boolean}
* Returns `true` if all columns have been found, otherwise `false`.
*/
hasColumns(columnIds) {
const table = this, columns = table.columns;
for (let i = 0, iEnd = columnIds.length, columnId; i < iEnd; ++i) {
columnId = columnIds[i];
if (!columns[columnId]) {
return false;
}
}
return true;
}
/**
* Checks if any row in the specified column contains the given cell value.
*
* @function Highcharts.DataTable#hasRowWith
*
* @param {string} columnId
* Column to search in.
*
* @param {Highcharts.DataTableCellType} cellValue
* Cell value to search for. `NaN` and `undefined` are not supported.
*
* @return {boolean}
* True, if a row has been found, otherwise false.
*/
hasRowWith(columnId, cellValue) {
const table = this;
const column = table.columns[columnId];
// Normal array
if (Array.isArray(column)) {
return (column.indexOf(cellValue) !== -1);
}
// Typed array
if (defined(cellValue) && Number.isFinite(cellValue)) {
return (column.indexOf(+cellValue) !== -1);
}
return false;
}
/**
* Registers a callback function to be executed when a specific event is
* emitted. To stop listening to the event, call the function returned by
* this method.
*
* @function Highcharts.DataTable#on
*
* @param {string} type
* Event type as a string.
*
* @param {Highcharts.EventCallbackFunction<Highcharts.DataTable>} callback
* Function to register for an event callback.
*
* @return {Function}
* Function to unregister callback from the event.
*/
on(type, callback) {
return addEvent(this, type, callback);
}
/**
* Changes the ID of an existing column to a new ID, effectively renaming
* the column.
*
* @function Highcharts.DataTable#changeColumnId
*
* @param {string} columnId
* Id of the column to be changed.
*
* @param {string} newColumnId
* New id of the column.
*
* @return {boolean}
* Returns `true` if successful, `false` if the column was not found.
*/
changeColumnId(columnId, newColumnId) {
const table = this, columns = table.columns;
if (columns[columnId]) {
if (columnId !== newColumnId) {
columns[newColumnId] = columns[columnId];
delete columns[columnId];
}
return true;
}
return false;
}
/**
* Sets the value of a specific cell identified by column ID and row index.
* If the column does not exist, it will be created. If the row index is
* beyond the current row count, the table will be expanded to accommodate
* the new cell.
*
* @function Highcharts.DataTable#setCell
*
* @param {string} columnId
* Column name to set.
*
* @param {number|undefined} rowIndex
* Row index to set.
*
* @param {Highcharts.DataTableCellType} cellValue
* Cell value to set.
*
* @param {Highcharts.DataTableEventDetail} [eventDetail]
* Custom information for pending events.
*
* @emits #setCell
* @emits #afterSetCell
*/
setCell(columnId, rowIndex, cellValue, eventDetail) {
const table = this, columns = table.columns, modifier = table.modifier;
let column = columns[columnId];
if (column && column[rowIndex] === cellValue) {
return;
}
table.emit({
type: 'setCell',
cellValue,
columnId: columnId,
detail: eventDetail,
rowIndex
});
if (!column) {
column = columns[columnId] = new Array(table.rowCount);
}
if (rowIndex >= table.rowCount) {
table.rowCount = (rowIndex + 1);
}
column[rowIndex] = cellValue;
if (modifier) {
modifier.modifyTable(table);
}
table.emit({
type: 'afterSetCell',
cellValue,
columnId: columnId,
detail: eventDetail,
rowIndex
});
}
/**
* Replaces or updates multiple columns in the table with new data. If a
* column does not exist, it will be created and added to the table.
*
* @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. Keep undefined to reset.
*
* @param {Highcharts.DataTableEventDetail} [eventDetail]
* Custom information for pending events.
*
* @param {boolean} [typeAsOriginal=false]
* Determines whether the original column retains its type when data
* replaced. If `true`, the original column keeps its type. If not
* (default), the original column will adopt the type of the replacement
* column.
*
* @emits #setColumns
* @emits #afterSetColumns
*/
setColumns(columns, rowIndex, eventDetail, typeAsOriginal) {
const table = this, tableColumns = table.columns, tableModifier = table.modifier, columnIds = Object.keys(columns);
let rowCount = table.rowCount;
table.emit({
type: 'setColumns',
columns,
columnIds,
detail: eventDetail,
rowIndex
});
if (!defined(rowIndex) && !typeAsOriginal) {
super.setColumns(columns, rowIndex, extend(eventDetail, { silent: true }));
}
else {
for (let i = 0, iEnd = columnIds.length, column, tableColumn, columnId, ArrayConstructor; i < iEnd; ++i) {
columnId = columnIds[i];
column = columns[columnId];
tableColumn = tableColumns[columnId];
ArrayConstructor = Object.getPrototypeOf((tableColumn && typeAsOriginal) ? tableColumn : column).constructor;
if (!tableColumn) {
tableColumn = new ArrayConstructor(rowCount);
}
else if (ArrayConstructor === Array) {
if (!Array.isArray(tableColumn)) {
tableColumn = Array.from(tableColumn);
}
}
else if (tableColumn.length < rowCount) {
tableColumn =
new ArrayConstructor(rowCount);
tableColumn.set(tableColumns[columnId]);
}
tableColumns[columnId] = tableColumn;
for (let i = (rowIndex || 0), iEnd = column.length; i < iEnd; ++i) {
tableColumn[i] = column[i];
}
rowCount = Math.max(rowCount, column.length);
}
this.applyRowCount(rowCount);
}
if (tableModifier) {
tableModifier.modifyTable(table);
}
table.emit({
type: 'afterSetColumns',
columns,
columnIds,
detail: eventDetail,
rowIndex
});
}
/**
* Assigns a new data modifier to the table.
*
* This method does not modify the table directly. Instead, it sets the
* `.modified` property of the table with a modified copy of this table,
* as produced by the modifier.
*
* @param {Highcharts.DataModifier} [modifier]
* Modifier to set, or `undefined` to unset.
*
* @param {Highcharts.DataTableEventDetail} [eventDetail]
* Custom information for pending events.
*
* @return {Promise<Highcharts.DataTable>}
* Resolves to this table if successful, or rejects on failure.
*
* @emits #setModifier
* @emits #afterSetModifier
*/
setModifier(modifier, eventDetail) {
const table = this;
let promise;
table.emit({
type: 'setModifier',
detail: eventDetail,
modifier,
modified: table.getModified()
});
table.modifier = modifier;
if (modifier) {
promise = modifier.modify(table);
}
else {
promise = Promise.resolve(table);
}
return promise
.then((table) => {
table.emit({
type: 'afterSetModifier',
detail: eventDetail,
modifier,
modified: table.getModified()
});
return table;
})['catch']((error) => {
table.emit({
type: 'setModifierError',
error,
modifier,
modified: table.getModified()
});
throw error;
});
}
/**
* Sets the original row indexes for the table. It is used to keep the
* reference to the original rows when modifying the table.
*
* @param {Array<number|undefined>} originalRowIndexes
* Original row indexes array.
*
* @param {boolean} omitLocalRowIndexes
* Whether to omit the local row indexes calculation. Defaults to `false`.
*/
setOriginalRowIndexes(originalRowIndexes, omitLocalRowIndexes = false) {
this.originalRowIndexes = originalRowIndexes;
if (omitLocalRowIndexes) {
return;
}
const modifiedIndexes = this.localRowIndexes = [];
for (let i = 0, iEnd = originalRowIndexes.length, originalIndex; i < iEnd; ++i) {
originalIndex = originalRowIndexes[i];
if (defined(originalIndex)) {
modifiedIndexes[originalIndex] = i;
}
}
}
/**
* 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.
*
* Note: This function is just a simplified wrap of
* {@link Highcharts.DataTable#setRows}.
*
* @function Highcharts.DataTable#setRow
*
* @param {Highcharts.DataTableRow|Highcharts.DataTableRowObject} 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 {Highcharts.DataTableEventDetail} [eventDetail]
* Custom information for pending events.
*
* @emits #setRows
* @emits #afterSetRows
*/
setRow(row, rowIndex, insert, eventDetail) {
this.setRows([row], rowIndex, insert, eventDetail);
}
/**
* Sets cell values for multiple rows. Will insert new rows, if no index was
* was provided, or if the index is higher than the total number of table
* rows.
*
* @function Highcharts.DataTable#setRows
*
* @param {Array<(Highcharts.DataTableRow|Highcharts.DataTableRowObject)>} rows
* Row values to set.
*
* @param {number} [rowIndex]
* Index of the first row to set. Leave `undefined` to add as new rows.
*
* @param {boolean} [insert]
* Whether to insert the row at the given index, or to overwrite the row.
*
* @param {Highcharts.DataTableEventDetail} [eventDetail]
* Custom information for pending events.
*
* @emits #setRows
* @emits #afterSetRows
*/
setRows(rows, rowIndex = this.rowCount, insert, eventDetail) {
const table = this, columns = table.columns, columnIds = Object.keys(columns), modifier = table.modifier, rowCount = rows.length;
table.emit({
type: 'setRows',
detail: eventDetail,
rowCount,
rowIndex,
rows
});
for (let i = 0, i2 = rowIndex, row; i < rowCount; ++i, ++i2) {
row = rows[i];
if (Object.keys(row).length === 0) { // Is empty Object
for (let j = 0, jEnd = columnIds.length; j < jEnd; ++j) {
const column = columns[columnIds[j]];
if (insert) {
columns[columnIds[j]] = DataTable_splice(column, i2, 0, true, [null]).array;
}
else {
column[i2] = null;
}
}
}
else if (Array.isArray(row)) {
for (let j = 0, jEnd = columnIds.length; j < jEnd; ++j) {
columns[columnIds[j]][i2] = row[j];
}
}
else {
super.setRow(row, i2, insert, { silent: true });
}
}
const indexRowCount = insert ?
rowCount + rows.length :
rowIndex + rowCount;
if (indexRowCount > table.rowCount) {
table.rowCount = indexRowCount;
for (let i = 0, iEnd = columnIds.length; i < iEnd; ++i) {
const columnId = columnIds[i];
columns[columnId] = DataTable_setLength(columns[columnId], indexRowCount);
}
}
if (modifier) {
modifier.modifyTable(table);
}
table.emit({
type: 'afterSetRows',
detail: eventDetail,
rowCount,
rowIndex,
rows
});
}
}
/* *
*
* Default Export
*
* */
/* harmony default export */ const Data_DataTable = (DataTable);
;// ./code/dashboards/es-modules/Data/Connectors/DataConnector.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
* - Wojciech Chmiel
* - Gøran Slettemark
* - Dawid Draguła
* - Kamil Kubik
*
* */
/* *
*
* Class
*
* */
/**
* Abstract class providing an interface for managing a DataConnector.
*/
class DataConnector {
/**
* Adds a connector class to the registry. The connector has to provide the
* `DataConnector.options` property and the `DataConnector.load` method to
* modify the table.
*
* @private
*
* @param {string} key
* Registry key of the connector class.
*
* @param {DataConnectorType} DataConnectorClass
* Connector class (aka class constructor) to register.
*
* @return {boolean}
* Returns true, if the registration was successful. False is returned, if
* their is already a connector registered with this key.
*/
static registerType(key, DataConnectorClass) {
return (!!key &&
!DataConnector.types[key] &&
!!(DataConnector.types[key] = DataConnectorClass));
}
/**
* Whether the connector is currently polling for new data.
*/
get polling() {
return !!this._polling;
}
/* *
*
* Constructor
*
* */
/**
* Constructor for the connector class.
*
* @param {DataConnectorOptions} [options]
* Options to use in the connector.
*/
constructor(options) {
/**
* Tables managed by this DataConnector instance.
*/
this.dataTables = {};
/**
* Helper flag for detecting whether the data connector is loaded.
* @internal
*/
this.loaded = false;
this.metadata = options.metadata || { columns: {} };
this.options = options;
// Create a data table for each defined in the dataTables user options.
const dataTables = options?.dataTables;
let dataTableIndex = 0;
if (options.options) {
// eslint-disable-next-line no-console
console.error('The `DataConnectorOptions.options` property was removed in Dashboards v4.0.0. Check how to upgrade your connector to use the new options structure here: https://api.highcharts.com/dashboards/#interfaces/Data_DataTableOptions.DataTableOptions');
}
if (dataTables && dataTables?.length > 0) {
for (let i = 0, iEnd = dataTables.length; i < iEnd; ++i) {
const dataTable = dataTables[i];
const key = dataTable?.key;
this.dataTables[key ?? dataTableIndex] =
new Data_DataTable(dataTable);
if (!key) {
dataTableIndex++;
}
}
}
else {
// If user options dataTables is not defined, generate a default
// table.
this.dataTables[0] = new Data_DataTable({
id: options.id // Required by DataTableCore
});
}
}
/* *
*
* Methods
*
* */
/**
* Returns a single data table instance based on the provided key.
* Otherwise, returns the first data table.
*
* @param {string} [key]
* The data table key.
*
* @return {DataTable}
* The data table instance.
*/
getTable(key) {
if (key) {
return this.dataTables[key];
}
return Object.values(this.dataTables)[0];
}
/**
* Method for adding metadata for a single column.
*
* @param {string} name
* The name of the column to be described.
*
* @param {MetaColumn} columnMeta
* The metadata to apply to the column.
*/
describeColumn(name, columnMeta) {
const connector = this;
const columns = connector.metadata.columns;
columns[name] = merge(columns[name] || {}, columnMeta);
}
/**
* Method for applying columns meta information to the whole DataConnector.
*
* @param {Record<string, MetaColumn>} columns
* Pairs of column names and MetaColumn objects.
*/
describeColumns(columns) {
const connector = this;
const columnIds = Object.keys(columns);
let columnId;
while (typeof (columnId = columnIds.pop()) === 'string') {
connector.describeColumn(columnId, columns[columnId]);
}
}
/**
* Returns the order of columns.
*
* @return {string[] | undefined}
* Order of columns.
*/
getColumnOrder() {
const connector = this, columns = connector.metadata.columns, names = Object.keys(columns || {});
if (names.length) {
return names.sort((a, b) => (pick(columns[a].index, 0) - pick(columns[b].index, 0)));
}
}
/**
* Retrieves the columns of the dataTable,
* applies column order from meta.
*
* @return {Highcharts.DataTableColumnCollection}
* An object with the properties `columnIds` and `columnValues`
*/
getSortedColumns() {
return this.getTable().getColumns(this.getColumnOrder());
}
/**
* Sets the index and order of columns.
*
* @param {Array<string>} columnIds
* Order of columns.
*/
setColumnOrder(columnIds) {
const connector = this;
for (let i = 0, iEnd = columnIds.length; i < iEnd; ++i) {
connector.describeColumn(columnIds[i], { index: i });
}
}
/**
* Updates the connector with new options.
*
* @param {object} newOptions
* The new options to be applied to the connector.
*
* @param {boolean} [reload=true]
* Whether to reload the connector after applying the new options.
*/
async update(newOptions, reload = true) {
this.emit({ type: 'beforeUpdate' });
merge(true, this.options, newOptions);
const { options } = this;
if ('enablePolling' in newOptions || 'dataRefreshRate' in newOptions) {
if ('enablePolling' in options && options.enablePolling) {
this.stopPolling();
this.startPolling(('dataRefreshRate' in options &&
typeof options.dataRefreshRate === 'number') ? Math.max(options.dataRefreshRate, 1) * 1000 : 1000);
}
else {
this.stopPolling();
}
}
if (reload) {
await this.load();
}
this.emit({ type: 'afterUpdate' });
}
/**
* The default load method, which fires the `afterLoad` event
*
* @return {Promise<DataConnector>}
* The loaded connector.
*
* @emits DataConnector#afterLoad
*/
load() {
this.emit({ type: 'afterLoad' });
return Promise.resolve(this);
}
/**
* Applies the data modifiers to the data tables according to the
* connector data tables options.
*/
async applyTableModifiers() {
const tableOptionsArray = this.options?.dataTables;
for (const [key, table] of Object.entries(this.dataTables)) {
// Take data modifier options from the corresponding data table
// options, otherwise take the data modifier options from the
// connector options.
const dataModifierOptions = tableOptionsArray?.find((dataTable) => dataTable.key === key)?.dataModifier ?? this.options?.dataModifier;
if (!dataModifierOptions) {
continue;
}
const ModifierClass = (dataModifierOptions &&
Modifiers_DataModifier.types[dataModifierOptions.type]);
await table.setModifier(ModifierClass ?
new ModifierClass(dataModifierOptions) :
void 0);
}
return this;
}
/**
* Starts polling new data after the specific time span in milliseconds.
*
* @param {number} refreshTime
* Refresh time in milliseconds between polls.
*/
startPolling(refreshTime = 1000) {
const connector = this;
// Assign a new abort controller.
this.pollingController = new AbortController();
// Clear the polling timeout.
window.clearTimeout(connector._polling);
connector._polling = window.setTimeout(
// eslint-disable-next-line @typescript-eslint/no-misused-promises
() => connector
.load()['catch']((error) => connector.emit({
type: 'loadError',
error
}))
.then(() => {
if (connector._polling) {
connector.startPolling(refreshTime);
}
}), refreshTime);
}
/**
* Stops polling data. Shouldn't be performed if polling is already stopped.
*/
stopPolling() {
const connector = this;
if (!connector.polling) {
return;
}
// Abort the existing request.
connector?.pollingController?.abort();
// Clear the polling timeout.
window.clearTimeout(connector._polling);
delete connector._polling;
}
/**
* Emits an event on the connector to all registered callbacks of this
* event.
*
* @param {Event} e
* Event object containing additional event information.
*/
emit(e) {
fireEvent(this, e.type, e);
}
/**
* Registers a callback for a specific connector event.
*
* @param {string} type
* Event type.
*
* @param {Function} callback
* Function to register for the connector callback.
*
* @return {Function}
* Function to unregister callback from the connector event.
*/
on(type, callback) {
return addEvent(this, type, callback);
}
/**
* Iterates over the dataTables and initiates the corresponding converters.
* Updates the dataTables and assigns the first converter.
*
* @param {T}[data]
* Data specific to the corresponding converter.
*
* @param {CreateConverterFunction}[createConverter]
* Creates a specific converter combining the dataTable options.
*
* @param {ParseDataFunction<T>}[parseData]
* Runs the converter parse method with the specific data type.
*/
initConverters(data, createConverter, parseData) {
let index = 0;
for (const [key, table] of Object.entries(this.dataTables)) {
// Create a proper converter and parse its data.
const converter = createConverter(key);
const columns = parseData(converter, data);
// Update the dataTable.
table.deleteColumns();
table.setColumns(columns);
// Assign the first converter.
if (index === 0) {
this.converter = converter;
}
index++;
}
}
}
/* *
*
* Static Properties
*
* */
/**
* Registry as a record object with connector names and their class.
*/
DataConnector.types = {};
/* *
*
* Default Export
*
* */
/* harmony default export */ const Connectors_DataConnector = (DataConnector);
;// ./code/dashboards/es-modules/Data/Connectors/CSVConnector.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:
* - Torstein Hønsi
* - Christer Vasseng
* - Gøran Slettemark
* - Sophie Bremer
* - Kamil Kubik
*
* */
/* *
*
* Class
*
* */
/**
* Class that handles creating a DataConnector from CSV
*
* @private
*/
class CSVConnector extends Connectors_DataConnector {
/* *
*
* Constructor
*
* */
/**
* Constructs an instance of CSVConnector.
*
* @param {Partial<CSVConnectorOptions>} [options]
* Options for the connector and converter.
*/
constructor(options) {
const mergedOptions = merge(CSVConnector.defaultOptions, options);
super(mergedOptions);
this.options = mergedOptions;
if (mergedOptions.enablePolling) {
this.startPolling(Math.max(mergedOptions.dataRefreshRate || 0, 1) * 1000);
}
}
/* *
*
* Functions
*
* */
/**
* Overrides the DataConnector method. Emits an event on the connector to
* all registered callbacks of this event.
*
* @param {Event} e
* Event object containing additional event information.
*/
emit(e) {
fireEvent(this, e.type, e);
}
/**
* Initiates the loading of the CSV source to the connector
*
* @param {DataEventDetail} [eventDetail]
* Custom information for pending events.
*
* @emits CSVConnector#load
* @emits CSVConnector#afterLoad
*/
load(eventDetail) {
const connector = this;
const options = connector.options;
const { csv, csvURL, dataTables, decimalPoint } = options;
connector.emit({
type: 'load',
csv
});
return Promise
.resolve(csvURL ?
fetch(csvURL, {
signal: connector?.pollingController?.signal
}).then((response) => response.text()) :
csv || '')
.then((csv) => {
if (csv) {
this.initConverters(csv, (key) => {
const tableOptions = dataTables?.find((dataTable) => dataTable.key === key);
// The data table options takes precedence over the
// connector options.
const { firstRowAsNames = options.firstRowAsNames, beforeParse = options.beforeParse } = tableOptions || {};
const converterOptions = {
decimalPoint,
firstRowAsNames,
beforeParse
};
return new Converters_CSVConverter(merge(options, converterOptions));
}, (converter, data) => converter.parse({ csv: data }));
}
return connector.applyTableModifiers().then(() => csv);
})
.then((csv) => {
connector.emit({
type: 'afterLoad',
detail: eventDetail,
csv
});
return connector;
})['catch']((error) => {
connector.emit({
type: 'loadError',
detail: eventDetail,
error
});
throw error;
});
}
}
/* *
*
* Static Properties
*
* */
CSVConnector.defaultOptions = {
id: 'csv-connector',
type: 'CSV',
csv: '',
csvURL: '',
enablePolling: false,
dataRefreshRate: 1,
firstRowAsNames: true
};
/* *
*
* Registry
*
* */
Connectors_DataConnector.registerType('CSV', CSVConnector);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Connectors_CSVConnector = (CSVConnector);
;// ./code/dashboards/es-modules/Data/Converters/GoogleSheetsConverter.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:
* - Torstein Hønsi
* - Gøran Slettemark
* - Wojciech Chmiel
* - Sophie Bremer
* - Kamil Kubik
*
* */
/* *
*
* Class
*
* */
/**
* Handles parsing and transformation of an Google Sheets to a table.
*
* @private
*/
class GoogleSheetsConverter extends Converters_DataConverter {
/* *
*
* Constructor
*
* */
/**
* Constructs an instance of the GoogleSheetsConverter.
*
* @param {Partial<GoogleSheetsConverterOptions>} [options]
* Options for the GoogleSheetsConverter.
*/
constructor(options) {
const mergedOptions = merge(GoogleSheetsConverter.defaultOptions, options);
super(mergedOptions);
this.header = [];
this.options = mergedOptions;
}
/* *
*
* Functions
*
* */
/**
* Initiates the parsing of the Google Sheet
*
* @param {Partial<GoogleSheetsConverterOptions>}[options]
* Options for the parser
*
* @param {DataEventDetail} [eventDetail]
* Custom information for pending events.
*
* @emits GoogleSheetsParser#parse
* @emits GoogleSheetsParser#afterParse
*/
parse(options, eventDetail) {
const converter = this, parseOptions = merge(converter.options, options);
let columnsArray = ((parseOptions.json?.values) || []).map((column) => column.slice());
if (columnsArray.length === 0) {
return {};
}
converter.header = [];
converter.emit({
type: 'parse',
columns: [],
detail: eventDetail,
headers: converter.header
});
// If beforeParse is defined, use it to modify the data
const { beforeParse, json } = parseOptions;
if (beforeParse && json) {
columnsArray = beforeParse(json.values);
}
let column;
for (let i = 0, iEnd = columnsArray.length; i < iEnd; i++) {
column = columnsArray[i];
converter.header[i] = (parseOptions.firstRowAsNames ?
`${column.shift()}` :
uniqueKey());
for (let j = 0, jEnd = column.length; j < jEnd; ++j) {
let cellValue = column[j];
if (isDateObject(cellValue)) {
cellValue = cellValue.getTime();
}
columnsArray[i][j] = cellValue;
}
}
converter.emit({
type: 'afterParse',
columns: columnsArray,
detail: eventDetail,
headers: converter.header
});
return Converters_DataConverterUtils.getColumnsCollection(columnsArray, converter.header);
}
}
/* *
*
* Static Properties
*
* */
/**
* Default options
*/
GoogleSheetsConverter.defaultOptions = {
...Converters_DataConverter.defaultOptions
};
Converters_DataConverter.registerType('GoogleSheets', GoogleSheetsConverter);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Converters_GoogleSheetsConverter = (GoogleSheetsConverter);
/**
* Check if a value is a Date object
*
* @param {unknown} value to verify
* @return {boolean}
* True if the value is a Date object, false otherwise.
*/
function isDateObject(value) {
return Object.prototype.toString.call(value) === '[object Date]';
}
;// ./code/dashboards/es-modules/Data/Connectors/GoogleSheetsConnector.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:
* - Torstein Hønsi
* - Gøran Slettemark
* - Wojciech Chmiel
* - Sophie Bremer
* - Jomar Hønsi
* - Kamil Kubik
*
* */
/* *
*
* Functions
*
* */
/**
* Tests Google's response for error.
* @private
*/
function isGoogleError(json) {
return (typeof json === 'object' && json &&
typeof json.error === 'object' && json.error &&
typeof json.error.code === 'number' &&
typeof json.error.message === 'string' &&
typeof json.error.status === 'string');
}
/* *
*
* Class
*
* */
/**
* @private
* @todo implement save, requires oauth2
*/
class GoogleSheetsConnector extends Connectors_DataConnector {
/* *
*
* Constructor
*
* */
/**
* Constructs an instance of GoogleSheetsConnector
*
* @param {Partial<GoogleSheetsConnectorOptions>} [options]
* Options for the connector and converter.
*/
constructor(options) {
const mergedOptions = merge(GoogleSheetsConnector.defaultOptions, options);
super(mergedOptions);
this.options = mergedOptions;
}
/* *
*
* Functions
*
* */
/**
* Overrides the DataConnector method. Emits an event on the connector to
* all registered callbacks of this event.
*
* @param {Event} e
* Event object containing additional event information.
*/
emit(e) {
fireEvent(this, e.type, e);
}
/**
* Loads data from a Google Spreadsheet.
*
* @param {DataEventDetail} [eventDetail]
* Custom information for pending events.
*
* @return {Promise<this>}
* Same connector instance with modified table.
*/
load(eventDetail) {
const connector = this;
const options = connector.options;
const { dataRefreshRate, enablePolling, googleAPIKey, googleSpreadsheetKey, dataTables } = options;
const url = buildFetchURL(googleAPIKey, googleSpreadsheetKey, options);
connector.emit({
type: 'load',
detail: eventDetail,
url
});
if (!URL.canParse(url)) {
throw new Error('Invalid URL: ' + url);
}
return fetch(url, { signal: connector?.pollingController?.signal })
.then((response) => (response.json()))
.then((json) => {
if (isGoogleError(json)) {
throw new Error(json.error.message);
}
this.initConverters(json, (key) => {
const tableOptions = dataTables?.find((dataTable) => dataTable.key === key);
// The data table options takes precedence over the
// connector options.
const { firstRowAsNames = options.firstRowAsNames, beforeParse = options.beforeParse } = tableOptions || {};
const converterOptions = {
firstRowAsNames,
beforeParse
};
return new Converters_GoogleSheetsConverter(converterOptions);
}, (converter, data) => converter.parse({ json: data }));
return connector.applyTableModifiers();
})
.then(() => {
connector.emit({
type: 'afterLoad',
detail: eventDetail,
url
});
// Polling
if (enablePolling) {
setTimeout(() => connector.load(), Math.max(dataRefreshRate || 0, 1) * 1000);
}
return connector;
})['catch']((error) => {
connector.emit({
type: 'loadError',
detail: eventDetail,
error
});
throw error;
});
}
}
/* *
*
* Static Properties
*
* */
GoogleSheetsConnector.defaultOptions = {
id: 'google-sheets-connector',
type: 'GoogleSheets',
googleAPIKey: '',
googleSpreadsheetKey: '',
enablePolling: false,
dataRefreshRate: 2,
firstRowAsNames: true
};
/* *
*
* Constants
*
* */
const alphabet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
/* *
*
* Functions
*
* */
/**
* Creates GoogleSheets API v4 URL.
* @private
*/
function buildFetchURL(apiKey, sheetKey, options = {}) {
const url = new URL(`https://sheets.googleapis.com/v4/spreadsheets/${sheetKey}/values/`);
const range = options.onlyColumnIds ?
'A1:Z1' : buildQueryRange(options);
url.pathname += range;
const searchParams = url.searchParams;
searchParams.set('alt', 'json');
if (!options.onlyColumnIds) {
searchParams.set('dateTimeRenderOption', 'FORMATTED_STRING');
searchParams.set('majorDimension', 'COLUMNS');
searchParams.set('valueRenderOption', 'UNFORMATTED_VALUE');
}
searchParams.set('prettyPrint', 'false');
searchParams.set('key', apiKey);
return url.href;
}
/**
* Creates sheets range.
* @private
*/
function buildQueryRange(options = {}) {
const { endColumn, endRow, googleSpreadsheetRange, startColumn, startRow } = options;
return googleSpreadsheetRange || ((alphabet[startColumn || 0] || 'A') +
(Math.max((startRow || 0), 0) + 1) +
':' +
(alphabet[pick(endColumn, 25)] || 'Z') +
(endRow ?
Math.max(endRow, 0) :
'Z'));
}
/* *
*
* Registry
*
* */
Connectors_DataConnector.registerType('GoogleSheets', GoogleSheetsConnector);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Connectors_GoogleSheetsConnector = (GoogleSheetsConnector);
;// ./code/dashboards/es-modules/Data/Converters/HTMLTableConverter.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:
* - Torstein Hønsi
* - Gøran Slettemark
* - Wojciech Chmiel
* - Sophie Bremer
* - Kamil Kubik
*
* */
/* *
*
* Functions
*
* */
/**
* Row equal
*/
function isRowEqual(row1, row2) {
let i = row1.length;
if (row2.length === i) {
while (--i) {
if (row1[i] !== row2[i]) {
return false;
}
}
}
else {
return false;
}
return true;
}
/* *
*
* Class
*
* */
/**
* Handles parsing and transformation of an HTML table to a table.
*
* @private
*/
class HTMLTableConverter extends Converters_DataConverter {
/* *
*
* Constructor
*
* */
/**
* Constructs an instance of the HTMLTableConverter.
*
* @param {Partial<HTMLTableConverterOptions>} [options]
* Options for the HTMLTableConverter.
*/
constructor(options) {
const mergedOptions = merge(HTMLTableConverter.defaultOptions, options);
super(mergedOptions);
this.headers = [];
this.options = mergedOptions;
if (mergedOptions.tableElement) {
this.tableElement = mergedOptions.tableElement;
this.tableElementID = mergedOptions.tableElement.id;
}
}
/* *
*
* Functions
*
* */
/**
* Exports the dataconnector as an HTML string, using the options
* provided on import unless other options are provided.
*
* @param {DataConnector} connector
* Connector instance to export from.
*
* @param {HTMLTableConnector.ExportOptions} [options]
* Options that override default or existing export options.
*
* @return {string}
* HTML from the current dataTable.
*/
export(connector, options = this.options) {
const exportNames = (options.firstRowAsNames !== false), useMultiLevelHeaders = options.useMultiLevelHeaders;
const columns = connector.getSortedColumns(), columnIds = Object.keys(columns), htmlRows = [], columnsCount = columnIds.length;
const rowArray = [];
let tableHead = '';
// Add the names as the first row if they should be exported
if (exportNames) {
const subcategories = [];
// If using multilevel headers, the first value
// of each column is a subcategory
if (useMultiLevelHeaders) {
for (const columnId of columnIds) {
let column = columns[columnId];
if (!Array.isArray(column)) {
// Convert to conventional array from typed array
// if needed
column = Array.from(column);
}
const subhead = (column.shift() || '').toString();
columns[columnId] = column;
subcategories.push(subhead);
}
tableHead = this.getTableHeaderHTML(columnIds, subcategories, options);
}
else {
tableHead = this.getTableHeaderHTML(void 0, columnIds, options);
}
}
for (let columnIndex = 0; columnIndex < columnsCount; columnIndex++) {
const columnId = columnIds[columnIndex], column = columns[columnId], columnLength = column.length;
for (let rowIndex = 0; rowIndex < columnLength; rowIndex++) {
let cellValue = column[rowIndex];
if (!rowArray[rowIndex]) {
rowArray[rowIndex] = [];
}
if (!(typeof cellValue === 'string' ||
typeof cellValue === 'number' ||
typeof cellValue === 'undefined')) {
cellValue = (cellValue || '').toString();
}
rowArray[rowIndex][columnIndex] = this.getCellHTMLFromValue(columnIndex ? 'td' : 'th', null, columnIndex ? '' : 'scope="row"', cellValue);
// On the final column, push the row to the array
if (columnIndex === columnsCount - 1) {
htmlRows.push('<tr>' +
rowArray[rowIndex].join('') +
'</tr>');
}
}
}
let caption = '';
// Add table caption
// Current exportdata falls back to chart title
// but that should probably be handled elsewhere?
if (options.tableCaption) {
caption = '<caption class="highcharts-table-caption">' +
options.tableCaption +
'</caption>';
}
return ('<table>' +
caption +
tableHead +
'<tbody>' +
htmlRows.join('') +
'</tbody>' +
'</table>');
}
/**
* Get table cell markup from row data.
*/
getCellHTMLFromValue(tag, classes, attrs, value, decimalPoint) {
let val = value, className = 'text' + (classes ? ' ' + classes : '');
// Convert to string if number
if (typeof val === 'number') {
val = val.toString();
if (decimalPoint === ',') {
val = val.replace('.', decimalPoint);
}
className = 'number';
}
else if (!value) {
val = '';
className = 'empty';
}
return '<' + tag + (attrs ? ' ' + attrs : '') +
' class="' + className + '">' +
val + '</' + tag + '>';
}
/**
* Get table header markup from row data.
*/
getTableHeaderHTML(topheaders = [], subheaders = [], options = this.options) {
const { useMultiLevelHeaders, useRowspanHeaders } = options;
let html = '<thead>', i = 0, len = subheaders && subheaders.length, next, cur, curColspan = 0, rowspan;
// Clean up multiple table headers. Exporting.getDataRows() returns two
// levels of headers when using multilevel, not merged. We need to
// merge identical headers, remove redundant headers, and keep it
// all marked up nicely.
if (useMultiLevelHeaders &&
topheaders &&
subheaders &&
!isRowEqual(topheaders, subheaders)) {
html += '<tr>';
for (; i < len; ++i) {
cur = topheaders[i];
next = topheaders[i + 1];
if (cur === next) {
++curColspan;
}
else if (curColspan) {
// Ended colspan
// Add cur to HTML with colspan.
html += this.getCellHTMLFromValue('th', 'highcharts-table-topheading', 'scope="col" ' +
'colspan="' + (curColspan + 1) + '"', cur);
curColspan = 0;
}
else {
// Cur is standalone. If it is same as sublevel,
// remove sublevel and add just toplevel.
if (cur === subheaders[i]) {
if (useRowspanHeaders) {
rowspan = 2;
subheaders.splice(i, 1);
}
else {
rowspan = 1;
subheaders[i] = '';
}
}
else {
rowspan = 1;
}
html += this.getCellHTMLFromValue('th', 'highcharts-table-topheading', 'scope="col"' +
(rowspan > 1 ?
' valign="top" rowspan="' + rowspan + '"' :
''), cur);
}
}
html += '</tr>';
}
// Add the subheaders (the only headers if not using multilevels)
if (subheaders) {
html += '<tr>';
for (i = 0, len = subheaders.length; i < len; ++i) {
if (typeof subheaders[i] !== 'undefined') {
html += this.getCellHTMLFromValue('th', null, 'scope="col"', subheaders[i]);
}
}
html += '</tr>';
}
html += '</thead>';
return html;
}
/**
* Initiates the parsing of the HTML table
*
* @param {Partial<HTMLTableConverterOptions>}[options]
* Options for the parser
*
* @param {DataEventDetail} [eventDetail]
* Custom information for pending events.
*
* @emits CSVDataParser#parse
* @emits CSVDataParser#afterParse
* @emits HTMLTableParser#parseError
*/
parse(options, eventDetail) {
const converter = this, columnsArray = [], headers = [], parseOptions = merge(converter.options, options), { endRow, startColumn, endColumn, firstRowAsNames } = parseOptions, tableHTML = parseOptions.tableElement || this.tableElement;
if (!(tableHTML instanceof HTMLElement)) {
converter.emit({
type: 'parseError',
columns: columnsArray,
detail: eventDetail,
headers,
error: 'Not a valid HTML Table'
});
return {};
}
converter.tableElement = tableHTML;
converter.tableElementID = tableHTML.id;
this.emit({
type: 'parse',
columns: columnsArray,
detail: eventDetail,
headers: converter.headers
});
const rows = tableHTML.getElementsByTagName('tr'), rowsCount = rows.length;
let rowIndex = 0, item, { startRow } = parseOptions;
// Insert headers from the first row
if (firstRowAsNames && rowsCount) {
const items = rows[0].children, itemsLength = items.length;
for (let i = startColumn; i < itemsLength; i++) {
if (i > endColumn) {
break;
}
item = items[i];
if (item.tagName === 'TD' ||
item.tagName === 'TH') {
headers.push(item.innerHTML);
}
}
startRow++;
}
while (rowIndex < rowsCount) {
if (rowIndex >= startRow && rowIndex <= endRow) {
const columnsInRow = rows[rowIndex].children, columnsInRowLength = columnsInRow.length;
let columnIndex = 0;
while (columnIndex < columnsInRowLength) {
const relativeColumnIndex = columnIndex - startColumn, row = columnsArray[relativeColumnIndex];
item = columnsInRow[columnIndex];
if ((item.tagName === 'TD' ||
item.tagName === 'TH') &&
(columnIndex >= startColumn &&
columnIndex <= endColumn)) {
if (!columnsArray[relativeColumnIndex]) {
columnsArray[relativeColumnIndex] = [];
}
let cellValue = converter.convertByType(item.innerHTML);
if (cellValue instanceof Date) {
cellValue = cellValue.getTime();
}
columnsArray[relativeColumnIndex][rowIndex - startRow] = cellValue;
// Loop over all previous indices and make sure
// they are nulls, not undefined.
let i = 1;
while (rowIndex - startRow >= i &&
row[rowIndex - startRow - i] === void 0) {
row[rowIndex - startRow - i] = null;
i++;
}
}
columnIndex++;
}
}
rowIndex++;
}
this.headers = headers;
this.emit({
type: 'afterParse',
columns: columnsArray,
detail: eventDetail,
headers
});
return Converters_DataConverterUtils.getColumnsCollection(columnsArray, converter.headers);
}
}
/* *
*
* Static Properties
*
* */
/**
* Default options
*/
HTMLTableConverter.defaultOptions = {
...Converters_DataConverter.defaultOptions,
useRowspanHeaders: true,
useMultiLevelHeaders: true,
startColumn: 0,
endColumn: Number.MAX_VALUE,
startRow: 0,
endRow: Number.MAX_VALUE
};
Converters_DataConverter.registerType('HTMLTable', HTMLTableConverter);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Converters_HTMLTableConverter = (HTMLTableConverter);
;// ./code/dashboards/es-modules/Data/Connectors/HTMLTableConnector.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:
* - Torstein Hønsi
* - Gøran Slettemark
* - Wojciech Chmiel
* - Sophie Bremer
* - Kamil Kubik
*
* */
const { win: HTMLTableConnector_win } = Core_Globals;
/* *
*
* Class
*
* */
/**
* Class that handles creating a data connector from an HTML table.
*
* @private
*/
class HTMLTableConnector extends Connectors_DataConnector {
/* *
*
* Constructor
*
* */
/**
* Constructs an instance of HTMLTableConnector.
*
* @param {CombinedHTMLTableConnectorOptions} [options]
* Options for the connector and converter.
*/
constructor(options) {
const mergedOptions = merge(HTMLTableConnector.defaultOptions, options);
super(mergedOptions);
this.options = mergedOptions;
this.converter = new Converters_HTMLTableConverter(mergedOptions);
}
/**
* Initiates creating the dataconnector from the HTML table
*
* @param {DataEventDetail} [eventDetail]
* Custom information for pending events.
*
* @emits HTMLTableConnector#load
* @emits HTMLTableConnector#afterLoad
* @emits HTMLTableConnector#loadError
*/
async load(eventDetail) {
const connector = this;
const options = connector.options;
const converter = connector.converter;
const table = connector.getTable();
const htmlTable = options.htmlTable;
connector.emit({
type: 'load',
detail: eventDetail
});
let tableElement;
if (typeof htmlTable === 'string') {
connector.tableID = htmlTable;
tableElement = HTMLTableConnector_win.document.getElementById(htmlTable);
}
else {
tableElement = htmlTable;
connector.tableID = tableElement.id;
}
connector.tableElement = tableElement || void 0;
if (!connector.tableElement) {
const error = 'HTML table not provided, or element with ID not found';
connector.emit({
type: 'loadError',
detail: eventDetail,
error
});
return Promise.reject(new Error(error));
}
const columns = converter.parse(merge({ tableElement: connector.tableElement }, options), eventDetail);
// If already loaded, clear the current rows
table.deleteColumns();
table.setColumns(columns);
await connector.applyTableModifiers();
connector.emit({
type: 'afterLoad',
detail: eventDetail
});
return connector;
}
}
/* *
*
* Static Properties
*
* */
HTMLTableConnector.defaultOptions = {
id: 'HTML-table-connector',
type: 'HTMLTable',
htmlTable: ''
};
Connectors_DataConnector.registerType('HTMLTable', HTMLTableConnector);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Connectors_HTMLTableConnector = (HTMLTableConnector);
;// ./code/dashboards/es-modules/Data/Converters/JSONConverter.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:
* - Paweł Lysy
* - Kamil Kubik
*
* */
/* *
*
* Class
*
* */
/**
* Handles parsing and transforming JSON to a table.
*
* @private
*/
class JSONConverter extends Converters_DataConverter {
/* *
*
* Constructor
*
* */
/**
* Constructs an instance of the JSON parser.
*
* @param {Partial<JSONConverterOptions>} [options]
* Options for the JSON parser.
*/
constructor(options) {
const mergedOptions = merge(JSONConverter.defaultOptions, options);
super(mergedOptions);
/* *
*
* Properties
*
* */
this.headerColumnIds = [];
this.headers = [];
this.options = mergedOptions;
}
/* *
*
* Functions
*
* */
/**
* Initiates parsing of JSON structure.
*
* @param {Partial<JSONConverterOptions>}[options]
* Options for the parser
*
* @param {DataEventDetail} [eventDetail]
* Custom information for pending events.
*
* @emits JSONConverter#parse
* @emits JSONConverter#afterParse
*/
parse(options, eventDetail) {
const converter = this;
options = merge(converter.options, options);
const { beforeParse, orientation, firstRowAsNames, columnIds } = options;
let data = options.data;
if (!data) {
return {};
}
converter.headers = [];
const columnsArray = [];
converter.emit({
type: 'parse',
columns: columnsArray,
detail: eventDetail,
headers: converter.headers
});
if (beforeParse) {
data = beforeParse(data);
}
data = data.slice();
if (orientation === 'columns') {
this.parseColumnsOrientation(columnsArray, data, firstRowAsNames, columnIds);
}
else if (orientation === 'rows') {
this.parseRowsOrientation(columnsArray, data, firstRowAsNames, columnIds);
}
converter.emit({
type: 'afterParse',
columns: columnsArray,
detail: eventDetail,
headers: converter.headers
});
return Converters_DataConverterUtils.getColumnsCollection(columnsArray, converter.headers);
}
/**
* Helper for parsing data in 'columns' orientation.
*
* @param {DataTableBasicColumn[]} [columnsArray]
* Array of columns.
*
* @param {unknown[]} [data]
* Array of data elements.
*
* @param {Boolean} [firstRowAsNames]
* Defines row as names.
*
* @param {Array<string>} [columnIds]
* Column ids to retrieve.
*
* @return {void}
*/
parseColumnsOrientation(columnsArray, data, firstRowAsNames, columnIds) {
const converter = this;
for (let i = 0, iEnd = data.length; i < iEnd; i++) {
const item = data[i];
if (!(Array.isArray(item))) {
return;
}
if (Array.isArray(converter.headers)) {
if (firstRowAsNames) {
converter.headers.push(`${item.shift()}`);
}
else if (columnIds && Array.isArray(columnIds)) {
converter.headers.push(columnIds[i]);
}
columnsArray.push(item);
}
else {
error('JSONConverter: Invalid `columnIds` option.', false);
}
}
}
/**
* Helper for parsing data in 'rows' orientation.
*
* @param {DataTableBasicColumn[]} [columnsArray]
* Array of columns.
*
* Helper for parsing data in 'rows' orientation.
*
* @param {unknown[]} [data]
* Array of data elements.
*
* @param {Boolean} [firstRowAsNames]
* Defines row as names.
*
* @param {Array<string>} [columnIds]
* Column ids to retrieve.
*
* @return {DataTableBasicColumn[]}
* Parsed columns.
*/
parseRowsOrientation(columnsArray, data, firstRowAsNames, columnIds) {
const converter = this;
if (firstRowAsNames) {
converter.headers = data.shift();
}
else if (columnIds) {
converter.headerColumnIds = columnIds;
}
for (let rowIndex = 0, iEnd = data.length; rowIndex < iEnd; rowIndex++) {
let row = data[rowIndex];
if (!isArray(row)) {
row = this.convertItemToRow(row, columnIds);
}
for (let columnIndex = 0, jEnd = row.length; columnIndex < jEnd; columnIndex++) {
if (columnsArray.length < columnIndex + 1) {
columnsArray.push([]);
}
columnsArray[columnIndex].push(row[columnIndex]);
// Create headers only once.
if (!firstRowAsNames && rowIndex === 0) {
if (Array.isArray(converter.headerColumnIds)) {
converter.headers.push(converter.headerColumnIds[columnIndex] ||
columnIndex.toString());
}
else {
error('JSONConverter: Invalid `columnIds` option.', false);
}
}
}
}
}
/**
* Extracts a row from an object, using columnIds if provided.
*
* @param {Record<string, string|number>} [rowObj]
* Set of props.
*
* @param {Array<string>} [columnIds]
* Column ids to retrieve.
*
* @return {Array<string | number>}
* Row converted to array.
*/
convertItemToRow(rowObj, columnIds) {
const converter = this;
if (columnIds && !(Array.isArray(columnIds))) {
const newRow = [];
objectEach(columnIds, (arrayWithPath, name) => {
newRow.push(arrayWithPath.reduce((acc, key) => acc[key], rowObj));
if (converter.headers.indexOf(name) < 0) {
converter.headers.push(name);
}
});
return newRow;
}
converter.headerColumnIds = Object.keys(rowObj);
return Object.values(rowObj);
}
}
/* *
*
* Static Properties
*
* */
/**
* Default options
*/
JSONConverter.defaultOptions = {
...Converters_DataConverter.defaultOptions,
orientation: 'rows'
};
Converters_DataConverter.registerType('JSON', JSONConverter);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Converters_JSONConverter = (JSONConverter);
;// ./code/dashboards/es-modules/Data/Connectors/JSONConnector.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:
* - Paweł Lysy
* - Kamil Kubik
*
* */
/* *
*
* Class
*
* */
/**
* Class that handles creating a DataConnector from JSON structure
*
* @private
*/
class JSONConnector extends Connectors_DataConnector {
/* *
*
* Constructor
*
* */
/**
* Constructs an instance of JSONConnector.
*
* @param {Partial<JSONConnectorOptions>} [options]
* Options for the connector and converter.
*/
constructor(options) {
const mergedOptions = merge(JSONConnector.defaultOptions, options);
super(mergedOptions);
this.options = mergedOptions;
if (mergedOptions.enablePolling) {
this.startPolling(Math.max(mergedOptions.dataRefreshRate || 0, 1) * 1000);
}
}
/* *
*
* Functions
*
* */
/**
* Overrides the DataConnector method. Emits an event on the connector to
* all registered callbacks of this event.
*
* @param {Event} e
* Event object containing additional event information.
*/
emit(e) {
fireEvent(this, e.type, e);
}
/**
* Initiates the loading of the JSON source to the connector
*
* @param {DataEventDetail} [eventDetail]
* Custom information for pending events.
*
* @emits JSONConnector#load
* @emits JSONConnector#afterLoad
*/
load(eventDetail) {
const connector = this;
const options = connector.options;
const { data, dataUrl, dataTables } = options;
connector.emit({
type: 'load',
detail: eventDetail,
data
});
return Promise
.resolve(dataUrl ?
fetch(dataUrl, {
signal: connector?.pollingController?.signal
}).then((response) => response.json())['catch']((error) => {
connector.emit({
type: 'loadError',
detail: eventDetail,
error
});
console.warn(`Unable to fetch data from ${dataUrl}.`); // eslint-disable-line no-console
}) :
data || [])
.then(async (data) => {
if (data) {
this.initConverters(data, (key) => {
const tableOptions = dataTables?.find((dataTable) => dataTable.key === key);
// The data table options takes precedence over the
// connector options.
const { columnIds = options.columnIds, firstRowAsNames = options.firstRowAsNames, orientation = options.orientation, beforeParse = options.beforeParse } = tableOptions || {};
const converterOptions = {
data,
columnIds,
firstRowAsNames,
orientation,
beforeParse
};
return new Converters_JSONConverter(converterOptions);
}, (converter, data) => converter.parse({ data }));
}
return connector.applyTableModifiers().then(() => data ?? []);
})
.then((data) => {
connector.emit({
type: 'afterLoad',
detail: eventDetail,
data
});
return connector;
})['catch']((error) => {
connector.emit({
type: 'loadError',
detail: eventDetail,
error
});
throw error;
});
}
}
/* *
*
* Static Properties
*
* */
JSONConnector.defaultOptions = {
type: 'JSON',
id: 'json-connector',
data: [],
enablePolling: false,
dataRefreshRate: 0,
firstRowAsNames: true,
orientation: 'rows'
};
/* *
*
* Registry
*
* */
Connectors_DataConnector.registerType('JSON', JSONConnector);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Connectors_JSONConnector = ((/* unused pure expression or super */ null && (JSONConnector)));
;// ./code/dashboards/es-modules/Data/Modifiers/ChainModifier.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
* - Dawid Draguła
*
* */
/* *
*
* Class
*
* */
/**
* Modifies a table with the help of modifiers in an ordered chain.
*
*/
class ChainModifier extends Modifiers_DataModifier {
/* *
*
* Constructor
*
* */
/**
* Constructs an instance of the modifier chain.
*
* @param {Partial<ChainModifierOptions>} [options]
* Options to configure the modifier chain.
*
* @param {...DataModifier} [chain]
* Ordered chain of modifiers.
*/
constructor(options, ...chain) {
super();
this.chain = chain;
this.options = merge(ChainModifier.defaultOptions, options);
const optionsChain = this.options.chain || [];
for (let i = 0, iEnd = optionsChain.length, modifierOptions, ModifierClass; i < iEnd; ++i) {
modifierOptions = optionsChain[i];
if (!modifierOptions.type) {
continue;
}
ModifierClass = Modifiers_DataModifier.types[modifierOptions.type];
if (ModifierClass) {
chain.push(new ModifierClass(modifierOptions));
}
}
}
/* *
*
* Functions
*
* */
/**
* Adds a configured modifier to the end of the modifier chain. Please note,
* that the modifier can be added multiple times.
*
* @param {DataModifier} modifier
* Configured modifier to add.
*
* @param {DataEventDetail} [eventDetail]
* Custom information for pending events.
*/
add(modifier, eventDetail) {
this.emit({
type: 'addModifier',
detail: eventDetail,
modifier
});
this.chain.push(modifier);
this.emit({
type: 'addModifier',
detail: eventDetail,
modifier
});
}
/**
* Clears all modifiers from the chain.
*
* @param {DataEventDetail} [eventDetail]
* Custom information for pending events.
*/
clear(eventDetail) {
this.emit({
type: 'clearChain',
detail: eventDetail
});
this.chain.length = 0;
this.emit({
type: 'afterClearChain',
detail: eventDetail
});
}
/**
* Sequentially applies all modifiers in the chain to the given table,
* updating its `modified` property with the final result.
*
* *Note:* The `modified` property reference of the table gets replaced.
*
* @param {Highcharts.DataTable} table
* Table to modify.
*
* @param {DataEventDetail} [eventDetail]
* Custom information for pending events.
*
* @return {Promise<Highcharts.DataTable>}
* Table with `modified` property as a reference.
*/
async modify(table, eventDetail) {
const modifiers = (this.options.reverse ?
this.chain.slice().reverse() :
this.chain.slice());
if (!table.modified) {
table.modified = table.clone(false, eventDetail);
}
let modified = table;
for (let i = 0, iEnd = modifiers.length; i < iEnd; ++i) {
try {
await modifiers[i].modify(modified, eventDetail);
}
catch (error) {
this.emit({
type: 'error',
detail: eventDetail,
table
});
throw error;
}
modified = modified.getModified();
}
table.modified = modified;
return table;
}
/**
* Applies several modifications to the table.
*
* *Note:* The `modified` property reference of the table gets replaced.
*
* @param {DataTable} table
* Table to modify.
*
* @param {DataEventDetail} [eventDetail]
* Custom information for pending events.
*
* @return {DataTable}
* Table as a reference.
*
* @emits ChainDataModifier#execute
* @emits ChainDataModifier#afterExecute
*/
modifyTable(table, eventDetail) {
const chain = this;
chain.emit({
type: 'modify',
detail: eventDetail,
table
});
const modifiers = (chain.options.reverse ?
chain.chain.reverse() :
chain.chain.slice());
let modified = table.getModified();
for (let i = 0, iEnd = modifiers.length, modifier; i < iEnd; ++i) {
modifier = modifiers[i];
modified =
modifier.modifyTable(modified, eventDetail).getModified();
}
table.modified = modified;
chain.emit({
type: 'afterModify',
detail: eventDetail,
table
});
return table;
}
/**
* Removes a configured modifier from all positions in the modifier chain.
*
* @param {DataModifier} modifier
* Configured modifier to remove.
*
* @param {DataEventDetail} [eventDetail]
* Custom information for pending events.
*/
remove(modifier, eventDetail) {
const modifiers = this.chain;
this.emit({
type: 'removeModifier',
detail: eventDetail,
modifier
});
modifiers.splice(modifiers.indexOf(modifier), 1);
this.emit({
type: 'afterRemoveModifier',
detail: eventDetail,
modifier
});
}
emit(e) {
fireEvent(this, e.type, e);
}
on(type, callback) {
return addEvent(this, type, callback);
}
}
/* *
*
* Static Properties
*
* */
/**
* Default option for the ordered modifier chain.
*/
ChainModifier.defaultOptions = {
type: 'Chain'
};
Modifiers_DataModifier.registerType('Chain', ChainModifier);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Modifiers_ChainModifier = ((/* unused pure expression or super */ null && (ChainModifier)));
;// ./code/dashboards/es-modules/Data/Modifiers/InvertModifier.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:
* - Wojciech Chmiel
* - Sophie Bremer
*
* */
/* *
*
* Class
*
* */
/**
* Inverts columns and rows in a table.
*
* @private
*/
class InvertModifier extends Modifiers_DataModifier {
/* *
*
* Constructor
*
* */
/**
* Constructs an instance of the invert modifier.
*
* @param {Partial<InvertModifierOptions>} [options]
* Options to configure the invert modifier.
*/
constructor(options) {
super();
this.options = merge(InvertModifier.defaultOptions, options);
}
/* *
*
* Functions
*
* */
/**
* Inverts rows and columns in the table. If the given table does not have
* defined a `modified` property, the filtering is applied in-place on the
* original table rather than on a `modified` copy.
*
* @param {DataTable} table
* Table to invert.
*
* @param {DataEventDetail} [eventDetail]
* Custom information for pending events.
*
* @return {DataTable}
* Table with inverted `modified` property as a reference or modified table,
* if `modified` property of the original table is undefined.
*/
modifyTable(table, eventDetail) {
const modifier = this;
modifier.emit({ type: 'modify', detail: eventDetail, table });
const modified = table.getModified();
if (table.hasColumns(['columnIds'])) { // Inverted table
const columnIdsColumn = ((table.deleteColumns(['columnIds']) || {})
.columnIds || []), columns = {}, columnIds = [];
for (let i = 0, iEnd = columnIdsColumn.length; i < iEnd; ++i) {
columnIds.push('' + columnIdsColumn[i]);
}
for (let i = 0, iEnd = table.getRowCount(), row; i < iEnd; ++i) {
row = table.getRow(i);
if (row) {
columns[columnIds[i]] = row;
}
}
modified.deleteColumns();
modified.setColumns(columns);
}
else { // Regular table
const columns = {};
for (let i = 0, iEnd = table.getRowCount(), row; i < iEnd; ++i) {
row = table.getRow(i);
if (row) {
columns[`${i}`] = row;
}
}
columns.columnIds = table.getColumnIds();
modified.deleteColumns();
modified.setColumns(columns);
}
modifier.emit({ type: 'afterModify', detail: eventDetail, table });
return table;
}
}
/* *
*
* Static Properties
*
* */
/**
* Default options for the invert modifier.
*/
InvertModifier.defaultOptions = {
type: 'Invert'
};
Modifiers_DataModifier.registerType('Invert', InvertModifier);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Modifiers_InvertModifier = ((/* unused pure expression or super */ null && (InvertModifier)));
;// ./code/dashboards/es-modules/Data/Modifiers/RangeModifier.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
* - Dawid Draguła
*
* */
/* *
*
* Class
*
* */
/**
* Slices the table rows based on the specified range.
*/
class RangeModifier extends Modifiers_DataModifier {
/* *
*
* Constructor
*
* */
/**
* Constructs an instance of the range modifier.
*
* @param {Partial<RangeModifierOptions>} [options]
* Options to configure the range modifier.
*/
constructor(options) {
super();
this.options = merge(RangeModifier.defaultOptions, options);
}
/* *
*
* Functions
*
* */
/**
* Replaces table rows with ranged rows. If the given table does not have
* defined a `modified` property, the filtering is applied in-place on the
* original table rather than on a `modified` copy.
*
* @param {DataTable} table
* Table to modify.
*
* @param {DataEventDetail} [eventDetail]
* Custom information for pending events.
*
* @return {DataTable}
* Table with `modified` property as a reference or modified table, if
* `modified` property of the original table is undefined.
*/
modifyTable(table, eventDetail) {
const modifier = this;
modifier.emit({ type: 'modify', detail: eventDetail, table });
let { start, end } = modifier.options;
start = Math.max(0, start || 0);
end = Math.min(end || Infinity, table.getRowCount());
const length = Math.max(end - start, 0);
const modified = table.getModified();
modified.deleteRows();
modified.setRows(table.getRows(start, length));
modified.setOriginalRowIndexes(Array.from({ length }, (_, i) => table.getOriginalRowIndex(start + i)));
modifier.emit({ type: 'afterModify', detail: eventDetail, table });
return table;
}
}
/* *
*
* Static Properties
*
* */
/**
* Default options for the range modifier.
*/
RangeModifier.defaultOptions = {
type: 'Range',
start: 0,
end: Infinity
};
Modifiers_DataModifier.registerType('Range', RangeModifier);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Modifiers_RangeModifier = ((/* unused pure expression or super */ null && (RangeModifier)));
;// ./code/dashboards/es-modules/Data/Modifiers/SortModifier.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
* - Dawid Draguła
*
* */
/* *
*
* Class
*
* */
/**
* Sort table rows according to values of a column.
*
*/
class SortModifier extends Modifiers_DataModifier {
/* *
*
* Static Functions
*
* */
static ascending(a, b) {
return ((a || 0) < (b || 0) ? -1 :
(a || 0) > (b || 0) ? 1 :
0);
}
static descending(a, b) {
return ((b || 0) < (a || 0) ? -1 :
(b || 0) > (a || 0) ? 1 :
0);
}
static compareFactory(direction, customCompare) {
if (customCompare) {
if (direction === 'desc') {
return (a, b) => -customCompare(a, b);
}
return customCompare;
}
return (direction === 'asc' ?
SortModifier.ascending :
SortModifier.descending);
}
/* *
*
* Constructor
*
* */
/**
* Constructs an instance of the sort modifier.
*
* @param {Partial<SortModifierOptions>} [options]
* Options to configure the sort modifier.
*/
constructor(options) {
super();
this.options = merge(SortModifier.defaultOptions, options);
}
/* *
*
* Functions
*
* */
/**
* Returns index and row for sort reference.
*
* @private
*
* @param {Highcharts.DataTable} table
* Table with rows to reference.
*
* @return {Array<SortRowReference>}
* Array of row references.
*/
getRowReferences(table) {
const rows = table.getRows(), rowReferences = [];
for (let i = 0, iEnd = rows.length; i < iEnd; ++i) {
rowReferences.push({
index: i,
row: rows[i]
});
}
return rowReferences;
}
modifyTable(table, eventDetail) {
const modifier = this;
modifier.emit({ type: 'modify', detail: eventDetail, table });
const columnIds = table.getColumnIds(), rowCount = table.getRowCount(), rowReferences = this.getRowReferences(table), { direction, orderInColumn, compare: customCompare } = modifier.options, modified = table.getModified();
const orderBy = ('columns' in modifier.options ?
modifier.options.columns :
[modifier.options.orderByColumn]);
const orderByIndexes = [];
for (let i = 0, iEnd = orderBy.length; i < iEnd; ++i) {
const sort = orderBy[i];
const isString = typeof sort === 'string';
const column = isString ? sort : sort.column;
const columnIndex = columnIds.indexOf(column);
if (columnIndex === -1) {
continue;
}
orderByIndexes.push({
columnIndex,
compare: SortModifier.compareFactory(isString ? direction : (sort.direction || direction), isString ? customCompare : (sort.compare || customCompare))
});
}
if (orderByIndexes.length) {
rowReferences.sort((a, b) => {
for (let i = 0, iEnd = orderByIndexes.length; i < iEnd; ++i) {
const { columnIndex, compare } = orderByIndexes[i];
const result = compare(a.row[columnIndex], b.row[columnIndex]);
if (result) {
return result;
}
}
return a.index - b.index;
});
}
if (orderInColumn) {
const column = [];
for (let i = 0; i < rowCount; ++i) {
column[rowReferences[i].index] = i;
}
modified.setColumns({ [orderInColumn]: column });
}
else {
const originalIndexes = [];
const rows = [];
let rowReference;
for (let i = 0; i < rowCount; ++i) {
rowReference = rowReferences[i];
originalIndexes.push(table.getOriginalRowIndex(rowReference.index));
rows.push(rowReference.row);
}
modified.setRows(rows, 0);
modified.setOriginalRowIndexes(originalIndexes);
}
modifier.emit({ type: 'afterModify', detail: eventDetail, table });
return table;
}
}
/* *
*
* Static Properties
*
* */
/**
* Default options to group table rows.
*/
SortModifier.defaultOptions = {
type: 'Sort',
direction: 'desc',
orderByColumn: 'y'
};
Modifiers_DataModifier.registerType('Sort', SortModifier);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Modifiers_SortModifier = ((/* unused pure expression or super */ null && (SortModifier)));
;// ./code/dashboards/es-modules/Data/Modifiers/FilterModifier.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:
* - Dawid Draguła
*
* */
/* *
*
* Class
*
* */
/**
* Filters out table rows matching a given condition.
*/
class FilterModifier extends Modifiers_DataModifier {
/* *
*
* Static Functions
*
* */
/**
* Compiles a filter condition into a callback function.
*
* @param {FilterCondition} condition
* Condition to compile.
*/
static compile(condition) {
if (isFunction(condition)) {
return condition;
}
const op = condition.operator;
switch (op) {
case 'and': {
const subs = condition.conditions.map((c) => this.compile(c));
return (row, table, i) => subs.every((cond) => cond(row, table, i));
}
case 'or': {
const subs = condition.conditions.map((c) => this.compile(c));
return (row, table, i) => subs.some((cond) => cond(row, table, i));
}
case 'not': {
const sub = this.compile(condition.condition);
return (row, table, i) => !sub(row, table, i);
}
}
const { columnId: col, value } = condition;
switch (op) {
case '==':
// eslint-disable-next-line eqeqeq
return (row) => row[col] == value;
case '===':
return (row) => row[col] === value;
case '!=':
// eslint-disable-next-line eqeqeq
return (row) => row[col] != value;
case '!==':
return (row) => row[col] !== value;
case '>':
return (row) => (row[col] || 0) > (value || 0);
case '>=':
return (row) => (row[col] || 0) >= (value || 0);
case '<':
return (row) => (row[col] || 0) < (value || 0);
case '<=':
return (row) => (row[col] || 0) <= (value || 0);
case 'empty':
return (row) => row[col] === null || row[col] === '';
}
const { ignoreCase } = condition;
const str = (val) => {
const s = '' + val;
return (ignoreCase ?? true) ? s.toLowerCase() : s;
};
switch (op) {
case 'contains':
return (row) => str(row[col]).includes(str(value));
default:
return (row) => str(row[col])[op](str(value));
}
}
/* *
*
* Constructor
*
* */
/**
* Constructs an instance of the filter modifier.
*
* @param {Partial<FilterModifierOptions>} [options]
* Options to configure the filter modifier.
*/
constructor(options) {
super();
this.options = merge(FilterModifier.defaultOptions, options);
}
/* *
*
* Functions
*
* */
/**
* Filters out table rows matching a given condition. If the given table
* does not have defined a `modified` property, the filtering is applied
* in-place on the original table rather than on a `modified` copy.
*
* @param {DataTable} table
* Table to modify.
*
* @param {DataEventDetail} [eventDetail]
* Custom information for pending events.
*
* @return {DataTable}
* Table with `modified` property as a reference or modified table, if
* `modified` property of the original table is undefined.
*/
modifyTable(table, eventDetail) {
const modifier = this;
modifier.emit({ type: 'modify', detail: eventDetail, table });
const { condition } = modifier.options;
if (!condition) {
// If no condition is set, return the unmodified table.
return table;
}
const matchRow = FilterModifier.compile(condition);
const modified = table.getModified();
const rows = [];
const indexes = [];
for (let i = 0, iEnd = table.getRowCount(); i < iEnd; ++i) {
const row = table.getRowObject(i);
if (!row) {
continue;
}
if (matchRow(row, table, i)) {
rows.push(row);
indexes.push(table.getOriginalRowIndex(i));
}
}
modified.deleteRows();
modified.setRows(rows);
modified.setOriginalRowIndexes(indexes);
modifier.emit({ type: 'afterModify', detail: eventDetail, table });
return table;
}
}
/* *
*
* Static Properties
*
* */
/**
* Default options for the filter modifier.
*/
FilterModifier.defaultOptions = {
type: 'Filter'
};
Modifiers_DataModifier.registerType('Filter', FilterModifier);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Modifiers_FilterModifier = ((/* unused pure expression or super */ null && (FilterModifier)));
;// ./code/dashboards/es-modules/Dashboards/Serializable.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
*
* */
/* *
*
* Constants
*
* */
/**
* Registry of serializable classes.
*/
const classRegistry = {};
/**
* Registry of function sets.
*/
const helperRegistry = {};
/* *
*
* Functions
*
* */
/**
* Creates a class instance from the given JSON, if a suitable serializer
* has been found.
*
* @function Serializable.fromJSON
*
* @param {JSON} json
* JSON to create a class instance or object from.
*
* @return {AnyRecord}
* Returns the class instance or object, or throws an exception.
*/
function fromJSON(json) {
const $class = json.$class;
if (typeof $class !== 'string') {
throw new Error('JSON has no $class property.');
}
const classs = classRegistry[$class];
if (classs) {
return classs.fromJSON(json);
}
const helper = helperRegistry[$class];
if (helper) {
return helper.fromJSON(json);
}
throw new Error(`'${$class}' unknown.`);
}
/**
* Registers a class prototype for the given JSON $class.
*
* @function Serializable.registerClassPrototype
*
* @param {string} $class
* JSON $class to register for.
*
* @param {Serializable} classPrototype
* Class to register.
*/
function registerClassPrototype($class, classPrototype) {
if (classRegistry[$class]) {
throw new Error('A serializer for \'' + $class + '\' is already registered.');
}
classRegistry[$class] = classPrototype;
}
/**
* Registers helper functions for the given JSON $class.
*
* @function Serializable.registerHelper
*
* @param {Helper} helperFunctions
* Helper functions to register.
*/
function registerHelper(helperFunctions) {
if (helperRegistry[helperFunctions.$class]) {
throw new Error('A serializer for \'' + helperFunctions.$class +
'\' is already registered.');
}
helperRegistry[helperFunctions.$class] = helperFunctions;
}
/**
* Creates JSON from a class instance.
*
* @function Serializable.toJSON
*
* @param {AnyRecord} obj
* Class instance or object to serialize as JSON.
*
* @return {JSON}
* JSON of the class instance.
*/
function toJSON(obj) {
if (typeof obj.fromJSON === 'function' &&
typeof obj.toJSON === 'function') {
return obj.toJSON();
}
const classes = Object.keys(helperRegistry), numberOfHelpers = classes.length;
let $class, serializer;
for (let i = 0; i < numberOfHelpers; ++i) {
$class = classes[i];
serializer = helperRegistry[$class];
if (serializer.jsonSupportFor(obj)) {
return serializer.toJSON(obj);
}
}
throw new Error('Object is not supported.');
}
/* *
*
* Default Export
*
* */
const Serializable = {
fromJSON,
registerClassPrototype,
registerHelper,
toJSON
};
/* harmony default export */ const Dashboards_Serializable = (Serializable);
;// ./code/dashboards/es-modules/Dashboards/SerializeHelper/DataTableHelper.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
*
* */
/* *
*
* Functions
*
* */
/**
* Converts the given JSON to a class instance.
*
* @param {JSON} json
* JSON to deserialize as a class instance or object.
*
* @return {DataTable}
* Returns the class instance or object, or throws an exception.
*/
function DataTableHelper_fromJSON(json) {
return new Data_DataTable({ columns: json.columns, id: json.id });
}
/**
* Validates the given class instance for JSON support.
*
* @param {AnyRecord} obj
* Class instance or object to validate.
*
* @return {boolean}
* Returns true, if the function set can convert the given object, otherwise
* false.
*/
function jsonSupportFor(obj) {
return obj instanceof Data_DataTable;
}
/**
* Converts the given class instance to JSON.
*
* @param {DataTable} obj
* Class instance or object to serialize as JSON.
*
* @return {JSON}
* Returns the JSON of the class instance or object.
*/
function DataTableHelper_toJSON(obj) {
const json = {
$class: 'Data.DataTable',
columns: obj.getColumns(void 0, false, true)
};
// Custom ID
if (!obj.autoId) {
json.id = obj.id;
}
// Done
return json;
}
/* *
*
* Registry
*
* */
const DataTableHelper = {
$class: 'Data.DataTable',
fromJSON: DataTableHelper_fromJSON,
jsonSupportFor,
toJSON: DataTableHelper_toJSON
};
Dashboards_Serializable.registerHelper(DataTableHelper);
/* *
*
* Default Export
*
* */
/* harmony default export */ const SerializeHelper_DataTableHelper = (DataTableHelper);
;// ./code/dashboards/es-modules/Dashboards/SerializeHelper/CSVConnectorHelper.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
*
* */
/* *
*
* Functions
*
* */
/**
* Converts the given JSON to a class instance.
*
* @param {JSON} json
* JSON to deserialize as a class instance or object.
*
* @return {CSVConnector}
* Returns the class instance or object, or throws an exception.
*/
function CSVConnectorHelper_fromJSON(json) {
return new Connectors_CSVConnector(json.options);
}
/**
* Validates the given class instance for JSON support.
*
* @param {AnyRecord} obj
* Class instance or object to validate.
*
* @return {boolean}
* Returns true, if the function set can convert the given object, otherwise
* false.
*/
function CSVConnectorHelper_jsonSupportFor(obj) {
return obj instanceof Connectors_CSVConnector;
}
/**
* Converts the given class instance to JSON.
*
* @param {CSVConnector} obj
* Class instance or object to serialize as JSON.
*
* @return {JSON}
* Returns the JSON of the class instance or object.
*/
function CSVConnectorHelper_toJSON(obj) {
const options = merge(obj.options);
options.dataTable = SerializeHelper_DataTableHelper.toJSON(obj.getTable());
return {
$class: 'Data.CSVConnector',
options
};
}
/* *
*
* Registry
*
* */
const CSVConnectorHelper = {
$class: 'Data.CSVConnector',
fromJSON: CSVConnectorHelper_fromJSON,
jsonSupportFor: CSVConnectorHelper_jsonSupportFor,
toJSON: CSVConnectorHelper_toJSON
};
Dashboards_Serializable.registerHelper(CSVConnectorHelper);
/* *
*
* Default Export
*
* */
/* harmony default export */ const SerializeHelper_CSVConnectorHelper = ((/* unused pure expression or super */ null && (CSVConnectorHelper)));
;// ./code/dashboards/es-modules/Dashboards/SerializeHelper/DataConverterHelper.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:
* - Paweł Lysy
* - Sophie Bremer
*
* */
/* *
*
* Functions
*
* */
/**
* JSON object as a base.
* @param json Serialized object
* @return {DataConverter} New Data Converter object created from serialized object
*/
function DataConverterHelper_fromJSON(json) {
return new Converters_DataConverter(json.options);
}
/**
* Check if the Object on the input is the correct Object to be serialized
* @param obj Obj to check
* @return {obj is DataConverter} If object is DataConverter
*/
function DataConverterHelper_jsonSupportFor(obj) {
return obj instanceof Converters_DataConverter;
}
/**
* Change the obj of DataConverter to its Serialized form.
* @param obj Object to serialize
* @return Serialized object
*/
function DataConverterHelper_toJSON(obj) {
return {
$class: 'Data.DataConverter',
options: merge(obj.options)
};
}
/* *
*
* Registry
*
* */
const DataConverterHelper = {
$class: 'Data.DataConverter',
fromJSON: DataConverterHelper_fromJSON,
jsonSupportFor: DataConverterHelper_jsonSupportFor,
toJSON: DataConverterHelper_toJSON
};
Dashboards_Serializable.registerHelper(DataConverterHelper);
/* *
*
* Default Export
*
* */
/* harmony default export */ const SerializeHelper_DataConverterHelper = ((/* unused pure expression or super */ null && (DataConverterHelper)));
;// ./code/dashboards/es-modules/Data/DataCursor.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:
* - Sophie Bremer
*
* */
/* *
*
* Class
*
* */
/**
* This class manages state cursors pointing on {@link Data.DataTable}. It
* creates a relation between states of the user interface and the table cells,
* columns, or rows.
*
* @class
* @name Data.DataCursor
*/
class DataCursor {
/* *
*
* Static Properties
*
* */
/* *
*
* Constructor
*
* */
constructor(stateMap = {}) {
this.emittingRegister = [];
this.listenerMap = {};
this.stateMap = stateMap;
}
/* *
*
* Functions
*
* */
/**
* This function registers a listener for a specific state and table.
*
* @example
* ```TypeScript
* dataCursor.addListener(myTable.id, 'hover', (e: DataCursorEvent) => {
* if (e.cursor.type === 'position') {
* console.log(`Hover over row #${e.cursor.row}.`);
* }
* });
* ```
*
* @function #addListener
*
* @param {Data.DataCursorTableId} tableId
* The ID of the table to listen to.
*
* @param {Data.DataCursorState} state
* The state on the table to listen to.
*
* @param {Data.DataCursorListener} listener
* The listener to register.
*
* @return {Data.DataCursor}
* Returns the DataCursor instance for a call chain.
*/
addListener(tableId, state, listener) {
const listenerMap = this.listenerMap[tableId] = (this.listenerMap[tableId] ||
{});
const listeners = listenerMap[state] = (listenerMap[state] ||
[]);
listeners.push(listener);
return this;
}
/**
* @private
*/
buildEmittingTag(e) {
return (e.cursor.type === 'position' ?
[
e.table.id,
e.cursor.column,
e.cursor.row,
e.cursor.state,
e.cursor.type
] :
[
e.table.id,
e.cursor.columns,
e.cursor.firstRow,
e.cursor.lastRow,
e.cursor.state,
e.cursor.type
]).join('\0');
}
/**
* This function emits a state cursor related to a table. It will provide
* lasting state cursors of the table to listeners.
*
* @example
* ```ts
* dataCursor.emit(myTable, {
* type: 'position',
* column: 'city',
* row: 4,
* state: 'hover',
* });
* ```
*
* @param {Data.DataTable} table
* The related table of the cursor.
*
* @param {Data.DataCursorType} cursor
* The state cursor to emit.
*
* @param {Event} [event]
* Optional event information from a related source.
*
* @param {boolean} [lasting]
* Whether this state cursor should be kept until it is cleared with
* {@link DataCursor#remitCursor}.
*
* @return {Data.DataCursor}
* Returns the DataCursor instance for a call chain.
*/
emitCursor(table, cursor, event, lasting) {
const tableId = table.id, state = cursor.state, listeners = (this.listenerMap[tableId] &&
this.listenerMap[tableId][state]);
if (listeners) {
const stateMap = this.stateMap[tableId] = (this.stateMap[tableId] ?? {});
const cursors = stateMap[cursor.state] || [];
if (lasting) {
if (!cursors.length) {
stateMap[cursor.state] = cursors;
}
if (getIndex(cursor, cursors) === -1) {
cursors.push(cursor);
}
}
const e = {
cursor,
cursors,
table
};
if (event) {
e.event = event;
}
const emittingRegister = this.emittingRegister, emittingTag = this.buildEmittingTag(e);
if (emittingRegister.indexOf(emittingTag) >= 0) {
// Break call stack loops
return this;
}
try {
this.emittingRegister.push(emittingTag);
for (let i = 0, iEnd = listeners.length; i < iEnd; ++i) {
listeners[i].call(this, e);
}
}
finally {
const index = this.emittingRegister.indexOf(emittingTag);
if (index >= 0) {
this.emittingRegister.splice(index, 1);
}
}
}
return this;
}
/**
* Removes a lasting state cursor.
*
* @function #remitCursor
*
* @param {string} tableId
* ID of the related cursor table.
*
* @param {Data.DataCursorType} cursor
* Copy or reference of the cursor.
*
* @return {Data.DataCursor}
* Returns the DataCursor instance for a call chain.
*/
remitCursor(tableId, cursor) {
const cursors = (this.stateMap[tableId] &&
this.stateMap[tableId][cursor.state]);
if (cursors) {
const index = getIndex(cursor, cursors);
if (index >= 0) {
cursors.splice(index, 1);
}
}
return this;
}
/**
* This function removes a listener.
*
* @function #addListener
*
* @param {Data.DataCursorTableId} tableId
* The ID of the table the listener is connected to.
*
* @param {Data.DataCursorState} state
* The state on the table the listener is listening to.
*
* @param {Data.DataCursorListener} listener
* The listener to deregister.
*
* @return {Data.DataCursor}
* Returns the DataCursor instance for a call chain.
*/
removeListener(tableId, state, listener) {
const listeners = (this.listenerMap[tableId] &&
this.listenerMap[tableId][state]);
if (listeners) {
const index = listeners.indexOf(listener);
if (index >= 0) {
listeners.splice(index, 1);
}
}
return this;
}
}
/* *
*
* Functions
*
* */
/**
* Finds the index of an cursor in an array.
* @private
*/
function getIndex(needle, cursors) {
if (needle.type === 'position') {
for (let cursor, i = 0, iEnd = cursors.length; i < iEnd; ++i) {
cursor = cursors[i];
if (cursor.type === 'position' &&
cursor.state === needle.state &&
cursor.column === needle.column &&
cursor.row === needle.row) {
return i;
}
}
}
else {
const columnNeedle = JSON.stringify(needle.columns);
for (let cursor, i = 0, iEnd = cursors.length; i < iEnd; ++i) {
cursor = cursors[i];
if (cursor.type === 'range' &&
cursor.state === needle.state &&
cursor.firstRow === needle.firstRow &&
cursor.lastRow === needle.lastRow &&
JSON.stringify(cursor.columns) === columnNeedle) {
return i;
}
}
}
return -1;
}
/**
* Checks whether two cursor share the same properties.
* @private
*/
function isEqual(cursorA, cursorB) {
if (cursorA.type === 'position' && cursorB.type === 'position') {
return (cursorA.column === cursorB.column &&
cursorA.row === cursorB.row &&
cursorA.state === cursorB.state);
}
if (cursorA.type === 'range' && cursorB.type === 'range') {
return (cursorA.firstRow === cursorB.firstRow &&
cursorA.lastRow === cursorB.lastRow &&
(JSON.stringify(cursorA.columns) ===
JSON.stringify(cursorB.columns)));
}
return false;
}
/**
* Checks whether a cursor is in a range.
* @private
*/
function isInRange(needle, range) {
if (range.type === 'position') {
range = toRange(range);
}
if (needle.type === 'position') {
needle = toRange(needle, range);
}
const needleColumns = needle.columns;
const rangeColumns = range.columns;
return (needle.firstRow >= range.firstRow &&
needle.lastRow <= range.lastRow &&
(!needleColumns ||
!rangeColumns ||
needleColumns.every((column) => rangeColumns.indexOf(column) >= 0)));
}
/**
* @private
*/
function toPositions(cursor) {
if (cursor.type === 'position') {
return [cursor];
}
const columns = (cursor.columns || []);
const positions = [];
const state = cursor.state;
for (let row = cursor.firstRow, rowEnd = cursor.lastRow; row < rowEnd; ++row) {
if (!columns.length) {
positions.push({
type: 'position',
row,
state
});
continue;
}
for (let column = 0, columnEnd = columns.length; column < columnEnd; ++column) {
positions.push({
type: 'position',
column: columns[column],
row,
state
});
}
}
return positions;
}
/**
* @private
*/
function toRange(cursor, defaultRange) {
if (cursor.type === 'range') {
return cursor;
}
const range = {
type: 'range',
firstRow: (cursor.row ??
(defaultRange && defaultRange.firstRow) ??
0),
lastRow: (cursor.row ??
(defaultRange && defaultRange.lastRow) ??
Number.MAX_VALUE),
state: cursor.state
};
if (typeof cursor.column !== 'undefined') {
range.columns = [cursor.column];
}
return range;
}
/* *
*
* Default Export
*
* */
/* harmony default export */ const Data_DataCursor = (DataCursor);
;// ./code/dashboards/es-modules/Dashboards/SerializeHelper/DataCursorHelper.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
*
* */
/* *
*
* Functions
*
* */
/**
* Converts the given JSON to a class instance.
*
* @param {JSON} json
* JSON to deserialize as a class instance or object.
*
* @return {DataCursor}
* Returns the class instance or object, or throws an exception.
*/
function DataCursorHelper_fromJSON(json) {
return new Data_DataCursor(json.stateMap);
}
/**
* Validates the given class instance for JSON support.
*
* @param {AnyRecord} obj
* Class instance or object to validate.
*
* @return {boolean}
* Returns true, if the function set can convert the given object, otherwise
* false.
*/
function DataCursorHelper_jsonSupportFor(obj) {
return obj instanceof Data_DataCursor;
}
/**
* Converts the given class instance to JSON.
*
* @param {DataTable} obj
* Class instance or object to serialize as JSON.
*
* @return {DataTableHelper.JSON}
* Returns the JSON of the class instance or object.
*/
function DataCursorHelper_toJSON(obj) {
const stateMap = obj.stateMap, stateMapJSON = {}, tableIds = Object.keys(obj.stateMap);
let cursors, cursorsJSON, tableId, state, states;
for (let i = 0, iEnd = tableIds.length; i < iEnd; ++i) {
tableId = tableIds[i];
states = Object.keys(stateMap[tableId]);
stateMapJSON[tableId] = {};
for (let j = 0, jEnd = states.length; j < jEnd; ++j) {
state = states[j];
cursors = stateMap[tableId][state];
stateMapJSON[tableId][state] = cursorsJSON = [];
for (let k = 0, kEnd = cursors.length; k < kEnd; ++k) {
cursorsJSON.push({ ...cursors[k] });
}
}
}
return {
$class: 'Data.DataCursor',
stateMap: stateMapJSON
};
}
/* *
*
* Registry
*
* */
const DataCursorHelper = {
$class: 'Data.DataCursor',
fromJSON: DataCursorHelper_fromJSON,
jsonSupportFor: DataCursorHelper_jsonSupportFor,
toJSON: DataCursorHelper_toJSON
};
Dashboards_Serializable.registerHelper(DataCursorHelper);
/* *
*
* Default Export
*
* */
/* harmony default export */ const SerializeHelper_DataCursorHelper = ((/* unused pure expression or super */ null && (DataCursorHelper)));
;// ./code/dashboards/es-modules/Dashboards/SerializeHelper/GoogleSheetsConnectorHelper.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
*
* */
/* *
*
* Functions
*
* */
/**
* Converts the given JSON to a class instance.
*
* @param {JSON} json
* JSON to deserialize as a class instance or object.
*
* @return {GoogleSheetsConnector}
* Returns the class instance or object, or throws an exception.
*/
function GoogleSheetsConnectorHelper_fromJSON(json) {
return new Connectors_GoogleSheetsConnector(json.options || { googleAPIKey: '', googleSpreadsheetKey: '' });
}
/**
* Validates the given class instance for JSON support.
*
* @param {AnyRecord} obj
* Class instance or object to validate.
*
* @return {boolean}
* Returns true, if the function set can convert the given object, otherwise
* false.
*/
function GoogleSheetsConnectorHelper_jsonSupportFor(obj) {
return obj instanceof Connectors_GoogleSheetsConnector;
}
/**
* Converts the given class instance to JSON.
*
* @param {GoogleSheetsConnector} obj
* Class instance or object to serialize as JSON.
*
* @return {JSON}
* Returns the JSON of the class instance or object.
*/
function GoogleSheetsConnectorHelper_toJSON(obj) {
const options = merge(obj.options);
options.dataTable = SerializeHelper_DataTableHelper.toJSON(obj.getTable());
return {
$class: 'Data.GoogleSheetsConnector',
options
};
}
/* *
*
* Registry
*
* */
const GoogleSheetsConnectorHelper = {
$class: 'Data.GoogleSheetsConnector',
fromJSON: GoogleSheetsConnectorHelper_fromJSON,
jsonSupportFor: GoogleSheetsConnectorHelper_jsonSupportFor,
toJSON: GoogleSheetsConnectorHelper_toJSON
};
Dashboards_Serializable.registerHelper(GoogleSheetsConnectorHelper);
/* *
*
* Default Export
*
* */
/* harmony default export */ const SerializeHelper_GoogleSheetsConnectorHelper = ((/* unused pure expression or super */ null && (GoogleSheetsConnectorHelper)));
;// ./code/dashboards/es-modules/Dashboards/SerializeHelper/HTMLTableConnectorHelper.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
*
* */
/* *
*
* Functions
*
* */
/**
* Converts the given JSON to a class instance.
*
* @param {JSON} json
* JSON to deserialize as a class instance or object.
*
* @return {HTMLTableConnector}
* Returns the class instance or object, or throws an exception.
*/
function HTMLTableConnectorHelper_fromJSON(json) {
return new Connectors_HTMLTableConnector(json.options);
}
/**
* Validates the given class instance for JSON support.
*
* @param {AnyRecord} obj
* Class instance or object to validate.
*
* @return {boolean}
* Returns true, if the function set can convert the given object, otherwise
* false.
*/
function HTMLTableConnectorHelper_jsonSupportFor(obj) {
return obj instanceof Connectors_HTMLTableConnector;
}
/**
* Converts the given class instance to JSON.
*
* @param {HTMLTableConnector} obj
* Class instance or object to serialize as JSON.
*
* @return {JSON}
* Returns the JSON of the class instance or object.
*/
function HTMLTableConnectorHelper_toJSON(obj) {
const options = merge(obj.options);
options.dataTable = SerializeHelper_DataTableHelper.toJSON(obj.getTable());
return {
$class: 'Data.HTMLTableConnector',
options
};
}
/* *
*
* Default Export
*
* */
const HTMLTableConnectorHelper = {
$class: 'Data.HTMLTableConnector',
fromJSON: HTMLTableConnectorHelper_fromJSON,
jsonSupportFor: HTMLTableConnectorHelper_jsonSupportFor,
toJSON: HTMLTableConnectorHelper_toJSON
};
Dashboards_Serializable.registerHelper(HTMLTableConnectorHelper);
/* *
*
* Default Export
*
* */
/* harmony default export */ const SerializeHelper_HTMLTableConnectorHelper = ((/* unused pure expression or super */ null && (HTMLTableConnectorHelper)));
;// ./code/dashboards/es-modules/Dashboards/SerializeHelper/JSONConnectorHelper.js
/* unused harmony import specifier */ var JSONConnectorHelper_JSONConnector;
/* unused harmony import specifier */ var JSONConnectorHelper_DataTableHelper;
/* unused harmony import specifier */ var JSONConnectorHelper_merge;
/* *
*
* (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:
* - Paweł Lysy
*
* */
/* *
*
* Functions
*
* */
/**
* Converts the given JSON to a class instance.
*
* @param {JSON} json
* JSON to deserialize as a class instance or object.
*
* @return {JSONConnector}
* Returns the class instance or object, or throws an exception.
*/
function JSONConnectorHelper_fromJSON(json) {
return new JSONConnectorHelper_JSONConnector(json.options);
}
/**
* Validates the given class instance for JSON support.
*
* @param {AnyRecord} obj
* Class instance or object to validate.
*
* @return {boolean}
* Returns true, if the function set can convert the given object, otherwise
* false.
*/
function JSONConnectorHelper_jsonSupportFor(obj) {
return obj instanceof JSONConnectorHelper_JSONConnector;
}
/**
* Converts the given class instance to JSON.
*
* @param {JSONConnector} obj
* Class instance or object to serialize as JSON.
*
* @return {JSON}
* Returns the JSON of the class instance or object.
*/
function JSONConnectorHelper_toJSON(obj) {
const options = JSONConnectorHelper_merge(obj.options);
options.dataTable = JSONConnectorHelper_DataTableHelper.toJSON(obj.getTable());
return {
$class: 'Data.JSONConnector',
options
};
}
/* *
*
* Registry
*
* */
const JSONConnectorHelper = (/* unused pure expression or super */ null && ({
$class: 'Data.JSONConnector',
fromJSON: JSONConnectorHelper_fromJSON,
jsonSupportFor: JSONConnectorHelper_jsonSupportFor,
toJSON: JSONConnectorHelper_toJSON
}));
/* *
*
* Default Export
*
* */
/* harmony default export */ const SerializeHelper_JSONConnectorHelper = ((/* unused pure expression or super */ null && (JSONConnectorHelper)));
;// ./code/dashboards/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, '<');
}
});
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) => {
// Preserve the camelCase of SVG tags via localName (#24702).
const tagName = node.localName ||
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',
'stop-color',
'stop-opacity',
'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/dashboards/es-modules/Dashboards/Components/ComponentRegistry.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
*
*
* */
/* *
*
* Constants
*
* */
/**
*
* Record of component classes
* @todo
*
*/
const types = {};
/* *
*
* Functions
*
* */
/**
* Method used to register new component classes.
*
* @param {string} key
* Registry key of the component class.
*
* @param {ComponentType} ComponentClass
* Component class (aka class constructor) to register.
*/
function registerComponent(key, ComponentClass) {
return (!!key &&
!types[key] &&
!!(types[key] = ComponentClass));
}
/* *
*
* Default Export
*
* */
const ComponentRegistry = {
registerComponent,
types
};
/* harmony default export */ const Components_ComponentRegistry = (ComponentRegistry);
;// ./code/dashboards/es-modules/Dashboards/Globals.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:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
* - Paweł Lysy
* - Karol Kołodziej
*
* */
/* *
*
* Constants
*
* */
/**
* Prefix of a GUIElement HTML class name.
*/
const classNamePrefix = 'highcharts-dashboards-';
const version = '4.2.1';
/** @internal */
const classNames = {
layout: classNamePrefix + 'layout',
cell: classNamePrefix + 'cell',
cellHover: classNamePrefix + 'cell-state-hover',
cellActive: classNamePrefix + 'cell-state-active',
cellLoading: classNamePrefix + 'cell-state-loading',
row: classNamePrefix + 'row',
layoutsWrapper: classNamePrefix + 'layouts-wrapper',
boardContainer: classNamePrefix + 'wrapper'
};
/** @internal */
const guiElementType = {
row: 'row',
cell: 'cell',
layout: 'layout'
};
/**
* Contains all Board instances of this window.
*/
const boards = [];
/**
* Reference to the window used by Dashboards.
*/
const Globals_win = window;
const Globals_doc = document;
const noop = function () { };
const isMS = /(edge|msie|trident)/i
.test((Globals_win.navigator && Globals_win.navigator.userAgent) || '') && !Globals_win.opera;
const 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 (!isMS) {
const opts = Object.defineProperty({}, 'passive', {
get: function () {
supportsPassive = true;
}
});
if (Globals_win.addEventListener && Globals_win.removeEventListener) {
Globals_win.addEventListener('testPassive', noop, opts);
Globals_win.removeEventListener('testPassive', noop, opts);
}
}
return supportsPassive;
}());
const Globals_Globals = {
boards,
classNamePrefix,
classNames,
doc: Globals_doc,
guiElementType,
isMS,
noop,
supportsPassiveEvents,
version,
win: Globals_win
};
/* *
*
* Default Export
*
* */
/* harmony default export */ const Dashboards_Globals = (Globals_Globals);
;// ./code/dashboards/es-modules/Dashboards/EditMode/EditGlobals.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:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
const PREFIX = Dashboards_Globals.classNamePrefix + 'edit-';
const EditGlobals = {
classNames: {
resizeSnap: PREFIX + 'resize-snap',
resizeSnapX: PREFIX + 'resize-snap-x',
resizeSnapY: PREFIX + 'resize-snap-y',
separator: PREFIX + 'separator',
contextMenuBtn: PREFIX + 'context-menu-btn',
contextMenuBtnText: PREFIX + 'context-menu-btn-text',
contextMenu: PREFIX + 'context-menu',
contextMenuItem: PREFIX + 'context-menu-item',
editModeEnabled: PREFIX + 'enabled',
editToolbar: PREFIX + 'toolbar',
editToolbarCellOutline: PREFIX + 'toolbar-cell-outline',
editToolbarRowOutline: PREFIX + 'toolbar-row-outline',
editToolbarItem: PREFIX + 'toolbar-item',
editToolbarRow: PREFIX + 'toolbar-row',
editToolbarCell: PREFIX + 'toolbar-cell',
editSidebar: PREFIX + 'sidebar',
editSidebarShow: PREFIX + 'sidebar-show',
editSidebarHide: PREFIX + 'sidebar-hide',
editSidebarHeader: PREFIX + 'sidebar-header',
editSidebarTitle: PREFIX + 'sidebar-title',
editSidebarMenuItem: PREFIX + 'sidebar-item',
rowContextHighlight: PREFIX + 'row-context-highlight',
cellEditHighlight: PREFIX + 'cell-highlight',
dashboardCellEditHighlightActive: PREFIX + 'cell-highlight-active',
dragMock: PREFIX + 'drag-mock',
dropPointer: PREFIX + 'drop-pointer',
contextDetectionPointer: PREFIX + 'ctx-detection-pointer',
resizePointer: PREFIX + 'resize-pointer',
currentEditedElement: PREFIX + 'unmask',
maskElement: PREFIX + 'mask',
menuItem: PREFIX + 'menu-item',
menu: PREFIX + 'menu',
menuVerticalSeparator: PREFIX + 'menu-vertical-separator',
menuHorizontalSeparator: PREFIX + 'menu-horizontal-separator',
menuDestroy: PREFIX + 'menu-destroy',
editSidebarWrapper: PREFIX + 'sidebar-wrapper',
customSelect: PREFIX + 'custom-select',
customSelectButton: PREFIX + 'custom-option-button',
toggleContainer: PREFIX + 'toggle-container',
toggleWrapper: PREFIX + 'toggle-wrapper',
toggleSlider: PREFIX + 'toggle-slider',
toggleWrapperColored: PREFIX + 'toggle-wrapper-colored',
toggleLabels: PREFIX + 'toggle-labels',
labeledToggleWrapper: PREFIX + 'labeled-toggle-wrapper',
button: PREFIX + 'button',
sidebarNavButton: PREFIX + 'sidebar-button-nav',
labelText: PREFIX + 'label-text',
editSidebarTabBtn: PREFIX + 'sidebar-tab-btn',
editToolsBtn: PREFIX + 'tools-btn',
editTools: PREFIX + 'tools',
editGridItems: PREFIX + 'grid-items',
editStandaloneToggle: PREFIX + 'standalone-toggle',
// Confirmation popup
confirmationPopup: PREFIX + 'confirmation-popup',
popupButtonContainer: PREFIX + 'confirmation-popup-button-container',
popupContentContainer: PREFIX + 'confirmation-popup-content',
popupCancelBtn: PREFIX + 'confirmation-popup-cancel-btn',
popupConfirmBtn: PREFIX + 'confirmation-popup-confirm-btn',
popupCloseButton: PREFIX + 'popup-close',
editOverlay: PREFIX + 'overlay',
editOverlayActive: PREFIX + 'overlay-active',
resizerMenuBtnActive: PREFIX + 'resizer-menu-btn-active',
sidebarCloseButton: PREFIX + 'close-btn',
editSidebarTabBtnWrapper: PREFIX + 'tabs-buttons-wrapper',
editSidebarRight: PREFIX + 'sidebar-right',
editSidebarRightShow: PREFIX + 'sidebar-right-show',
viewFullscreen: PREFIX + 'view-fullscreen',
// Accordion
accordionMenu: PREFIX + 'accordion-menu',
accordionContainer: PREFIX + 'accordion',
accordionHeader: PREFIX + 'accordion-header',
accordionHeaderBtn: PREFIX + 'accordion-header-btn',
accordionHeaderWrapper: PREFIX + 'accordion-header-wrapper',
accordionHeaderIcon: PREFIX + 'accordion-header-icon',
accordionContent: PREFIX + 'accordion-content',
accordionNestedWrapper: PREFIX + 'accordion-nested',
accordionMenuButtonsContainer: PREFIX + 'accordion-menu-buttons-container',
accordionMenuButton: PREFIX + 'accordion-menu-button',
accordionStandaloneWrapper: PREFIX + 'accordion-standalone-wrapper',
hiddenElement: PREFIX + 'hidden-element',
collapsableContentHeader: PREFIX + 'collapsable-content-header',
standaloneElement: PREFIX + 'standalone-element',
// Custom dropdown with icons
collapsedElement: PREFIX + 'collapsed-element',
dropdown: PREFIX + 'dropdown',
dropdownContent: PREFIX + 'dropdown-content',
dropdownButton: PREFIX + 'dropdown-button',
dropdownButtonContent: PREFIX + 'dropdown-button-content',
dropdownIcon: PREFIX + 'pointer',
icon: PREFIX + 'icon'
},
lang: {
accessibility: {
contextMenu: {
button: 'Context menu'
},
editMode: {
editMode: 'Edit mode toggle button'
}
},
addComponent: 'Add component',
cancelButton: 'Cancel',
caption: 'Caption',
chartClassName: 'Chart class name',
chartConfig: 'Chart configuration',
chartID: 'Chart ID',
chartOptions: 'Chart options',
chartType: 'Chart type',
connectorName: 'Connector name',
confirmButton: 'Confirm',
confirmDestroyCell: 'Do you really want to destroy the cell?',
confirmDestroyRow: 'Do you really want to destroy the row?',
confirmDiscardChanges: 'Do you really want to discard the changes?',
dataLabels: 'Data labels',
editMode: 'Edit mode',
errorMessage: 'Something went wrong',
exitFullscreen: 'Exit full screen',
htmlInput: 'HTML',
id: 'Id',
off: 'off',
on: 'on',
pointFormat: 'Point format',
settings: 'Settings',
style: 'Styles',
title: 'Title',
viewFullscreen: 'View in full screen',
sidebar: {
HTML: 'HTML',
row: 'Row',
Highcharts: 'Highcharts',
Grid: 'Grid',
KPI: 'KPI'
}
}
};
/* harmony default export */ const EditMode_EditGlobals = (EditGlobals);
;// ./code/dashboards/es-modules/Dashboards/Layout/GUIElement.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:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
class GUIElement {
/* *
*
* Static Properties
*
* */
/**
* Get offsets of the guiElement relative to the referenceElement or the
* Viewport.
*
* @param guiElement
* The element to get the offsets from.
*
* @param referenceElement
* The element to get the offsets relative to.
*
* @returns
* The offsets of the guiElement.
*/
static getOffsets(guiElement, referenceElement) {
const offset = { left: 0, top: 0, right: 0, bottom: 0 };
if (!guiElement.container) {
return offset;
}
const guiElementClientRect = guiElement.container.getBoundingClientRect();
const referenceClientRect = referenceElement ?
referenceElement.getBoundingClientRect() : { left: 0, top: 0 };
offset.left = guiElementClientRect.left - referenceClientRect.left;
offset.top = guiElementClientRect.top - referenceClientRect.top;
offset.right =
guiElementClientRect.right - referenceClientRect.left;
offset.bottom =
guiElementClientRect.bottom - referenceClientRect.top;
return offset;
}
/**
* Get dimensions of the guiElement container from offsets.
*
* @param offsets
* The offsets of the guiElement container.
*
* @returns
* The dimensions of the guiElement container.
*/
static getDimFromOffsets(offsets) {
return {
width: offsets.right - offsets.left,
height: offsets.bottom - offsets.top
};
}
/**
* Based on the element provided, generate an unique id.
*
* @param elementType
* Type of the element.
*
* @returns
* The unique id.
*/
static getElementId(elementType) {
return (Dashboards_Globals.classNamePrefix + elementType + '-' +
uniqueKey().slice(11));
}
/**
* Get width in percentages (0% - 100%).
*
* @param width
* The width of the element. Supported formats '50%' or '1/2'.
*
* @returns
* The width in percentages.
*/
static getPercentageWidth(width) {
const fractionRegEx = /^(\d{1})[\-\/\.](\d{1,2})$/;
let result;
if (fractionRegEx.test(width)) {
const match = width.match(fractionRegEx) || [], multiplier = +match[1], divider = +match[2];
result = 100 * multiplier / divider;
result = (result <= 100 ? result : 100) + '%';
}
else if (width.indexOf('%') !== -1) {
const value = parseFloat(width);
result = (value <= 100 ?
(value >= 0 ? value : 0) : 100) + '%';
}
return result;
}
/* *
*
* Functions
*
* */
/**
* Create or get existing HTML element as a GUIElement container.
*
* @param {GetElementContainerOptions} options
* Options.
*
* @returns
* The HTML element for the element container.
*/
getElementContainer(options) {
const guiElement = this;
let elem;
if (options.render) {
if (options.attribs && !options.attribs.id) {
delete options.attribs.id;
}
}
else if (typeof options.elementId === 'string') {
const div = document.getElementById(options.elementId);
if (div) {
guiElement.container = div;
}
else {
error('Element ' + options.elementId + ' does not exist');
}
}
if (options.element instanceof HTMLElement) {
elem = options.element;
}
else {
elem = createElement('div', options.attribs || {}, options.style || {}, options.parentContainer);
}
// Set bindedGUIElement event on GUIElement container.
guiElement.removeBindedEventFn = addEvent(elem, 'bindedGUIElement', function (e) {
e.guiElement = guiElement;
e.stopImmediatePropagation();
});
return elem;
}
/**
* Destroy the element, its container, event hooks and all properties.
*/
destroy() {
const guiElement = this;
// Remove bindedGUIElement event.
if (guiElement.removeBindedEventFn) {
guiElement.removeBindedEventFn();
}
// Remove HTML container.
if (guiElement.container && guiElement.container.parentNode) {
guiElement.container.parentNode.removeChild(guiElement.container);
}
// Delete all properties.
objectEach(guiElement, function (val, key) {
delete guiElement[key];
});
}
/**
* Return the GUIElement instance type.
*
* @returns
* The GUIElement instance type
*/
getType() {
return this.type;
}
changeVisibility(setVisible = true, displayStyle) {
const visibilityChanged = (this.isVisible && !setVisible ||
!this.isVisible && setVisible);
if (this.container && visibilityChanged) {
this.container.style.display = (setVisible ?
(displayStyle || 'block') :
'none');
this.isVisible = setVisible;
}
}
hide() {
this.changeVisibility(false);
}
show() {
this.changeVisibility();
}
}
/* harmony default export */ const Layout_GUIElement = (GUIElement);
;// ./code/dashboards/es-modules/Dashboards/Layout/CellHTML.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:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
/* *
*
* Class
*
* */
/**
* @internal
**/
class CellHTML extends Layout_GUIElement {
/* *
*
* Constructor
*
* */
/**
* Constructs an instance of the CellHTML class.
*
* @param {Options} options
* Options for the cell.
*/
constructor(options) {
super();
/**
* The type of a GUIElement instance.
*/
this.type = 'cell-html';
this.options = options;
this.id = options.id;
this.container = options.container;
this.mountedComponent = options.mountedComponent;
}
/**
* Destroy the element, its container, event hooks
* and mounted component.
*/
destroy() {
const cell = this;
// Destroy mounted component.
cell.mountedComponent?.destroy();
super.destroy();
}
/**
* Highlight the cell.
*/
setHighlight() {
const cell = this;
cell.container.classList.toggle(EditMode_EditGlobals.classNames.cellEditHighlight);
cell.mountedComponent?.board.container.classList.toggle(EditMode_EditGlobals.classNames.dashboardCellEditHighlightActive);
}
setActiveState() {
const cell = this;
// Apply class
if (cell.container) {
cell.container.classList.add(Dashboards_Globals.classNames.cellActive);
}
}
}
/* *
*
* Type Declarations
*
* */
/**
* Checks if a valid cell HTML instance.
*/
function isCellHTML(cellHTML) {
return cellHTML instanceof CellHTML;
}
/* *
*
* Default Export
*
* */
/* harmony default export */ const Layout_CellHTML = (CellHTML);
;// ./code/dashboards/es-modules/Dashboards/Actions/Bindings.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:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
/* *
*
* Functions
*
* */
function getGUIElement(idOrElement, parentElement) {
let guiElement;
if (typeof idOrElement === 'string' &&
document.querySelectorAll('#' + idOrElement).length > 1) {
// eslint-disable-next-line no-console
console.warn(`Multiple cells have identical ID %c${idOrElement}%c, potentially leading to unexpected behavior. \nEnsure that each cell has a unique ID on the page.`, 'font-weight: bold', '');
}
const container = parentElement ?
parentElement.querySelector('#' + idOrElement) :
document.getElementById(idOrElement);
if (container !== null) {
fireEvent(container, 'bindedGUIElement', {}, function (e) {
guiElement = e.guiElement;
});
}
return guiElement;
}
async function addComponent(options, board, cell) {
const optionsStates = options.states;
const optionsEvents = options.events;
const renderTo = options.renderTo;
if (!renderTo) {
// eslint-disable-next-line no-console
console.error('The%c renderTo%c option is required to render the component.', 'font-weight: bold', '');
return;
}
if (board.mountedComponents.filter((el) => el.options.renderTo === renderTo).length > 0) {
// eslint-disable-next-line no-console
console.error(`A component has already been declared in the cell %c${renderTo}%c use a different cell.`, 'font-weight: bold', '');
return;
}
cell = cell || Bindings.getCell(renderTo, board.container);
const componentContainer = cell?.container || document.querySelector('#' + renderTo);
if (!componentContainer || !options.type) {
// eslint-disable-next-line no-console
console.error(`The component is unable to find the HTML cell element %c${renderTo}%c to render the content.`, 'font-weight: bold', '');
return;
}
let ComponentClass = Components_ComponentRegistry.types[options.type];
if (!ComponentClass) {
// eslint-disable-next-line no-console
console.error(`The component's type %c${options.type}%c does not exist.`, 'font-weight: bold', '');
if (cell) {
ComponentClass =
Components_ComponentRegistry.types['HTML'];
options.title = {
text: board.editMode?.lang.errorMessage ||
'Something went wrong',
className: Dashboards_Globals.classNamePrefix + 'component-title-error ' +
Dashboards_Globals.classNamePrefix + 'component-title'
};
}
}
const component = new ComponentClass(cell, options, board);
const promise = component.load()['catch']((e) => {
// eslint-disable-next-line no-console
console.error(e);
return component.update({
connector: {
id: ''
},
title: {
text: board.editMode?.lang.errorMessage ||
'Something went wrong',
className: Dashboards_Globals.classNamePrefix + 'component-title-error ' +
Dashboards_Globals.classNamePrefix + 'component-title'
}
});
});
if (cell) {
component.setCell(cell);
cell.mountedComponent = component;
}
board.mountedComponents.push({
options: options,
component: component,
cell: cell || new Layout_CellHTML({
id: renderTo,
container: componentContainer,
mountedComponent: component
})
});
if (cell &&
optionsStates?.active?.enabled &&
optionsStates?.active?.isActive) {
cell.setActiveState();
component.isActive = true;
}
fireEvent(component, 'mount');
// Events
addEvent(componentContainer, 'click', () => {
// Call the component's click callback
if (optionsEvents && optionsEvents.click) {
optionsEvents.click.call(component);
}
// Default behavior
if (cell &&
component &&
componentContainer &&
optionsStates?.active?.enabled) {
cell.setActiveState();
component.isActive = true;
}
});
// States
if (optionsStates?.hover?.enabled) {
componentContainer.classList.add(Dashboards_Globals.classNames.cellHover);
}
fireEvent(component, 'afterLoad');
return promise;
}
function getCell(idOrElement, parentElement) {
const cell = getGUIElement(idOrElement, parentElement);
if (!(cell && cell.getType() === 'cell')) {
return;
}
return cell;
}
function getRow(idOrElement, parentElement) {
const row = getGUIElement(idOrElement, parentElement);
if (!(row && row.getType() === 'row')) {
return;
}
return row;
}
function getLayout(idOrElement, parentElement) {
const layout = getGUIElement(idOrElement, parentElement);
if (!(layout && layout.getType() === 'layout')) {
return;
}
return layout;
}
/* *
*
* Default Export
*
* */
const Bindings = {
addComponent,
getCell,
getLayout,
getRow
};
/* harmony default export */ const Actions_Bindings = (Bindings);
;// ./code/dashboards/es-modules/Dashboards/Accessibility/DashboardsAccessibility.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:
* - Sebastian Bochan
*
* */
/* *
*
* Functions
*
* */
/* *
*
* Class
*
* */
class DashboardsAccessibility {
/* *
*
* Constructor
*
* */
constructor(board) {
this.board = board;
this.addTabIndexToCells();
}
/* *
*
* Functions
*
* */
addTabIndexToCells() {
const components = this.board.mountedComponents;
let cell;
for (let i = 0, iEnd = components.length; i < iEnd; ++i) {
cell = components[i].cell;
if (cell && cell.container) {
cell.container.setAttribute('tabindex', -1);
}
}
}
}
/* *
*
* Default Export
*
* */
/* harmony default export */ const Accessibility_DashboardsAccessibility = (DashboardsAccessibility);
;// ./code/dashboards/es-modules/Data/DataPool.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
*
* */
/* *
*
* Class
*
* */
/**
* Data pool to load connectors on-demand.
*
* @class
* @name Data.DataPool
*
* @param {DataPoolOptions} options
* Pool options with all connectors.
*/
class DataPool {
/* *
*
* Constructor
*
* */
constructor(options) {
this.options = merge(DataPool.defaultOptions, options);
this.connectors = {};
this.waiting = {};
}
/* *
*
* Methods
*
* */
/**
* Emits an event on this data pool to all registered callbacks of the given
* event.
*
* @param {DataTableEvent} e
* Event object with event information.
*/
emit(e) {
fireEvent(this, e.type, e);
}
/**
* Loads the connector.
*
* @function Data.DataPool#getConnector
*
* @param {string} connectorId
* ID of the connector.
*
* @return {Promise<Data.DataConnectorType>}
* Returns the connector.
*/
getConnector(connectorId) {
const connector = this.connectors[connectorId];
// Already loaded
if (connector?.loaded) {
return Promise.resolve(connector);
}
let waitingList = this.waiting[connectorId];
// Start loading
if (!waitingList) {
waitingList = this.waiting[connectorId] = [];
const connectorOptions = this.getConnectorOptions(connectorId);
if (!connectorOptions) {
throw new Error(`Connector '${connectorId}' not found.`);
}
// eslint-disable-next-line @typescript-eslint/no-floating-promises
this
.loadConnector(connectorOptions)
.then((connector) => {
delete this.waiting[connectorId];
for (let i = 0, iEnd = waitingList.length; i < iEnd; ++i) {
waitingList[i][0](connector);
}
})['catch']((error) => {
delete this.waiting[connectorId];
for (let i = 0, iEnd = waitingList.length; i < iEnd; ++i) {
waitingList[i][1](error);
}
});
}
// Add request to waiting list
return new Promise((resolve, reject) => {
waitingList.push([resolve, reject]);
});
}
/**
* Returns the IDs of all connectors.
*
* @private
*
* @return {Array<string>}
* Names of all connectors.
*/
getConnectorIds() {
const connectors = this.options.connectors, connectorIds = [];
for (let i = 0, iEnd = connectors.length; i < iEnd; ++i) {
connectorIds.push(connectors[i].id);
}
return connectorIds;
}
/**
* Loads the options of the connector.
*
* @private
*
* @param {string} connectorId
* ID of the connector.
*
* @return {DataConnectorTypeOptions | undefined}
* Returns the options of the connector, or `undefined` if not found.
*/
getConnectorOptions(connectorId) {
const connectors = this.options.connectors;
for (let i = 0, iEnd = connectors.length; i < iEnd; ++i) {
if (connectors[i].id === connectorId) {
return connectors[i];
}
}
}
/**
* Tests whether the connector has never been requested.
*
* @param {string} connectorId
* Name of the connector.
*
* @return {boolean}
* Returns `true`, if the connector has never been requested, otherwise
* `false`.
*/
isNewConnector(connectorId) {
return !this.connectors[connectorId];
}
/**
* Instantiates the connector class for the given options and loads its
* data.
*
* @private
*
* @param {Data.DataPoolConnectorOptions} options
* Options of connector.
*
* @return {Promise<Data.DataConnectorType>}
* Returns the connector.
*/
loadConnector(options) {
return new Promise((resolve, reject) => {
this.emit({
type: 'load',
options
});
const ConnectorClass = Connectors_DataConnector.types[options.type];
if (!ConnectorClass) {
throw new Error(`Connector type not found. (${options.type})`);
}
const connector = this.connectors[options.id] =
new ConnectorClass(options);
// eslint-disable-next-line @typescript-eslint/no-floating-promises
connector
.load()
.then(({ converter }) => {
connector.converter = converter;
connector.loaded = true;
this.emit({
type: 'afterLoad',
options
});
resolve(connector);
})['catch'](reject);
});
}
/**
* Cancels all data connectors pending requests.
*/
cancelPendingRequests() {
const { connectors } = this;
for (const connectorKey of Object.keys(connectors)) {
connectors[connectorKey].stopPolling();
}
}
/**
* Registers a callback for a specific event.
*
* @function Highcharts.DataPool#on
*
* @param {string} type
* Event type as a string.
*
* @param {Highcharts.EventCallbackFunction<Highcharts.DataPool>} callback
* Function to register for an event callback.
*
* @return {Function}
* Function to unregister callback from the event.
*/
on(type, callback) {
return addEvent(this, type, callback);
}
/**
* Sets connector options under the specified `options.id`.
*
* @param {object} options
* Connector options to set.
*
* @param {boolean} [update]
* Whether to update the existing connector with the new options and reload
* it (`true`) or replace it with a new connector instance (`false`).
*/
async setConnectorOptions(options, update) {
const connectorsOptions = this.options.connectors;
const connectorsInstances = this.connectors;
this.emit({
type: 'setConnectorOptions',
options
});
for (let i = 0, iEnd = connectorsOptions.length; i < iEnd; ++i) {
if (connectorsOptions[i].id === options.id) {
connectorsOptions.splice(i, 1);
break;
}
}
let existingConnector = connectorsInstances[options.id];
if (existingConnector) {
if (update) {
await existingConnector.update(options, true);
}
else {
existingConnector.stopPolling();
existingConnector = void 0;
delete connectorsInstances[options.id];
}
}
if (!existingConnector) {
connectorsOptions.push(options);
}
this.emit({
type: 'afterSetConnectorOptions',
options
});
}
}
/* *
*
* Static Properties
*
* */
DataPool.defaultOptions = {
connectors: []
};
/* *
*
* Default Export
*
* */
/* harmony default export */ const Data_DataPool = (DataPool);
;// ./code/dashboards/es-modules/Dashboards/Defaults.js
/* *
*
* Dashboards default options
*
* (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
*
*
* */
/**
* Default options for the Board.
*/
const defaultOptions = {
gui: {
enabled: true,
layoutOptions: {
rowClassName: void 0,
cellClassName: void 0
},
layouts: []
},
components: []
};
/**
* Merge the default options with custom options. Commonly used for defining
* reusable templates.
*
* @param options
* The new custom board options.
*/
function setOptions(options) {
merge(true, defaultOptions, options);
}
/* *
*
* Default Export
*
* */
const Defaults = {
defaultOptions,
setOptions
};
/* harmony default export */ const Dashboards_Defaults = (Defaults);
;// ./code/dashboards/es-modules/Dashboards/Layout/Cell.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:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
/* *
*
* Class
*
* */
/**
* @internal
**/
class Cell extends Layout_GUIElement {
/* *
*
* Constructor
*
* */
/**
* Constructs an instance of the Cell class.
*
* @param {Row} row
* Reference to the row instance.
*
* @param {Options} options
* Options for the cell.
*
* @param {HTMLElement} cellElement
* The container of the cell HTML element.
*/
constructor(row, options, cellElement) {
super();
/**
* The type of GUI element.
*/
this.type = Dashboards_Globals.guiElementType.cell;
this.id = options.id;
this.options = options;
this.row = row;
this.isVisible = true;
// Get parent container
const parentContainer = document.getElementById(options.parentContainerId || '') ||
row.container;
const layoutOptions = row.layout.options || {}, rowOptions = row.options || {}, cellClassName = layoutOptions.cellClassName || '';
const cellStyle = options.style || {};
const elementStyle = merge(layoutOptions.style, rowOptions.style, cellStyle);
this.applySizeOptions(options, cellStyle, elementStyle);
this.container = this.getElementContainer({
render: row.layout.board.guiEnabled,
parentContainer: parentContainer,
attribs: {
id: options.id,
className: Dashboards_Globals.classNames.cell + ' ' +
cellClassName
},
element: cellElement,
elementId: options.id,
style: elementStyle
});
// Nested layout
if (this.options.layout) {
this.setNestedLayout();
}
}
/* *
*
* Functions
*
* */
/**
* Create a nested layout in the cell and assign it to the nestedCell
* property.
* @internal
*/
setNestedLayout() {
const board = this.row.layout.board, Layout = this.row.layout.constructor;
const optionsGui = board.options.gui;
this.nestedLayout = new Layout(board, merge({}, optionsGui && optionsGui.layoutOptions, this.options.layout, {
parentContainerId: this.options.id
}), this);
}
/**
* Destroy the element, its container, event hooks
* and mounted component.
*/
destroy() {
fireEvent(this, 'outdate');
const cell = this;
const { row } = cell;
// Destroy mounted component.
cell.mountedComponent?.destroy();
// If layout exists in the cell - destroy it
cell.nestedLayout?.destroy();
row.unmountCell(cell);
const destroyRow = row.cells?.length === 0;
super.destroy();
if (destroyRow) {
row.destroy();
}
}
/**
* Get the cell's options.
* @returns
* The JSON of cell's options.
*
* @internal
*
*/
getOptions() {
const cell = this;
if (cell.options.layout && cell.nestedLayout) {
return {
...cell.options,
layout: cell.nestedLayout.getOptions()
};
}
return cell.options;
}
changeVisibility(setVisible = true) {
super.changeVisibility(setVisible);
const cell = this, row = cell.row;
// Change row visibility if needed.
if (!cell.row.getVisibleCells().length) {
cell.row.hide();
}
else if (cell.isVisible && !row.isVisible) {
cell.row.show();
}
setTimeout(() => {
fireEvent(row, 'cellChange', { row, cell });
}, 0);
}
getParentCell(level) {
const cell = this;
let parentCell;
if (level <= cell.row.layout.level) {
if (cell.row.layout.level === level) {
return cell;
}
if (cell.row.layout.level - 1 >= 0) {
parentCell = cell.row.layout.parentCell;
if (parentCell) {
return parentCell.getParentCell(level);
}
}
}
}
// Method to get array of overlapping levels.
getOverlappingLevels(align, levelMaxGap, // Max distance between levels
offset // Analyzed cell offset
) {
const cell = this, parentCell = cell.row.layout.parentCell;
let levels = [cell.row.layout.level];
if (parentCell) {
const cellOffset = offset || Layout_GUIElement.getOffsets(cell)[align];
const parentCellOffset = Layout_GUIElement.getOffsets(parentCell)[align];
if (Math.abs(cellOffset - parentCellOffset) < levelMaxGap) {
levels = [
...levels,
...parentCell.getOverlappingLevels(align, levelMaxGap, parentCellOffset)
];
}
}
return levels;
}
/**
* Set cell size.
*
* @param width
* % value or 'auto' or px
*
* @param height
* value in px
*/
setSize(width, height) {
const cell = this, editMode = cell.row.layout.board.editMode;
if (cell.container) {
if (defined(width)) {
if (width === 'auto' &&
cell.container.style.flex !== '1 1 0%') {
cell.container.style.flex = '1 1 0%';
cell.options.width = cell.container.style.flex;
}
else {
const cellWidth = cell.convertWidthToValue(width);
if (cellWidth) {
cell.container.style.flex = '0 0 ' + cellWidth;
cell.options.width = cell.container.style.flex;
}
}
}
if (defined(height)) {
const heightValue = (typeof height === 'number' ?
height + 'px' :
height);
cell.height = cell.container.style.height = heightValue;
cell.options.height = heightValue;
}
if (editMode) {
editMode.hideContextPointer();
if (editMode.cellToolbar &&
editMode.cellToolbar.isVisible) {
if (editMode.cellToolbar.cell === cell) {
editMode.cellToolbar.showToolbar(cell);
}
else {
editMode.cellToolbar.hide();
}
}
}
// Call cellResize board event.
fireEvent(cell.row.layout.board, 'cellResize', { cell: cell });
fireEvent(cell.row, 'cellChange', { cell: cell, row: cell.row });
}
}
setHighlight(remove) {
const cell = this, editMode = cell.row?.layout.board.editMode;
if (cell.container && editMode) {
const cnt = cell.container, isSet = cnt.classList.contains(EditMode_EditGlobals.classNames.cellEditHighlight);
if (!remove && !isSet) {
cnt.classList.add(EditMode_EditGlobals.classNames.cellEditHighlight);
cell.row.layout.board.container.classList.add(EditMode_EditGlobals.classNames.dashboardCellEditHighlightActive);
cell.isHighlighted = true;
}
else if (remove && isSet) {
cnt.classList.remove(EditMode_EditGlobals.classNames.cellEditHighlight);
cell.row.layout.board.container.classList.remove(EditMode_EditGlobals.classNames.dashboardCellEditHighlightActive);
cell.isHighlighted = false;
}
}
}
/**
* Sets the active state of the cell and resets the state of other cells.
*/
setActiveState() {
const cell = this;
// Reset other boxes
cell.row.layout.board.mountedComponents.forEach((mountedComponent) => {
if (mountedComponent.cell.container) {
mountedComponent.cell.container.classList.remove(Dashboards_Globals.classNames.cellActive);
}
mountedComponent.component.isActive = false;
});
// Apply class
if (cell.container) {
cell.container.classList.add(Dashboards_Globals.classNames.cellActive);
}
}
/**
* Enables or disables the loading indicator in the cell.
*
* @internal
*/
setLoadingState(enabled = true) {
this.container?.classList?.toggle(Dashboards_Globals.classNames.cellLoading, enabled);
}
convertWidthToValue(width) {
if (typeof width === 'number') {
return width + 'px';
}
if (/px/.test(width)) {
return width;
}
return Layout_GUIElement.getPercentageWidth(width) || '';
}
applySizeOptions(options, cellStyle, elementStyle) {
const heightValue = defined(options.height) ?
options.height :
cellStyle.height;
if (defined(heightValue)) {
this.height = typeof heightValue === 'number' ?
heightValue + 'px' :
String(heightValue);
elementStyle.height = this.height;
options.height = this.height;
}
else if (defined(elementStyle.height)) {
delete elementStyle.height;
}
const widthSource = defined(options.width) ?
options.width :
cellStyle.flex;
if (defined(widthSource) &&
(typeof widthSource === 'string' ||
typeof widthSource === 'number')) {
let flexValue;
if (typeof widthSource === 'string' &&
widthSource.indexOf(' ') !== -1) {
flexValue = widthSource;
}
else if (widthSource === 'auto') {
flexValue = '1 1 0%';
}
else {
const cellWidth = this.convertWidthToValue(widthSource);
if (cellWidth) {
flexValue = '0 0 ' + cellWidth;
}
}
if (flexValue) {
elementStyle.flex = flexValue;
options.width = flexValue;
}
}
}
}
/**
* Checks if a valid cell instance.
*/
function isCell(cell) {
return (!!cell && 'row' in cell && cell.type === 'cell');
}
/* *
*
* Default Export
*
* */
/* harmony default export */ const Layout_Cell = (Cell);
;// ./code/dashboards/es-modules/Dashboards/Layout/Row.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:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
/**
* @internal
**/
class Row extends Layout_GUIElement {
/* *
*
* Static Properties
*
* */
static setContainerHeight(rowContainer, height) {
if (height) {
rowContainer.style.height = height + 'px';
}
}
/* *
*
* Constructor
*
* */
/**
* Constructs an instance of the Row class.
*
* @param {Layout} layout
* Reference to the layout instance.
*
* @param {Options} options
* Options for the row.
*
* @param {HTMLElement} rowElement
* The container of the row HTML element.
*/
constructor(layout, options, rowElement) {
super();
/**
* The type of GUI element.
*/
this.type = Dashboards_Globals.guiElementType.row;
this.layout = layout;
this.cells = [];
this.options = options;
this.isVisible = true;
// Get parent container
const parentContainer = document.getElementById(options.parentContainerId || '') ||
layout.container;
const layoutOptions = (layout.options || {}), rowClassName = layoutOptions.rowClassName || '';
this.container = this.getElementContainer({
render: layout.board.guiEnabled,
parentContainer: parentContainer,
attribs: {
id: options.id,
className: Dashboards_Globals.classNames.row + ' ' +
rowClassName
},
element: rowElement,
elementId: options.id,
style: merge(layoutOptions.style, options.style)
});
// Init rows from options.
if (this.options.cells) {
this.setCells();
}
}
/* *
*
* Functions
*
* */
/**
* Set the row cells using cell options or cellClassName.
*/
setCells() {
const row = this, cellClassName = (row.layout.options || {}).cellClassName || '', cellsElements = pick(row.options.cells, row.container && row.container.getElementsByClassName(cellClassName)) || [];
let cellElement, i, iEnd;
for (i = 0, iEnd = cellsElements.length; i < iEnd; ++i) {
cellElement = cellsElements[i];
row.addCell(row.layout.board.guiEnabled ? cellElement : { id: '' }, cellElement instanceof HTMLElement ? cellElement : void 0);
}
}
/**
* Add a new Cell instance to the row cells array.
*
* @param {CellOptions} [options]
* Options for the row cell.
*
* @param {HTMLElement} [cellElement]
* The container for a new cell HTML element.
*
* @return {Cell}
* Returns the Cell object.
*/
addCell(options, cellElement, index) {
const row = this, cell = new Layout_Cell(row, options, cellElement);
if (!defined(index)) {
row.cells.push(cell);
}
else {
row.mountCell(cell, index);
}
// Set editMode events.
if (row.layout.board.editMode) {
row.layout.board.editMode.setCellEvents(cell);
}
return cell;
}
/**
* Destroy the element, its container, event hooks
* and inner cells.
*/
destroy() {
const row = this;
const { layout } = row;
const board = row.layout.board;
const editMode = board.editMode;
// Destroy cells.
if (row.cells) {
// Copy to avoid problem with index when shifting array of cells
// during the destroy.
const rowCells = [...row.cells];
for (let i = 0, iEnd = rowCells.length; i < iEnd; ++i) {
if (rowCells[i]) {
rowCells[i].destroy();
}
}
}
if (row.layout) {
row.layout.unmountRow(row);
super.destroy();
if (layout.rows?.length === 0) {
layout.destroy();
}
}
fireEvent(editMode, 'rowDestroyed', {
target: row,
board: board
});
}
/**
* Get the row's options.
* @returns
* The JSON of row's options.
*
* @internal
*
*/
getOptions() {
const row = this, cells = [];
for (let i = 0, iEnd = row.cells.length; i < iEnd; ++i) {
cells.push(row.cells[i].getOptions());
}
return {
id: this.options.id,
style: this.options.style,
cells
};
}
setSize(height) {
Row.setContainerHeight(this.container, height);
}
// Get cell index from the row.cells array.
getCellIndex(cell) {
for (let i = 0, iEnd = this.cells?.length; i < iEnd; ++i) {
if (this.cells[i].id === cell.id) {
return i;
}
}
}
// Add cell to the row.cells array and move cell container.
mountCell(cell, index = 0) {
const row = this, nextCell = row.cells[index], prevCell = row.cells[index - 1];
if (cell.container) {
if (nextCell && nextCell.container) {
nextCell.container.parentNode.insertBefore(cell.container, nextCell.container);
}
else if (prevCell && prevCell.container) {
prevCell.container.parentNode.insertBefore(cell.container, prevCell.container.nextSibling);
}
else if (!prevCell && !nextCell && row.container) {
row.container.appendChild(cell.container);
}
row.cells.splice(index, 0, cell);
cell.row = row;
setTimeout(() => {
fireEvent(row, 'cellChange', { row, cell });
}, 0);
}
}
// Remove cell from the row.cells array.
unmountCell(cell) {
const cellIndex = this.getCellIndex(cell);
if (defined(cellIndex)) {
this.cells.splice(cellIndex, 1);
}
setTimeout(() => {
fireEvent(this, 'cellChange', { row: this, cell });
}, 0);
}
getVisibleCells() {
const cells = [];
for (let i = 0, iEnd = this.cells.length; i < iEnd; ++i) {
if (this.cells[i].isVisible) {
cells.push(this.cells[i]);
}
}
return cells;
}
changeVisibility(setVisible = true, displayStyle) {
const row = this;
super.changeVisibility(setVisible, displayStyle);
// Change layout visibility if needed.
if (!row.layout.getVisibleRows().length) {
row.layout.hide();
}
else if (row.isVisible && !row.layout.isVisible) {
row.layout.show();
}
}
show() {
this.changeVisibility(true, 'flex');
}
setHighlight(remove) {
const classList = this.container.classList;
const highlightClass = EditMode_EditGlobals.classNames.rowContextHighlight;
if (remove === true) {
classList.remove(highlightClass);
}
else {
classList.toggle(highlightClass, !remove);
}
}
// Row can have cells below each others.
// This method returns cells split into levels.
getRowLevels() {
const row = this, rowLevels = {}, rowLevelsArray = [];
let cell, cellOffsets;
for (let k = 0, kEnd = row.cells.length; k < kEnd; ++k) {
cell = row.cells[k];
if (cell.isVisible) {
cellOffsets = Layout_GUIElement.getOffsets(cell);
if (!rowLevels[cellOffsets.top]) {
rowLevels[cellOffsets.top] = {
top: cellOffsets.top,
bottom: cellOffsets.bottom,
cells: []
};
}
if (rowLevels[cellOffsets.top].bottom < cellOffsets.bottom) {
rowLevels[cellOffsets.top].bottom = cellOffsets.bottom;
}
rowLevels[cellOffsets.top].cells.push(cell);
}
}
objectEach(rowLevels, (value) => {
rowLevelsArray.push(value);
});
return rowLevelsArray;
}
// Get row level with additional info
// on a specific Y position.
getRowLevelInfo(posY) {
const rowLevels = this.getRowLevels();
let rowLevelInfo;
for (let i = 0, iEnd = rowLevels.length; i < iEnd; ++i) {
if (rowLevels[i].top <= posY && rowLevels[i].bottom > posY) {
rowLevelInfo = {
index: i,
rowLevels: rowLevels,
rowLevel: rowLevels[i]
};
}
}
return rowLevelInfo;
}
}
/* harmony default export */ const Layout_Row = (Row);
;// ./code/dashboards/es-modules/Dashboards/Layout/Layout.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:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
/**
* @internal
**/
class Layout extends Layout_GUIElement {
/* *
*
* Constructor
*
* */
/**
* Constructs an instance of the Layout class.
*
* @param {Dashboard} board
* Reference to the dashboard instance.
*
* @param {Options} options
* Options for the layout.
*/
constructor(board, options, parentCell) {
super();
/**
* The type of GUI element.
*/
this.type = Dashboards_Globals.guiElementType.layout;
this.board = board;
this.rows = [];
this.options = options;
this.isVisible = true;
// Get parent container
const parentContainer = parentCell ? parentCell.container :
document.getElementById(options.parentContainerId || '') || board.layoutsWrapper;
// Set layout level.
if (parentCell) {
this.parentCell = parentCell;
this.level = parentCell.row.layout.level + 1;
}
else {
this.level = 0;
}
// GUI structure
if (options.copyId) {
this.copyId = options.copyId;
}
const layoutOptions = (this.options || {}), layoutClassName = layoutOptions.rowClassName || '';
this.container = this.getElementContainer({
render: board.guiEnabled,
parentContainer: parentContainer,
attribs: {
id: (options.id || '') + (this.copyId ? '_' + this.copyId : ''),
className: Dashboards_Globals.classNames.layout + ' ' +
layoutClassName
},
elementId: options.id,
style: this.options.style
});
// Init rows from options.
if (this.options.rows) {
this.setRows();
}
}
/* *
*
* Functions
*
* */
/**
* Set the layout rows using rows options or rowClassName.
*/
setRows() {
const layout = this, rowsElements = pick(layout.options.rows, layout.container && layout.container.getElementsByClassName(layout.options.rowClassName || '')) || [];
let rowElement, i, iEnd;
for (i = 0, iEnd = rowsElements.length; i < iEnd; ++i) {
rowElement = rowsElements[i];
layout.addRow(layout.board.guiEnabled ? rowElement : {}, rowElement instanceof HTMLElement ? rowElement : void 0);
}
}
/**
* Add a new Row instance to the layout rows array.
*
* @param {RowOptions} options
* Options of a row.
*
* @param {HTMLElement} rowElement
* The container for a new row HTML element.
*
* @return {Row}
* Returns the Row object.
*/
addRow(options, rowElement, index) {
const layout = this, row = new Layout_Row(layout, options, rowElement);
if (!defined(index)) {
layout.rows.push(row);
}
else {
layout.mountRow(row, index);
}
// Set editMode events.
if (layout.board.editMode) {
layout.board.editMode.setRowEvents(row);
}
return row;
}
/**
* Destroy the element, its container, event hooks
* and inner rows.
*/
destroy() {
const layout = this;
for (let i = layout.board.layouts.length - 1; i >= 0; i--) {
if (layout.board.layouts[i] === layout) {
layout.board.layouts.splice(i, 1);
}
}
if (layout.parentCell) {
delete layout.parentCell.nestedLayout;
}
// Destroy rows.
for (let i = layout.rows.length - 1; i >= 0; i--) {
layout.rows[i].destroy();
}
if (layout.parentCell) {
layout.parentCell.destroy();
}
super.destroy();
}
// Get row index from the layout.rows array.
getRowIndex(row) {
for (let i = 0, iEnd = this.rows.length; i < iEnd; ++i) {
if (this.rows[i] === row) {
return i;
}
}
}
// Add cell to the layout.rows array and move row container.
mountRow(row, index) {
const nextRow = this.rows[index], prevRow = this.rows[index - 1];
if (row.container) {
if (nextRow && nextRow.container) {
nextRow.container.parentNode.insertBefore(row.container, nextRow.container);
}
else if (prevRow && prevRow.container) {
prevRow.container.parentNode.insertBefore(row.container, prevRow.container.nextSibling);
}
this.rows.splice(index, 0, row);
row.layout = this;
}
}
// Remove row from the layout.rows array.
unmountRow(row) {
const rowIndex = this.getRowIndex(row);
if (defined(rowIndex)) {
this.rows.splice(rowIndex, 1);
}
}
getVisibleRows() {
const rows = [];
for (let i = 0, iEnd = this.rows.length; i < iEnd; ++i) {
if (this.rows[i].isVisible) {
rows.push(this.rows[i]);
}
}
return rows;
}
changeVisibility(setVisible = true) {
const layout = this;
super.changeVisibility(setVisible);
// Change parentCell visibility.
if (layout.parentCell) {
if (layout.isVisible && !layout.parentCell.isVisible) {
layout.parentCell.show();
}
else if (!layout.isVisible && layout.parentCell.isVisible) {
layout.parentCell.hide();
}
}
}
/**
* Get the layout's options.
* @returns
* Layout's options.
*
* @internal
*
*/
getOptions() {
const layout = this, rows = [];
// Get rows JSON.
for (let i = 0, iEnd = layout.rows.length; i < iEnd; ++i) {
rows.push(layout.rows[i].getOptions());
}
return {
id: this.options.id,
layoutClassName: this.options.layoutClassName,
rowClassName: this.options.rowClassName,
cellClassName: this.options.cellClassName,
style: this.options.style,
rows
};
}
}
/* harmony default export */ const Layout_Layout = (Layout);
;// ./code/dashboards/es-modules/Dashboards/CallbackRegistry.js
class CallbackRegistry {
constructor() {
this.registry = {};
}
addCallback(id, callback) {
this.registry[id] = callback;
}
getCallback(id) {
return this.registry[id];
}
/** @internal */
toJSON() {
const json = {};
Object.keys(this.registry).forEach((key) => {
const entry = this.getCallback(key);
const { func, type } = entry;
json[key] = {
func: func.toString(),
type
};
});
return json;
}
}
/* harmony default export */ const Dashboards_CallbackRegistry = (CallbackRegistry);
;// ./code/dashboards/es-modules/Dashboards/Components/ConnectorHandler.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:
* - Dawid Draguła
*
* */
/* *
*
* Class
*
* */
/**
* A class that handles the connection between the component and the data
* connector.
*/
class ConnectorHandler {
/* *
*
* Constructor
*
* */
/**
* Creates an object that manages the data layer for the component.
*
* @param component
* The component that the connector is tied to.
*
* @param options
* The options for the connector.
*
*/
constructor(component, options) {
/**
* Event listeners tied to the current DataTable. Used for rerendering the
* component on data changes.
* @internal
*/
this.tableEvents = [];
this.component = component;
this.options = options;
}
/* *
*
* Functions
*
* */
/**
* Inits connectors for the component and rerenders it.
*
* @returns
* Promise resolving to the component.
*/
async initConnector() {
const component = this.component;
const connectorId = this.options.id;
const dataPool = this.component.board.dataPool;
if (connectorId &&
(this.connectorId !== connectorId ||
dataPool.isNewConnector(connectorId))) {
if (isCell(component.cell)) {
component.cell.setLoadingState();
}
const connector = await dataPool.getConnector(connectorId);
// The connector shouldn't be set if the handler was destroyed
// during its creation.
if (!this.destroyed) {
this.setConnector(connector);
}
}
return component;
}
/**
* Sets the data table settings and events.
*
* @param table
* The data table instance for settings and events.
*/
setTable(table) {
// Set up event listeners
this.clearTableListeners(table);
this.setupTableListeners(table);
// Re-setup if modifier changes
table.on('setModifier', () => this.clearTableListeners(table));
table.on('afterSetModifier', (e) => {
if (e.type === 'afterSetModifier' && e.modified) {
this.setupTableListeners(e.modified);
this.component.emit({
type: 'tableChanged',
connector: this.connector
});
}
});
this.dataTable = table;
}
/**
* Sets the connector for the component connector handler.
*
* @param connector
* The connector to set.
*/
setConnector(connector) {
// Clean up old event listeners
while (this.tableEvents.length) {
const eventCallback = this.tableEvents.pop();
if (typeof eventCallback === 'function') {
eventCallback();
}
}
this.connector = connector;
if (connector) {
this.setTable(connector.getTable(this.options.dataTableKey));
}
this.addConnectorAssignment();
return this.component;
}
/**
* Adds event listeners to data table.
* @param table
* Data table that is source of data.
* @internal
*/
setupTableListeners(table) {
const connector = this.connector;
if (connector) {
if (table) {
[
'afterDeleteRows',
'afterSetCell',
'afterSetColumns',
'afterSetRows'
].forEach((event) => {
this.tableEvents.push(table.on(event, (e) => {
clearTimeout(this.tableEventTimeout);
this.tableEventTimeout = Dashboards_Globals.win.setTimeout(() => {
this.component.emit({
...e,
type: 'tableChanged',
targetConnector: connector
});
this.tableEventTimeout = void 0;
});
}));
});
}
}
}
/**
* Remove event listeners in data table.
*
* @param table
* The connector data table (data source).
*
* @internal
*/
clearTableListeners(table) {
const connector = this.connector;
const tableEvents = this.tableEvents;
this.removeTableEvents();
if (connector) {
tableEvents.push(table.on('afterSetModifier', (e) => {
if (e.type === 'afterSetModifier') {
clearTimeout(this.tableEventTimeout);
this.tableEventTimeout = Dashboards_Globals.win.setTimeout(() => {
this.component.emit({
...e,
type: 'tableChanged',
targetConnector: connector
});
this.tableEventTimeout = void 0;
});
}
}));
}
}
/**
* Adds the component to the provided connector.
* Starts the connector polling if inactive and one component is provided.
*/
addConnectorAssignment() {
const { connector } = this;
if (!connector) {
return;
}
if (!connector.components) {
connector.components = [];
}
if (!connector.components.includes(this.component)) {
const options = connector.options;
// Add the component assignment.
connector.components.push(this.component);
// Start the connector polling.
if ('enablePolling' in options &&
options.enablePolling &&
!connector.polling &&
connector.components.length === 1 &&
'dataRefreshRate' in options) {
connector.startPolling(Math.max(options.dataRefreshRate || 0, 1) * 1000);
}
}
}
/**
* Removes the component instance from the provided connector.
* Stops the connector polling if the last element is removed.
*/
removeConnectorAssignment() {
const { connector } = this;
if (!connector?.components) {
return;
}
const index = connector.components.indexOf(this.component);
if (index > -1) {
connector.components.splice(index, 1);
if (!connector.components.length) {
connector.stopPolling();
delete connector.components;
}
}
}
/**
* Clears all event listeners in the table.
*/
removeTableEvents() {
this.tableEvents.forEach((clearEvent) => clearEvent());
this.tableEvents.length = 0;
}
/**
* Updates the options for the connector handler.
*
* @param newOptions
* The new options to update.
*/
updateOptions(newOptions) {
this.options = newOptions;
}
/**
* Destroys the connector handler.
* @internal
*/
destroy() {
this.destroyed = true;
this.removeConnectorAssignment();
this.removeTableEvents();
}
}
/* harmony default export */ const Components_ConnectorHandler = (ConnectorHandler);
;// ./code/dashboards/es-modules/Dashboards/Components/EditableOptions.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:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
class EditableOptions {
constructor(component, bindings = EditableOptions.defaultBindings) {
this.component = component;
this.bindings = bindings;
}
getOptions() {
const options = this.component.options.editableOptions;
if (!options) {
return [];
}
for (let i = 0, iEnd = options.length; i < iEnd; i++) {
const option = options[i];
if (option.propertyPath?.some((path) => path === 'connector')) {
const board = this.component.board;
const selectOptions = !board ?
[] :
board.dataPool
.getConnectorIds()
.map((name) => ({ name }));
option.selectOptions = selectOptions;
}
}
return options;
}
}
EditableOptions.defaultBindings = {
keyMap: {
color: 'colorPicker',
title: 'text',
caption: 'text',
style: 'textarea'
},
typeMap: {
'string': 'text',
'number': 'input',
'boolean': 'toggle'
},
skipRedraw: []
};
// Bindings of basic types to "editor components"
EditableOptions.defaultTypeMap = {
'string': 'text',
'number': 'input',
'boolean': 'toggle'
};
/* harmony default export */ const Components_EditableOptions = (EditableOptions);
;// ./code/dashboards/es-modules/Dashboards/Components/Sync/Emitter.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:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
/**
* Class responsible for adding event listeners on a component
* @internal
*/
class SyncEmitter {
/**
* Adds an emitter to the emitter registry.
*
* @param emitter the emitter to add to the registry.
*/
static register(emitter) {
const { id } = emitter;
this.registry[id] = emitter;
}
/**
* Gets an emitter from emitter registry.
*
* @param emitterID The ID of the emitter to get.
*/
static get(emitterID) {
return this.registry[emitterID];
}
/**
* Creates a new emitter instance.
*
* @param id An unique ID for the emitter.
*
* @param func
* The function to be called when the emitter is activated.
*/
constructor(id, func) {
this.id = id;
this.func = func;
SyncEmitter.register(this);
}
/**
* Attaches the emitter to a component.
*
* @param component The component to attach to.
*/
create(component) {
this.callback = this.func.call(component);
}
/**
* To be used when removing the emitter from the component.
* Calls the {@link callback} function.
*/
remove() {
if (this.callback) {
this.callback();
}
}
}
/**
* Registry for reusable emitter.
* The emitter is stored by ID.
*/
SyncEmitter.registry = {};
/* harmony default export */ const Emitter = (SyncEmitter);
;// ./code/dashboards/es-modules/Dashboards/Components/Sync/Handler.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:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
/* *
*
* Class
*
* */
/**
* Class responsible for storing handler callbacks used in component sync.
* @internal
*/
class SyncHandler {
/**
* Adds a handler to the handler registry.
*
* @param handler
* The handler to add to the registry.
*/
static register(handler) {
const { id } = handler;
this.registry[id] = handler;
}
/**
* Gets a handler from handler registry.
*
* @param handlerID
* The ID of the handler to get.
*/
static get(handlerID) {
return this.registry[handlerID];
}
/**
* Creates a new handler instance.
*
* @param id
* An unique ID for the handler.
*
* @param func
* The function to be called when the handler is activated.
*/
constructor(id, func) {
this.id = id;
this.func = func;
SyncHandler.register(this);
}
/**
* Calls the activation function on the component and sets the callback to
* the return function.
*
* @param component
* The component to register on.
*/
register(component) {
const { func } = this;
this.callback = func.call(component);
}
/**
* To be used when removing the handler from the component.
* Calls the {@link callback} function.
*/
remove() {
if (this.callback) {
this.callback();
}
}
}
/**
* Registry for reusable handlers.
* The handler is stored by ID.
*/
SyncHandler.registry = {};
/* *
*
* Default Export
*
* */
/* harmony default export */ const Handler = (SyncHandler);
;// ./code/dashboards/es-modules/Dashboards/Components/Sync/Sync.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:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
/* *
*
* Class
*
* */
/** @internal */
class Sync {
/* *
*
* Constructor
*
* */
/**
* Creates an instance of the sync class.
*
* @param component
* The component to which the emitters and handlers are attached.
*
* @param predefinedSyncConfig
* The predefined sync configuration.
*/
constructor(component, predefinedSyncConfig) {
this.component = component;
this.predefinedSyncConfig = predefinedSyncConfig;
this.syncConfig = Sync.prepareSyncConfig(predefinedSyncConfig, component.options.sync);
this.registeredSyncHandlers = {};
this.registeredSyncEmitters = {};
this.isSyncing = false;
this.listeners = [];
}
/* *
*
* Functions
*
* */
/**
* Method that prepares the sync configuration from the predefined config
* and current component options.
*
* @param predefinedConfig The predefined sync configuration.
* @param componentSyncOptions The current component sync options.
* @returns The sync configuration.
*/
static prepareSyncConfig(predefinedConfig, componentSyncOptions = {}) {
const { defaultSyncPairs: defaultPairs, defaultSyncOptions: defaultOptionsList } = predefinedConfig;
return Object.keys(componentSyncOptions).reduce((acc, syncName) => {
if (syncName) {
const defaultPair = defaultPairs[syncName];
const defaultOptions = defaultOptionsList[syncName];
const entry = componentSyncOptions[syncName];
const preparedOptions = merge(defaultOptions || {}, { enabled: isObject(entry) ? entry.enabled : entry }, isObject(entry) ? entry : {});
if (defaultPair && preparedOptions.enabled) {
const keys = [
'emitter',
'handler'
];
for (const key of keys) {
if (preparedOptions[key] === true ||
preparedOptions[key] === void 0) {
preparedOptions[key] =
defaultPair[key];
}
}
}
acc[syncName] = preparedOptions;
}
return acc;
}, {});
}
/**
* Add new emitter to the registered emitters.
*
* @param emitter
* The emitter to register.
*/
registerSyncEmitter(emitter) {
const { id } = emitter;
this.registeredSyncEmitters[id] = emitter;
}
/**
* Method that checks if the emitter is registered.
*
* @param id
* The id of the emitter to check.
*
* @returns
* Whether the emitter is registered.
*/
isRegisteredEmitter(id) {
return Boolean(this.registeredSyncEmitters[id]);
}
/**
* Register new handler to the registered handlers.
*
* @param handler
* The handler to register.
*/
registerSyncHandler(handler) {
const { id } = handler;
this.registeredSyncHandlers[id] = handler;
}
/**
* Method that checks if the handler is registered.
*
* @param handlerID
* The id of the handler to check.
*
* @returns
* Whether the handler is registered.
*/
isRegisteredHandler(handlerID) {
return Boolean(this.registeredSyncHandlers[handlerID]);
}
/**
* Registers the handlers and emitters on the component
*/
start() {
const { component } = this;
this.syncConfig = Sync.prepareSyncConfig(this.predefinedSyncConfig, component.options.sync);
for (const id of Object.keys(this.syncConfig)) {
const syncOptions = this.syncConfig[id];
if (!syncOptions) {
continue;
}
let { emitter: emitterConfig, handler: handlerConfig } = syncOptions;
if (handlerConfig) {
if (handlerConfig === true) {
handlerConfig =
Sync.defaultHandlers[id]
.handler;
}
const handler = new Handler(id, handlerConfig);
if (!this.isRegisteredHandler(handler.id)) {
this.registerSyncHandler(handler);
handler.register(component);
}
}
if (emitterConfig) {
if (emitterConfig === true) {
emitterConfig =
Sync.defaultHandlers[id]
.emitter;
}
const emitter = new Emitter(id, emitterConfig);
if (!this.isRegisteredEmitter(emitter.id)) {
this.registerSyncEmitter(emitter);
emitter.create(component);
}
}
}
this.isSyncing = true;
this.listeners.push(component.on('update', () => this.stop()));
}
/**
* Removes the handlers and emitters from the component.
*/
stop() {
const { component, listeners, registeredSyncHandlers, registeredSyncEmitters } = this;
Object.keys(registeredSyncHandlers).forEach((id) => {
registeredSyncHandlers[id].remove();
delete registeredSyncHandlers[id];
});
Object.keys(registeredSyncEmitters).forEach((id) => {
registeredSyncEmitters[id].remove();
delete registeredSyncEmitters[id];
});
this.isSyncing = false;
for (let i = 0, iEnd = listeners.length; i < iEnd; ++i) {
listeners[i]();
}
this.listeners.length = 0;
this.listeners.push(component.on('afterUpdate', () => {
this.start();
}));
}
}
/**
* Default handlers for the sync class. This property is extended by
* different Components, where default syncs are added. Allows overwriting
* the configuration before creating the dashboard.
*/
Sync.defaultHandlers = {};
/* *
*
* Default Export
*
* */
/* harmony default export */ const Sync_Sync = (Sync);
;// ./code/dashboards/es-modules/Dashboards/Components/ComponentUtilities.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:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
/* *
*
* Functions
*
* */
function getMargins(element, includeBorders = true) {
const borders = {
x: ['borderLeft', 'borderRight'],
y: ['borderTop', 'borderBottom']
};
return {
y: getStyles(element, [
'marginTop',
'marginBottom',
...(includeBorders ? borders.y : [])
]).reduce(sumPixels, 0),
x: getStyles(element, [
'marginLeft',
'marginTop',
...(includeBorders ? borders.x : [])
]).reduce(sumPixels, 0)
};
}
function getPaddings(element) {
return {
x: getStyles(element, ['paddingLeft', 'paddingRight']).reduce(sumPixels, 0),
y: getStyles(element, ['paddingTop', 'paddingBottom']).reduce(sumPixels, 0)
};
}
function getStyles(element, styles) {
const elementStyles = window.getComputedStyle(element);
return styles.map((style) => elementStyles[style]); // Cannot use getPropertyValue?
}
function sumPixels(accumulator, value) {
if (value) {
accumulator += (typeof value === 'number' ? value : parseFloat(value));
}
return accumulator;
}
/* *
*
* Default Export
*
* */
const ComponentUtilities = {
getMargins,
getPaddings,
getStyles,
sumPixels
};
/* harmony default export */ const Components_ComponentUtilities = (ComponentUtilities);
;// ./code/dashboards/es-modules/Dashboards/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
*
*
* Authors:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
/* *
*
* Functions
*
* */
/**
* Returns a deep copy of an argument. It differs from `merge` in that it copies
* also arrays.
*
* @param value
* The value to clone.
*
* @param excludedKeys
* An array of keys to exclude from the clone.
*/
function deepClone(value, excludedKeys) {
if (Array.isArray(value)) {
return value.map((v) => deepClone(v, excludedKeys));
}
if (value && typeof value === 'object') {
const clone = {};
const keys = Object.keys(value);
for (const key of keys) {
if (excludedKeys && excludedKeys.includes(key)) {
clone[key] = value[key];
}
else {
clone[key] = deepClone(value[key], excludedKeys);
}
}
return clone;
}
return value;
}
/**
* 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 Dashboards.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.
*
* @return {void}
*/
function Utilities_error(code, stop) {
// TODO- replace with proper error handling
if (code === 16) {
console.warn(// eslint-disable-line no-console
'Dashboard error: Dashboards library loaded more than once.' +
'This may cause undefined behavior.');
return;
}
error(code, stop);
}
/* *
*
* Default Export
*
* */
const Utilities = {
deepClone,
error: Utilities_error
};
/* harmony default export */ const Dashboards_Utilities = (Utilities);
;// ./code/dashboards/es-modules/Dashboards/Components/Component.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:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
* - Dawid Draguła
*
* */
const { classNamePrefix: Component_classNamePrefix } = Dashboards_Globals;
const { getMargins: Component_getMargins, getPaddings: Component_getPaddings } = Components_ComponentUtilities;
const { deepClone: Component_deepClone } = Dashboards_Utilities;
/* *
*
* Class
*
* */
/**
* Abstract class of component.
*/
class Component {
/* *
*
* Static Functions
*
* */
/**
* Creates HTML text element like header or title
*
* @param tagName
* HTML tag name used as wrapper of text like `h2` or `p`.
*
* @param elementName
* Name of element
*
* @param textOptions
* The options for the component
*
* @returns
* HTML object when title is created, otherwise undefined
*
* @internal
*/
static createTextElement(tagName, elementName, textOptions) {
if (typeof textOptions === 'object') {
const { className, text, style } = textOptions;
return createElement(tagName, {
className: className || `${Component_classNamePrefix}component-${elementName}`,
textContent: text
}, style);
}
if (typeof textOptions === 'string') {
return createElement(tagName, {
className: `${Component_classNamePrefix}component-${elementName}`,
textContent: textOptions
}, {});
}
}
/* *
*
* Constructor
*
* */
/**
* Creates a component in the cell.
*
* @param cell
* Instance of cell, where component is attached.
*
* @param options
* The options for the component.
*/
constructor(cell, options, board) {
/**
* The connector handlers for the component. They are used to handle the
* connector options and data tables.
*/
this.connectorHandlers = [];
/**
* Registry of callbacks registered on the component. Used in the Highcharts
* component to keep track of chart events.
* @internal
*/
this.callbackRegistry = new Dashboards_CallbackRegistry();
/**
* Event listeners tied to the parent cell. Used for rendering/resizing the
* component on interactions.
* @internal
*/
this.cellListeners = [];
/**
* Timeouts for calls to `Component.resizeTo()`.
* @internal
*/
this.resizeTimeouts = [];
/**
* Timeouts for resizing the content. I.e. `chart.setSize()`.
* @internal
*/
this.innerResizeTimeouts = [];
const renderTo = options.renderTo;
this.board = board || cell?.row?.layout?.board || {};
this.parentElement =
cell?.container || document.querySelector('#' + renderTo);
this.cell = cell;
this.options = merge(Component.defaultOptions, options);
this.id = this.options.id && this.options.id.length ?
this.options.id :
uniqueKey();
if (this.options.connector) {
const connectorOptionsArray = isArray(this.options.connector) ?
this.options.connector :
[this.options.connector];
for (const connectorOptions of connectorOptionsArray) {
this.connectorHandlers.push(new Components_ConnectorHandler(this, connectorOptions));
}
}
this.editableOptions =
new Components_EditableOptions(this, options.editableOptionsBindings);
this.dimensions = {
width: null,
height: null
};
this.element = createElement('div', {
className: this.options.className
}, {}, this.parentElement);
if (!Number(getStyle(this.element, 'padding'))) {
// Fix flex problem, because of wrong height in internal elements
this.element.style.padding = '0.1px';
}
this.contentElement = createElement('div', {
className: `${this.options.className}-content`
}, {}, this.element, true);
this.sync = new Sync_Sync(this, this.constructor.predefinedSyncConfig);
this.setupEventListeners();
if (cell) {
this.attachCellListeners();
this.on('update', () => {
if (isCell(this.cell)) {
this.cell.setLoadingState();
}
});
this.on('afterRender', () => {
if (isCell(this.cell)) {
this.cell.setLoadingState(false);
}
});
}
this.on('tableChanged', () => {
this.onTableChanged();
});
}
/**
* Returns the component's options when it is dropped from the sidebar.
*
* @param sidebar
* The sidebar popup.
*/
getOptionsOnDrop(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
sidebar) {
return {};
}
/**
* Returns the first connector of the component if it exists.
*
* @internal
* @deprecated
*/
getFirstConnector() {
return this.connectorHandlers[0]?.connector;
}
/**
* Returns the data table connected to the component by the `connectorId`
* and `dataTableKey`. If both args are undefined, the first data table is
* returned.
*
* @param connectorId
* The id of the connector.
*
* @param dataTableKey
* The key of the data table within the connector.
*
* @returns
* The data table, or undefined if no matching handler is found.
*/
getDataTable(connectorId, dataTableKey) {
for (const handler of this.connectorHandlers) {
if ((!connectorId ||
handler.options.id === connectorId) && (!dataTableKey ||
handler.options.dataTableKey === dataTableKey)) {
return handler.dataTable;
}
}
}
/**
* Setup listeners on cell/other things up the chain
* @internal
*/
attachCellListeners() {
// Remove old listeners
while (this.cellListeners.length) {
const destroy = this.cellListeners.pop();
if (destroy) {
destroy();
}
}
if (this.cell &&
isCell(this.cell) &&
Object.keys(this.cell).length) {
const board = this.cell.row.layout.board;
this.cellListeners.push(
// Listen for resize on dashboard
addEvent(board, 'cellResize', () => {
this.resizeTo(this.parentElement);
}),
// Listen for changed parent
addEvent(this.cell.row, 'cellChange', (e) => {
const { row } = e;
if (row && this.cell) {
const hasLeftTheRow = row.getCellIndex(this.cell) === void 0;
if (hasLeftTheRow) {
if (this.cell) {
this.setCell(this.cell);
}
}
}
}));
}
}
/**
* Set a parent cell.
*
* @param cell
* Instance of a cell.
*
* @param resize
* Flag that allow to resize the component.
*
* @internal
*/
setCell(cell, resize = false) {
this.cell = cell;
if (cell.container) {
this.parentElement = cell.container;
}
this.attachCellListeners();
if (resize) {
this.resizeTo(this.parentElement);
}
}
/**
* Initializes connector handlers for the component.
*/
async initConnectors() {
fireEvent(this, 'setConnectors', {
connectorHandlers: this.connectorHandlers
});
for (const connectorHandler of this.connectorHandlers) {
await connectorHandler.initConnector();
}
fireEvent(this, 'afterSetConnectors', {
connectorHandlers: this.connectorHandlers
});
return this;
}
/**
* Gets height of the component's content.
*
* @returns
* Current height as number.
*
* @internal
*/
getContentHeight() {
const titleHeight = this.titleElement ?
this.titleElement.clientHeight + Component_getMargins(this.titleElement).y :
0;
const captionHeight = this.captionElement ?
this.captionElement.clientHeight +
Component_getMargins(this.captionElement).y :
0;
return titleHeight + captionHeight;
}
/**
* Resize the component
*
* @param width
* The width to set the component to.
* Can be pixels, a percentage string or null.
* Null will unset the style
*
* @param height
* The height to set the component to.
* Can be pixels, a percentage string or null.
* Null will unset the style.
*/
resize(width, height) {
if (height) {
// Get offset for border, padding
const pad = Component_getPaddings(this.element).y + Component_getMargins(this.element).y;
this.element.style.height = 'calc(100% - ' + pad + 'px)';
this.contentElement.style.height =
'calc(100% - ' + this.getContentHeight() + 'px)';
}
else if (height === null) {
this.dimensions.height = null;
this.element.style.removeProperty('height');
}
fireEvent(this, 'resize', {
width,
height
});
}
/**
* It's a temporary alternative for the `resize` method. It sets the strict
* pixel height for the component so that the content can be distributed in
* the right way, without looping the resizers in the content and container.
*
* @param width
* The width to set the component to.
*
* @param height
* The height to set the component to.
*/
resizeDynamicContent(width, height) {
const { element } = this;
if (height) {
const margins = Component_getMargins(element).y;
const paddings = Component_getPaddings(element).y;
if (typeof height === 'string') {
height = parseFloat(height);
}
height = Math.round(height);
element.style.height = `${height - margins - paddings}px`;
this.contentElement.style.height = `${element.clientHeight - this.getContentHeight() - paddings}px`;
}
else if (height === null) {
this.dimensions.height = null;
element.style.removeProperty('height');
}
fireEvent(this, 'resize', {
width,
height
});
}
/**
* Adjusts size of component to parent's cell size when animation is done.
*
* @param element
* HTML element that is resized.
*/
resizeTo(element) {
while (this.resizeTimeouts.length) {
const timeout = this.resizeTimeouts.pop();
if (timeout) {
cancelAnimationFrame(timeout);
}
}
const timeoutID = requestAnimationFrame(() => {
const { width, height } = element.getBoundingClientRect();
const padding = Component_getPaddings(element);
const margins = Component_getMargins(element);
this.resize(width - padding.x - margins.x, height - padding.y - margins.y);
});
this.resizeTimeouts.push(timeoutID);
}
/**
* Handles updating via options.
*
* @param newOptions
* The options to apply.
*
* @param shouldRerender
* Set to true if the update should rerender the component.
*/
async update(newOptions, shouldRerender = true) {
const eventObject = {
options: newOptions,
shouldForceRerender: false
};
// Update options
fireEvent(this, 'update', eventObject);
if (newOptions.connector && Array.isArray(this.options.connector)) {
this.options.connector = void 0;
}
this.options = merge(this.options, newOptions);
const connectorOptions = (this.options.connector ? (isArray(this.options.connector) ? this.options.connector :
[this.options.connector]) : []);
let connectorsHaveChanged = connectorOptions.length !== this.connectorHandlers.length;
if (!connectorsHaveChanged) {
for (let i = 0, iEnd = connectorOptions.length; i < iEnd; i++) {
const oldOptions = this.connectorHandlers[i]?.options;
const newOptions = connectorOptions[i];
// Check if the connector id has changed.
if (oldOptions.id !== newOptions.id) {
connectorsHaveChanged = true;
break;
}
// Check if the data table key has changed.
if (oldOptions.dataTableKey !== newOptions.dataTableKey) {
connectorsHaveChanged = true;
break;
}
this.connectorHandlers[i].updateOptions(newOptions);
}
}
if (connectorsHaveChanged) {
for (const connectorHandler of this.connectorHandlers) {
connectorHandler.destroy();
}
this.connectorHandlers.length = 0;
for (const options of connectorOptions) {
this.connectorHandlers.push(new Components_ConnectorHandler(this, options));
}
await this.initConnectors();
}
if (shouldRerender || eventObject.shouldForceRerender) {
this.render();
}
}
/**
* Private method which sets up event listeners for the component.
*
* @internal
*/
setupEventListeners() {
const events = this.options.events;
if (events) {
Object.keys(events).forEach((key) => {
const eventCallback = events[key];
if (eventCallback) {
this.callbackRegistry.addCallback(key, {
type: 'component',
func: eventCallback
});
}
});
objectEach(events, (eventCallback, eventType) => {
if (isFunction(eventCallback)) {
this.on(eventType, eventCallback);
}
});
}
const resizeObserverCallback = () => {
this.resizeTo(this.parentElement);
};
if (typeof ResizeObserver === 'function') {
this.resizeObserver = new ResizeObserver(resizeObserverCallback);
this.resizeObserver.observe(this.element);
}
else {
const unbind = addEvent(window, 'resize', resizeObserverCallback);
addEvent(this, 'destroy', unbind);
}
}
/**
* Adds title at the top of component's container.
*
* @param titleOptions
* The options for the title.
*/
setTitle(titleOptions) {
const titleElement = this.titleElement, shouldExist = titleOptions &&
(typeof titleOptions === 'string' || titleOptions.text);
if (shouldExist) {
const newTitle = Component.createTextElement('h2', 'title', titleOptions);
if (newTitle) {
if (!titleElement) {
this.element.insertBefore(newTitle, this.element.firstChild);
}
else {
titleElement.replaceWith(newTitle);
}
this.titleElement = newTitle;
}
}
else {
if (titleElement) {
titleElement.remove();
delete this.titleElement;
return;
}
}
}
/**
* Adds caption at the bottom of component's container.
*
* @param captionOptions
* The options for the caption.
*/
setCaption(captionOptions) {
const captionElement = this.captionElement, shouldExist = captionOptions &&
(typeof captionOptions === 'string' || captionOptions.text);
if (shouldExist) {
const newCaption = Component.createTextElement('div', 'caption', captionOptions);
if (newCaption) {
if (!captionElement) {
this.element.appendChild(newCaption);
}
else {
captionElement.replaceWith(newCaption);
}
this.captionElement = newCaption;
}
}
else {
if (captionElement) {
captionElement.remove();
delete this.captionElement;
return;
}
}
}
/**
* Handles setting things up on initial render.
*
* @returns
* The component for chaining.
*
* @internal
*/
async load() {
await this.initConnectors();
this.render();
return this;
}
/**
* Renders the component.
*
* @returns
* The component for chaining.
*
* @internal
*/
render() {
this.emit({ type: 'render' });
this.setTitle(this.options.title);
this.setCaption(this.options.caption);
this.resizeTo(this.parentElement);
return this;
}
/**
* Destroys the component.
*/
destroy() {
/**
* TODO: Should perhaps set an `isActive` flag to false.
*/
if (this.sync.isSyncing) {
this.sync.stop();
}
while (this.element.firstChild) {
this.element.firstChild.remove();
}
// Call unmount
fireEvent(this, 'unmount');
for (const connectorHandler of this.connectorHandlers) {
connectorHandler.destroy();
}
// Used to removed the onTableChanged event.
removeEvent(this);
this.element.remove();
}
/**
* Adds an event listener to the component.
*
* @param type
* The type of event to listen for.
*
* @param callback
* The callback to call when the event is triggered.
*
* @returns
* The function to remove the event listener.
*
* @internal
*/
on(type, callback) {
return addEvent(this, type, callback);
}
/** @internal */
emit(e) {
if (!e.target) {
e.target = this;
}
fireEvent(this, e.type, e);
}
/**
* Get the component's options.
* @returns
* The JSON of component's options.
*
* @internal
*
*/
getOptions() {
return diffObjects(this.options, Component.defaultOptions);
}
getEditableOptions() {
const component = this;
// When refactoring, limit the copied options to the ones that are
// actually editable to avoid unnecessary memory usage.
return Component_deepClone(component.options, [
'dataTable', 'points', 'series', 'data', 'editableOptions'
]);
}
getEditableOptionValue(propertyPath) {
const component = this;
if (!propertyPath) {
return;
}
let result = component.getEditableOptions();
for (let i = 0, end = propertyPath.length; i < end; i++) {
if (isArray(result) &&
propertyPath[0] === 'connector' &&
result.length > 1) {
return 'multiple connectors';
}
if (!result) {
return;
}
result = result[propertyPath[i]];
if (result === false &&
(propertyPath.indexOf('title') >= 0 ||
propertyPath.indexOf('subtitle') >= 0 ||
propertyPath.indexOf('caption') >= 0)) {
result = '';
}
}
return result;
}
}
/* *
*
* Static Properties
*
* */
/** @internal */
Component.Sync = Sync_Sync;
/**
* Predefined sync config for component.
*/
Component.predefinedSyncConfig = {
defaultSyncOptions: {},
defaultSyncPairs: {}
};
/**
* Default options of the component.
*/
Component.defaultOptions = {
className: `${Component_classNamePrefix}component`,
id: '',
title: false,
caption: false,
sync: Sync_Sync.defaultHandlers,
editableOptions: [{
name: 'title',
propertyPath: ['title'],
type: 'input'
}, {
name: 'caption',
propertyPath: ['caption'],
type: 'input'
}]
};
/* harmony default export */ const Components_Component = (Component);
;// ./code/dashboards/es-modules/Dashboards/Components/HTMLComponent/HTMLComponentDefaults.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:
* - Karol Kołodziej
*
* */
/* *
*
* Constants
*
* */
const HTMLComponentDefaults = {
type: 'HTML',
className: [
Components_Component.defaultOptions.className,
`${Components_Component.defaultOptions.className}-html`
].join(' '),
elements: [],
editableOptions: [
...Components_Component.defaultOptions.editableOptions || [],
{
name: 'htmlInput',
propertyPath: ['html'],
type: 'textarea'
}
]
};
/* *
*
* Default Export
*
* */
/* harmony default export */ const HTMLComponent_HTMLComponentDefaults = (HTMLComponentDefaults);
;// ./code/dashboards/es-modules/Dashboards/Components/HTMLComponent/HTMLSyncs/HTMLSyncs.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:
* - Dawid Draguła
*
* */
/* *
*
* Constants
*
* */
const predefinedSyncConfig = {
defaultSyncPairs: {},
defaultSyncOptions: {}
};
/* *
*
* Default export
*
* */
/* harmony default export */ const HTMLSyncs = (predefinedSyncConfig);
;// ./code/dashboards/es-modules/Dashboards/Components/HTMLComponent/HTMLComponent.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:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
const { deepClone: HTMLComponent_deepClone } = Dashboards_Utilities;
// TODO: This may affect the AST parsing in Highcharts
// should look into adding these as options if possible
// Needs to go in a composition in the Highcharts plugin
HTML_AST.allowedTags = [
...HTML_AST.allowedTags,
'option',
'select',
'label',
'input',
'textarea'
];
HTML_AST.allowedAttributes = [
...HTML_AST.allowedAttributes,
'for',
'value',
'checked',
'src',
'name',
'selected'
];
HTML_AST.allowedReferences = [
...HTML_AST.allowedReferences,
'data:image/'
];
/* *
*
* Class
*
* */
/**
*
* Class that represents a HTML component.
*
*/
class HTMLComponent extends Components_Component {
/* *
*
* Constructor
*
* */
/**
* Creates a HTML component in the cell.
*
* @param cell
* Instance of cell, where component is attached.
*
* @param options
* The options for the component.
*/
constructor(cell, options) {
if (options.className) {
options.className = `${HTMLComponent.defaultOptions.className} ${options.className}`;
}
options = merge(HTMLComponent.defaultOptions, options);
super(cell, options);
this.options = options;
this.type = 'HTML';
this.elements = [];
}
/* *
*
* Functions
*
* */
/** @internal */
async load() {
this.emit({
type: 'load'
});
await super.load();
const options = this.options;
let isError = false;
if (options.elements?.length) {
this.elements = options.elements.map(function (element) {
if (typeof element === 'string') {
return new HTML_AST(element).nodes[0];
}
if (!element.textContent &&
!element.tagName &&
element.attributes) {
isError = true;
}
return element;
});
}
else if (options.html) {
this.elements = this.getElementsFromString(options.html);
this.options.elements = this.elements;
}
this.constructTree();
this.emit({ type: 'afterLoad' });
if (isError) {
throw new Error(`Missing tagName param in component: ${options.renderTo}`);
}
return this;
}
render() {
super.render();
this.constructTree();
this.sync.start();
this.emit({ type: 'afterRender' });
return this;
}
resize(width, height) {
super.resize(width, height);
return this;
}
/**
* Handles updating via options.
*
* @param options
* The options to apply.
*/
async update(options, shouldRerender = true) {
if (options.html) {
this.elements = this.getElementsFromString(options.html);
this.options.elements = this.elements;
this.constructTree();
}
else if (options.elements) {
this.elements = options.elements;
}
await super.update(options, shouldRerender);
this.emit({ type: 'afterUpdate' });
}
getOptionsOnDrop() {
return {
type: 'HTML',
elements: [{
tagName: 'span',
textContent: '[Your custom HTML here- edit the component]'
}]
};
}
/**
* Constructs the HTML tree.
* @internal
*/
constructTree() {
// Remove old tree if rerendering.
while (this.contentElement.firstChild) {
this.contentElement.firstChild.remove();
}
const parser = new HTML_AST(this.options.elements || []);
parser.addToDOM(this.contentElement);
}
/**
* When HTML definition is a string, it needs to be parsed to AST.
*
* @internal
*/
getElementsFromString(htmlString) {
return new HTML_AST(htmlString).nodes;
}
/**
* Get the HTML component's options.
* @returns
* HTML component's options.
*
* @internal
*
*/
getOptions() {
return {
...diffObjects(this.options, HTMLComponent.defaultOptions),
type: 'HTML'
};
}
/**
* Retrieves editable options for the HTML component.
*/
getEditableOptions() {
return HTMLComponent_deepClone(this.options, ['editableOptions']);
}
/**
* Get the value of the editable option by property path. Parse the elements
* if the HTML options is not set.
*
* @param propertyPath
* The property path of the option.
*/
getEditableOptionValue(propertyPath) {
if (!propertyPath) {
return;
}
if (propertyPath[0] === 'html') {
const result = this.getEditableOptions();
if (!result.html && result.elements) {
return this.getStringFromElements(result.elements);
}
return result[propertyPath[0]];
}
return super.getEditableOptionValue(propertyPath);
}
/**
* Returns the HTML string from the given elements.
*
* @param elements
* The array of elements to serialize.
*/
getStringFromElements(elements) {
let html = '';
for (const element of elements) {
html += this.serializeNode(element);
}
return html;
}
/**
* Serializes the HTML node to string.
*
* @param node
* The HTML node to serialize.
*/
serializeNode(node) {
if (!node.tagName || node.tagName === '#text') {
// Text node
return node.textContent || '';
}
const attributes = node.attributes;
let html = `<${node.tagName}`;
if (attributes) {
for (const key in attributes) {
if (Object.prototype.hasOwnProperty.call(attributes, key)) {
const value = attributes[key];
if (value !== void 0) {
html += ` ${key}="${value}"`;
}
}
}
}
html += '>';
html += node.textContent || '';
(node.children || []).forEach((child) => {
html += this.serializeNode(child);
});
html += `</${node.tagName}>`;
return html;
}
/**
* @internal
*/
onTableChanged(e) {
if (e.detail?.sender !== this.id) {
this.render();
}
}
}
/* *
*
* Static properties
*
* */
/**
* Default options of the HTML component.
*/
HTMLComponent.defaultOptions = merge(Components_Component.defaultOptions, HTMLComponent_HTMLComponentDefaults);
/**
* Predefined sync config for HTML component.
*/
HTMLComponent.predefinedSyncConfig = HTMLSyncs;
/* *
*
* Default export
*
* */
/* harmony default export */ const HTMLComponent_HTMLComponent = (HTMLComponent);
;// ./code/dashboards/es-modules/Dashboards/Board.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:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
* - Paweł Lysy
* - Karol Kołodziej
*
* */
/* *
*
* Class
*
* */
/**
* Class that represents a dashboard.
*
* @example
* const dashboard = Dashboards.board('container', {
* gui: {
* layouts: [{
* id: 'layout-1',
* rows: [{
* cells: [{
* id: 'dashboard-col-0'
* }]
* }]
* }]
* },
* components: [{
* renderTo: 'dashboard-col-0',
* type: 'Highcharts',
* chartOptions: {
* series: [{
* data: [1, 2, 3, 4]
* }]
* }
* }]
* });
*/
class Board {
// Implementation:
static board(renderTo, options, async) {
return new Board(renderTo, options).init(async);
}
/* *
*
* Constructor
*
* */
/**
* Creates a dashboard with components like charts, tables, and HTML
* elements.
*
* @internal
* @param renderTo
* The DOM element to render to, or its id.
*
* @param options
* The options for the dashboard.
*/
constructor(renderTo, options) {
/**
* All types of components available in the dashboard.
* @internal
*/
this.componentTypes = Components_ComponentRegistry.types;
this.options = merge(Dashboards_Defaults.defaultOptions, options);
this.dataPool = new Data_DataPool(options.dataPool);
this.id = uniqueKey();
this.guiEnabled = !options.gui ?
false : this.options?.gui?.enabled;
this.editModeEnabled = !options.editMode ?
false : this.options?.editMode?.enabled;
this.layouts = [];
this.mountedComponents = [];
this.initContainer(renderTo);
this.initEditMode();
// Add table cursors support.
this.dataCursor = new Data_DataCursor();
this.index = Dashboards_Globals.boards.length;
Dashboards_Globals.boards.push(this);
// A11y module
this.a11y = new Accessibility_DashboardsAccessibility(this);
}
// Implementation:
init(async) {
const options = this.options;
const componentPromises = (options.components) ?
this.setComponents(options.components) : [];
// Init events.
this.initEvents();
if (async) {
return Promise.all(componentPromises).then(() => {
options.events?.mounted?.call(this);
return this;
});
}
options.events?.mounted?.call(this);
return this;
}
/**
* Initializes the events.
* @internal
*/
initEvents() {
const board = this, runReflow = () => {
board.reflow();
};
if (typeof ResizeObserver === 'function') {
this.resizeObserver = new ResizeObserver(runReflow);
this.resizeObserver.observe(board.container);
}
else {
const unbind = addEvent(window, 'resize', runReflow);
addEvent(this, 'destroy', unbind);
}
}
/**
* Initialize the container for the dashboard.
* @internal
*
* @param renderTo
* The DOM element to render to, or its id.
*/
initContainer(renderTo) {
const board = this;
if (typeof renderTo === 'string') {
renderTo = window.document.getElementById(renderTo);
}
// Display an error if the renderTo doesn't exist.
if (!renderTo) {
error(13, true);
}
board.container = renderTo;
}
/**
* Inits creating a layouts and setup the EditMode tools.
* @internal
*/
initEditMode() {
const { EditMode } = Dashboards_Globals.win.Dashboards;
if (EditMode) {
this.editMode = new EditMode(this, this.options.editMode);
}
else if (this.editModeEnabled) {
throw new Error('Missing layout.js module');
}
}
/**
* Set the components from options.
* @internal
*
* @param components
* An array of component options.
*
*/
setComponents(components) {
const promises = [];
const board = this;
for (let i = 0, iEnd = components.length; i < iEnd; ++i) {
promises.push(Actions_Bindings.addComponent(components[i], board));
}
return promises;
}
/**
* Destroy the whole dashboard, its layouts and elements.
*/
destroy() {
const board = this;
const index = this.index;
// Cancel all data connectors pending requests.
this.dataPool.cancelPendingRequests();
// Destroy layouts. Iterate over a copy, since each layout removes
// itself from `board.layouts` on destroy (#24857).
if (this.guiEnabled) {
for (const layout of (board.layouts || []).slice()) {
layout.destroy();
}
}
else {
for (const mountedComponent of board.mountedComponents) {
mountedComponent.component.destroy();
}
}
// Remove resizeObserver from the board
this.resizeObserver?.unobserve(board.container);
// Destroy container.
if (this.guiEnabled) {
board.container?.remove();
}
// @ToDo Destroy bindings.
// Delete all properties.
objectEach(board, function (val, key) {
delete board[key];
});
Dashboards_Globals.boards[index] = void 0;
return;
}
/**
* Reflow the dashboard. Hide the toolbars and context pointer. Reflow the
* layouts and its cells.
*/
reflow() {
const board = this;
if (board.editMode) {
const editModeTools = board.editMode.tools;
board.editMode.hideToolbars(['cell', 'row']);
board.editMode.hideContextPointer();
// Update expanded context menu container
if (editModeTools.contextMenu) {
editModeTools.contextMenu
.updatePosition(editModeTools.contextButtonElement);
}
}
}
/**
* Update the dashboard with new options.
*
* @param newOptions
* The new options to apply to the dashboard.
*/
update(newOptions) {
const board = this;
// Merge new options with existing ones
board.options = merge(board.options, newOptions);
// Update dataPool if dataPool options changed
if (newOptions.dataPool) {
board.dataPool = new Data_DataPool(board.options.dataPool);
}
// Update guiEnabled and editModeEnabled flags if changed
if (newOptions.gui !== void 0) {
board.guiEnabled = !newOptions.gui ?
false : board.options?.gui?.enabled;
}
if (newOptions.editMode !== void 0) {
board.editModeEnabled = !newOptions.editMode ?
false : board.options?.editMode?.enabled;
}
// Destroy existing components
for (const mountedComponent of board.mountedComponents) {
mountedComponent.component.destroy();
}
board.mountedComponents = [];
// Destroy existing layouts if GUI is enabled
if (board.guiEnabled && board.layouts) {
// Iterate over a copy, since each layout removes itself from
// `board.layouts` on destroy (#24857).
for (const layout of board.layouts.slice()) {
layout.destroy();
}
board.layouts = [];
// Ensure layoutsWrapper exists
if (!board.layoutsWrapper && board.container) {
board.layoutsWrapper = createElement('div', {
className: Dashboards_Globals.classNames.layoutsWrapper
}, {}, board.container);
}
// Create new layouts if they are provided
if (board.options.gui?.layouts) {
const guiOptions = board.options.gui;
for (let i = 0, iEnd = guiOptions.layouts.length; i < iEnd; ++i) {
board.layouts.push(new Layout_Layout(board, merge({}, guiOptions.layoutOptions, guiOptions.layouts[i])));
}
// Re-initialize editMode events if editMode exists
if (board.editMode) {
// Re-initialize events for all layouts
let j = 0;
const jEnd = board.layouts.length;
for (j; j < jEnd; ++j) {
board.editMode.setLayoutEvents(board.layouts[j]);
}
}
}
}
// Add new components
if (board.options.components) {
void board.setComponents(board.options.components);
}
}
/**
* Convert the current state of board's options into JSON. The function does
* not support converting functions or events into JSON object.
*
* @returns
* Dashboards options.
*/
getOptions() {
const board = this, options = {
...this.options,
components: []
};
for (let i = 0, iEnd = board.mountedComponents.length; i < iEnd; ++i) {
if (board.mountedComponents[i].cell &&
board.mountedComponents[i].cell.mountedComponent) {
options.components?.push(board.mountedComponents[i].component.getOptions());
}
}
if (this.guiEnabled) {
options.gui = {
layouts: []
};
for (let i = 0, iEnd = board.layouts.length; i < iEnd; ++i) {
options.gui.layouts?.push(board.layouts[i].getOptions());
}
}
else {
delete options.gui;
}
return options;
}
/**
* Get a Dashboards component by its identifier.
*
* @param id
* The identifier of the requested component.
*
* @returns
* The component with the given identifier.
*/
getComponentById(id) {
return this.mountedComponents.find((c) => c.component.id === id)?.component;
}
/**
* Get a Dashboards component by its cell identifier.
*
* @param id
* The identifier of the cell that contains the requested component.
*
* @returns
* The component with the given cell identifier.
*/
getComponentByCellId(id) {
return this.mountedComponents.find((c) => c.cell.id === id)?.component;
}
}
/* *
*
* Registry
*
* */
Components_ComponentRegistry.registerComponent('HTML', HTMLComponent_HTMLComponent);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Dashboards_Board = (Board);
;// ./code/dashboards/es-modules/Dashboards/Components/GridComponent/GridDataProvider.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
* */
/* *
*
* Functions
*
* */
/**
* Returns whether the provider exposes `getDataTable`.
*
* @param provider
* Data provider instance to test.
*
* @returns
* `true` when provider exposes `getDataTable`.
*/
function hasDataTableProvider(provider) {
return !!(provider &&
typeof provider.getDataTable === 'function');
}
;// ./code/dashboards/es-modules/Dashboards/Components/GridComponent/GridSyncs/GridExtremesSync.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:
* - Dawid Draguła
*
* */
/* *
*
* Constants
*
* */
const GridExtremesSync_defaultOptions = {};
const syncPair = {
emitter: void 0,
handler: function () {
if (this.type !== 'Grid') {
return;
}
const component = this;
const syncOptions = this.sync.syncConfig.extremes;
const groupKey = syncOptions.group ?
':' + syncOptions.group : '';
const { board } = component;
const handleChangeExtremes = (e) => {
const cursor = e.cursor;
if (cursor.type === 'position' &&
component.grid &&
typeof cursor?.row === 'number') {
const { row } = cursor;
const dataProvider = component.grid.dataProvider;
const rowIndex = hasDataTableProvider(dataProvider) ?
dataProvider.getDataTable(true)?.getLocalRowIndex(row) :
void 0;
if (rowIndex !== void 0) {
component.grid.viewport?.scrollToRow(rowIndex);
}
}
};
const registerCursorListeners = () => {
const { dataCursor: cursor } = board;
if (!cursor) {
return;
}
const table = component.connectorHandlers?.[0]?.connector?.getTable();
if (!table) {
return;
}
cursor.addListener(table.id, 'xAxis.extremes.min' + groupKey, handleChangeExtremes);
};
const unregisterCursorListeners = () => {
const table = component.connectorHandlers?.[0]?.connector?.getTable();
const { dataCursor: cursor } = board;
if (!table) {
return;
}
cursor.removeListener(table.id, 'xAxis.extremes.min' + groupKey, handleChangeExtremes);
};
if (board) {
registerCursorListeners();
return unregisterCursorListeners;
}
}
};
/* *
*
* Default export
*
* */
/* harmony default export */ const GridExtremesSync = ({ defaultOptions: GridExtremesSync_defaultOptions, syncPair });
;// ./code/dashboards/es-modules/Dashboards/Components/GridComponent/GridSyncs/GridHighlightSync.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:
* - Dawid Draguła
*
* */
/* *
*
* Constants
*
* */
const GridHighlightSync_defaultOptions = {
autoScroll: false
};
const GridHighlightSync_syncPair = {
emitter: function () {
if (this.type !== 'Grid') {
return;
}
const component = this;
const { grid, board } = component;
const highlightOptions = this.sync.syncConfig.highlight;
const groupKey = highlightOptions.group ?
':' + highlightOptions.group : '';
if (!board || !grid || !highlightOptions?.enabled) {
return;
}
const { dataCursor: cursor } = board;
const table = this.getDataTable();
const emitCellCursor = (cell, state) => {
if (table) {
const localIndex = cell.row.index;
const dataProvider = grid.dataProvider;
const presentationTable = hasDataTableProvider(dataProvider) ?
dataProvider.getDataTable(true) :
void 0;
const originalIndex = presentationTable?.getOriginalRowIndex(localIndex);
if (typeof originalIndex !== 'number') {
return;
}
cursor.emitCursor(table, {
type: 'position',
row: originalIndex,
column: cell.column.id,
state: state + groupKey,
sourceId: this.id
});
}
};
const onCellHover = (e) => {
emitCellCursor(e.target, 'point.mouseOver');
};
const onCellMouseOut = (e) => {
emitCellCursor(e.target, 'point.mouseOut');
};
addEvent(grid, 'cellMouseOver', onCellHover);
addEvent(grid, 'cellMouseOut', onCellMouseOut);
// Return a function that calls the callbacks
return function () {
removeEvent(grid, 'cellMouseOver', onCellHover);
removeEvent(grid, 'cellMouseOut', onCellMouseOut);
};
},
handler: function () {
if (this.type !== 'Grid') {
return;
}
const component = this;
const { board } = component;
const highlightOptions = component.sync.syncConfig.highlight;
const groupKey = highlightOptions.group ?
':' + highlightOptions.group : '';
if (!highlightOptions?.enabled) {
return;
}
const table = component.getDataTable();
const handleCursor = (e) => {
const cursor = e.cursor;
if (cursor.sourceId === component.id ||
cursor.type !== 'position') {
return;
}
const { row, column } = cursor;
const { grid } = component;
const viewport = grid?.viewport;
const dataProvider = grid?.dataProvider;
const presentationTable = hasDataTableProvider(dataProvider) ?
dataProvider.getDataTable(true) :
void 0;
if (row === void 0 || !viewport) {
return;
}
const rowIndex = presentationTable?.getLocalRowIndex(row);
if (rowIndex === void 0) {
return;
}
if (highlightOptions.autoScroll) {
viewport.scrollToRow(rowIndex);
}
grid.syncRow(rowIndex);
grid.syncColumn(column);
};
const handleCursorOut = (e) => {
const { grid } = component;
if (grid && e.cursor.sourceId !== component.id) {
grid.syncColumn();
grid.syncRow();
}
};
const registerCursorListeners = () => {
const { dataCursor: cursor } = board;
if (!cursor) {
return;
}
if (!table) {
return;
}
cursor.addListener(table.id, 'point.mouseOver' + groupKey, handleCursor);
cursor.addListener(table.id, 'point.mouseOut' + groupKey, handleCursorOut);
};
const unregisterCursorListeners = () => {
if (!table) {
return;
}
const cursor = board.dataCursor;
cursor.removeListener(table.id, 'point.mouseOver' + groupKey, handleCursor);
cursor.removeListener(table.id, 'point.mouseOut' + groupKey, handleCursorOut);
};
if (board) {
registerCursorListeners();
return unregisterCursorListeners;
}
}
};
/* *
*
* Default export
*
* */
/* harmony default export */ const GridHighlightSync = ({ defaultOptions: GridHighlightSync_defaultOptions, syncPair: GridHighlightSync_syncPair });
;// ./code/dashboards/es-modules/Dashboards/Components/GridComponent/GridSyncs/GridVisibilitySync.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:
* - Dawid Draguła
*
* */
/* *
*
* Constants
*
* */
const GridVisibilitySync_defaultOptions = {};
const GridVisibilitySync_syncPair = {
emitter: void 0,
handler: function () {
if (this.type !== 'Grid') {
return;
}
const component = this;
const syncOptions = this.sync.syncConfig.visibility;
const groupKey = syncOptions.group ?
':' + syncOptions.group : '';
const { board } = component;
const handleVisibilityChange = (e) => {
const cursor = e.cursor, grid = component.grid;
if (!(grid && cursor.type === 'position' && cursor.column)) {
return;
}
void grid.updateColumn(cursor.column, {
enabled: cursor.state !== 'series.hide' + groupKey
});
};
const registerCursorListeners = () => {
const { dataCursor: cursor } = board;
if (!cursor) {
return;
}
const table = component.getDataTable();
if (!table) {
return;
}
cursor.addListener(table.id, 'series.show' + groupKey, handleVisibilityChange);
cursor.addListener(table.id, 'series.hide' + groupKey, handleVisibilityChange);
};
const unregisterCursorListeners = () => {
const table = component.getDataTable();
const { dataCursor: cursor } = board;
if (!table) {
return;
}
cursor.removeListener(table.id, 'series.show' + groupKey, handleVisibilityChange);
cursor.removeListener(table.id, 'series.hide' + groupKey, handleVisibilityChange);
};
if (board) {
registerCursorListeners();
return unregisterCursorListeners;
}
}
};
/* *
*
* Default export
*
* */
/* harmony default export */ const GridVisibilitySync = ({ defaultOptions: GridVisibilitySync_defaultOptions, syncPair: GridVisibilitySync_syncPair });
;// ./code/dashboards/es-modules/Dashboards/Components/GridComponent/GridSyncs/GridSyncs.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:
* - Dawid Draguła
*
* */
/* *
*
* Constants
*
* */
const GridSyncs_predefinedSyncConfig = {
defaultSyncPairs: {
extremes: GridExtremesSync.syncPair,
highlight: GridHighlightSync.syncPair,
visibility: GridVisibilitySync.syncPair
},
defaultSyncOptions: {
extremes: GridExtremesSync.defaultOptions,
highlight: GridHighlightSync.defaultOptions,
visibility: GridVisibilitySync.defaultOptions
}
};
/* *
*
* Default export
*
* */
/* harmony default export */ const GridSyncs = (GridSyncs_predefinedSyncConfig);
;// ./code/dashboards/es-modules/Dashboards/Components/GridComponent/GridComponentDefaults.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:
* - Karol Kołodziej
*
* */
/* *
*
* Constants
*
* */
const GridComponentDefaults = {
gridClassName: 'highcharts-grid-container',
gridID: 'grid-' + uniqueKey(),
gridOptions: {},
editableOptions: [
{
name: 'connectorName',
propertyPath: ['connector', 'id'],
type: 'select'
}, {
name: 'title',
propertyPath: ['title'],
type: 'input'
}, {
name: 'caption',
propertyPath: ['caption'],
type: 'input'
}, {
name: 'Grid options',
type: 'nested',
nestedOptions: [{
name: 'General',
options: [
{
name: 'Caption/title',
propertyPath: ['gridOptions', 'caption', 'text'],
type: 'input'
}, {
name: 'Columns resizing mode',
propertyPath: [
'gridOptions',
'rendering',
'columns',
'resizing',
'mode'
],
type: 'select',
selectOptions: [{
name: 'adjacent'
}, {
name: 'distributed'
}, {
name: 'independent'
}]
}, {
name: 'Editable Grid',
propertyPath: [
'gridOptions',
'columnDefaults',
'cells',
'editMode',
'enabled'
],
type: 'toggle'
}, {
name: 'Resizable columns',
propertyPath: [
'gridOptions',
'rendering',
'columns',
'resizing',
'enabled'
],
type: 'toggle'
}, {
name: 'Sortable columns',
propertyPath: [
'gridOptions',
'columnDefaults',
'sorting',
'enabled'
],
type: 'toggle'
}, {
name: 'Cell text truncation',
propertyPath: [
'gridOptions',
'rendering',
'rows',
'strictHeights'
],
type: 'toggle'
}
]
}]
}, {
name: 'Grid class name',
propertyPath: ['gridClassName'],
type: 'input'
}, {
name: 'Grid ID',
propertyPath: ['gridID'],
type: 'input'
}
],
onUpdate: (e, connector) => {
const inputElement = e.target;
if (inputElement) {
const parentRow = inputElement
.closest('.hcg-row');
const cell = inputElement.closest('.hcg-cell');
if (parentRow &&
parentRow instanceof HTMLElement &&
cell &&
cell instanceof HTMLElement) {
const dataTableRowIndex = parentRow.dataset.rowIndex;
const { columnId } = cell.dataset;
if (dataTableRowIndex !== void 0 &&
columnId !== void 0) {
const table = connector.getTable();
if (table) {
const converter = new Converters_DataConverter();
let valueToSet = converter
.convertByType(inputElement.value);
if (valueToSet instanceof Date) {
valueToSet = valueToSet.toString();
}
table.setCell(columnId, Number(dataTableRowIndex), valueToSet);
}
}
}
}
}
};
/* *
*
* Default Export
*
* */
/* harmony default export */ const GridComponent_GridComponentDefaults = (GridComponentDefaults);
;// ./code/dashboards/es-modules/Dashboards/Components/GridComponent/GridComponent.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:
* - Karol Kołodziej
* - Dawid Draguła
*
* */
const { deepClone: GridComponent_deepClone } = Dashboards_Utilities;
/* *
*
* Class
*
* */
/**
* Grid Component for Highcharts Dashboards.
* @private
*/
class GridComponent extends Components_Component {
/* *
*
* Constructor
*
* */
constructor(cell, options, board) {
options = merge(GridComponent.defaultOptions, options);
super(cell, options, board);
this.options = options;
this.type = 'Grid';
this.setOptions();
}
/* *
*
* Functions
*
* */
async update(options, shouldRerender = true) {
const previousGridDataTableId = this.getGridDataTable(true)?.id;
// Avoid triggering GridComponent.render() from Component.update().
// That render starts a fire-and-forget renderViewport() which can
// race with the awaited redraw() below when connector data changes.
await super.update(options, false);
this.setOptions();
const grid = this.grid;
const table = this.getDataTable();
if (grid &&
this.options.connector &&
previousGridDataTableId !== table?.id) {
this.recreateGrid(shouldRerender);
this.emit({ type: 'afterUpdate' });
return;
}
if (grid && shouldRerender) {
super.render();
}
if (grid) {
void grid.update(options.gridOptions, false);
if (
// #24067 - Update dataTable in options when changed.
options.gridOptions?.dataTable &&
this.options.gridOptions) {
this.options.gridOptions.dataTable =
options.gridOptions.dataTable;
}
await grid.redraw();
this.options.gridOptions = this.getGridOptionsSnapshot(grid);
if (shouldRerender) {
this.finalizeGridRender();
}
}
else if (shouldRerender) {
this.render();
}
this.emit({ type: 'afterUpdate' });
}
render() {
super.render();
if (!this.grid) {
this.grid = this.constructGrid();
}
else {
void this.grid.renderViewport();
}
this.finalizeGridRender();
return this;
}
resize(width, height) {
if (height) {
this.contentElement.style.minHeight = '0';
}
else if (height === null) {
this.contentElement.style.removeProperty('min-height');
}
this.resizeDynamicContent(width, height);
this.grid?.viewport?.reflow();
}
onTableChanged() {
const { grid } = this;
if (!grid) {
return;
}
// Check if the grid is of the legacy version (not using the data
// provider).
if (!('dataProvider' in grid)) {
// eslint-disable-next-line no-console
console.warn('GridComponent: Legacy Grid detected. Using legacy handler ' +
'for table changes. Consider upgrading the Highcharts Grid ' +
'Library to the latest version.');
this.onTableChangedLegacy();
return;
}
if (!grid?.dataProvider ||
!hasDataTableProvider(grid.dataProvider) ||
!this.connectorHandlers?.length) {
return;
}
const dataTable = this.getDataTable()?.getModified();
if (!dataTable) {
this.recreateGrid(true);
return;
}
if (!grid.options?.header) {
// If the header is not defined, we need to check if the column
// names have changed, so we can update the whole grid. If they
// have not changed, we can just update the rows (more efficient).
const newColumnIds = dataTable.getColumnIds();
const { enabledColumns, columnPolicy } = grid;
let index = 0;
for (const newColumn of newColumnIds) {
if (columnPolicy.getIndividualColumnOptions(newColumn)
?.enabled === false) {
continue;
}
if (enabledColumns?.[index] !== newColumn) {
// If the visible columns have changed,
// update the whole grid.
this.recreateGrid(true);
return;
}
index++;
}
}
if (this.getGridDataTable() !== dataTable) {
this.recreateGrid(true);
return;
}
// Data has changed and the whole grid is not re-rendered, so mark in
// the querying that data table was modified.
grid.querying.shouldBeUpdated = true;
// If the column names have not changed, just update the rows.
void grid.viewport?.updateRows();
}
/**
* Legacy handler for table changes.
*/
onTableChangedLegacy() {
const { grid } = this;
if (!grid) {
return;
}
const dataTable = this.getDataTable()?.getModified();
if (!dataTable) {
void grid.update({ dataTable: void 0 });
return;
}
if (!grid.options?.header) {
// If the header is not defined, we need to check if the column
// names have changed, so we can update the whole grid. If they
// have not changed, we can just update the rows (more efficient).
const newColumnIds = dataTable.getColumnIds();
const { enabledColumns, columnPolicy } = grid;
let index = 0;
for (const newColumn of newColumnIds) {
if (columnPolicy.getIndividualColumnOptions(newColumn)
?.enabled === false) {
continue;
}
if (enabledColumns?.[index] !== newColumn) {
// If the visible columns have changed,
// update the whole grid.
void grid.update({ dataTable });
return;
}
index++;
}
}
// Workaround for legacy Grid component.
grid.dataTable =
dataTable;
// Data has changed and the whole grid is not re-rendered, so mark in
// the querying that data table was modified.
grid.querying.shouldBeUpdated = true;
// If the column names have not changed, just update the rows.
void grid.viewport?.updateRows();
}
getEditableOptions() {
const componentOptions = this.options;
const gridOptions = this.grid ?
this.getGridOptionsSnapshot(this.grid) :
void 0;
return GridComponent_deepClone(merge({
gridOptions: gridOptions
}, componentOptions), ['editableOptions', 'dataTable']);
}
getOptionsOnDrop(sidebar) {
const connectorsIds = sidebar.editMode.board.dataPool.getConnectorIds();
let options = {
type: 'Grid'
};
if (connectorsIds.length) {
options = {
...options,
connector: {
id: connectorsIds[0]
}
};
}
return options;
}
/**
* Get the Grid Component's options.
*
* @returns
* Grid Component's options.
*
* @internal
*/
getOptions() {
const optionsCopy = merge(this.options);
optionsCopy.gridOptions = this.grid ?
this.getGridOptionsSnapshot(this.grid) :
void 0;
// Remove the table from the options copy if the connector is set.
if (optionsCopy.connector?.id) {
delete optionsCopy.gridOptions?.dataTable;
}
else if (optionsCopy.gridOptions?.dataTable?.id) {
optionsCopy.gridOptions.dataTable = {
columns: optionsCopy.gridOptions.dataTable.columns
};
}
return {
...diffObjects(optionsCopy, GridComponent.defaultOptions),
type: 'Grid'
};
}
/**
* Destroys the data grid component.
*/
destroy() {
this.sync.stop();
this.grid?.destroy();
super.destroy();
}
/**
* Sets the options for the data grid component content container.
*/
setOptions() {
const options = this.options, gridClassName = options.gridClassName, gridID = options.gridID;
if (gridClassName) {
this.contentElement.classList.value =
GridComponent_GridComponentDefaults.gridClassName + ' ' + gridClassName;
}
if (gridID) {
this.contentElement.id = gridID;
}
}
finalizeGridRender() {
const { grid } = this;
if (!grid) {
return;
}
grid.initialContainerHeight =
getStyle(this.parentElement, 'height', true) || 0;
this.sync.start();
this.emit({ type: 'afterRender' });
}
getGridOptionsSnapshot(grid) {
const gridOptions = merge(grid.getOptions());
if (!this.options.connector) {
return gridOptions;
}
delete gridOptions.dataTable;
if (gridOptions.data?.providerType === 'local') {
delete gridOptions.data.dataTable;
delete gridOptions.data.columns;
if (Object.keys(gridOptions.data).length === 1 &&
gridOptions.data.providerType === 'local') {
delete gridOptions.data;
}
}
return gridOptions;
}
getGridOptionsWithConnectorData() {
const gridOptions = merge(this.options.gridOptions) ?? {};
if (!this.options.connector) {
return gridOptions;
}
delete gridOptions.dataTable;
if (gridOptions.data?.providerType === 'local') {
delete gridOptions.data.dataTable;
delete gridOptions.data.columns;
if (Object.keys(gridOptions.data).length === 1 &&
gridOptions.data.providerType === 'local') {
delete gridOptions.data;
}
}
const dataTable = this.getDataTable();
if (dataTable) {
gridOptions.data = merge(gridOptions.data?.providerType === 'local' ?
gridOptions.data :
{}, {
providerType: 'local',
dataTable: dataTable.getModified()
});
}
return gridOptions;
}
recreateGrid(shouldRerender) {
this.sync.stop();
this.grid?.destroy();
delete this.grid;
if (shouldRerender) {
this.render();
return;
}
this.grid = this.constructGrid();
this.finalizeGridRender();
}
getGridDataTable(presentation = false) {
const dataProvider = this.grid?.dataProvider;
return hasDataTableProvider(dataProvider) ?
dataProvider.getDataTable(presentation) :
void 0;
}
/**
* Function to create the Grid.
*
* @returns The Grid.
*/
constructGrid() {
const DGN = GridComponent.GridNamespace;
if (!DGN) {
throw new Error('Grid not connected.');
}
const gridOptions = this.getGridOptionsWithConnectorData();
const gridInstance = new DGN.Grid(this.contentElement, gridOptions ?? {});
this.options.gridOptions = this.getGridOptionsSnapshot(gridInstance);
return gridInstance;
}
}
/* *
*
* Static Properties
*
* */
/**
* Predefined sync config for the Grid Component.
*/
GridComponent.predefinedSyncConfig = GridSyncs;
/**
* The default options for the Grid Component.
*/
GridComponent.defaultOptions = merge(Components_Component.defaultOptions, GridComponent_GridComponentDefaults);
/* *
*
* Default Export
*
* */
/* harmony default export */ const GridComponent_GridComponent = (GridComponent);
;// ./code/dashboards/es-modules/Dashboards/Plugins/GridPlugin.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:
* - Karol Kołodziej
* - Dawid Draguła
*
* */
/* *
*
* Functions
*
* */
/**
* Connects Grid with the Dashboard plugin.
*
* @param GridNS
* Grid core to connect.
*/
function connectGrid(GridNS) {
GridComponent_GridComponent.GridNamespace = GridNS;
}
/**
* Callback function of the Dashboard plugin.
*
* @param {Dashboards.PluginHandler.Event} e
* Plugin context provided by the Dashboard.
*/
function onRegister(e) {
const { ComponentRegistry } = e;
ComponentRegistry.registerComponent('Grid', GridComponent_GridComponent);
}
/**
* Callback function of the Dashboard plugin.
*
* @param {Dashboard.PluginHandler.Event} e Plugin context provided by the Dashboard.
*/
function onUnregister(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
e) { }
/* *
*
* Default Export
*
* */
const GridCustom = {
connectGrid
};
const GridPlugin = {
custom: GridCustom,
name: 'Grid.DashboardsPlugin',
onRegister,
onUnregister
};
/* harmony default export */ const Plugins_GridPlugin = (GridPlugin);
;// ./code/dashboards/es-modules/Dashboards/Components/HighchartsComponent/HighchartsSyncs/HighchartsExtremesSync.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:
* - Dawid Draguła
*
* */
/* *
*
* Constants
*
* */
const HighchartsExtremesSync_defaultOptions = {};
const HighchartsExtremesSync_syncPair = {
emitter: function () {
if (this.type !== 'Highcharts') {
return;
}
const component = this;
const cleanupCallbacks = [];
const { chart, board } = component;
const connector = component.connectorHandlers?.[0]?.connector;
const table = connector && connector.getTable();
const syncOptions = this.sync.syncConfig.extremes;
const groupKey = syncOptions.group ?
':' + syncOptions.group : '';
const { dataCursor: cursor } = board;
if (table && chart) {
const extremesEventHandler = (e) => {
const reset = !!e.resetSelection;
if ((!e.trigger || (e.trigger && e.trigger !== 'dashboards-sync')) && !reset) {
// TODO: investigate this type?
const axis = e.target;
const seriesFromConnectorArray = Object.keys(component.seriesFromConnector);
// Prefer a series that's in a related table,
// but allow for other data
const series = seriesFromConnectorArray.length > 0 ?
chart.get(seriesFromConnectorArray[0]) :
axis.series[0];
if (series) {
// Get the indexes of the first and last drawn points
const visiblePoints = series.points.filter((point) => point.isInside || false);
const minCursorData = {
type: 'position',
state: `${axis.coll}.extremes.min${groupKey}`
};
const maxCursorData = {
type: 'position',
state: `${axis.coll}.extremes.max${groupKey}`
};
if (seriesFromConnectorArray.length > 0 &&
axis.coll === 'xAxis' &&
visiblePoints.length) {
let columnId;
const columnAssignment = (component.connectorHandlers[0]
?.options).columnAssignment;
if (columnAssignment) {
const assignment = columnAssignment.find((assignment) => (assignment.seriesId ===
series.options.id));
if (assignment) {
const data = assignment.data;
if (isString(data)) {
columnId = data;
}
else if (Array.isArray(data)) {
columnId = data[data.length - 1];
}
else {
columnId = data.y;
}
}
}
if (!columnId) {
columnId = axis.dateTime && (table.hasColumns(['x']) ? 'x' :
series.options.id ?? series.name);
}
minCursorData.row = visiblePoints[0].index;
minCursorData.column = columnId;
maxCursorData.row =
visiblePoints[visiblePoints.length - 1].index;
maxCursorData.column = columnId;
}
// Emit as lasting cursors
cursor.emitCursor(table, minCursorData, e, true).emitCursor(table, maxCursorData, e, true);
}
}
};
const addExtremesEvent = () => chart.axes.map((axis) => addEvent(axis, 'afterSetExtremes', extremesEventHandler));
let addExtremesEventCallbacks = addExtremesEvent();
const resetExtremesEvent = () => {
addExtremesEventCallbacks.forEach((callback) => {
callback();
});
addExtremesEventCallbacks = [];
};
const handleChartResetSelection = (e) => {
if (e.resetSelection) {
resetExtremesEvent();
cursor.emitCursor(table, {
type: 'position',
state: 'chart.zoomOut' + groupKey
}, e);
addExtremesEventCallbacks.push(...addExtremesEvent());
}
};
cleanupCallbacks.push(addEvent(chart, 'selection', handleChartResetSelection));
cleanupCallbacks.push(() => {
cursor.remitCursor(table.id, {
type: 'position',
state: 'xAxis.extremes.min' + groupKey
});
cursor.remitCursor(table.id, {
type: 'position',
state: 'xAxis.extremes.max' + groupKey
});
resetExtremesEvent();
});
}
// Return cleanup
return function () {
// Call back the cleanup callbacks
cleanupCallbacks.forEach((callback) => {
callback();
});
};
},
handler: function () {
if (this.type !== 'Highcharts') {
return;
}
const component = this;
const syncOptions = this.sync.syncConfig.extremes;
const groupKey = syncOptions.group ?
':' + syncOptions.group : '';
const { chart, board } = component;
if (chart && board && chart.zooming?.type) {
const dimensions = chart.zooming.type.split('')
.map((c) => c + 'Axis');
const unregisterCallbacks = [];
dimensions.forEach((dimension) => {
const handleUpdateExtremes = (e) => {
const { cursor, event } = e;
if (cursor.type === 'position') {
const eventTarget = event?.target;
if (eventTarget && chart) {
const axes = chart[dimension];
let didZoom = false;
axes.forEach((axis) => {
if (eventTarget.coll === axis.coll &&
eventTarget !== axis &&
eventTarget.min !== null &&
eventTarget.max !== null && (axis.max !== eventTarget.max ||
axis.min !== eventTarget.min)) {
axis.setExtremes(eventTarget.min, eventTarget.max, false, void 0, {
trigger: 'dashboards-sync'
});
didZoom = true;
}
});
if (didZoom && !chart.resetZoomButton) {
chart.showResetZoom();
}
chart.redraw();
}
}
};
const addCursorListeners = () => {
const { dataCursor: cursor } = board;
const connector = component.connectorHandlers?.[0]?.connector;
if (connector) {
const table = connector.getTable();
cursor.addListener(table.id, `${dimension}.extremes.min${groupKey}`, handleUpdateExtremes);
cursor.addListener(table.id, `${dimension}.extremes.max${groupKey}`, handleUpdateExtremes);
const handleChartZoomOut = () => {
chart.zoomOut();
setTimeout(() => {
// Workaround for zoom button not being removed
const resetZoomButtons = component.element
.querySelectorAll('.highcharts-reset-zoom');
resetZoomButtons.forEach((button) => {
button.remove();
});
});
};
cursor.addListener(table.id, 'chart.zoomOut', handleChartZoomOut);
unregisterCallbacks.push(() => {
cursor.removeListener(table.id, `${dimension}.extremes.min${groupKey}`, handleUpdateExtremes);
cursor.removeListener(table.id, `${dimension}.extremes.max${groupKey}`, handleUpdateExtremes);
cursor.removeListener(table.id, 'chart.zoomOut' + groupKey, handleChartZoomOut);
});
}
};
if (board) {
addCursorListeners();
}
});
return function () {
unregisterCallbacks.forEach((callback) => {
callback();
});
};
}
}
};
/* *
*
* Default export
*
* */
/* harmony default export */ const HighchartsExtremesSync = ({ defaultOptions: HighchartsExtremesSync_defaultOptions, syncPair: HighchartsExtremesSync_syncPair });
;// ./code/dashboards/es-modules/Dashboards/Components/HighchartsComponent/HighchartsSyncs/HighchartsHighlightSync.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:
* - Dawid Draguła
*
* */
const { error: HighchartsHighlightSync_error } = Dashboards_Utilities;
/* *
*
* Constants
*
* */
const HighchartsHighlightSync_defaultOptions = {
affectedSeriesId: null,
highlightPoint: true,
showTooltip: true,
showCrosshair: true
};
const HighchartsHighlightSync_syncPair = {
emitter: function () {
if (this.type !== 'Highcharts') {
return;
}
const component = this;
const { chart, board } = component;
const highlightOptions = this.sync.syncConfig.highlight;
const groupKey = highlightOptions.group ?
':' + highlightOptions.group : '';
if (!highlightOptions.enabled || !chart) {
return;
}
const { dataCursor: cursor } = board;
for (let i = 0, iEnd = chart.series?.length ?? 0; i < iEnd; ++i) {
const series = chart.series[i];
const seriesId = series.options.id ?? '';
const connectorHandler = component.seriesFromConnector[seriesId];
const connectorId = connectorHandler?.options.id;
const table = this.getDataTable(connectorId);
let columnId;
if (!table) {
continue;
}
const presTable = table?.getModified();
const colAssignment = connectorHandler?.columnAssignment?.find((s) => s.seriesId === seriesId);
// TODO: Better way to recognize the column name.
if (colAssignment) {
const { data } = colAssignment;
if (typeof data === 'string') {
columnId = data;
}
else if (Array.isArray(data)) {
columnId = data[1];
}
else {
columnId = data.y ?? data.value;
}
}
if (!columnId) {
columnId = series.name;
}
series.update({
point: {
events: {
// Emit table cursor
mouseOver: function () {
cursor.emitCursor(table, {
type: 'position',
row: presTable.getOriginalRowIndex(this.index),
column: columnId,
state: 'point.mouseOver' + groupKey,
sourceId: component.id
});
},
mouseOut: function () {
cursor.emitCursor(table, {
type: 'position',
row: presTable.getOriginalRowIndex(this.index),
column: columnId,
state: 'point.mouseOut' + groupKey,
sourceId: component.id
});
}
}
}
}, false);
}
chart.redraw();
// Return function that handles cleanup
return function () {
if (chart && chart.series) {
chart.series.forEach((series) => {
series.update({
point: {
events: {
mouseOver: void 0,
mouseOut: void 0
}
}
}, false);
});
chart.redraw();
}
};
},
handler: function () {
if (this.type !== 'Highcharts') {
return;
}
const component = this;
const groupKey = this.sync.syncConfig.highlight.group ?
':' + this.sync.syncConfig.highlight.group : '';
const { chart, board } = component;
const getHoveredPoint = (e) => {
const { table, cursor } = e;
const highlightOptions = this.sync
.syncConfig.highlight;
if (chart && chart.series?.length && cursor.type === 'position') {
let series;
const seriesId = highlightOptions.affectedSeriesId;
if (highlightOptions.affectedSeriesId) {
const foundSeries = chart.get(highlightOptions.affectedSeriesId);
if (foundSeries?.points) {
series = foundSeries;
}
else {
HighchartsHighlightSync_error('No series with ID \'' + seriesId + '\' found in ' +
'the chart. Affected series will be selected ' +
'according to the column assignment.');
}
}
if (!series) {
const seriesIds = Object.keys(component.seriesFromConnector);
for (let i = 0, iEnd = seriesIds.length; i < iEnd; ++i) {
const seriesId = seriesIds[i];
const connectorHandler = component.seriesFromConnector[seriesId];
const dataTableKey = connectorHandler?.options.dataTableKey;
const connectorTable = connectorHandler?.connector?.getTable(dataTableKey);
if (connectorTable !== table) {
continue;
}
const colAssignment = connectorHandler.columnAssignment;
series = chart.get(seriesId);
if (!colAssignment) {
break;
}
const { data } = colAssignment.find((s) => s.seriesId === seriesId) ?? {};
if (!data || !cursor.column) {
break;
}
if (typeof data === 'string') {
if (data === cursor.column) {
break;
}
}
else if (Array.isArray(data)) {
if (data.includes(cursor.column)) {
break;
}
}
else {
if (Object.keys(data)
.map((key) => data[key])
.includes(cursor.column)) {
break;
}
}
}
}
const row = cursor.row;
if (series?.visible && row !== void 0) {
const rowIndex = table.getModified().getLocalRowIndex(row);
if (rowIndex === void 0) {
return;
}
const point = series.data[rowIndex];
if (point?.visible) {
return point;
}
}
}
};
const handleCursor = (e) => {
const highlightOptions = this.sync
.syncConfig.highlight;
if (!highlightOptions.enabled ||
e.cursor.sourceId === component.id) {
return;
}
const point = getHoveredPoint(e);
if (!point || !chart ||
// Non-cartesian points do not use 'isInside'
(!point.isInside && point.series.isCartesian) ||
// Abort if the affected chart is the same as the one
// that is currently affected manually.
point === chart.hoverPoint) {
return;
}
const tooltip = chart.tooltip;
if (tooltip && highlightOptions.showTooltip) {
const useSharedTooltip = tooltip.shared;
const hoverPoint = chart.hoverPoint;
const hoverSeries = hoverPoint?.series ||
chart.hoverSeries;
const points = chart.pointer?.getHoverData(point, hoverSeries, chart.series, true, true);
if (chart.tooltip && points?.hoverPoints.length) {
tooltip.refresh(useSharedTooltip ? points.hoverPoints : point);
}
}
if (highlightOptions.highlightPoint && (
// If the tooltip is shared, the hover state is
// already set on the point.
(!tooltip?.shared && highlightOptions.showTooltip) ||
!highlightOptions.showTooltip)) {
point.setState('hover');
}
if (highlightOptions.showCrosshair) {
point.series.xAxis?.drawCrosshair(void 0, point);
point.series.yAxis?.drawCrosshair(void 0, point);
}
};
const handleCursorOut = (e) => {
const highlightOptions = this.sync
.syncConfig.highlight;
if (!chart || !chart.series.length ||
!highlightOptions.enabled ||
e.cursor.sourceId === component.id) {
return;
}
const point = getHoveredPoint(e);
// Abort if the affected chart is the same as the one
// that is currently affected manually.
if (point && (!point.isInside && point.series.isCartesian ||
point === chart.hoverPoint)) {
return;
}
let unhovered = false;
const unhoverAllPoints = () => {
// If the 'row' parameter is missing in the event
// object, the unhovered point cannot be identified.
const series = chart.series;
const seriesLength = series.length;
for (let i = 0; i < seriesLength; i++) {
const points = chart.series[i].points;
const pointsLength = points.length;
for (let j = 0; j < pointsLength; j++) {
points[j].setState();
}
}
};
const tooltip = chart.tooltip;
if (tooltip && highlightOptions.showTooltip) {
tooltip.hide();
// Shared tooltip refresh always hovers points, so it's
// important to unhover all points on cursor out.
if (tooltip.shared) {
unhoverAllPoints();
unhovered = true;
}
}
if (highlightOptions.highlightPoint && !unhovered) {
if (point) {
point.setState();
}
else {
unhoverAllPoints();
}
}
if (highlightOptions.showCrosshair) {
if (point) {
point.series.xAxis?.drawCrosshair();
point.series.yAxis?.drawCrosshair();
}
else {
// If the 'row' parameter is missing in the event
// object, the unhovered point cannot be identified.
const xAxes = chart.xAxis;
const yAxes = chart.yAxis;
for (let i = 0, l = xAxes.length; i < l; i++) {
xAxes[i].drawCrosshair();
}
for (let i = 0, l = yAxes.length; i < l; i++) {
yAxes[i].drawCrosshair();
}
}
}
};
const registerCursorListeners = () => {
const { dataCursor: cursor } = board;
const { connectorHandlers } = this;
if (!cursor) {
return;
}
for (let i = 0, iEnd = connectorHandlers.length; i < iEnd; ++i) {
const connectorId = connectorHandlers[i]?.options.id;
const table = this.getDataTable(connectorId);
if (!table) {
continue;
}
cursor.addListener(table.id, 'point.mouseOver' + groupKey, handleCursor);
cursor.addListener(table.id, 'point.mouseOut' + groupKey, handleCursorOut);
}
};
const unregisterCursorListeners = () => {
const { dataCursor: cursor } = board;
const { connectorHandlers } = this;
if (!cursor) {
return;
}
for (let i = 0, iEnd = connectorHandlers.length; i < iEnd; ++i) {
const table = connectorHandlers[i]?.connector?.getTable();
if (!table) {
continue;
}
cursor.removeListener(table.id, 'point.mouseOver' + groupKey, handleCursor);
cursor.removeListener(table.id, 'point.mouseOut' + groupKey, handleCursorOut);
}
};
if (board) {
registerCursorListeners();
return unregisterCursorListeners;
}
}
};
/* *
*
* Default export
*
* */
/* harmony default export */ const HighchartsHighlightSync = ({ defaultOptions: HighchartsHighlightSync_defaultOptions, syncPair: HighchartsHighlightSync_syncPair });
;// ./code/dashboards/es-modules/Dashboards/Components/HighchartsComponent/HighchartsSyncs/HighchartsVisibilitySync.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:
* - Dawid Draguła
*
* */
/* *
*
* Constants
*
* */
const HighchartsVisibilitySync_defaultOptions = {};
const HighchartsVisibilitySync_syncPair = {
emitter: function () {
if (this.type !== 'Highcharts') {
return;
}
const component = this;
const syncOptions = this.sync.syncConfig.visibility;
const groupKey = syncOptions.group ? ':' + syncOptions.group : '';
const { chart, board } = component;
if (!board || !chart) {
return;
}
const table = this.getDataTable();
if (table) { // Has a connector
const { dataCursor: cursor } = board;
const { series } = chart;
series.forEach((series) => {
series.update({
events: {
show: function () {
cursor.emitCursor(table, {
type: 'position',
state: 'series.show' + groupKey,
column: this.name
});
},
hide: function () {
cursor.emitCursor(table, {
type: 'position',
state: 'series.hide' + groupKey,
column: this.name
});
}
}
}, false);
});
chart.redraw();
}
return function () {
if (!chart || !chart.series?.length) {
return;
}
chart.series.forEach((series) => {
series.update({
events: {
show: void 0,
hide: void 0
}
}, false);
});
chart.redraw();
};
},
handler: function () {
if (this.type !== 'Highcharts') {
return;
}
const component = this;
const syncOptions = this.sync.syncConfig.visibility;
const groupKey = syncOptions.group ? ':' + syncOptions.group : '';
const { board } = component;
const findSeries = (seriesArray, name) => {
for (const series of seriesArray) {
if (series.name === name) {
return series;
}
}
};
const handleShow = (e) => {
const chart = component.chart;
if (!chart || !chart.series?.length) {
return;
}
if (e.cursor.type === 'position' && e.cursor.column !== void 0) {
const series = findSeries(chart.series, e.cursor.column);
if (series) {
series.setVisible(true, true);
}
}
};
const handleHide = (e) => {
const chart = component.chart;
if (!chart || !chart.series?.length) {
return;
}
if (e.cursor.type === 'position' && e.cursor.column !== void 0) {
const series = findSeries(chart.series, e.cursor.column);
if (series) {
series.setVisible(false, true);
}
}
};
const registerCursorListeners = () => {
const { dataCursor } = board;
if (!dataCursor) {
return;
}
const table = component.getDataTable();
if (!table) {
return;
}
dataCursor.addListener(table.id, 'series.show' + groupKey, handleShow);
dataCursor.addListener(table.id, 'series.hide' + groupKey, handleHide);
};
const unregisterCursorListeners = () => {
const table = component.getDataTable();
if (table) {
board.dataCursor.removeListener(table.id, 'series.show' + groupKey, handleShow);
board.dataCursor.removeListener(table.id, 'series.hide' + groupKey, handleHide);
}
};
if (board) {
registerCursorListeners();
return unregisterCursorListeners;
}
}
};
/* *
*
* Default export
*
* */
/* harmony default export */ const HighchartsVisibilitySync = ({ defaultOptions: HighchartsVisibilitySync_defaultOptions, syncPair: HighchartsVisibilitySync_syncPair });
;// ./code/dashboards/es-modules/Dashboards/Components/HighchartsComponent/HighchartsSyncs/HighchartsSyncs.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:
* - Dawid Draguła
*
* */
/* *
*
* Constants
*
* */
const HighchartsSyncs_predefinedSyncConfig = {
defaultSyncPairs: {
extremes: HighchartsExtremesSync.syncPair,
highlight: HighchartsHighlightSync.syncPair,
visibility: HighchartsVisibilitySync.syncPair
},
defaultSyncOptions: {
extremes: HighchartsExtremesSync.defaultOptions,
highlight: HighchartsHighlightSync.defaultOptions,
visibility: HighchartsVisibilitySync.defaultOptions
}
};
/* *
*
* Default export
*
* */
/* harmony default export */ const HighchartsSyncs = (HighchartsSyncs_predefinedSyncConfig);
;// ./code/dashboards/es-modules/Dashboards/Components/HighchartsComponent/HighchartsComponentDefaults.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:
* - Karol Kołodziej
*
* */
/* *
*
* Constants
*
* */
const HighchartsComponentDefaults = {
allowConnectorUpdate: true,
className: [
Components_Component.defaultOptions.className,
`${Components_Component.defaultOptions.className}-highcharts`
].join(' '),
chartClassName: 'chart-container',
chartID: 'chart-' + uniqueKey(),
chartOptions: {
series: []
},
chartConstructor: 'chart',
editableOptions: [
{
name: 'connectorName',
propertyPath: ['connector', 'id'],
type: 'select'
},
...Components_Component.defaultOptions.editableOptions || [],
{
name: 'chartOptions',
type: 'nested',
nestedOptions: [{
name: 'chart',
options: [{
name: 'title',
propertyPath: ['chartOptions', 'title', 'text'],
type: 'input'
}, {
name: 'subtitle',
propertyPath: ['chartOptions', 'subtitle', 'text'],
type: 'input'
}, {
name: 'type',
propertyPath: ['chartOptions', 'chart', 'type'],
type: 'select',
selectOptions: [{
name: 'column',
iconURL: 'series-types/icon-column.svg'
}, {
name: 'line',
iconURL: 'series-types/icon-line.svg'
}, {
name: 'scatter',
iconURL: 'series-types/icon-scatter.svg'
}, {
name: 'pie',
iconURL: 'series-types/icon-pie.svg'
}]
}]
}, {
name: 'xAxis',
options: [{
name: 'title',
propertyPath: ['chartOptions', 'xAxis', 'title', 'text'],
type: 'input'
}, {
name: 'type',
propertyPath: ['chartOptions', 'xAxis', 'type'],
type: 'select',
selectOptions: [{
name: 'linear'
}, {
name: 'datetime'
}, {
name: 'logarithmic'
}]
}]
}, {
name: 'yAxis',
options: [{
name: 'title',
propertyPath: ['chartOptions', 'yAxis', 'title', 'text'],
type: 'input'
}, {
name: 'type',
propertyPath: ['chartOptions', 'yAxis', 'type'],
type: 'select',
selectOptions: [{
name: 'linear'
}, {
name: 'datetime'
}, {
name: 'logarithmic'
}]
}]
}, {
name: 'legend',
showToggle: true,
propertyPath: ['chartOptions', 'legend', 'enabled'],
options: [{
name: 'align',
propertyPath: ['chartOptions', 'legend', 'align'],
type: 'select',
selectOptions: [{
name: 'left'
}, {
name: 'center'
}, {
name: 'right'
}]
}]
}, {
name: 'tooltip',
showToggle: true,
propertyPath: ['chartOptions', 'tooltip', 'enabled'],
options: [{
name: 'split',
propertyPath: ['chartOptions', 'tooltip', 'split'],
type: 'toggle'
}]
}, {
name: 'dataLabels',
propertyPath: [
'chartOptions',
'plotOptions',
'series',
'dataLabels',
'enabled'
],
showToggle: true,
options: [{
name: 'align',
propertyPath: [
'chartOptions',
'plotOptions',
'series',
'dataLabels',
'align'
],
type: 'select',
selectOptions: [{
name: 'left'
}, {
name: 'center'
}, {
name: 'right'
}]
}]
}, {
name: 'credits',
showToggle: true,
propertyPath: ['chartOptions', 'credits', 'enabled'],
options: [{
name: 'name',
propertyPath: [
'chartOptions',
'credits',
'text'
],
type: 'input'
}, {
name: 'url',
propertyPath: [
'chartOptions',
'credits',
'href'
],
type: 'input'
}]
}]
}, {
name: 'chartConfig',
propertyPath: ['chartOptions'],
type: 'textarea'
}, {
name: 'chartClassName',
propertyPath: ['chartClassName'],
type: 'input'
}, {
name: 'chartID',
propertyPath: ['chartID'],
type: 'input'
}
],
editableOptionsBindings: merge(Components_Component.defaultOptions.editableOptionsBindings, {
skipRedraw: [
'chartOptions',
'chartConfig'
]
})
};
/* *
*
* Default Export
*
* */
/* harmony default export */ const HighchartsComponent_HighchartsComponentDefaults = (HighchartsComponentDefaults);
;// ./code/dashboards/es-modules/Dashboards/Components/HighchartsComponent/HighchartsComponent.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:
* - Gøran Slettemark
* - Wojciech Chmiel
* - Sebastian Bochan
* - Sophie Bremer
* - Dawid Draguła
*
* */
const { deepClone: HighchartsComponent_deepClone } = Dashboards_Utilities;
/* *
*
* Class
*
* */
/**
*
* Class that represents a Highcharts component.
*
*/
class HighchartsComponent extends Components_Component {
/* *
*
* Constructor
*
* */
/**
* Creates a Highcharts component in the cell.
*
* @param options
* The options for the component.
*/
constructor(cell, options, board) {
options = merge(HighchartsComponent.defaultOptions, options);
super(cell, options, board);
/**
* An object of series IDs and their connector handlers.
*/
this.seriesFromConnector = {};
this.options = options;
this.chartConstructor = this.options.chartConstructor || 'chart';
this.type = 'Highcharts';
this.chartContainer = createElement('figure', void 0, void 0, this.contentElement, true);
this.setOptions();
this.chartOptions = merge((this.options.chartOptions ||
{ chart: {} }), {
tooltip: {} // Temporary fix for #18876
});
this.innerResizeTimeouts = [];
}
onTableChanged() {
this.updateSeries();
}
/* *
*
* Functions
*
* */
async load() {
this.emit({ type: 'load' });
await super.load();
this.emit({ type: 'afterLoad' });
return this;
}
render() {
const hcComponent = this;
super.render();
hcComponent.chart = hcComponent.getChart();
hcComponent.updateSeries();
hcComponent.emit({ type: 'afterRender' });
hcComponent.setupConnectorUpdate();
this.sync.start();
return this;
}
resize(width, height) {
this.resizeDynamicContent(width, height);
while (this.innerResizeTimeouts.length) {
const timeoutID = this.innerResizeTimeouts.pop();
if (timeoutID) {
clearTimeout(timeoutID);
}
}
this.innerResizeTimeouts.push(setTimeout(() => {
if (this.chart && this.chart.container) {
const heightOffset = this.contentElement.offsetHeight -
this.chart?.container.offsetHeight;
this.chart.setSize(null, (Math.abs(heightOffset) > 1) ?
this.contentElement.offsetHeight : null, false);
}
}, 33));
return this;
}
/**
* Adds call update value in store, when chart's point is updated.
* @private
*/
setupConnectorUpdate() {
const { connectorHandlers, chart } = this;
if (!chart || !this.options.allowConnectorUpdate) {
return;
}
const seriesLength = chart.series.length;
for (let i = 0, iEnd = connectorHandlers.length; i < iEnd; i++) {
const connectorHandler = connectorHandlers[i];
for (let j = 0; j < seriesLength; j++) {
const series = chart.series[j];
series.update({
point: {
events: {
update: (e) => {
this.onChartUpdate(e.target, connectorHandler);
}
}
}
}, false);
}
}
}
/**
* Update the store, when the point is being dragged.
* @param point Dragged point.
* @param connectorHandler Connector handler with data to update.
*/
onChartUpdate(point, connectorHandler) {
const table = connectorHandler.dataTable;
const columnAssignment = connectorHandler.columnAssignment;
const seriesId = point.series.options.id;
const converter = new Converters_DataConverter();
const valueToSet = Converters_DataConverterUtils.asNumber(point.y, converter.decimalRegExp);
if (!table) {
return;
}
let columnId;
if (columnAssignment && seriesId) {
const data = columnAssignment.find((s) => s.seriesId === seriesId)?.data;
if (isString(data)) {
columnId = data;
}
else if (Array.isArray(data)) {
columnId = data[1];
}
else if (data) {
columnId = data.y ?? data.value;
}
}
if (!columnId) {
columnId = seriesId ?? point.series.name;
}
table.setCell(columnId, point.index, valueToSet);
}
/**
* Internal method for handling option updates.
* @internal
*/
setOptions() {
if (this.options.chartClassName) {
this.chartContainer.classList.value =
HighchartsComponent_HighchartsComponentDefaults.className + ' ' +
this.options.chartClassName;
}
if (this.options.chartID) {
this.chartContainer.id = this.options.chartID;
}
}
/**
* Handles updating via options.
*
* @param options
* The options to apply.
*/
async update(options, shouldRerender = true) {
await super.update(options, false);
this.setOptions();
if (this.options.chartConstructor !== this.chartConstructor) {
this.chartConstructor = this.options.chartConstructor || 'chart';
this.chartOptions = this.options.chartOptions || {};
this.chart?.destroy();
delete this.chart;
}
else {
this.chart?.update(merge(this.options.chartOptions) || {});
}
this.emit({ type: 'afterUpdate' });
shouldRerender && this.render();
}
/**
* Updates chart's series when the data table is changed.
* @private
*/
updateSeries() {
const { chart } = this;
const connectorHandlers = this.connectorHandlers;
if (!chart) {
return;
}
const newSeriesIds = [];
for (const connectorHandler of connectorHandlers) {
const options = connectorHandler.options;
let columnAssignment = options.columnAssignment;
if (!columnAssignment && connectorHandler.dataTable) {
columnAssignment = this.getDefaultColumnAssignment(connectorHandler.dataTable.getColumnIds(), connectorHandler.dataTable);
}
if (columnAssignment) {
connectorHandler.columnAssignment = columnAssignment;
for (const { seriesId } of columnAssignment) {
if (seriesId) {
newSeriesIds.push(seriesId);
}
}
}
}
const seriesArray = Object.keys(this.seriesFromConnector);
// Remove series that were added in the previous update and are not
// present in the new columnAssignment.
for (let i = 0, iEnd = seriesArray.length; i < iEnd; ++i) {
const oldSeriesId = seriesArray[i];
if (newSeriesIds.some((newSeriesId) => newSeriesId === oldSeriesId)) {
continue;
}
const series = chart.get(oldSeriesId);
if (series) {
series.destroy();
}
}
this.seriesFromConnector = {};
for (const connectorHandler of connectorHandlers) {
this.updateSeriesFromConnector(connectorHandler);
}
chart.redraw();
}
/**
* Updates the series based on the connector from each connector handler.
* @param connectorHandler The connector handler.
* @private
*/
updateSeriesFromConnector(connectorHandler) {
const chart = this.chart;
if (!connectorHandler.connector ||
!chart ||
!connectorHandler.dataTable) {
return;
}
const table = connectorHandler.dataTable.getModified();
const modifierOptions = connectorHandler.dataTable.getModifier()?.options;
const columnAssignment = connectorHandler.columnAssignment ?? [];
// Create the series or update the existing ones.
for (let i = 0, iEnd = columnAssignment.length; i < iEnd; ++i) {
const assignment = columnAssignment[i];
const dataStructure = assignment.data;
const series = chart.get(assignment.seriesId);
const seriesOptions = {};
// Prevent dragging on series, which were created out of a
// columns which are created by MathModifier.
const adjustDraggableOptions = (compare) => {
if (modifierOptions?.type === 'Math' &&
modifierOptions
.columnFormulas?.some((formula) => compare(formula.column))) {
seriesOptions.dragDrop = {
draggableY: false
};
}
};
// Set the series data based on the column assignment data structure
// type.
if (isString(dataStructure)) {
const column = table.getColumn(dataStructure);
if (column) {
seriesOptions.data = column.slice();
}
adjustDraggableOptions((columnId) => (columnId === dataStructure));
}
else if (Array.isArray(dataStructure)) {
seriesOptions.data = table.getRows(0, table.rowCount, dataStructure);
adjustDraggableOptions((columnId) => (dataStructure.some((name) => name === columnId)));
}
else {
const keys = Object.keys(dataStructure);
const columnIds = [];
for (let j = 0, jEnd = keys.length; j < jEnd; ++j) {
columnIds.push(dataStructure[keys[j]]);
}
seriesOptions.keys = keys;
seriesOptions.data = table.getRows(0, table.rowCount, columnIds);
adjustDraggableOptions((columnId) => (columnIds.some((name) => name === columnId)));
}
if (!series) {
chart.addSeries({
name: assignment.seriesId,
id: assignment.seriesId,
...seriesOptions
}, false);
}
else {
series.update(seriesOptions, false);
}
this.seriesFromConnector[assignment.seriesId] = connectorHandler;
}
}
/**
* Destroy chart and create a new one.
*
* @returns
* The chart.
*
* @private
*
*/
getChart() {
return this.chart || this.createChart();
}
/**
* Destroys the highcharts component.
*/
destroy() {
// Cleanup references in the global Highcharts scope
// Destroy chart before destroying the component element
// to ensure chart has access to its renderTo element
if (this.chart && this.chart.renderTo && this.chart.renderer) {
try {
this.chart.destroy();
}
catch (e) {
// Chart may already be destroyed or renderTo/renderer
// eslint-disable-next-line no-console
console.warn('Error destroying chart:', e);
}
this.chart = void 0;
}
super.destroy();
}
/**
* Creates default mapping when columnAssignment is not declared.
* @param { Array<string>} columnIds all columns returned from dataTable.
*
* @returns
* The record of mapping
*
* @private
*
*/
getDefaultColumnAssignment(columnIds = [], presentationTable) {
const result = [];
const firstColumn = presentationTable.getColumn(columnIds[0]);
if (firstColumn && isString(firstColumn[0])) {
for (let i = 1, iEnd = columnIds.length; i < iEnd; ++i) {
result.push({
seriesId: columnIds[i],
data: [columnIds[0], columnIds[i]]
});
}
return result;
}
for (let i = 0, iEnd = columnIds.length; i < iEnd; ++i) {
result.push({
seriesId: columnIds[i],
data: columnIds[i]
});
}
return result;
}
/**
* Creates chart.
*
* @returns
* The chart.
*
* @private
*
*/
createChart() {
const charter = HighchartsComponent.charter || Dashboards_Globals.win.Highcharts;
if (!this.chartConstructor) {
this.chartConstructor = 'chart';
}
const Factory = charter[this.chartConstructor];
if (Factory) {
try {
if (this.chartConstructor === 'chart') {
return charter.Chart.chart(this.chartContainer, this.chartOptions);
}
return new Factory(this.chartContainer, this.chartOptions);
}
catch (e) {
throw new Error(`The Highcharts component in cell '${this.cell.id}' ` +
'is misconfigured. \n____________\n' +
String(e));
}
}
if (typeof charter.chart !== 'function') {
throw new Error('Chart constructor not found');
}
return this.chart;
}
getOptionsOnDrop(sidebar) {
const connectorsIds = sidebar.editMode.board.dataPool.getConnectorIds();
let options = {
type: 'Highcharts',
chartOptions: {
chart: {
animation: false,
type: 'column',
zooming: {}
}
}
};
if (connectorsIds.length) {
options = {
...options,
connector: {
id: connectorsIds[0]
}
};
}
return options;
}
/**
* Get the HighchartsComponent component's options.
* @returns
* HighchartsComponent component's options.
*
* @internal
*
*/
getOptions() {
return {
...diffObjects(this.options, HighchartsComponent.defaultOptions),
type: 'Highcharts'
};
}
/**
* Retrieves editable options for the chart.
*
* @returns
* The editable options for the chart and its values.
*/
getEditableOptions() {
const component = this;
const componentOptions = component.options;
const chart = component.chart;
const chartOptions = chart && chart.options;
const chartType = chartOptions?.chart?.type || 'line';
return HighchartsComponent_deepClone(merge({
chartOptions
}, {
chartOptions: {
yAxis: splat(chart && chart.yAxis[0].options),
xAxis: splat(chart && chart.xAxis[0].options),
plotOptions: {
series: ((chartOptions && chartOptions.plotOptions) ||
{})[chartType]
}
}
}, componentOptions), ['dataTable', 'points', 'series', 'data', 'editableOptions']);
}
getEditableOptionValue(propertyPath) {
const component = this;
if (!propertyPath) {
return;
}
if (propertyPath.length === 1 && propertyPath[0] === 'chartOptions') {
return JSON.stringify(component.options.chartOptions, null, 2);
}
return super.getEditableOptionValue.call(this, propertyPath);
}
}
/**
* Predefined sync config for Highcharts component.
*/
HighchartsComponent.predefinedSyncConfig = HighchartsSyncs;
/**
* Default options of the Highcharts component.
*/
HighchartsComponent.defaultOptions = merge(Components_Component.defaultOptions, HighchartsComponent_HighchartsComponentDefaults);
/* *
*
* Default Export
*
* */
/* harmony default export */ const HighchartsComponent_HighchartsComponent = (HighchartsComponent);
;// ./code/dashboards/es-modules/Dashboards/Components/KPIComponent/KPISyncs/KPIExtremesSync.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:
* - Dawid Draguła
*
* */
/* *
*
* Constants
*
* */
const KPIExtremesSync_defaultOptions = {};
const KPIExtremesSync_syncPair = {
emitter: void 0,
handler: function () {
if (this.type !== 'KPI') {
return;
}
const component = this;
const syncOptions = this.sync.syncConfig.extremes;
const groupKey = syncOptions.group ? ':' + syncOptions.group : '';
const { board } = this;
const handleChangeExtremes = (e) => {
const cursor = e.cursor;
if (cursor.type === 'position' &&
typeof cursor?.row === 'number' &&
defined(cursor.column) &&
component.connectorHandlers?.[0]?.connector &&
!defined(component.options.value)) {
const value = String(component.connectorHandlers[0].connector
.getTable()
.getModified()
.getCell(cursor.column, cursor.row));
component.setValue(value);
}
};
const registerCursorListeners = () => {
const { dataCursor: cursor } = board;
if (!cursor) {
return;
}
const table = this.getDataTable();
if (!table) {
return;
}
cursor.addListener(table.id, 'xAxis.extremes.max' + groupKey, handleChangeExtremes);
};
const unregisterCursorListeners = () => {
const table = this.getDataTable();
const { dataCursor: cursor } = board;
if (!table) {
return;
}
cursor.removeListener(table.id, 'xAxis.extremes.max' + groupKey, handleChangeExtremes);
};
if (board) {
registerCursorListeners();
return unregisterCursorListeners;
}
}
};
/* *
*
* Default export
*
* */
/* harmony default export */ const KPIExtremesSync = ({ defaultOptions: KPIExtremesSync_defaultOptions, syncPair: KPIExtremesSync_syncPair });
;// ./code/dashboards/es-modules/Dashboards/Components/KPIComponent/KPISyncs/KPISyncs.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:
* - Dawid Draguła
*
* */
/* *
*
* Constants
*
* */
const KPISyncs_predefinedSyncConfig = {
defaultSyncPairs: {
extremes: KPIExtremesSync.syncPair
},
defaultSyncOptions: {
extremes: KPIExtremesSync.defaultOptions
}
};
/* *
*
* Default export
*
* */
/* harmony default export */ const KPISyncs = (KPISyncs_predefinedSyncConfig);
;// ./code/dashboards/es-modules/Dashboards/Components/KPIComponent/KPIComponentDefaults.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
*
* */
/* *
*
* Constants
*
* */
const KPIComponentDefaults = {
type: 'KPI',
className: [
Components_Component.defaultOptions.className,
`${Components_Component.defaultOptions.className}-kpi`
].join(' '),
minFontSize: 20,
thresholdColors: ['#f45b5b', '#90ed7d'],
editableOptions: [
{
name: 'connectorName',
propertyPath: ['connector', 'id'],
type: 'select'
},
...Components_Component.defaultOptions.editableOptions || [],
{
name: 'Value',
type: 'input',
propertyPath: ['value']
}, {
name: 'Column name',
type: 'input',
propertyPath: ['columnId']
}, {
name: 'Value format',
type: 'input',
propertyPath: ['valueFormat']
}
],
linkedValueTo: {
enabled: true,
seriesIndex: 0,
pointIndex: 0
}
};
/* *
*
* Default Export
*
* */
/* harmony default export */ const KPIComponent_KPIComponentDefaults = (KPIComponentDefaults);
;// ./code/dashboards/es-modules/Data/Formula/FormulaTypes.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
*
* */
/* *
*
* Constants
*
* */
/**
* Array of all possible operators.
* @private
*/
const operators = ['+', '-', '*', '/', '^', '=', '<', '<=', '>', '>='];
/* *
*
* Functions
*
* */
/**
* Tests an item for a Formula array.
*
* @private
*
* @param {Highcharts.FormulaItem} item
* Item to test.
*
* @return {boolean}
* `true`, if the item is a formula (or argument) array.
*/
function isFormula(item) {
return Array.isArray(item);
}
/**
* Tests an item for a Function structure.
*
* @private
*
* @param {Highcharts.FormulaItem} item
* Item to test.
*
* @return {boolean}
* `true`, if the item is a formula function.
*/
function FormulaTypes_isFunction(item) {
return (typeof item === 'object' &&
!(Array.isArray(item)) &&
item.type === 'function');
}
/**
* Tests an item for an Operator string.
*
* @private
*
* @param {Highcharts.FormulaItem} item
* Item to test.
*
* @return {boolean}
* `true`, if the item is an operator string.
*/
function isOperator(item) {
return (typeof item === 'string' &&
operators.indexOf(item) >= 0);
}
/**
* Tests an item for a Range structure.
*
* @private
*
* @param {Highcharts.FormulaItem} item
* Item to test.
*
* @return {boolean}
* `true`, if the item is a range.
*/
function isRange(item) {
return (typeof item === 'object' &&
!(Array.isArray(item)) &&
item.type === 'range');
}
/**
* Tests an item for a Reference structure.
*
* @private
*
* @param {Highcharts.FormulaItem} item
* Item to test.
*
* @return {boolean}
* `true`, if the item is a reference.
*/
function isReference(item) {
return (typeof item === 'object' &&
!(Array.isArray(item)) &&
item.type === 'reference');
}
/**
* Tests an item for a Value structure.
*
* @private
*
* @param {Highcharts.FormulaItem|null|undefined} item
* Item to test.
*
* @return {boolean}
* `true`, if the item is a value.
*/
function isValue(item) {
return (typeof item === 'boolean' ||
typeof item === 'number' ||
typeof item === 'string');
}
/* *
*
* Default Export
*
* */
const MathFormula = {
isFormula,
isFunction: FormulaTypes_isFunction,
isOperator,
isRange,
isReference,
isValue
};
/* harmony default export */ const FormulaTypes = (MathFormula);
;// ./code/dashboards/es-modules/Data/Formula/FormulaProcessor.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
*
* */
const { isFormula: FormulaProcessor_isFormula, isFunction: FormulaProcessor_isFunction, isOperator: FormulaProcessor_isOperator, isRange: FormulaProcessor_isRange, isReference: FormulaProcessor_isReference, isValue: FormulaProcessor_isValue } = FormulaTypes;
/* *
*
* Constants
*
* */
const asLogicalStringRegExp = / */;
const MAX_FALSE = Number.MAX_VALUE / 1.000000000001;
const MAX_STRING = Number.MAX_VALUE / 1.000000000002;
const MAX_TRUE = Number.MAX_VALUE;
const operatorPriority = {
'^': 3,
'*': 2,
'/': 2,
'+': 1,
'-': 1,
'=': 0,
'<': 0,
'<=': 0,
'>': 0,
'>=': 0
};
const processorFunctions = {};
const processorFunctionNameRegExp = /^[A-Z][A-Z\.]*$/;
/* *
*
* Functions
*
* */
/**
* Converts non-number types to logical numbers.
*
* @param {Highcharts.FormulaValue} value
* Value to convert.
*
* @return {number}
* Logical number value. `NaN` if not convertable.
*/
function asLogicalNumber(value) {
switch (typeof value) {
case 'boolean':
return value ? MAX_TRUE : MAX_FALSE;
case 'string':
return MAX_STRING;
case 'number':
return value;
default:
return NaN;
}
}
/**
* Converts strings to logical strings, while other types get passed through. In
* logical strings the space character is the lowest value and letters are case
* insensitive.
*
* @param {Highcharts.FormulaValue} value
* Value to convert.
*
* @return {Highcharts.FormulaValue}
* Logical string value or passed through value.
*/
function asLogicalString(value) {
if (typeof value === 'string') {
return value.toLowerCase().replace(asLogicalStringRegExp, '\0');
}
return value;
}
/**
* Converts non-number types to a logic number.
*
* @param {Highcharts.FormulaValue} value
* Value to convert.
*
* @return {number}
* Number value. `NaN` if not convertable.
*/
function FormulaProcessor_asNumber(value) {
switch (typeof value) {
case 'boolean':
return value ? 1 : 0;
case 'string':
return parseFloat(value.replace(',', '.'));
case 'number':
return value;
default:
return NaN;
}
}
/**
* Process a basic operation of two given values.
*
* @private
*
* @param {Highcharts.FormulaOperator} operator
* Operator between values.
*
* @param {Highcharts.FormulaValue} x
* First value for operation.
*
* @param {Highcharts.FormulaValue} y
* Second value for operation.
*
* @return {Highcharts.FormulaValue}
* Operation result. `NaN` if operation is not support.
*/
function basicOperation(operator, x, y) {
switch (operator) {
case '=':
return asLogicalString(x) === asLogicalString(y);
case '<':
if (typeof x === typeof y) {
return asLogicalString(x) < asLogicalString(y);
}
return asLogicalNumber(x) < asLogicalNumber(y);
case '<=':
if (typeof x === typeof y) {
return asLogicalString(x) <= asLogicalString(y);
}
return asLogicalNumber(x) <= asLogicalNumber(y);
case '>':
if (typeof x === typeof y) {
return asLogicalString(x) > asLogicalString(y);
}
return asLogicalNumber(x) > asLogicalNumber(y);
case '>=':
if (typeof x === typeof y) {
return asLogicalString(x) >= asLogicalString(y);
}
return asLogicalNumber(x) >= asLogicalNumber(y);
}
x = FormulaProcessor_asNumber(x);
y = FormulaProcessor_asNumber(y);
let result;
switch (operator) {
case '+':
result = x + y;
break;
case '-':
result = x - y;
break;
case '*':
result = x * y;
break;
case '/':
result = x / y;
break;
case '^':
result = Math.pow(x, y);
break;
default:
return NaN;
}
// Limit decimal to 9 digits
return (result % 1 ?
Math.round(result * 1000000000) / 1000000000 :
result);
}
/**
* Converts an argument to Value and in case of a range to an array of Values.
*
* @function Highcharts.Formula.getArgumentValue
*
* @param {Highcharts.FormulaRange|Highcharts.FormulaTerm} arg
* Formula range or term to convert.
*
* @param {Highcharts.DataTable} [table]
* Table to use for references and ranges.
*
* @return {Highcharts.FormulaValue|Array<Highcharts.FormulaValue>}
* Converted value.
*/
function getArgumentValue(arg, table) {
// Add value
if (FormulaProcessor_isValue(arg)) {
return arg;
}
// Add values of a range
if (FormulaProcessor_isRange(arg)) {
return (table && getRangeValues(arg, table) || []);
}
// Add values of a function
if (FormulaProcessor_isFunction(arg)) {
return processFunction(arg, table);
}
// Process functions, operations, references with formula processor
return processFormula((FormulaProcessor_isFormula(arg) ? arg : [arg]), table);
}
/**
* Converts all arguments to Values and in case of ranges to arrays of Values.
*
* @function Highcharts.Formula.getArgumentsValues
*
* @param {Highcharts.FormulaArguments} args
* Formula arguments to convert.
*
* @param {Highcharts.DataTable} [table]
* Table to use for references and ranges.
*
* @return {Array<(Highcharts.FormulaValue|Array<Highcharts.FormulaValue>)>}
* Converted values.
*/
function getArgumentsValues(args, table) {
const values = [];
for (let i = 0, iEnd = args.length; i < iEnd; ++i) {
values.push(getArgumentValue(args[i], table));
}
return values;
}
/**
* Extracts cell values from a table for a given range.
*
* @function Highcharts.Formula.getRangeValues
*
* @param {Highcharts.FormulaRange} range
* Formula range to use.
*
* @param {Highcharts.DataTable} table
* Table to extract from.
*
* @return {Array<Highcharts.FormulaValue>}
* Extracted values.
*/
function getRangeValues(range, table) {
const columnIds = table
.getColumnIds()
.slice(range.beginColumn, range.endColumn + 1), values = [];
for (let i = 0, iEnd = columnIds.length, cell; i < iEnd; ++i) {
const cells = table.getColumn(columnIds[i], true) || [];
for (let j = range.beginRow, jEnd = range.endRow + 1; j < jEnd; ++j) {
cell = cells[j];
if (typeof cell === 'string' &&
cell[0] === '=' &&
table !== table.getModified()) {
// Look in the modified table for formula result
cell = table.getModified().getCell(columnIds[i], j);
}
values.push(FormulaProcessor_isValue(cell) ? cell : NaN);
}
}
return values;
}
/**
* Extracts the cell value from a table for a given reference.
*
* @private
*
* @param {Highcharts.FormulaReference} reference
* Formula reference to use.
*
* @param {Highcharts.DataTable} table
* Table to extract from.
*
* @return {Highcharts.FormulaValue}
* Extracted value. 'undefined' might also indicate that the cell was not found.
*/
function getReferenceValue(reference, table) {
const columnId = table.getColumnIds()[reference.column];
if (columnId) {
const cell = table.getCell(columnId, reference.row);
if (typeof cell === 'string' &&
cell[0] === '=' &&
table !== table.getModified()) {
// Look in the modified table for formula result
const result = table.getModified().getCell(columnId, reference.row);
return FormulaProcessor_isValue(result) ? result : NaN;
}
if (FormulaProcessor_isValue(cell)) {
return reference.isNegative ? -cell : cell;
}
return NaN;
}
return NaN;
}
/**
* Calculates a value based on the two top values and the related operator.
*
* Used to properly process the formula's values based on its operators.
*
* @private
* @function Highcharts.applyOperator
*
* @param {Array<Highcharts.Value>} values
* Processed formula values.
*
* @param {Array<Highcharts.Operator>} operators
* Processed formula operators.
*/
function applyOperator(values, operators) {
if (values.length < 2 || operators.length < 1) {
values.push(NaN);
}
const secondValue = values.pop();
const firstValue = values.pop();
const operator = operators.pop();
if (!defined(secondValue) || !defined(firstValue) || !defined(operator)) {
values.push(NaN);
}
else {
values.push(basicOperation(operator, firstValue, secondValue));
}
}
/**
* Processes a formula array on the given table. If the formula does not contain
* references or ranges, then no table has to be provided.
*
* Performs formulas considering the operators precedence.
*
* // Example of the `2 * 3 + 4` formula:
* 2 -> values: [2], operators: []
* * -> values: [2], operators: [*]
* 3 -> values: [2, 3], operators: [*]
* // Since the higher precedence operator exists (* > +), perform it first.
* + -> values: [6], operators: [+]
* 4 -> values: [6, 4], operators: [+]
* // When non-higher precedence operators remain, perform rest calculations.
* -> values: [10], operators: []
*
* @private
* @function Highcharts.processFormula
*
* @param {Highcharts.Formula} formula
* Formula array to process.
*
* @param {Highcharts.DataTable} [table]
* Table to use for references and ranges.
*
* @return {Highcharts.FormulaValue}
* Result value of the process. `NaN` indicates an error.
*/
function processFormula(formula, table) {
// Keeps all the values to calculate them in a proper priority, based on the
// given operators.
const values = [];
// Keeps all the operators to calculate the values above, following the
// proper priority.
const operators = [];
// Indicates if the next item is a value (not an operator).
let expectingValue = true;
for (let i = 0, iEnd = formula.length; i < iEnd; ++i) {
const item = formula[i];
if (FormulaProcessor_isOperator(item)) {
if (expectingValue && item === '-') {
// Split the negative values to be handled as a binary
// operation if the next item is a value.
values.push(0);
operators.push('-');
expectingValue = true;
}
else {
// Perform if the higher precedence operator exist.
while (operators.length &&
operatorPriority[operators[operators.length - 1]] >=
operatorPriority[item]) {
applyOperator(values, operators);
}
operators.push(item);
expectingValue = true;
}
continue;
}
let value;
// Assign the proper value, starting from the most common types.
if (FormulaProcessor_isValue(item)) {
value = item;
}
else if (FormulaProcessor_isReference(item)) {
value = table ? getReferenceValue(item, table) : NaN;
}
else if (FormulaProcessor_isFunction(item)) {
const result = processFunction(item, table);
value = FormulaProcessor_isValue(result) ? result : NaN;
}
else if (FormulaProcessor_isFormula(item)) {
value = processFormula(item, table);
}
if (typeof value !== 'undefined') {
values.push(value);
expectingValue = false;
}
else {
return NaN;
}
}
// Handle the remaining operators that weren't taken into consideration, due
// to non-higher precedence.
while (operators.length) {
applyOperator(values, operators);
}
if (values.length !== 1) {
return NaN;
}
return values[0];
}
/**
* Process a function on the given table. If the arguments do not contain
* references or ranges, then no table has to be provided.
*
* @private
*
* @param {Highcharts.FormulaFunction} formulaFunction
* Formula function to process.
*
* @param {Highcharts.DataTable} [table]
* Table to use for references and ranges.
*
* @param {Highcharts.FormulaReference} [reference]
* Table cell reference to use for relative references and ranges.
*
* @return {Highcharts.FormulaValue|Array<Highcharts.FormulaValue>}
* Result value (or values) of the process. `NaN` indicates an error.
*/
function processFunction(formulaFunction, table,
// eslint-disable-next-line @typescript-eslint/no-unused-vars
reference // @todo
) {
const processor = processorFunctions[formulaFunction.name];
if (processor) {
try {
return processor(formulaFunction.args, table);
}
catch {
return NaN;
}
}
const error = new Error(`Function "${formulaFunction.name}" not found.`);
error.name = 'FormulaProcessError';
throw error;
}
/**
* Registers a function for the FormulaProcessor.
*
* @param {string} name
* Name of the function in spreadsheets notation with upper case.
*
* @param {Highcharts.FormulaFunction} processorFunction
* ProcessorFunction for the FormulaProcessor. This is an object so that it
* can take additional parameter for future validation routines.
*
* @return {boolean}
* Return true, if the ProcessorFunction has been registered.
*/
function registerProcessorFunction(name, processorFunction) {
return (processorFunctionNameRegExp.test(name) &&
!processorFunctions[name] &&
!!(processorFunctions[name] = processorFunction));
}
/**
* Translates relative references and ranges in-place.
*
* @param {Highcharts.Formula} formula
* Formula to translate references and ranges in.
*
* @param {number} [columnDelta=0]
* Column delta to translate to. Negative translate back.
*
* @param {number} [rowDelta=0]
* Row delta to translate to. Negative numbers translate back.
*
* @return {Highcharts.Formula}
* Formula with translated reference and ranges. This formula is equal to the
* first argument.
*/
function translateReferences(formula, columnDelta = 0, rowDelta = 0) {
for (let i = 0, iEnd = formula.length, item; i < iEnd; ++i) {
item = formula[i];
if (Array.isArray(item)) {
translateReferences(item, columnDelta, rowDelta);
}
else if (FormulaProcessor_isFunction(item)) {
translateReferences(item.args, columnDelta, rowDelta);
}
else if (FormulaProcessor_isRange(item)) {
if (item.beginColumnRelative) {
item.beginColumn += columnDelta;
}
if (item.beginRowRelative) {
item.beginRow += rowDelta;
}
if (item.endColumnRelative) {
item.endColumn += columnDelta;
}
if (item.endRowRelative) {
item.endRow += rowDelta;
}
}
else if (FormulaProcessor_isReference(item)) {
if (item.columnRelative) {
item.column += columnDelta;
}
if (item.rowRelative) {
item.row += rowDelta;
}
}
}
return formula;
}
/* *
*
* Default Export
*
* */
const FormulaProcessor = {
asNumber: FormulaProcessor_asNumber,
getArgumentValue,
getArgumentsValues,
getRangeValues,
getReferenceValue,
processFormula,
processorFunctions,
registerProcessorFunction,
translateReferences
};
/* harmony default export */ const Formula_FormulaProcessor = (FormulaProcessor);
;// ./code/dashboards/es-modules/Data/Formula/Functions/SUM.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
*
* */
/* *
*
* Functions
*
* */
/**
* Processor for the `SUM(...values)` implementation. Calculates the sum of the
* given values.
*
* @private
* @function Formula.processorFunctions.SUM
*
* @param {Highcharts.FormulaArguments} args
* Arguments to process.
*
* @param {Highcharts.DataTable} [table]
* Table to process.
*
* @return {number}
* Result value of the process.
*/
function SUM(args, table) {
const values = Formula_FormulaProcessor.getArgumentsValues(args, table);
let result = 0;
for (let i = 0, iEnd = values.length, value; i < iEnd; ++i) {
value = values[i];
switch (typeof value) {
case 'number':
if (!isNaN(value)) {
result += value;
}
break;
case 'object':
result += SUM(value, table);
break;
}
}
return result;
}
/* *
*
* Registry
*
* */
Formula_FormulaProcessor.registerProcessorFunction('SUM', SUM); // 🐝
/* *
*
* Default Export
*
* */
/* harmony default export */ const Functions_SUM = (SUM);
;// ./code/dashboards/es-modules/Data/Formula/Functions/AVERAGE.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
*
* */
const { getArgumentsValues: AVERAGE_getArgumentsValues } = Formula_FormulaProcessor;
/* *
*
* Functions
*
* */
/**
* Processor for the `AVERAGE(...values)` implementation. Calculates the average
* of the given values that are numbers.
*
* @private
* @function Formula.processorFunctions.AVERAGE
*
* @param {Highcharts.FormulaArguments} args
* Arguments to process.
*
* @param {Highcharts.DataTable} [table]
* Table to use for references and ranges.
*
* @return {number}
* Result value of the process.
*/
function AVERAGE(args, table) {
const values = AVERAGE_getArgumentsValues(args, table);
let count = 0, result = 0;
for (let i = 0, iEnd = values.length, value; i < iEnd; ++i) {
value = values[i];
switch (typeof value) {
case 'number':
if (!isNaN(value)) {
++count;
result += value;
}
break;
case 'object':
for (let j = 0, jEnd = value.length, value2; j < jEnd; ++j) {
value2 = value[j];
if (typeof value2 === 'number' &&
!isNaN(value2)) {
++count;
result += value2;
}
}
break;
}
}
return (count ? (result / count) : 0);
}
/* *
*
* Registry
*
* */
Formula_FormulaProcessor.registerProcessorFunction('AVERAGE', AVERAGE);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Functions_AVERAGE = (AVERAGE);
;// ./code/dashboards/es-modules/Data/Formula/Functions/MEDIAN.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
*
* */
/* *
*
* Functions
*
* */
/**
* Processor for the `MEDIAN(...values)` implementation. Calculates the median
* average of the given values.
*
* @private
* @function Formula.processorFunctions.MEDIAN
*
* @param {Highcharts.FormulaArguments} args
* Arguments to process.
*
* @param {Highcharts.DataTable} [table]
* Table to process.
*
* @return {number}
* Result value of the process.
*/
function MEDIAN(args, table) {
const median = [], values = Formula_FormulaProcessor.getArgumentsValues(args, table);
for (let i = 0, iEnd = values.length, value; i < iEnd; ++i) {
value = values[i];
switch (typeof value) {
case 'number':
if (!isNaN(value)) {
median.push(value);
}
break;
case 'object':
for (let j = 0, jEnd = value.length, value2; j < jEnd; ++j) {
value2 = value[j];
if (typeof value2 === 'number' &&
!isNaN(value2)) {
median.push(value2);
}
}
break;
}
}
const count = median.length;
if (!count) {
return NaN;
}
const half = Math.floor(count / 2); // Floor because index starts at 0
return (count % 2 ?
median[half] : // Odd
(median[half - 1] + median[half]) / 2 // Even
);
}
/* *
*
* Registry
*
* */
Formula_FormulaProcessor.registerProcessorFunction('MEDIAN', MEDIAN);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Functions_MEDIAN = (MEDIAN);
;// ./code/dashboards/es-modules/Data/Formula/Functions/MAX.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
*
* */
const { getArgumentsValues: MAX_getArgumentsValues } = Formula_FormulaProcessor;
/* *
*
* Functions
*
* */
/**
* Processor for the `MAX(...values)` implementation. Calculates the largest
* of the given values that are numbers.
*
* @private
* @function Formula.processorFunctions.MAX
*
* @param {Highcharts.FormulaArguments} args
* Arguments to process.
*
* @param {Highcharts.DataTable} [table]
* Table to use for references and ranges.
*
* @return {number}
* Result value of the process.
*/
function MAX(args, table) {
const values = MAX_getArgumentsValues(args, table);
let result = Number.NEGATIVE_INFINITY;
for (let i = 0, iEnd = values.length, value; i < iEnd; ++i) {
value = values[i];
switch (typeof value) {
case 'number':
if (value > result) {
result = value;
}
break;
case 'object':
value = MAX(value);
if (value > result) {
result = value;
}
break;
}
}
return isFinite(result) ? result : 0;
}
/* *
*
* Registry
*
* */
Formula_FormulaProcessor.registerProcessorFunction('MAX', MAX);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Functions_MAX = (MAX);
;// ./code/dashboards/es-modules/Data/Formula/Functions/MIN.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
*
* */
const { getArgumentsValues: MIN_getArgumentsValues } = Formula_FormulaProcessor;
/* *
*
* Functions
*
* */
/**
* Processor for the `MIN(...values)` implementation. Calculates the lowest
* of the given values that are numbers.
*
* @private
* @function Formula.processorFunctions.MIN
*
* @param {Highcharts.FormulaArguments} args
* Arguments to process.
*
* @param {Highcharts.DataTable} [table]
* Table to use for references and ranges.
*
* @return {number}
* Result value of the process.
*/
function MIN(args, table) {
const values = MIN_getArgumentsValues(args, table);
let result = Number.POSITIVE_INFINITY;
for (let i = 0, iEnd = values.length, value; i < iEnd; ++i) {
value = values[i];
switch (typeof value) {
case 'number':
if (value < result) {
result = value;
}
break;
case 'object':
value = MIN(value);
if (value < result) {
result = value;
}
break;
}
}
return isFinite(result) ? result : 0;
}
/* *
*
* Registry
*
* */
Formula_FormulaProcessor.registerProcessorFunction('MIN', MIN);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Functions_MIN = (MIN);
;// ./code/dashboards/es-modules/Data/Formula/Functions/COUNT.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
*
* */
/* *
*
* Functions
*
* */
/**
* Processor for the `COUNT(...values)` implementation. Returns the count of
* given values that are numbers.
*
* @private
* @function Formula.processorFunctions.COUNT
*
* @param {Highcharts.FormulaArguments} args
* Arguments to process.
*
* @param {Highcharts.DataTable} [table]
* Table to use for references and ranges.
*
* @return {number}
* Result value of the process.
*/
function COUNT(args, table) {
const values = Formula_FormulaProcessor.getArgumentsValues(args, table);
let count = 0;
for (let i = 0, iEnd = values.length, value; i < iEnd; ++i) {
value = values[i];
switch (typeof value) {
case 'number':
if (!isNaN(value)) {
++count;
}
break;
case 'object':
count += COUNT(value, table);
break;
}
}
return count;
}
/* *
*
* Registry
*
* */
Formula_FormulaProcessor.registerProcessorFunction('COUNT', COUNT);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Functions_COUNT = (COUNT);
;// ./code/dashboards/es-modules/Data/Formula/Functions/PRODUCT.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
*
* */
const { getArgumentsValues: PRODUCT_getArgumentsValues } = Formula_FormulaProcessor;
/* *
*
* Functions
*
* */
/**
* Processor for the `PRODUCT(...values)` implementation. Calculates the product
* of the given values.
*
* @private
* @function Formula.processorFunctions.PRODUCT
*
* @param {Highcharts.FormulaArguments} args
* Arguments to process.
*
* @param {Highcharts.DataTable} [table]
* Table to use for references and ranges.
*
* @return {number}
* Result value of the process.
*/
function PRODUCT(args, table) {
const values = PRODUCT_getArgumentsValues(args, table);
let result = 1, calculated = false;
for (let i = 0, iEnd = values.length, value; i < iEnd; ++i) {
value = values[i];
switch (typeof value) {
case 'number':
if (!isNaN(value)) {
calculated = true;
result *= value;
}
break;
case 'object':
calculated = true;
result *= PRODUCT(value, table);
break;
}
}
return (calculated ? result : 0);
}
/* *
*
* Registry
*
* */
Formula_FormulaProcessor.registerProcessorFunction('PRODUCT', PRODUCT);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Functions_PRODUCT = (PRODUCT);
;// ./code/dashboards/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(in srgb, 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/dashboards/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/dashboards/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), localHours = (hours - offset / timeUnits.hour + 24) % 24;
d += offset;
if (
// Limit the number of calls to `getTimezoneOffset` to months
// where DST changes may occur. 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 changes only occur at night (#24420)
(localHours < 5 || localHours > 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/dashboards/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/dashboards/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 Defaults_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(Defaults_defaultOptions.time, Defaults_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 Defaults_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 Defaults_setOptions(options) {
fireEvent(Core_Globals, 'setOptions', { options });
// Copy in the default options
merge(true, Defaults_defaultOptions, options);
// Update the time object
if (options.time) {
defaultTime.update(Defaults_defaultOptions.time);
}
if (options.lang && 'locale' in options.lang) {
defaultTime.update({
locale: options.lang.locale
});
}
if (options.lang?.chartTitle) {
Defaults_defaultOptions.title = {
...Defaults_defaultOptions.title,
text: options.lang.chartTitle
};
}
return Defaults_defaultOptions;
}
/* *
*
* Default Export
*
* */
const DefaultOptions = {
defaultOptions: Defaults_defaultOptions,
defaultTime,
getOptions,
setOptions: Defaults_setOptions
};
/* harmony default export */ const Core_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/dashboards/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 } = Core_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
* */
/**
* The Highcharts.TemplatingObject 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)
*
* @interface Highcharts.TemplatingObject
*/ /**
* @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.TemplatingObject#helpers
* @type {Record<string, Function>}
*/ /**
* @name Highcharts.Templating
* @type {Highcharts.TemplatingObject}
*/
(''); // Keeps doclets above in file
;// ./code/dashboards/es-modules/Dashboards/Components/KPIComponent/KPIComponent.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:
* - Sebastian Bochan
* - Wojciech Chmiel
* - Gøran Slettemark
* - Sophie Bremer
*
* */
const { format: KPIComponent_format } = Core_Templating;
/* *
*
* Class
*
* */
/**
*
* Class that represents a KPI component.
*
*/
class KPIComponent extends Components_Component {
/* *
*
* Constructor
*
* */
/**
* Creates a KPI component in the cell.
*
* @param cell
* Instance of cell, where component is attached.
*
* @param options
* The options for the component.
*/
constructor(cell, options, board) {
options = merge(KPIComponent.defaultOptions, options);
super(cell, options, board);
this.options = options;
this.type = 'KPI';
this.value = createElement('span', {
className: `${options.className}-value`
}, {}, this.contentElement);
this.subtitle = createElement('span', {
className: this.getSubtitleClassName()
}, {}, this.contentElement);
}
/* *
*
* Functions
*
* */
/** @internal */
async load() {
await super.load();
this.linkValueToChart();
return this;
}
resize(width, height) {
super.resize(width, height);
// Animate
if (this.chart && this.chart.container) {
this.chart.reflow();
}
return this;
}
render() {
super.render();
this.updateElements();
const charter = KPIComponent.charter?.Chart;
if (charter &&
this.options.chartOptions &&
!this.chart) {
if (!this.chartContainer) {
this.chartContainer = createElement('div', {
className: `${this.options.className}-chart-container`
}, {
// Fix inner height, when using flex box
padding: '0.1px'
}, this.contentElement);
}
this.chart = charter.chart(this.chartContainer, merge(KPIComponent.defaultChartOptions, this.options.chartOptions));
}
else if (this.chart &&
!this.options.chartOptions &&
'chartOptions' in this.options) {
this.chart.destroy();
this.chart = void 0;
}
this.sync.start();
this.emit({ type: 'afterRender' });
return this;
}
/**
* Handles updating via options.
*
* @param options
* The options to apply.
*/
async update(options, shouldRerender = true) {
await super.update(options);
if (options.chartOptions && this.chart) {
this.chart.update(options.chartOptions);
}
shouldRerender && this.render();
}
/**
* @internal
*/
onTableChanged() {
this.setValue();
}
/**
* Destroys the highcharts component.
*/
destroy() {
// Cleanup references in the global Highcharts scope
this.chart?.destroy();
super.destroy();
}
/**
* Gets a proper value, according to the provided formula option.
*
* @returns
* The formula value. Can be a number internally, or a string from the
* callback function.
*
* @internal
*/
getFormulaValue() {
const formula = this.options.formula;
const table = this.getDataTable();
const column = table?.getColumn(this.options.columnId);
if (!column || !formula) {
return;
}
if (isFunction(formula)) {
return formula.call(this, column);
}
let filteredColumn = Array.isArray(column) ?
column.slice().filter(defined) : Array.from(column);
// Filter NaN values and empty strings since the formula functions don't
// handle it internally.
if (formula === 'MIN' || formula === 'MAX' || formula === 'MEDIAN') {
filteredColumn = filteredColumn.filter((val) => val !== '' && !isNaN(Number(val)));
}
// Sort values since the formula function don't handle it internally.
if (formula === 'MEDIAN') {
filteredColumn.sort((a, b) => Number(a) - Number(b));
}
try {
return KPIComponent.formulaFunctions[formula](filteredColumn);
}
catch {
console.warn('Invalid formula option provided.'); // eslint-disable-line no-console
}
}
/**
* Gets the default value that should be displayed in the KPI.
*
* @returns
* The value that should be displayed in the KPI.
*/
getValue() {
if (defined(this.options.value)) {
return this.options.value;
}
const dataTable = this.getDataTable()?.getModified();
if (dataTable && this.options.columnId) {
if (defined(this.options.formula)) {
return this.getFormulaValue();
}
const column = dataTable.getColumn(this.options.columnId), length = column?.length || 0;
return String(dataTable.getCell(this.options.columnId, length - 1));
}
}
/**
* Sets the value that should be displayed in the KPI.
*
* @param value
* The value to display in the KPI.
*/
setValue(value = this.getValue()) {
const { valueFormat, valueFormatter } = this.options;
if (defined(value)) {
let prevValue;
if (isNumber(+value)) {
prevValue = +value;
}
if (valueFormatter) {
value = valueFormatter.call(this, value);
}
else if (valueFormat) {
value = KPIComponent_format(valueFormat, { value });
}
else if (isNumber(value)) {
value = value.toLocaleString();
}
HTML_AST.setElementHTML(this.value, '' + value);
this.linkValueToChart(prevValue);
this.prevValue = prevValue;
}
}
/**
* Handles updating chart point value.
*
* @internal
*/
linkValueToChart(value = this.getValue()) {
const chart = this.chart;
const linkedValueTo = this.options.linkedValueTo;
if (!chart || !linkedValueTo.enabled ||
!defined(value) || !isNumber(+value)) {
return;
}
value = +value;
const targetSeries = chart.series[linkedValueTo.seriesIndex ?? 0], targetPoint = targetSeries?.points[linkedValueTo.pointIndex ?? 0];
if (targetSeries) {
if (targetPoint) {
targetPoint.update({
y: value
});
return;
}
targetSeries.addPoint({
y: value
});
return;
}
chart.addSeries({
data: [{
y: value
}]
});
}
/**
* Handles updating elements via options
*
* @internal
*/
updateElements() {
const { style, subtitle } = this.options;
this.setValue();
HTML_AST.setElementHTML(this.subtitle, this.getSubtitle());
if (style) {
css(this.element, style);
}
if (typeof subtitle === 'object') {
if (subtitle.style) {
css(this.subtitle, subtitle.style);
}
this.subtitle.className = this.getSubtitleClassName();
}
if (this.chartContainer) {
this.chartContainer.style.flex =
this.options.chartOptions ? '1' : '0';
}
if (this.chart) {
this.chart.reflow();
}
this.value.style.color = this.getValueColor();
}
/**
* Gets KPI subtitle text.
*
* @returns
* The subtitle's text.
*
* @internal
*/
getSubtitle() {
const { subtitle, value } = this.options;
if (typeof subtitle === 'string') {
return subtitle;
}
if (subtitle) {
if (isNumber(this.prevValue) && isNumber(value)) {
const diff = value - this.prevValue;
let prefix = '';
if (diff > 0) {
prefix = '<span style="color:green">▲</span> +';
}
else if (diff < 0) {
prefix = '<span style="color:red">▼</span> ';
}
else {
return this.subtitle.innerHTML;
}
if (subtitle.type === 'diff') {
return prefix + diff.toLocaleString();
}
if (subtitle.type === 'diffpercent') {
return prefix + KPIComponent_format('{v:,.2f}%', {
v: diff / this.prevValue * 100
});
}
}
return subtitle.text || '';
}
return '';
}
/**
* Gets CSS class name of the KPI subtitle.
*
* @returns
* The name of class.
*
* @internal
*/
getSubtitleClassName() {
const { subtitle } = this.options;
return `${Components_Component.defaultOptions.className}-subtitle` +
((typeof subtitle === 'object' && subtitle.className) || '');
}
/**
* Applies title's color according to the threshold.
*
* @returns
* Hex of color.
*
* @internal
*/
getValueColor() {
const { threshold, thresholdColors, value } = this.options;
if (thresholdColors && threshold && isNumber(value)) {
if (isArray(threshold)) {
for (let i = threshold.length - 1; i >= 0; i--) {
if (value >= threshold[i]) {
if (i + 1 < thresholdColors.length) {
return thresholdColors[i + 1];
}
return thresholdColors[thresholdColors.length - 1];
}
}
}
else if (value >= threshold) {
return thresholdColors[1];
}
return thresholdColors[0];
}
return '';
}
getOptionsOnDrop(sidebar) {
const connectorsIds = sidebar.editMode.board.dataPool.getConnectorIds();
let options = {
type: 'KPI'
};
if (connectorsIds.length) {
options = {
...options,
connector: {
id: connectorsIds[0]
}
};
}
return options;
}
/**
* Get the KPI component's options.
* @returns
* KPI component's options.
*
* @internal
*
*/
getOptions() {
return {
...diffObjects(this.options, KPIComponent.defaultOptions),
type: 'KPI'
};
}
}
/**
* Default options of the KPI component.
*/
KPIComponent.defaultOptions = merge(Components_Component.defaultOptions, KPIComponent_KPIComponentDefaults);
/**
* Predefined sync config for the KPI component.
*/
KPIComponent.predefinedSyncConfig = KPISyncs;
/**
* Default options of the KPI component.
*
* @default {
chart: {
type: 'spline',
zooming: {
mouseWheel: {
enabled: false
}
}
},
title: {
text: void 0
},
xAxis: {
visible: false
},
yAxis: {
visible: false,
title: {
text: null
}
},
legend: {
enabled: false
},
credits: {
enabled: false
},
tooltip: {
outside: true
},
plotOptions: {
series: {
marker: {
enabled: false
}
}
}
}
*/
KPIComponent.defaultChartOptions = {
chart: {
type: 'spline',
zooming: {
mouseWheel: {
enabled: false
}
}
},
title: {
text: void 0
},
xAxis: {
visible: false
},
yAxis: {
visible: false,
title: {
text: null
}
},
legend: {
enabled: false
},
credits: {
enabled: false
},
tooltip: {
outside: true
},
plotOptions: {
series: {
marker: {
enabled: false
}
}
}
};
/**
* The formula option's default formula functions map.
*/
KPIComponent.formulaFunctions = {
SUM: Functions_SUM,
AVERAGE: Functions_AVERAGE,
MEDIAN: Functions_MEDIAN,
MAX: Functions_MAX,
MIN: Functions_MIN,
COUNT: Functions_COUNT,
PRODUCT: Functions_PRODUCT
};
/* *
*
* Default Export
*
* */
/* harmony default export */ const KPIComponent_KPIComponent = (KPIComponent);
;// ./code/dashboards/es-modules/Dashboards/Components/NavigatorComponent/NavigatorComponentDefaults.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
*
* */
/* *
*
* Constants
*
* */
const NavigatorComponentDefaults = {
type: 'Navigator',
className: [
Components_Component.defaultOptions.className,
`${Components_Component.defaultOptions.className}-navigator`
].join(' '),
chartOptions: {
chart: {
animation: false,
height: 200,
type: 'column',
zooming: {
mouseWheel: {
enabled: false
}
}
},
credits: {
enabled: false
},
legend: {
enabled: false
},
navigator: {
enabled: true,
outlineWidth: 0,
series: {
animation: false,
lineWidth: 0,
colorIndex: 0
},
xAxis: {
endOnTick: true,
gridZIndex: 4,
labels: {
x: 1,
y: 22
},
opposite: true,
showFirstLabel: true,
showLastLabel: true,
startOnTick: true,
tickPosition: 'inside'
},
yAxis: {
maxPadding: 0.5
}
},
plotOptions: {
series: {
borderRadius: 0,
marker: {
enabled: false
},
states: {
hover: {
enabled: false
}
}
}
},
scrollbar: {
enabled: true
},
title: {
text: ''
},
tooltip: {
enabled: false
},
xAxis: {
visible: false,
minRange: Number.MIN_VALUE
},
yAxis: {
visible: false
}
},
editableOptions: (Components_Component.defaultOptions.editableOptions || []).concat()
};
/* *
*
* Default Export
*
* */
/* harmony default export */ const NavigatorComponent_NavigatorComponentDefaults = (NavigatorComponentDefaults);
;// ./code/dashboards/es-modules/Dashboards/Components/NavigatorComponent/NavigatorSyncs/NavigatorSyncUtils.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:
* - Dawid Draguła
*
* */
/* *
*
* Utility Functions
*
* */
/**
* Adds or updates range options for a specific column.
* @param filterOptions Filter modifier options object reference.
* @param column Column name.
* @param minValue Minimum value.
* @param maxValue Maximum value.
* @internal
*/
function setRangeOptions(filterOptions, column, minValue, maxValue) {
let changedMin = false;
let changedMax = false;
if (typeof filterOptions.condition !== 'object' ||
filterOptions.condition.operator !== 'and') {
filterOptions.condition = {
operator: 'and',
conditions: []
};
}
const { conditions } = filterOptions.condition;
for (let i = 0, iEnd = conditions.length; i < iEnd; ++i) {
const condition = conditions[i];
if (!condition ||
typeof condition !== 'object' ||
!(condition.operator === '<=' || condition.operator === '>=') ||
condition.columnId !== column) {
continue;
}
if (condition.operator === '<=') {
condition.value = maxValue;
changedMax = true;
}
else {
condition.value = minValue;
changedMin = true;
}
if (changedMin && changedMax) {
return;
}
}
if (!changedMax) {
conditions.push({
operator: '<=',
columnId: column,
value: maxValue
});
}
if (!changedMin) {
conditions.push({
operator: '>=',
columnId: column,
value: minValue
});
}
}
/**
* Removes range options for a specific column.
* @param filterOptions Filter modifier options object reference.
* @param column Column name.
* @internal
*/
function unsetRangeOptions(filterOptions, column) {
if (typeof filterOptions.condition !== 'object' ||
filterOptions.condition.operator !== 'and') {
return;
}
const { conditions } = filterOptions.condition;
for (let i = 0, iEnd = conditions.length; i < iEnd; ++i) {
const condition = conditions[i];
if (!condition ||
typeof condition !== 'object' ||
!(condition.operator === '<=' || condition.operator === '>=') ||
condition.columnId !== column) {
continue;
}
conditions.splice(i, 1)[0];
}
}
/**
* Converts filter options to ranges array.
*
* @param filterOptions
* Filter modifier options object reference.
*/
function NavigatorSyncUtils_toRange(filterOptions) {
const rangesMap = {};
if (typeof filterOptions.condition !== 'object' ||
filterOptions.condition.operator !== 'and') {
return [];
}
const { conditions } = filterOptions.condition;
for (let i = 0, iEnd = conditions.length; i < iEnd; ++i) {
const condition = conditions[i];
if (!condition ||
typeof condition !== 'object' ||
!(condition.operator === '<=' || condition.operator === '>=') ||
typeof condition.columnId !== 'string' ||
!defined(condition.value)) {
continue;
}
const colName = condition.columnId;
if (!rangesMap[colName]) {
rangesMap[colName] = {
maxValue: Infinity,
minValue: -Infinity,
columnId: colName
};
}
if (condition.operator === '<=') {
rangesMap[colName].maxValue = condition.value;
}
else {
rangesMap[colName].minValue = condition.value;
}
}
return Object.values(rangesMap);
}
const NavigatorSyncUtils = {
setRangeOptions,
unsetRangeOptions,
toRange: NavigatorSyncUtils_toRange
};
/* *
*
* Default Export
*
* */
/* harmony default export */ const NavigatorSyncs_NavigatorSyncUtils = (NavigatorSyncUtils);
;// ./code/dashboards/es-modules/Dashboards/Components/NavigatorComponent/NavigatorSyncs/NavigatorCrossfilterSync.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:
* - Dawid Draguła
*
* */
const { Filter: NavigatorCrossfilterSync_FilterModifier } = Modifiers_DataModifier.types;
/* *
*
* Constants
*
* */
const NavigatorCrossfilterSync_defaultOptions = {
affectNavigator: false
};
const NavigatorCrossfilterSync_syncPair = {
emitter: function () {
if (this.type !== 'Navigator') {
return;
}
const component = this;
const syncOptions = this.sync.syncConfig.crossfilter;
const groupKey = syncOptions.group ? ':' + syncOptions.group : '';
const afterSetExtremes = async (extremes) => {
if (component.connectorHandlers?.[0]?.connector) {
const table = component.connectorHandlers[0].connector.getTable(), dataCursor = component.board.dataCursor, filterColumn = component.getColumnAssignment()[0], [min, max] = component.getAxisExtremes();
let modifier = table.getModifier();
if (modifier instanceof NavigatorCrossfilterSync_FilterModifier) {
NavigatorSyncs_NavigatorSyncUtils.setRangeOptions(modifier.options, filterColumn, min, max);
}
else {
modifier = new NavigatorCrossfilterSync_FilterModifier({
condition: {
operator: 'and',
conditions: [{
columnId: filterColumn,
operator: '>=',
value: min
}, {
columnId: filterColumn,
operator: '<=',
value: max
}]
}
});
}
await table.setModifier(modifier);
dataCursor.emitCursor(table, {
type: 'position',
column: filterColumn,
row: table.getRowIndexBy(filterColumn, min),
state: 'crossfilter' + groupKey
}, extremes);
dataCursor.emitCursor(table, {
type: 'position',
column: filterColumn,
row: table.getRowIndexBy(filterColumn, max),
state: 'crossfilter' + groupKey
}, extremes);
}
};
let delay;
return addEvent(component.chart.xAxis[0], 'afterSetExtremes', function (extremes) {
clearTimeout(delay);
delay = setTimeout(afterSetExtremes, 50, this, extremes);
});
},
handler: void 0
};
/* *
*
* Default export
*
* */
/* harmony default export */ const NavigatorCrossfilterSync = ({ defaultOptions: NavigatorCrossfilterSync_defaultOptions, syncPair: NavigatorCrossfilterSync_syncPair });
;// ./code/dashboards/es-modules/Dashboards/Components/NavigatorComponent/NavigatorSyncs/NavigatorExtremesSync.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:
* - Dawid Draguła
*
* */
const { Filter: NavigatorExtremesSync_FilterModifier } = Modifiers_DataModifier.types;
/* *
*
* Constants
*
* */
const NavigatorExtremesSync_defaultOptions = {};
const NavigatorExtremesSync_syncPair = {
emitter: function () {
if (this.type !== 'Navigator') {
return;
}
const component = this;
const syncOptions = this.sync.syncConfig.extremes;
const groupKey = syncOptions.group ? ':' + syncOptions.group : '';
const afterSetExtremes = (extremes) => {
if (component.connectorHandlers?.[0]?.connector) {
const table = component.connectorHandlers[0].connector.getTable(), dataCursor = component.board.dataCursor, filterColumn = component.getColumnAssignment()[0], [min, max] = component.getAxisExtremes();
dataCursor.emitCursor(table, {
type: 'position',
column: filterColumn,
row: table.getRowIndexBy(filterColumn, min),
state: 'xAxis.extremes.min' + groupKey
}, extremes);
dataCursor.emitCursor(table, {
type: 'position',
column: filterColumn,
row: table.getRowIndexBy(filterColumn, max),
state: 'xAxis.extremes.max' + groupKey
}, extremes);
}
};
let delay;
return addEvent(component.chart.xAxis[0], 'afterSetExtremes', function (extremes) {
clearTimeout(delay);
delay = setTimeout(afterSetExtremes, 50, this, extremes);
});
},
handler: function () {
if (this.type !== 'Navigator') {
return;
}
const component = this;
const syncOptions = this.sync.syncConfig.extremes;
const groupKey = syncOptions.group ? ':' + syncOptions.group : '';
const dataCursor = component.board.dataCursor;
const extremesListener = (e) => {
const cursor = e.cursor;
if (!component.connectorHandlers?.[0]?.connector) {
return;
}
const table = component.connectorHandlers[0].connector.getTable();
// Assume first column with unique keys as fallback
let extremesColumn = table.getColumnIds()[0], maxIndex = table.getRowCount(), minIndex = 0;
if (cursor.type === 'range') {
maxIndex = cursor.lastRow;
minIndex = cursor.firstRow;
if (cursor.columns) {
extremesColumn = pick(cursor.columns[0], extremesColumn);
}
}
else if (cursor.state === 'xAxis.extremes.max' + groupKey) {
extremesColumn = pick(cursor.column, extremesColumn);
maxIndex = pick(cursor.row, maxIndex);
}
else {
extremesColumn = pick(cursor.column, extremesColumn);
minIndex = pick(cursor.row, minIndex);
}
const modifier = table.getModifier();
if (typeof extremesColumn === 'string' &&
modifier instanceof NavigatorExtremesSync_FilterModifier) {
const min = table.getCell(extremesColumn, minIndex);
const max = table.getCell(extremesColumn, maxIndex);
if (defined(max) && defined(min)) {
NavigatorSyncs_NavigatorSyncUtils.setRangeOptions(modifier.options, extremesColumn, min, max);
void table.setModifier(modifier);
}
}
};
const registerCursorListeners = () => {
const table = component.connectorHandlers?.[0]?.connector?.getTable();
if (table) {
dataCursor.addListener(table.id, 'xAxis.extremes' + groupKey, extremesListener);
dataCursor.addListener(table.id, 'xAxis.extremes.max' + groupKey, extremesListener);
dataCursor.addListener(table.id, 'xAxis.extremes.min' + groupKey, extremesListener);
}
};
const unregisterCursorListeners = () => {
const table = component.connectorHandlers?.[0]?.connector?.getTable();
if (table) {
dataCursor.removeListener(table.id, 'xAxis.extremes' + groupKey, extremesListener);
dataCursor.removeListener(table.id, 'xAxis.extremes.max' + groupKey, extremesListener);
dataCursor.removeListener(table.id, 'xAxis.extremes.min' + groupKey, extremesListener);
}
};
registerCursorListeners();
return unregisterCursorListeners;
}
};
/* *
*
* Default export
*
* */
/* harmony default export */ const NavigatorExtremesSync = ({ defaultOptions: NavigatorExtremesSync_defaultOptions, syncPair: NavigatorExtremesSync_syncPair });
;// ./code/dashboards/es-modules/Dashboards/Components/NavigatorComponent/NavigatorSyncs/NavigatorSyncs.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:
* - Dawid Draguła
*
* */
/* *
*
* Constants
*
* */
const NavigatorSyncs_predefinedSyncConfig = {
defaultSyncPairs: {
crossfilter: NavigatorCrossfilterSync.syncPair,
extremes: NavigatorExtremesSync.syncPair
},
defaultSyncOptions: {
crossfilter: NavigatorCrossfilterSync.defaultOptions,
extremes: NavigatorExtremesSync.defaultOptions
}
};
/* *
*
* Default export
*
* */
/* harmony default export */ const NavigatorSyncs = (NavigatorSyncs_predefinedSyncConfig);
;// ./code/dashboards/es-modules/Dashboards/Components/NavigatorComponent/NavigatorComponent.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
*
* */
/* *
*
* Class
*
* */
/**
* Setup a component with data navigation.
*/
class NavigatorComponent extends Components_Component {
/* *
*
* Constructor
*
* */
constructor(cell, options) {
super(cell, options);
this.type = 'Navigator';
this.options = merge(NavigatorComponent.defaultOptions, options);
const charter = (NavigatorComponent.charter.Chart ||
Dashboards_Globals.win.Highcharts);
this.chartContainer = Dashboards_Globals.win.document.createElement('div');
this.chart = charter
.chart(this.chartContainer, (this.options.chartOptions || {}));
this.chartContainer.classList
.add(Dashboards_Globals.classNamePrefix + 'navigator');
if (this.sync.syncConfig.crossfilter?.enabled) {
this.chart.update(merge({ navigator: { xAxis: { labels: { format: '{value}' } } } }, this.options.chartOptions || {}), false);
}
}
/* *
*
* Functions
*
* */
/** @private */
adjustNavigator() {
const chart = this.chart, height = pick(chart.chartHeight, this.contentElement.clientHeight), width = this.contentElement.clientWidth, chartUpdates = {};
if (chart.chartHeight !== height ||
chart.chartWidth !== width) {
chartUpdates.chart = {
height,
width
};
}
if (chart.navigator) {
const navigator = chart.navigator, navigatorHeight = (navigator.top - chart.plotTop + navigator.height);
if (navigator.height !== navigatorHeight) {
chartUpdates.navigator = {
handles: {
height: Math.round(height / 4)
},
height: navigatorHeight
};
}
if (Object.keys(chartUpdates).length) {
chart.update(chartUpdates, false);
}
if (navigator.series && navigator.series[0]) {
navigator.series[0].update({
type: chart.series[0].type
}, false);
}
}
else if (Object.keys(chartUpdates).length) {
chart.update(chartUpdates, false);
}
}
/**
* Returns the first column of columnAssignment to use for navigator data.
* @private
*
* @return
* Navigator column assignment.
*/
getColumnAssignment() {
const columnAssignment = this.options.columnAssignment ?? {};
let columnsAssignment;
for (const column of Object.keys(columnAssignment)) {
columnsAssignment = columnAssignment[column];
if (columnsAssignment !== null) {
return [column, columnsAssignment];
}
}
const table = this.getDataTable();
if (table) {
const columns = table.getColumnIds();
if (columns.length) {
return [columns[0], 'y'];
}
}
return ['', 'y'];
}
/**
* Gets the component's options.
* @internal
*/
getOptions() {
return {
...diffObjects(this.options, NavigatorComponent_NavigatorComponentDefaults),
type: 'Navigator'
};
}
/**
* Gets the extremes of the navigator's x-axis.
*/
getAxisExtremes() {
const axis = this.chart.xAxis[0], extremes = axis.getExtremes(), min = isNumber(extremes.min) ? extremes.min : extremes.dataMin, max = isNumber(extremes.max) ? extremes.max : extremes.dataMax;
if (this.categories) {
return [
this.categories[Math.max(0, Math.ceil(min))],
this.categories[Math.min(this.categories.length - 1, Math.floor(max))]
];
}
if (axis.hasNames) {
return [
axis.names[Math.ceil(min)],
axis.names[Math.floor(max)]
];
}
return [min, max];
}
/** @private */
async load() {
await super.load();
this.contentElement.appendChild(this.chartContainer);
this.parentElement.appendChild(this.element);
this.adjustNavigator();
this.emit({ type: 'afterLoad' });
return this;
}
onTableChanged() {
this.renderNavigator();
}
/** @private */
redrawNavigator() {
const timeouts = this.resizeTimeouts;
for (let i = 0, iEnd = timeouts.length; i < iEnd; ++i) {
clearTimeout(timeouts[i]);
}
timeouts.length = 0;
timeouts.push(setTimeout(() => {
this.adjustNavigator();
this.chart.redraw();
}, 33));
}
/** @private */
render() {
const component = this;
super.render();
component.renderNavigator();
component.sync.start();
component.emit({ type: 'afterRender' });
return component;
}
/** @private */
renderNavigator() {
const chart = this.chart;
const table = this.getDataTable();
if (table) {
const column = this.getColumnAssignment(), columnValues = table.getColumn(column[0], true) || [];
let data;
if (this.sync.syncConfig.crossfilter?.enabled) {
data = this.generateCrossfilterData();
}
else {
data = columnValues.slice();
}
if (!chart.series[0]) {
chart.addSeries({ id: table.id, data }, false);
}
else {
chart.series[0].setData(data, false);
}
}
this.redrawNavigator();
}
/**
* Generates the data for the crossfilter navigator.
*/
generateCrossfilterData() {
const crossfilterOptions = this.sync.syncConfig.crossfilter;
const table = this.getDataTable();
const columnValues = table?.getColumn(this.getColumnAssignment()[0], true) || [];
if (!table || columnValues.length < 1 || !crossfilterOptions) {
return [];
}
const values = [];
const uniqueXValues = [];
for (let i = 0, iEnd = columnValues.length; i < iEnd; i++) {
let value = columnValues[i];
if (value === null) {
continue;
}
else if (!isNumber(value)) {
value = `${value}`;
}
// Check if the x-axis data is not of mixed type.
if (this.stringData === void 0) {
this.stringData = isString(value);
}
else if (this.stringData !== isString(value)) {
throw new Error('Mixed data types in crossfilter navigator are ' +
'not supported.');
}
values.push(value);
if (uniqueXValues.indexOf(value) === -1) {
uniqueXValues.push(value);
}
}
uniqueXValues.sort((a, b) => (pick(a, NaN) < pick(b, NaN) ? -1 : a === b ? 0 : 1));
let filteredValues;
const modifierOptions = table.getModifier()?.options;
if (crossfilterOptions.affectNavigator &&
modifierOptions?.type === 'Filter') {
const appliedRanges = [];
const rangedColumns = [];
const ranges = NavigatorSyncs_NavigatorSyncUtils.toRange(modifierOptions);
for (let i = 0, iEnd = ranges.length; i < iEnd; i++) {
if (ranges[i].columnId !== this.getColumnAssignment()[0]) {
appliedRanges.push(ranges[i]);
rangedColumns.push(table.getColumn(ranges[i].columnId, true) || []);
}
}
filteredValues = [];
const appliedRagesLength = appliedRanges.length;
for (let i = 0, iEnd = values.length; i < iEnd; i++) {
const value = values[i];
let allConditionsMet = true;
for (let j = 0; j < appliedRagesLength; j++) {
const range = appliedRanges[j];
if (!(rangedColumns[j][i] >=
(range.minValue ?? -Infinity) &&
rangedColumns[j][i] <=
(range.maxValue ?? Infinity))) {
allConditionsMet = false;
break;
}
}
if (allConditionsMet) {
filteredValues.push(value);
}
}
}
else {
filteredValues = values;
}
const seriesData = [];
if (this.stringData) {
this.categories = uniqueXValues;
for (let i = 0, iEnd = uniqueXValues.length; i < iEnd; i++) {
seriesData.push([i, null]);
}
}
else {
for (let i = 0, iEnd = uniqueXValues.length; i < iEnd; i++) {
seriesData.push([uniqueXValues[i], null]);
}
}
for (let i = 0, iEnd = filteredValues.length; i < iEnd; i++) {
const index = uniqueXValues.indexOf(filteredValues[i]);
seriesData[index][1] = (seriesData[index][1] || 0) + 1;
}
return seriesData;
}
/** @private */
resize(width, height) {
super.resize(width, height);
this.redrawNavigator();
return this;
}
/**
* Handles updating via options.
*
* @param options
* The options to apply.
*/
async update(options, shouldRerender = true) {
const chart = this.chart;
await super.update(options, false);
if (options.chartOptions) {
chart.update(merge(this.sync.syncConfig.crossfilter?.enabled ? ({ navigator: { xAxis: { labels: { format: '{value}' } } } }) : {}, options.chartOptions), false);
}
this.emit({ type: 'afterUpdate' });
if (shouldRerender) {
this.render();
}
}
getOptionsOnDrop() {
return {};
}
}
/**
* Default options of the Navigator component.
*/
NavigatorComponent.defaultOptions = merge(Components_Component.defaultOptions, NavigatorComponent_NavigatorComponentDefaults);
/**
* Predefined sync configuration for the Navigator component.
*/
NavigatorComponent.predefinedSyncConfig = NavigatorSyncs;
/* *
*
* Default Export
*
* */
/* harmony default export */ const NavigatorComponent_NavigatorComponent = (NavigatorComponent);
;// ./code/dashboards/es-modules/Dashboards/Plugins/HighchartsPlugin.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
*
* */
/* *
*
* Functions
*
* */
/**
* Connects Highcharts core with the Dashboard plugin.
*
* @param {Highcharts} highcharts
* Highcharts core to connect.
*/
function connectHighcharts(highcharts) {
HighchartsComponent_HighchartsComponent.charter = highcharts;
KPIComponent_KPIComponent.charter = highcharts;
NavigatorComponent_NavigatorComponent.charter = highcharts;
}
/**
* Callback function of the Dashboard plugin.
*
* @param {Dashboards.PluginHandler.Event} e
* Plugin context provided by the Dashboard.
*/
function HighchartsPlugin_onRegister(e) {
const { ComponentRegistry } = e;
ComponentRegistry.registerComponent('Highcharts', HighchartsComponent_HighchartsComponent);
ComponentRegistry.registerComponent('KPI', KPIComponent_KPIComponent);
ComponentRegistry.registerComponent('Navigator', NavigatorComponent_NavigatorComponent);
}
/**
* Callback function of the Dashboard plugin.
*
* @param {Dashboard.PluginHandler.Event} e
* Plugin context provided by the Dashboard.
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
function HighchartsPlugin_onUnregister(e) {
}
/* *
*
* Default Export
*
* */
const HighchartsCustom = {
connectHighcharts
};
const HighchartsPlugin = {
custom: HighchartsCustom,
name: 'Highcharts.DashboardsPlugin',
onRegister: HighchartsPlugin_onRegister,
onUnregister: HighchartsPlugin_onUnregister
};
/* harmony default export */ const Plugins_HighchartsPlugin = (HighchartsPlugin);
;// ./code/dashboards/es-modules/Dashboards/PluginHandler.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
*
* */
/* *
*
* Constants
*
* */
/** @internal */
const registry = {};
/**
* Revision of the Dashboard plugin API.
*
* @internal
*/
const revision = 0;
/* *
*
* Functions
*
* */
/**
* Adds a dashboard plugin.
*
* @param {Dashboards.Plugin} plugin
* Dashboard plugin to register.
*
* @param {string} [key]
* Plugin key for the registry. (Default: `plugin.name`)
*/
function addPlugin(plugin, key = plugin.name) {
const { maxRevision, minRevision, onRegister } = plugin;
if (registry[key]) {
// Only throw error with custom key
if (key !== plugin.name) {
throw new Error(`Plugin '${key}' already registered.`);
}
return;
}
if ((typeof minRevision === 'number' && minRevision > revision) ||
(typeof maxRevision === 'number' && maxRevision < revision)) {
throw new Error(`Plugin '${key}' does not support revision ${revision}.`);
}
onRegister({
Board: Dashboards_Board,
ComponentRegistry: Components_ComponentRegistry,
Sync: Sync_Sync,
revision
});
registry[key] = plugin;
}
/**
* Removes a dashboard plugin.
*
* @param {string} key
* Plugin key in the registry.
*/
function removePlugin(key) {
if (registry[key]) {
registry[key].onUnregister({
ComponentRegistry: Components_ComponentRegistry,
Board: Dashboards_Board,
Sync: Sync_Sync,
revision
});
delete registry[key];
}
}
/* *
*
* Default Export
*
* */
const PluginHandler = {
addPlugin,
removePlugin,
registry,
revision
};
/* harmony default export */ const Dashboards_PluginHandler = (PluginHandler);
;// ./code/dashboards/es-modules/masters/dashboards.src.js
// Fill registries
// Import SerializeHelper modules to register them
// Import components
/* *
*
* Namespace
*
* */
const G = Dashboards_Globals;
G.board = Dashboards_Board.board;
G.addEvent = addEvent;
G.createElement = createElement;
G.css = css;
G.defined = defined;
G.diffObjects = diffObjects;
G.error = Dashboards_Utilities.error;
G.find = find;
G.fireEvent = fireEvent;
G.getStyle = getStyle;
G.isArray = isArray;
G.isFunction = isFunction;
G.isNumber = isNumber;
G.isObject = isObject;
G.isString = isString;
G.merge = merge;
G.objectEach = objectEach;
G.pick = pick;
G.removeEvent = removeEvent;
G.setOptions = Dashboards_Defaults.setOptions;
G.splat = splat;
G.uniqueKey = uniqueKey;
G.AST = HTML_AST;
G.Board = Dashboards_Board;
G.Component = Components_Component;
G.ComponentRegistry = Components_ComponentRegistry;
G.GridComponent = GridComponent_GridComponent;
G.HighchartsComponent = HighchartsComponent_HighchartsComponent;
G.HTMLComponent = HTMLComponent_HTMLComponent;
G.KPIComponent = KPIComponent_KPIComponent;
G.NavigatorComponent = NavigatorComponent_NavigatorComponent;
G.DataConnector = Connectors_DataConnector;
G.DataConverter = Converters_DataConverter;
G.DataCursor = Data_DataCursor;
G.DataModifier = Modifiers_DataModifier;
G.DataPool = Data_DataPool;
G.DataTable = Data_DataTable;
G.defaultOptions = Dashboards_Defaults.defaultOptions;
G.GridPlugin = Plugins_GridPlugin;
G.HighchartsPlugin = Plugins_HighchartsPlugin;
G.PluginHandler = Dashboards_PluginHandler;
G.Sync = Sync_Sync;
/* *
*
* Classic Export
*
* */
if (!G.win.Dashboards) {
G.win.Dashboards = G;
}
if (G.win.Grid) {
Plugins_GridPlugin.custom.connectGrid(G.win.Grid);
G.PluginHandler.addPlugin(Plugins_GridPlugin);
}
if (G.win.Highcharts) {
Plugins_HighchartsPlugin.custom.connectHighcharts(G.win.Highcharts);
G.PluginHandler.addPlugin(Plugins_HighchartsPlugin);
}
/* *
*
* Default Export
*
* */
/* harmony default export */ const dashboards_src = (G);
__webpack_exports__ = __webpack_exports__["default"];
/******/ return __webpack_exports__;
/******/ })()
;
});