@highcharts/dashboards
Version:
Highcharts Dashboards framework
1,780 lines (1,739 loc) • 76.5 kB
JavaScript
/**
* @license Highcharts Dashboards Math 4.0.0 (2025-10-29)
* @module dashboards/modules/math-modifier
* @requires dashboards
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*/
(function webpackUniversalModuleDefinition(root, factory) {
if(typeof exports === 'object' && typeof module === 'object')
module.exports = factory(root["_Dashboards"], root["_Dashboards"]["DataModifier"]);
else if(typeof define === 'function' && define.amd)
define("dashboards/modules/math-modifier", ["dashboards/dashboards"], function (amd1) {return factory(amd1,amd1["DataModifier"]);});
else if(typeof exports === 'object')
exports["dashboards/modules/math-modifier"] = factory(root["_Dashboards"], root["_Dashboards"]["DataModifier"]);
else
root["Dashboards"] = factory(root["Dashboards"], root["Dashboards"]["DataModifier"]);
})(typeof window === 'undefined' ? this : window, (__WEBPACK_EXTERNAL_MODULE__668__, __WEBPACK_EXTERNAL_MODULE__784__) => {
return /******/ (() => { // webpackBootstrap
/******/ "use strict";
/******/ var __webpack_modules__ = ({
/***/ 668:
/***/ ((module) => {
module.exports = __WEBPACK_EXTERNAL_MODULE__668__;
/***/ }),
/***/ 784:
/***/ ((module) => {
module.exports = __WEBPACK_EXTERNAL_MODULE__784__;
/***/ })
/******/ });
/************************************************************************/
/******/ // The module cache
/******/ var __webpack_module_cache__ = {};
/******/
/******/ // The require function
/******/ function __webpack_require__(moduleId) {
/******/ // Check if module is in cache
/******/ var cachedModule = __webpack_module_cache__[moduleId];
/******/ if (cachedModule !== undefined) {
/******/ return cachedModule.exports;
/******/ }
/******/ // Create a new module (and put it into the cache)
/******/ var module = __webpack_module_cache__[moduleId] = {
/******/ // no module.id needed
/******/ // no module.loaded needed
/******/ exports: {}
/******/ };
/******/
/******/ // Execute the module function
/******/ __webpack_modules__[moduleId](module, module.exports, __webpack_require__);
/******/
/******/ // Return the exports of the module
/******/ return module.exports;
/******/ }
/******/
/************************************************************************/
/******/ /* webpack/runtime/compat get default export */
/******/ (() => {
/******/ // getDefaultExport function for compatibility with non-harmony modules
/******/ __webpack_require__.n = (module) => {
/******/ var getter = module && module.__esModule ?
/******/ () => (module['default']) :
/******/ () => (module);
/******/ __webpack_require__.d(getter, { a: getter });
/******/ return getter;
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/define property getters */
/******/ (() => {
/******/ // define getter functions for harmony exports
/******/ __webpack_require__.d = (exports, definition) => {
/******/ for(var key in definition) {
/******/ if(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {
/******/ Object.defineProperty(exports, key, { enumerable: true, get: definition[key] });
/******/ }
/******/ }
/******/ };
/******/ })();
/******/
/******/ /* webpack/runtime/hasOwnProperty shorthand */
/******/ (() => {
/******/ __webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))
/******/ })();
/******/
/************************************************************************/
var __webpack_exports__ = {};
// EXPORTS
__webpack_require__.d(__webpack_exports__, {
"default": () => (/* binding */ math_modifier_src)
});
// EXTERNAL MODULE: external {"amd":["dashboards/dashboards"],"commonjs":["dashboards"],"commonjs2":["dashboards"],"root":["Dashboards"]}
var dashboards_commonjs_dashboards_commonjs2_dashboards_root_Dashboards_ = __webpack_require__(668);
var dashboards_commonjs_dashboards_commonjs2_dashboards_root_Dashboards_default = /*#__PURE__*/__webpack_require__.n(dashboards_commonjs_dashboards_commonjs2_dashboards_root_Dashboards_);
;// ./code/dashboards/es-modules/Data/Formula/FormulaParser.js
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
const { isString } = (dashboards_commonjs_dashboards_commonjs2_dashboards_root_Dashboards_default());
/* *
*
* Constants
*
* */
/**
* @private
*/
const booleanRegExp = /^(?:FALSE|TRUE)/;
/**
* `.`-separated decimal.
* @private
*/
const decimal1RegExp = /^[+\-]?\d+(?:\.\d+)?(?:e[+\-]\d+)?/;
/**
* `,`-separated decimal.
* @private
*/
const decimal2RegExp = /^[+\-]?\d+(?:,\d+)?(?:e[+\-]\d+)?/;
/**
* - Group 1: Function name
* @private
*/
const functionRegExp = /^([A-Z][A-Z\d\.]*)\(/;
/**
* @private
*/
const operatorRegExp = /^(?:[+\-*\/^<=>]|<=|=>)/;
/**
* - Group 1: Start column
* - Group 2: Start row
* - Group 3: End column
* - Group 4: End row
* @private
*/
const rangeA1RegExp = /^(\$?[A-Z]+)(\$?\d+)\:(\$?[A-Z]+)(\$?\d+)/;
/**
* - Group 1: Start row
* - Group 2: Start column
* - Group 3: End row
* - Group 4: End column
* @private
*/
const rangeR1C1RegExp = /^R(\d*|\[\d+\])C(\d*|\[\d+\])\:R(\d*|\[\d+\])C(\d*|\[\d+\])/;
/**
* - Group 1: Column
* - Group 2: Row
* @private
*/
const referenceA1RegExp = /^(\$?[A-Z]+)(\$?\d+)(?![\:C])/;
/**
* - Group 1: Row
* - Group 2: Column
* @private
*/
const referenceR1C1RegExp = /^R(\d*|\[\d+\])C(\d*|\[\d+\])(?!\:)/;
/* *
*
* Functions
*
* */
/**
* Extracts the inner string of the most outer parantheses.
*
* @private
*
* @param {string} text
* Text string to extract from.
*
* @return {string}
* Extracted parantheses. If not found an exception will be thrown.
*/
function extractParantheses(text) {
let parantheseLevel = 0;
for (let i = 0, iEnd = text.length, char, parantheseStart = 1; i < iEnd; ++i) {
char = text[i];
if (char === '(') {
if (!parantheseLevel) {
parantheseStart = i + 1;
}
++parantheseLevel;
continue;
}
if (char === ')') {
--parantheseLevel;
if (!parantheseLevel) {
return text.substring(parantheseStart, i);
}
}
}
if (parantheseLevel > 0) {
const error = new Error('Incomplete parantheses.');
error.name = 'FormulaParseError';
throw error;
}
return '';
}
/**
* Extracts the inner string value.
*
* @private
*
* @param {string} text
* Text string to extract from.
*
* @return {string}
* Extracted string. If not found an exception will be thrown.
*/
function extractString(text) {
let start = -1;
for (let i = 0, iEnd = text.length, char, escaping = false; i < iEnd; ++i) {
char = text[i];
if (char === '\\') {
escaping = !escaping;
continue;
}
if (escaping) {
escaping = false;
continue;
}
if (char === '"') {
if (start < 0) {
start = i;
}
else {
return text.substring(start + 1, i); // `ì` is excluding
}
}
}
const error = new Error('Incomplete string.');
error.name = 'FormulaParseError';
throw error;
}
/**
* Parses an argument string. Formula arrays with a single term will be
* simplified to the term.
*
* @private
*
* @param {string} text
* Argument string to parse.
*
* @param {boolean} alternativeSeparators
* Whether to expect `;` as argument separator and `,` as decimal separator.
*
* @return {Formula|Function|Range|Reference|Value}
* The recognized term structure.
*/
function parseArgument(text, alternativeSeparators) {
let match;
// Check for a R1C1:R1C1 range notation
match = text.match(rangeR1C1RegExp);
if (match) {
const beginColumnRelative = (match[2] === '' || match[2][0] === '[');
const beginRowRelative = (match[1] === '' || match[1][0] === '[');
const endColumnRelative = (match[4] === '' || match[4][0] === '[');
const endRowRelative = (match[3] === '' || match[3][0] === '[');
const range = {
type: 'range',
beginColumn: (beginColumnRelative ?
parseInt(match[2].substring(1, -1) || '0', 10) :
parseInt(match[2], 10) - 1),
beginRow: (beginRowRelative ?
parseInt(match[1].substring(1, -1) || '0', 10) :
parseInt(match[1], 10) - 1),
endColumn: (endColumnRelative ?
parseInt(match[4].substring(1, -1) || '0', 10) :
parseInt(match[4], 10) - 1),
endRow: (endRowRelative ?
parseInt(match[3].substring(1, -1) || '0', 10) :
parseInt(match[3], 10) - 1)
};
if (beginColumnRelative) {
range.beginColumnRelative = true;
}
if (beginRowRelative) {
range.beginRowRelative = true;
}
if (endColumnRelative) {
range.endColumnRelative = true;
}
if (endRowRelative) {
range.endRowRelative = true;
}
return range;
}
// Check for a A1:A1 range notation
match = text.match(rangeA1RegExp);
if (match) {
const beginColumnRelative = match[1][0] !== '$';
const beginRowRelative = match[2][0] !== '$';
const endColumnRelative = match[3][0] !== '$';
const endRowRelative = match[4][0] !== '$';
const range = {
type: 'range',
beginColumn: parseReferenceColumn(beginColumnRelative ?
match[1] :
match[1].substring(1)) - 1,
beginRow: parseInt(beginRowRelative ?
match[2] :
match[2].substring(1), 10) - 1,
endColumn: parseReferenceColumn(endColumnRelative ?
match[3] :
match[3].substring(1)) - 1,
endRow: parseInt(endRowRelative ?
match[4] :
match[4].substring(1), 10) - 1
};
if (beginColumnRelative) {
range.beginColumnRelative = true;
}
if (beginRowRelative) {
range.beginRowRelative = true;
}
if (endColumnRelative) {
range.endColumnRelative = true;
}
if (endRowRelative) {
range.endRowRelative = true;
}
return range;
}
// Fallback to formula processing for other pattern types
const formula = parseFormula(text, alternativeSeparators);
return (formula.length === 1 && typeof formula[0] !== 'string' ?
formula[0] :
formula);
}
/**
* Parse arguments string inside function parantheses.
*
* @private
*
* @param {string} text
* Parantheses string of the function.
*
* @param {boolean} alternativeSeparators
* Whether to expect `;` as argument separator and `,` as decimal separator.
*
* @return {Highcharts.FormulaArguments}
* Parsed arguments array.
*/
function parseArguments(text, alternativeSeparators) {
const args = [], argumentsSeparator = (alternativeSeparators ? ';' : ',');
let parantheseLevel = 0, term = '';
for (let i = 0, iEnd = text.length, char; i < iEnd; ++i) {
char = text[i];
// Check for separator
if (char === argumentsSeparator &&
!parantheseLevel &&
term) {
args.push(parseArgument(term, alternativeSeparators));
term = '';
// Check for a quoted string before skip logic
}
else if (char === '"' &&
!parantheseLevel &&
!term) {
const string = extractString(text.substring(i));
args.push(string);
i += string.length + 1; // Only +1 to cover ++i in for-loop
// Skip space and check paranthesis nesting
}
else if (char !== ' ') {
term += char;
if (char === '(') {
++parantheseLevel;
}
else if (char === ')') {
--parantheseLevel;
}
}
}
// Look for left-overs from last argument
if (!parantheseLevel && term) {
args.push(parseArgument(term, alternativeSeparators));
}
return args;
}
/**
* Checks if there's one of the following operator before the negative number
* value: '*', '/' or '^'.
*
* Used to properly indicate a negative value reference or negate a directly
* passed number value.
*/
function negativeReference(formula) {
const formulaLength = formula.length;
const priorFormula = formula[formulaLength - 2];
return (formula[formulaLength - 1] === '-' &&
isString(priorFormula) &&
!!priorFormula.match(/\*|\/|\^/));
}
/**
* Converts a spreadsheet formula string into a formula array. Throws a
* `FormulaParserError` when the string can not be parsed.
*
* @private
* @function Formula.parseFormula
*
* @param {string} text
* Spreadsheet formula string, without the leading `=`.
*
* @param {boolean} alternativeSeparators
* * `false` to expect `,` between arguments and `.` in decimals.
* * `true` to expect `;` between arguments and `,` in decimals.
*
* @return {Formula.Formula}
* Formula array representing the string.
*/
function parseFormula(text, alternativeSeparators) {
const decimalRegExp = (alternativeSeparators ?
decimal2RegExp :
decimal1RegExp), formula = [];
let match, next = (text[0] === '=' ? text.substring(1) : text).trim();
while (next) {
// Check for an R1C1 reference notation
match = next.match(referenceR1C1RegExp);
if (match) {
const columnRelative = (match[2] === '' || match[2][0] === '[');
const rowRelative = (match[1] === '' || match[1][0] === '[');
const reference = {
type: 'reference',
column: (columnRelative ?
parseInt(match[2].substring(1, -1) || '0', 10) :
parseInt(match[2], 10) - 1),
row: (rowRelative ?
parseInt(match[1].substring(1, -1) || '0', 10) :
parseInt(match[1], 10) - 1)
};
if (columnRelative) {
reference.columnRelative = true;
}
if (rowRelative) {
reference.rowRelative = true;
}
if (negativeReference(formula)) {
formula.pop();
reference.isNegative = true;
}
formula.push(reference);
next = next.substring(match[0].length).trim();
continue;
}
// Check for an A1 reference notation
match = next.match(referenceA1RegExp);
if (match) {
const columnRelative = match[1][0] !== '$';
const rowRelative = match[2][0] !== '$';
const reference = {
type: 'reference',
column: parseReferenceColumn(columnRelative ?
match[1] :
match[1].substring(1)) - 1,
row: parseInt(rowRelative ?
match[2] :
match[2].substring(1), 10) - 1
};
if (columnRelative) {
reference.columnRelative = true;
}
if (rowRelative) {
reference.rowRelative = true;
}
if (negativeReference(formula)) {
formula.pop();
reference.isNegative = true;
}
formula.push(reference);
next = next.substring(match[0].length).trim();
continue;
}
// Check for a formula operator
match = next.match(operatorRegExp);
if (match) {
formula.push(match[0]);
next = next.substring(match[0].length).trim();
continue;
}
// Check for a boolean value
match = next.match(booleanRegExp);
if (match) {
formula.push(match[0] === 'TRUE');
next = next.substring(match[0].length).trim();
continue;
}
// Check for a number value
match = next.match(decimalRegExp);
if (match) {
let number = parseFloat(match[0]);
// If the current value is multiplication-related and the previous
// one is a minus sign, set the current value to negative and remove
// the minus sign.
if (negativeReference(formula)) {
formula.pop();
number = -number;
}
formula.push(number);
next = next.substring(match[0].length).trim();
continue;
}
// Check for a quoted string
if (next[0] === '"') {
const string = extractString(next);
formula.push(string.substring(1, -1));
next = next.substring(string.length + 2).trim();
continue;
}
// Check for a function
match = next.match(functionRegExp);
if (match) {
next = next.substring(match[1].length).trim();
const parantheses = extractParantheses(next);
formula.push({
type: 'function',
name: match[1],
args: parseArguments(parantheses, alternativeSeparators)
});
next = next.substring(parantheses.length + 2).trim();
continue;
}
// Check for a formula in parantheses
if (next[0] === '(') {
const paranteses = extractParantheses(next);
if (paranteses) {
formula
.push(parseFormula(paranteses, alternativeSeparators));
next = next.substring(paranteses.length + 2).trim();
continue;
}
}
// Something is not right
const position = text.length - next.length, error = new Error('Unexpected character `' +
text.substring(position, position + 1) +
'` at position ' + (position + 1) +
'. (`...' + text.substring(position - 5, position + 6) + '...`)');
error.name = 'FormulaParseError';
throw error;
}
return formula;
}
/**
* Converts a reference column `A` of `A1` into a number. Supports endless sizes
* `ZZZ...`, just limited by integer precision.
*
* @private
*
* @param {string} text
* Column string to convert.
*
* @return {number}
* Converted column index.
*/
function parseReferenceColumn(text) {
let column = 0;
for (let i = 0, iEnd = text.length, code, factor = text.length - 1; i < iEnd; ++i) {
code = text.charCodeAt(i);
if (code >= 65 && code <= 90) {
column += (code - 64) * Math.pow(26, factor);
}
--factor;
}
return column;
}
/* *
*
* Default Export
*
* */
const FormulaParser = {
parseFormula
};
/* harmony default export */ const Formula_FormulaParser = (FormulaParser);
;// ./code/dashboards/es-modules/Data/Formula/FormulaTypes.js
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* 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 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,
isOperator,
isRange,
isReference,
isValue
};
/* harmony default export */ const FormulaTypes = (MathFormula);
;// ./code/dashboards/es-modules/Data/Formula/FormulaProcessor.js
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
const { isFormula: FormulaProcessor_isFormula, isFunction: FormulaProcessor_isFunction, isOperator: FormulaProcessor_isOperator, isRange: FormulaProcessor_isRange, isReference: FormulaProcessor_isReference, isValue: FormulaProcessor_isValue } = FormulaTypes;
const { defined } = (dashboards_commonjs_dashboards_commonjs2_dashboards_root_Dashboards_default());
/* *
*
* 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 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 = asNumber(x);
y = 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,
getArgumentValue,
getArgumentsValues,
getRangeValues,
getReferenceValue,
processFormula,
processorFunctions,
registerProcessorFunction,
translateReferences
};
/* harmony default export */ const Formula_FormulaProcessor = (FormulaProcessor);
;// ./code/dashboards/es-modules/Data/Formula/Functions/ABS.js
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
const { getArgumentValue: ABS_getArgumentValue } = Formula_FormulaProcessor;
/* *
*
* Functions
*
* */
/**
* Processor for the `ABS(value)` implementation. Returns positive numbers.
*
* @private
* @function Formula.processorFunctions.AND
*
* @param {Highcharts.FormulaArguments} args
* Arguments to process.
*
* @param {Highcharts.DataTable} [table]
* Table to use for references and ranges.
*
* @return {Array<number>}
* Result value of the process.
*/
function ABS(args, table) {
const value = ABS_getArgumentValue(args[0], table);
switch (typeof value) {
case 'number':
return Math.abs(value);
case 'object': {
const values = [];
for (let i = 0, iEnd = value.length, value2; i < iEnd; ++i) {
value2 = value[i];
if (typeof value2 !== 'number') {
return NaN;
}
values.push(Math.abs(value2));
}
return values;
}
default:
return NaN;
}
}
/* *
*
* Registry
*
* */
Formula_FormulaProcessor.registerProcessorFunction('ABS', ABS);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Functions_ABS = ((/* unused pure expression or super */ null && (ABS)));
;// ./code/dashboards/es-modules/Data/Formula/Functions/AND.js
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
const { getArgumentValue: AND_getArgumentValue } = Formula_FormulaProcessor;
/* *
*
* Functions
*
* */
/**
* Processor for the `AND(...tests)` implementation. Returns `TRUE`, if all test
* results are not `0` or `FALSE`.
*
* @private
* @function Formula.processorFunctions.AND
*
* @param {Highcharts.FormulaArguments} args
* Arguments to process.
*
* @param {Highcharts.DataTable} [table]
* Table to use for references and ranges.
*
* @return {boolean}
* Result value of the process.
*/
function AND(args, table) {
for (let i = 0, iEnd = args.length, value; i < iEnd; ++i) {
value = AND_getArgumentValue(args[i], table);
if (!value ||
(typeof value === 'object' &&
!AND(value, table))) {
return false;
}
}
return true;
}
/* *
*
* Registry
*
* */
Formula_FormulaProcessor.registerProcessorFunction('AND', AND);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Functions_AND = ((/* unused pure expression or super */ null && (AND)));
;// ./code/dashboards/es-modules/Data/Formula/Functions/AVERAGE.js
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* 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 = ((/* unused pure expression or super */ null && (AVERAGE)));
;// ./code/dashboards/es-modules/Data/Formula/Functions/AVERAGEA.js
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
const { getArgumentValue: AVERAGEA_getArgumentValue } = Formula_FormulaProcessor;
/* *
*
* Functions
*
* */
/**
* Processor for the `AVERAGEA(...values)` implementation. Calculates the
* average of the given values. Strings and FALSE are calculated as 0.
*
* @private
* @function Formula.processorFunctions.AVERAGEA
*
* @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 AVERAGEA(args, table) {
let count = 0, result = 0;
for (let i = 0, iEnd = args.length, value; i < iEnd; ++i) {
value = AVERAGEA_getArgumentValue(args[i], table);
switch (typeof value) {
case 'boolean':
++count;
result += (value ? 1 : 0);
continue;
case 'number':
if (!isNaN(value)) {
++count;
result += value;
}
continue;
case 'string':
++count;
continue;
default:
for (let j = 0, jEnd = value.length, value2; j < jEnd; ++j) {
value2 = value[j];
switch (typeof value2) {
case 'boolean':
++count;
result += (value2 ? 1 : 0);
continue;
case 'number':
if (!isNaN(value2)) {
++count;
result += value2;
}
continue;
case 'string':
++count;
continue;
}
}
continue;
}
}
return (count ? (result / count) : 0);
}
/* *
*
* Registry
*
* */
Formula_FormulaProcessor.registerProcessorFunction('AVERAGEA', AVERAGEA);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Functions_AVERAGEA = ((/* unused pure expression or super */ null && (AVERAGEA)));
;// ./code/dashboards/es-modules/Data/Formula/Functions/COUNT.js
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* 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 = ((/* unused pure expression or super */ null && (COUNT)));
;// ./code/dashboards/es-modules/Data/Formula/Functions/COUNTA.js
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
/* *
*
* Functions
*
* */
/**
* Processor for the `COUNTA(...values)` implementation. Returns the count of
* given values that are not empty.
*
* @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 COUNTA(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)) {
continue;
}
break;
case 'object':
count += COUNTA(value, table);
continue;
case 'string':
if (!value) {
continue;
}
break;
}
++count;
}
return count;
}
/* *
*
* Registry
*
* */
Formula_FormulaProcessor.registerProcessorFunction('COUNTA', COUNTA);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Functions_COUNTA = ((/* unused pure expression or super */ null && (COUNTA)));
;// ./code/dashboards/es-modules/Data/Formula/Functions/IF.js
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
const { getArgumentValue: IF_getArgumentValue } = Formula_FormulaProcessor;
/* *
*
* Functions
*
* */
/**
* Processor for the `IF(test, value1, value2)` implementation. Returns one of
* the values based on the test result. `value1` will be returned, if the test
* result is not `0` or `FALSE`.
*
* @private
* @function Formula.processorFunctions.IF
*
* @param {Highcharts.FormulaArguments} args
* Arguments to process.
*
* @param {Highcharts.DataTable} [table]
* Table to use for references and ranges.
*
* @return {Highcharts.FormulaValue|Array<Highcharts.FormulaValue>}
* Result value of the process.
*/
function IF(args, table) {
return (IF_getArgumentValue(args[0], table) ?
IF_getArgumentValue(args[1], table) :
IF_getArgumentValue(args[2], table));
}
/* *
*
* Registry
*
* */
Formula_FormulaProcessor.registerProcessorFunction('IF', IF);
/* *
*
* Default Export
*
* */
/* harmony default export */ const Functions_IF = ((/* unused pure expression or super */ null && (IF)));
;// ./code/dashboards/es-modules/Data/Formula/Functions/ISNA.js
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
const { getArgumentValue: ISNA_getArgumentValue } = Formula_FormulaProcessor;
/* *
*
* Functions
*
* */
/**
* Processor for the `ISNA(value)` implementation. Returns TRUE if value is not
*