@highcharts/dashboards
Version:
Highcharts Dashboards framework
1,460 lines (1,446 loc) • 53.6 kB
JavaScript
/**
* @license Highcharts Dashboards Math 3.6.0 (2025-09-10)
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*/
(function (factory) {
if (typeof module === 'object' && module.exports) {
factory['default'] = factory;
module.exports = factory;
} else if (typeof define === 'function' && define.amd) {
define('dashboards/modules/math-modifier', ['dashboards'], function (Dashboards) {
factory(Dashboards);
factory.Dashboards = Dashboards;
return factory;
});
} else {
factory(typeof Dashboards !== 'undefined' ? Dashboards : undefined);
}
}(function (Dashboards) {
'use strict';
var _modules = Dashboards ? Dashboards._modules : {};
function _registerModule(obj, path, args, fn) {
if (!obj.hasOwnProperty(path)) {
obj[path] = fn.apply(null, args);
if (typeof CustomEvent === 'function') {
Dashboards.win.dispatchEvent(new CustomEvent(
'DashboardsModuleLoaded',
{ detail: { path: path, module: obj[path] } }
));
}
}
}
_registerModule(_modules, 'Data/Formula/FormulaParser.js', [_modules['Core/Utilities.js']], function (U) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
const { isString } = U;
/* *
*
* 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
};
return FormulaParser;
});
_registerModule(_modules, 'Data/Formula/Functions/ABS.js', [_modules['Data/Formula/FormulaProcessor.js']], function (FormulaProcessor) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
const { getArgumentValue } = 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 = 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
*
* */
FormulaProcessor.registerProcessorFunction('ABS', ABS);
/* *
*
* Default Export
*
* */
return ABS;
});
_registerModule(_modules, 'Data/Formula/Functions/AND.js', [_modules['Data/Formula/FormulaProcessor.js']], function (FormulaProcessor) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
const { getArgumentValue } = 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 = getArgumentValue(args[i], table);
if (!value ||
(typeof value === 'object' &&
!AND(value, table))) {
return false;
}
}
return true;
}
/* *
*
* Registry
*
* */
FormulaProcessor.registerProcessorFunction('AND', AND);
/* *
*
* Default Export
*
* */
return AND;
});
_registerModule(_modules, 'Data/Formula/Functions/AVERAGEA.js', [_modules['Data/Formula/FormulaProcessor.js']], function (FormulaProcessor) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
const { getArgumentValue } = 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 = 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
*
* */
FormulaProcessor.registerProcessorFunction('AVERAGEA', AVERAGEA);
/* *
*
* Default Export
*
* */
return AVERAGEA;
});
_registerModule(_modules, 'Data/Formula/Functions/COUNTA.js', [_modules['Data/Formula/FormulaProcessor.js']], function (FormulaProcessor) {
/* *
*
* (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 = 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
*
* */
FormulaProcessor.registerProcessorFunction('COUNTA', COUNTA);
/* *
*
* Default Export
*
* */
return COUNTA;
});
_registerModule(_modules, 'Data/Formula/Functions/IF.js', [_modules['Data/Formula/FormulaProcessor.js']], function (FormulaProcessor) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
const { getArgumentValue } = 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 (getArgumentValue(args[0], table) ?
getArgumentValue(args[1], table) :
getArgumentValue(args[2], table));
}
/* *
*
* Registry
*
* */
FormulaProcessor.registerProcessorFunction('IF', IF);
/* *
*
* Default Export
*
* */
return IF;
});
_registerModule(_modules, 'Data/Formula/Functions/ISNA.js', [_modules['Data/Formula/FormulaProcessor.js']], function (FormulaProcessor) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
const { getArgumentValue } = FormulaProcessor;
/* *
*
* Functions
*
* */
/**
* Processor for the `ISNA(value)` implementation. Returns TRUE if value is not
* a number.
*
* @private
* @function Formula.processorFunctions.ISNA
*
* @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 ISNA(args, table) {
const value = getArgumentValue(args[0], table);
return (typeof value !== 'number' || isNaN(value));
}
/* *
*
* Registry
*
* */
FormulaProcessor.registerProcessorFunction('ISNA', ISNA);
/* *
*
* Default Export
*
* */
return ISNA;
});
_registerModule(_modules, 'Data/Formula/Functions/MOD.js', [_modules['Data/Formula/FormulaProcessor.js']], function (FormulaProcessor) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
const { getArgumentValue } = FormulaProcessor;
/* *
*
* Functions
*
* */
/**
* Processor for the `MOD(value1, value2)` implementation. Calculates the rest
* of the division with the given values.
*
* @private
* @function Formula.processorFunctions.MOD
*
* @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 MOD(args, table) {
let value1 = getArgumentValue(args[0], table), value2 = getArgumentValue(args[1], table);
if (typeof value1 === 'object') {
value1 = value1[0];
}
if (typeof value2 === 'object') {
value2 = value2[0];
}
if (typeof value1 !== 'number' ||
typeof value2 !== 'number' ||
value2 === 0) {
return NaN;
}
return value1 % value2;
}
/* *
*
* Registry
*
* */
FormulaProcessor.registerProcessorFunction('MOD', MOD);
/* *
*
* Default Export
*
* */
return MOD;
});
_registerModule(_modules, 'Data/Formula/Functions/MODE.js', [_modules['Data/Formula/FormulaProcessor.js']], function (FormulaProcessor) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
/* *
*
* Functions
*
* */
/**
* Creates the mode map of the given arguments.
*
* @private
* @function Formula.processorFunctions.MULT
*
* @param {Highcharts.FormulaArguments} args
* Arguments to process.
*
* @param {Highcharts.DataTable} [table]
* Table to process.
*
* @return {number}
* Result value of the process.
*/
function getModeMap(args, table) {
const modeMap = {}, values = 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)) {
modeMap[value] = (modeMap[value] || 0) + 1;
}
break;
case 'object':
for (let j = 0, jEnd = value.length, value2; j < jEnd; ++j) {
value2 = value[j];
if (typeof value2 === 'number' &&
!isNaN(value2)) {
modeMap[value2] = (modeMap[value2] || 0) + 1;
}
}
break;
}
}
return modeMap;
}
/**
* Processor for the `MODE.MULT(...values)` implementation. Calculates the most
* frequent values of the give values.
*
* @private
* @function Formula.processorFunctions.MULT
*
* @param {Highcharts.FormulaArguments} args
* Arguments to process.
*
* @param {Highcharts.DataTable} [table]
* Table to process.
*
* @return {number|Array<number>}
* Result value of the process.
*/
function MULT(args, table) {
const modeMap = getModeMap(args, table), keys = Object.keys(modeMap);
if (!keys.length) {
return NaN;
}
let modeKeys = [parseFloat(keys[0])], modeCount = modeMap[keys[0]];
for (let i = 1, iEnd = keys.length, key, count; i < iEnd; ++i) {
key = keys[i];
count = modeMap[key];
if (modeCount < count) {
modeKeys = [parseFloat(key)];
modeCount = count;
}
else if (modeCount === count) {
modeKeys.push(parseFloat(key));
}
}
return modeCount > 1 ? modeKeys : NaN;
}
/**
* Processor for the `MODE.SNGL(...values)` implementation. Calculates the
* lowest most frequent value of the give values.
*
* @private
* @function Formula.processorFunctions['MODE.SNGL']
*
* @param {Highcharts.FormulaArguments} args
* Arguments to process.
*
* @param {Highcharts.DataTable} [table]
* Table to process.
*
* @return {number}
* Result value of the process.
*/
function SNGL(args, table) {
const modeMap = getModeMap(args, table), keys = Object.keys(modeMap);
if (!keys.length) {
return NaN;
}
let modeKey = parseFloat(keys[0]), modeCount = modeMap[keys[0]];
for (let i = 1, iEnd = keys.length, key, keyValue, count; i < iEnd; ++i) {
key = keys[i];
count = modeMap[key];
if (modeCount < count) {
modeKey = parseFloat(key);
modeCount = count;
}
else if (modeCount === count) {
keyValue = parseFloat(key);
if (modeKey > keyValue) {
modeKey = keyValue;
modeCount = count;
}
}
}
return modeCount > 1 ? modeKey : NaN;
}
/* *
*
* Registry
*
* */
FormulaProcessor.registerProcessorFunction('MODE', SNGL);
FormulaProcessor.registerProcessorFunction('MODE.MULT', MULT);
FormulaProcessor.registerProcessorFunction('MODE.SNGL', SNGL);
/* *
*
* Default Export
*
* */
const MODE = {
MULT,
SNGL
};
return MODE;
});
_registerModule(_modules, 'Data/Formula/Functions/NOT.js', [_modules['Data/Formula/FormulaProcessor.js']], function (FormulaProcessor) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
const { getArgumentValue } = FormulaProcessor;
/* *
*
* Functions
*
* */
/**
* Processor for the `NOT(value)` implementation. Returns the opposite test
* result.
*
* @private
* @function Formula.processorFunctions.NOT
*
* @param {Highcharts.FormulaArguments} args
* Arguments to process.
*
* @param {Highcharts.DataTable} [table]
* Table to use for references and ranges.
*
* @return {boolean|number}
* Result value of the process.
*/
function NOT(args, table) {
let value = getArgumentValue(args[0], table);
if (typeof value === 'object') {
value = value[0];
}
switch (typeof value) {
case 'boolean':
case 'number':
return !value;
}
return NaN;
}
/* *
*
* Registry
*
* */
FormulaProcessor.registerProcessorFunction('NOT', NOT);
/* *
*
* Default Export
*
* */
return NOT;
});
_registerModule(_modules, 'Data/Formula/Functions/OR.js', [_modules['Data/Formula/FormulaProcessor.js']], function (FormulaProcessor) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
const { getArgumentValue } = FormulaProcessor;
/* *
*
* Functions
*
* */
/**
* Processor for the `OR(...tests)` implementation. Returns `TRUE`, if one test
* result is 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 OR(args, table) {
for (let i = 0, iEnd = args.length, value; i < iEnd; ++i) {
value = getArgumentValue(args[i], table);
if (typeof value === 'object') {
if (OR(value, table)) {
return true;
}
}
else if (value) {
return true;
}
}
return false;
}
/* *
*
* Registry
*
* */
FormulaProcessor.registerProcessorFunction('OR', OR);
/* *
*
* Default Export
*
* */
return OR;
});
_registerModule(_modules, 'Data/Formula/Functions/XOR.js', [_modules['Data/Formula/FormulaProcessor.js']], function (FormulaProcessor) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
const { getArgumentValue } = FormulaProcessor;
/* *
*
* Functions
*
* */
/**
* Processor for the `XOR(...tests)` implementation. Returns `TRUE`, if at least
* one of the given tests differs in result of other tests.
*
* @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|number}
* Result value of the process.
*/
function XOR(args, table) {
for (let i = 0, iEnd = args.length, lastValue, value; i < iEnd; ++i) {
value = getArgumentValue(args[i], table);
switch (typeof value) {
case 'boolean':
case 'number':
if (typeof lastValue === 'undefined') {
lastValue = !!value;
}
else if (!!value !== lastValue) {
return true;
}
break;
case 'object':
for (let j = 0, jEnd = value.length, value2; j < jEnd; ++j) {
value2 = value[j];
switch (typeof value2) {
case 'boolean':
case 'number':
if (typeof lastValue === 'undefined') {
lastValue = !!value2;
}
else if (!!value2 !== lastValue) {
return true;
}
break;
}
}
break;
}
}
return false;
}
/* *
*
* Registry
*
* */
FormulaProcessor.registerProcessorFunction('XOR', XOR);
/* *
*
* Default Export
*
* */
return XOR;
});
_registerModule(_modules, 'Data/Formula/Formula.js', [_modules['Data/Formula/FormulaParser.js'], _modules['Data/Formula/FormulaProcessor.js'], _modules['Data/Formula/FormulaTypes.js']], function (FormulaParser, FormulaProcessor, FormulaType) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
/* *
*
* Imports
*
* */
/* *
*
* Default Export
*
* */
/**
* Formula engine to make use of spreadsheet formula strings.
* @internal
*/
const Formula = {
...FormulaParser,
...FormulaProcessor,
...FormulaType
};
return Formula;
});
_registerModule(_modules, 'Data/Modifiers/MathModifier.js', [_modules['Data/Modifiers/DataModifier.js'], _modules['Data/Formula/FormulaParser.js'], _modules['Data/Formula/FormulaProcessor.js']], function (DataModifier, FormulaParser, FormulaProcessor) {
/* *
*
* (c) 2009-2025 Highsoft AS
*
* License: www.highcharts.com/license
*
* !!!!!!! SOURCE GETS TRANSPILED BY TYPESCRIPT. EDIT TS FILE ONLY. !!!!!!!
*
* Authors:
* - Sophie Bremer
*
* */
/* *
*
* Class
*
* */
/**
* Replaces formula strings in a table with calculated values.
*
* @class
* @name Highcharts.DataModifier.types.MathModifier
* @augments Highcharts.DataModifier
*/
class MathModifier extends DataModifier {
/* *
*
* Constructor
*
* */
constructor(options) {
super();
this.options = {
...MathModifier.defaultOptions,
...options
};
}
/* *
*
* Functions
*
* */
modifyTable(table, eventDetail) {
const modifier = this;
modifier.emit({ type: 'modify', detail: eventDetail, table });
const alternativeSeparators = modifier.options.alternativeSeparators, formulaColumns = (modifier.options.formulaColumns ||
table.getColumnNames()), modified = table.modified;
for (let i = 0, iEnd = formulaColumns.length, columnName; i < iEnd; ++i) {
columnName = formulaColumns[i];
if (formulaColumns.indexOf(columnName) >= 0) {
modified.setColumn(columnName, modifier.processColumn(table, columnName));
}
}
const columnFormulas = (modifier.options.columnFormulas || []);
for (let i = 0, iEnd = columnFormulas.length, columnFormula, formula; i < iEnd; ++i) {
columnFormula = columnFormulas[i];
formula = FormulaParser.parseFormula(columnFormula.formula, alternativeSeparators);
modified.setColumn(columnFormula.column, modifier.processColumnFormula(formula, table, columnFormula.rowStart, columnFormula.rowEnd));
}
modifier.emit({ type: 'afterModify', detail: eventDetail, table });
return table;
}
/**
* Process a column by replacing formula strings with calculated values.
*
* @private
*
* @param {Highcharts.DataTable} table
* Table to extract column from and use as reference.
*
* @param {string} columnName
* Name of column to process.
*
* @param {number} rowIndex
* Row index to start the replacing process from.
*
* @return {Highcharts.DataTableColumn}
* Returns the processed table column.
*/
processColumn(table, columnName, rowIndex = 0) {
const alternativeSeparators = this.options.alternativeSeparators, column = (table.getColumn(col