@odoo/o-spreadsheet 17.4.0-alpha.9 → 17.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 17.4.0-alpha.9
6
- * @date 2024-06-26T11:09:20.284Z
7
- * @hash 526be20
5
+ * @version 17.4.0
6
+ * @date 2024-07-12T04:41:51.675Z
7
+ * @hash f8e8543
8
8
  */
9
9
 
10
10
  'use strict';
@@ -592,7 +592,7 @@ function getAddHeaderStartIndex(position, base) {
592
592
  /**
593
593
  * Compares two objects.
594
594
  */
595
- function deepEquals(o1, o2, ignoreFunctions) {
595
+ function deepEquals(o1, o2) {
596
596
  if (o1 === o2)
597
597
  return true;
598
598
  if ((o1 && !o2) || (o2 && !o1))
@@ -608,17 +608,13 @@ function deepEquals(o1, o2, ignoreFunctions) {
608
608
  }
609
609
  }
610
610
  for (const key in o1) {
611
- const typeOfO1Key = typeof o1[key];
612
- if (typeOfO1Key !== typeof o2[key])
611
+ if (typeof o1[key] !== typeof o2[key])
613
612
  return false;
614
- if (typeOfO1Key === "object") {
615
- if (!deepEquals(o1[key], o2[key], ignoreFunctions))
613
+ if (typeof o1[key] === "object") {
614
+ if (!deepEquals(o1[key], o2[key]))
616
615
  return false;
617
616
  }
618
617
  else {
619
- if (ignoreFunctions && typeOfO1Key === "function") {
620
- continue;
621
- }
622
618
  if (o1[key] !== o2[key])
623
619
  return false;
624
620
  }
@@ -1156,31 +1152,15 @@ function numberToLetters(n) {
1156
1152
  return numberToLetters(Math.floor(n / 26) - 1) + numberToLetters(n % 26);
1157
1153
  }
1158
1154
  }
1159
- const LETTERS = "ABCDEFGHIJKLMNOPQRSTUVWXYZ";
1160
- const LETTERS_NUMBER_MAPPING = {};
1161
- for (const letter of LETTERS) {
1162
- const colIndex = letter.charCodeAt(0) - 64;
1163
- LETTERS_NUMBER_MAPPING[letter] = colIndex;
1164
- LETTERS_NUMBER_MAPPING[letter.toLowerCase()] = colIndex;
1165
- }
1166
- /**
1167
- * Convert a string (describing a column) to its number value.
1168
- *
1169
- * Examples:
1170
- * 'A' => 0
1171
- * 'Z' => 25
1172
- * 'AA' => 26
1173
- */
1174
1155
  function lettersToNumber(letters) {
1175
- let result = -1;
1156
+ let result = 0;
1176
1157
  const l = letters.length;
1177
- let pow = 1;
1178
- for (let i = l - 1; i >= 0; i--) {
1179
- const charCode = LETTERS_NUMBER_MAPPING[letters[i]];
1180
- result += charCode * pow;
1181
- pow *= 26;
1158
+ for (let i = 0; i < l; i++) {
1159
+ const charCode = letters.charCodeAt(i);
1160
+ const colIndex = charCode >= 65 && charCode <= 90 ? charCode - 64 : charCode - 96;
1161
+ result = result * 26 + colIndex;
1182
1162
  }
1183
- return result;
1163
+ return result - 1;
1184
1164
  }
1185
1165
  function isCharALetter(char) {
1186
1166
  return (char >= "A" && char <= "Z") || (char >= "a" && char <= "z");
@@ -1199,37 +1179,27 @@ function isCharADigit(char) {
1199
1179
  */
1200
1180
  function toCartesian(xc) {
1201
1181
  xc = xc.trim();
1202
- let numberPartStart = undefined;
1203
- // Note: looping by hand is uglier but ~2x faster than using a regex to match number/letter parts
1204
- for (let i = 0; i < xc.length; i++) {
1205
- const char = xc[i];
1206
- // as long as we haven't found the number part, keep advancing
1207
- if (!numberPartStart) {
1208
- if ((char === "$" && i === 0) || isCharALetter(char)) {
1209
- continue;
1210
- }
1211
- numberPartStart = i;
1212
- }
1213
- // Number part
1214
- if (!isCharADigit(char)) {
1215
- if (char === "$" && i === numberPartStart) {
1216
- continue;
1217
- }
1218
- throw new Error(`Invalid cell description: ${xc}`);
1219
- }
1182
+ let letterPart = "";
1183
+ let numberPart = "";
1184
+ let i = 0;
1185
+ // Process letter part
1186
+ if (xc[i] === "$")
1187
+ i++;
1188
+ while (i < xc.length && isCharALetter(xc[i])) {
1189
+ letterPart += xc[i++];
1220
1190
  }
1221
- if (!numberPartStart || numberPartStart === xc.length) {
1191
+ if (letterPart.length === 0 || letterPart.length > 3) {
1192
+ // limit to max 3 letters for performance reasons
1222
1193
  throw new Error(`Invalid cell description: ${xc}`);
1223
1194
  }
1224
- const letterPart = xc[0] === "$" ? xc.slice(1, numberPartStart) : xc.slice(0, numberPartStart);
1225
- const numberPart = xc[numberPartStart] === "$" ? xc.slice(numberPartStart + 1) : xc.slice(numberPartStart);
1226
- // limit to max 3 letters and 7 numbers to avoid
1227
- // gigantic numbers that would be a performance killer
1228
- // down the road
1229
- if (letterPart.length < 1 ||
1230
- letterPart.length > 3 ||
1231
- numberPart.length < 1 ||
1232
- numberPart.length > 7) {
1195
+ // Process number part
1196
+ if (xc[i] === "$")
1197
+ i++;
1198
+ while (i < xc.length && isCharADigit(xc[i])) {
1199
+ numberPart += xc[i++];
1200
+ }
1201
+ if (i !== xc.length || numberPart.length === 0 || numberPart.length > 7) {
1202
+ // limit to max 7 numbers for performance reasons
1233
1203
  throw new Error(`Invalid cell description: ${xc}`);
1234
1204
  }
1235
1205
  const col = lettersToNumber(letterPart);
@@ -1831,7 +1801,7 @@ const getFormulaNumberRegex = memoize(function getFormulaNumberRegex(decimalSepa
1831
1801
  });
1832
1802
  const getNumberRegex = memoize(function getNumberRegex(locale) {
1833
1803
  const decimalSeparator = escapeRegExp(locale.decimalSeparator);
1834
- const thousandsSeparator = escapeRegExp(locale.thousandsSeparator);
1804
+ const thousandsSeparator = escapeRegExp(locale.thousandsSeparator || "");
1835
1805
  const pIntegerAndDecimals = `(?:\\d+(?:${thousandsSeparator}\\d{3,})*(?:${decimalSeparator}\\d*)?)`; // pattern that match integer number with or without decimal digits
1836
1806
  const pOnlyDecimals = `(?:${decimalSeparator}\\d+)`; // pattern that match only expression with decimal digits
1837
1807
  const pScientificFormat = "(?:e(?:\\+|-)?\\d+)?"; // pattern that match scientific format between zero and one time (should be placed before pPercentFormat)
@@ -1864,7 +1834,7 @@ function isNumber(value, locale) {
1864
1834
  return getNumberRegex(locale).test(value.trim());
1865
1835
  }
1866
1836
  const getInvaluableSymbolsRegexp = memoize(function getInvaluableSymbolsRegexp(locale) {
1867
- return new RegExp(`[\$€${escapeRegExp(locale.thousandsSeparator)}]`, "g");
1837
+ return new RegExp(`[\$€${escapeRegExp(locale.thousandsSeparator || "")}]`, "g");
1868
1838
  });
1869
1839
  /**
1870
1840
  * Convert a string into a number. It assumes that the string actually represents
@@ -2667,11 +2637,11 @@ function generateMatrix(nColumns, nRows, callback) {
2667
2637
  }
2668
2638
  return returned;
2669
2639
  }
2670
- function matrixMap(matrix, fn) {
2640
+ function matrixMap(matrix, callback) {
2671
2641
  if (matrix.length === 0) {
2672
2642
  return [];
2673
2643
  }
2674
- return generateMatrix(matrix.length, matrix[0].length, (col, row) => fn(matrix[col][row]));
2644
+ return generateMatrix(matrix.length, matrix[0].length, (col, row) => callback(matrix[col][row]));
2675
2645
  }
2676
2646
  function matrixForEach(matrix, fn) {
2677
2647
  const numberOfCols = matrix.length;
@@ -2771,6 +2741,9 @@ function getPredicate(descr, locale) {
2771
2741
  * If the character is a special regular expression character, it is escaped with "\\".
2772
2742
  */
2773
2743
  const wildcardToRegExp = memoize(function wildcardToRegExp(operand) {
2744
+ if (operand === "*") {
2745
+ return /.+/;
2746
+ }
2774
2747
  let exp = "";
2775
2748
  let predecessor = "";
2776
2749
  for (let char of operand) {
@@ -2794,9 +2767,9 @@ const wildcardToRegExp = memoize(function wildcardToRegExp(operand) {
2794
2767
  }
2795
2768
  return new RegExp("^" + exp + "$", "i");
2796
2769
  });
2797
- function evaluatePredicate(value, criterion) {
2770
+ function evaluatePredicate(value = "", criterion) {
2798
2771
  const { operator, operand } = criterion;
2799
- if (value === undefined || operand === undefined || value === null || operand === null) {
2772
+ if (operand === undefined || value === null || operand === null) {
2800
2773
  return false;
2801
2774
  }
2802
2775
  if (typeof operand === "number" && operator === "=") {
@@ -5888,19 +5861,22 @@ class TokenizingChars {
5888
5861
  }
5889
5862
 
5890
5863
  function isValidLocale(locale) {
5891
- if (!(locale &&
5892
- typeof locale === "object" &&
5893
- typeof locale.name === "string" &&
5894
- typeof locale.code === "string" &&
5895
- typeof locale.thousandsSeparator === "string" &&
5896
- typeof locale.decimalSeparator === "string" &&
5897
- typeof locale.dateFormat === "string" &&
5898
- typeof locale.timeFormat === "string" &&
5899
- typeof locale.formulaArgSeparator === "string")) {
5864
+ if (!locale ||
5865
+ typeof locale !== "object" ||
5866
+ !(!locale.thousandsSeparator || typeof locale.thousandsSeparator === "string")) {
5900
5867
  return false;
5901
5868
  }
5902
- if (!Object.values(locale).every((v) => v)) {
5903
- return false;
5869
+ for (const property of [
5870
+ "code",
5871
+ "name",
5872
+ "decimalSeparator",
5873
+ "dateFormat",
5874
+ "timeFormat",
5875
+ "formulaArgSeparator",
5876
+ ]) {
5877
+ if (!locale[property] || typeof locale[property] !== "string") {
5878
+ return false;
5879
+ }
5904
5880
  }
5905
5881
  if (locale.formulaArgSeparator === locale.decimalSeparator) {
5906
5882
  return false;
@@ -5998,7 +5974,10 @@ function canonicalizeNumberLiteral(content, locale) {
5998
5974
  if (locale.decimalSeparator === "." || !isNumber(content, locale)) {
5999
5975
  return content;
6000
5976
  }
6001
- return content.replace(locale.thousandsSeparator, "").replace(locale.decimalSeparator, ".");
5977
+ if (locale.thousandsSeparator) {
5978
+ content = content.replaceAll(locale.thousandsSeparator, "");
5979
+ }
5980
+ return content.replace(locale.decimalSeparator, ".");
6002
5981
  }
6003
5982
  /**
6004
5983
  * Change a content string from the given locale to its canonical form (en_US locale). Also convert date string.
@@ -6295,24 +6274,6 @@ const dayOfMonthAdapter = {
6295
6274
  return `${normalizedValue}`;
6296
6275
  },
6297
6276
  };
6298
- /**
6299
- * Normalized value: "2/2023" for week 2 of 2023
6300
- */
6301
- const weekAdapter = {
6302
- normalizeFunctionValue(value) {
6303
- const [week, year] = toString(value).split("/");
6304
- return `${Number(week)}/${Number(year)}`;
6305
- },
6306
- toValueAndFormat(normalizedValue, locale) {
6307
- const [week, year] = normalizedValue.split("/");
6308
- return {
6309
- value: _t("W%(week)s %(year)s", { week, year }),
6310
- };
6311
- },
6312
- toFunctionValue(normalizedValue) {
6313
- return `"${normalizedValue}"`;
6314
- },
6315
- };
6316
6277
  /**
6317
6278
  * normalizes iso week number
6318
6279
  */
@@ -6334,25 +6295,6 @@ const isoWeekNumberAdapter = {
6334
6295
  return `${normalizedValue}`;
6335
6296
  },
6336
6297
  };
6337
- /**
6338
- * normalized month value is a string formatted as "MM/yyyy" (luxon format)
6339
- * e.g. "01/2020" for January 2020
6340
- */
6341
- const monthAdapter = {
6342
- normalizeFunctionValue(value) {
6343
- const date = toNumber(value, DEFAULT_LOCALE);
6344
- return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
6345
- },
6346
- toValueAndFormat(normalizedValue) {
6347
- return {
6348
- value: toNumber(normalizedValue, DEFAULT_LOCALE),
6349
- format: "mmmm yyyy",
6350
- };
6351
- },
6352
- toFunctionValue(normalizedValue) {
6353
- return `"${normalizedValue}"`;
6354
- },
6355
- };
6356
6298
  /**
6357
6299
  * normalizes month number
6358
6300
  */
@@ -6374,25 +6316,6 @@ const monthNumberAdapter = {
6374
6316
  return `${normalizedValue}`;
6375
6317
  },
6376
6318
  };
6377
- /**
6378
- * normalized quarter value is "quarter/year"
6379
- * e.g. "1/2020" for Q1 2020
6380
- */
6381
- const quarterAdapter = {
6382
- normalizeFunctionValue(value) {
6383
- const [quarter, year] = toString(value).split("/");
6384
- return `${quarter}/${year}`;
6385
- },
6386
- toValueAndFormat(normalizedValue) {
6387
- const [quarter, year] = normalizedValue.split("/");
6388
- return {
6389
- value: _t("Q%(quarter)s %(year)s", { quarter, year }),
6390
- };
6391
- },
6392
- toFunctionValue(normalizedValue) {
6393
- return `"${normalizedValue}"`;
6394
- },
6395
- };
6396
6319
  /**
6397
6320
  * normalizes quarter number
6398
6321
  */
@@ -6406,7 +6329,7 @@ const quarterNumberAdapter = {
6406
6329
  },
6407
6330
  toValueAndFormat(normalizedValue) {
6408
6331
  return {
6409
- value: toNumber(normalizedValue, DEFAULT_LOCALE),
6332
+ value: _t("Q%(quarter_number)s", { quarter_number: normalizedValue }),
6410
6333
  format: "0",
6411
6334
  };
6412
6335
  },
@@ -6456,15 +6379,11 @@ function nullHandlerDecorator(adapter) {
6456
6379
  }
6457
6380
  pivotTimeAdapterRegistry
6458
6381
  .add("day", nullHandlerDecorator(dayAdapter))
6459
- .add("week", nullHandlerDecorator(weekAdapter))
6460
- .add("month", nullHandlerDecorator(monthAdapter))
6461
- .add("quarter", nullHandlerDecorator(quarterAdapter))
6462
6382
  .add("year", nullHandlerDecorator(yearAdapter))
6463
6383
  .add("day_of_month", nullHandlerDecorator(dayOfMonthAdapter))
6464
6384
  .add("iso_week_number", nullHandlerDecorator(isoWeekNumberAdapter))
6465
6385
  .add("month_number", nullHandlerDecorator(monthNumberAdapter))
6466
- .add("quarter_number", nullHandlerDecorator(quarterNumberAdapter))
6467
- .add("year_number", nullHandlerDecorator(yearAdapter));
6386
+ .add("quarter_number", nullHandlerDecorator(quarterNumberAdapter));
6468
6387
 
6469
6388
  const AGGREGATOR_NAMES = {
6470
6389
  count: _t("Count"),
@@ -6540,11 +6459,10 @@ function getMaxObjectId(o) {
6540
6459
  }
6541
6460
  const ALL_PERIODS = {
6542
6461
  year: _t("Year"),
6543
- quarter: _t("Quarter"),
6544
- month: _t("Month"),
6545
- week: _t("Week"),
6462
+ quarter: _t("Quarter & Year"),
6463
+ month: _t("Month & Year"),
6464
+ week: _t("Week & Year"),
6546
6465
  day: _t("Day"),
6547
- year_number: _t("Year"),
6548
6466
  quarter_number: _t("Quarter"),
6549
6467
  month_number: _t("Month"),
6550
6468
  iso_week_number: _t("Week"),
@@ -6695,7 +6613,7 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
6695
6613
  const pivotId = this.getters.getPivotIdFromPosition(position);
6696
6614
  const spreader = this.getters.getArrayFormulaSpreadingOn(position);
6697
6615
  if (pivotId) {
6698
- if (!deepEquals(spreader, position) || !isCopyingOneCell) {
6616
+ if (spreader && (!deepEquals(spreader, position) || !isCopyingOneCell)) {
6699
6617
  const pivotCell = this.getters.getPivotCellFromPosition(position);
6700
6618
  const formulaPivotId = this.getters.getPivotFormulaId(pivotId);
6701
6619
  const pivotFormula = createPivotFormula(formulaPivotId, pivotCell);
@@ -7500,6 +7418,146 @@ function transformRangeData(range, executed) {
7500
7418
  return undefined;
7501
7419
  }
7502
7420
 
7421
+ var State;
7422
+ (function (State) {
7423
+ /**
7424
+ * Initial state.
7425
+ * Expecting any reference for the left part of a range
7426
+ * e.g. "A1", "1", "A", "Sheet1!A1", "Sheet1!A"
7427
+ */
7428
+ State[State["LeftRef"] = 0] = "LeftRef";
7429
+ /**
7430
+ * Expecting any reference for the right part of a range
7431
+ * e.g. "A1", "1", "A", "Sheet1!A1", "Sheet1!A"
7432
+ */
7433
+ State[State["RightRef"] = 1] = "RightRef";
7434
+ /**
7435
+ * Expecting the separator without any constraint on the right part
7436
+ */
7437
+ State[State["Separator"] = 2] = "Separator";
7438
+ /**
7439
+ * Expecting the separator for a full column range
7440
+ */
7441
+ State[State["FullColumnSeparator"] = 3] = "FullColumnSeparator";
7442
+ /**
7443
+ * Expecting the separator for a full row range
7444
+ */
7445
+ State[State["FullRowSeparator"] = 4] = "FullRowSeparator";
7446
+ /**
7447
+ * Expecting the right part of a full column range
7448
+ * e.g. "1", "A1"
7449
+ */
7450
+ State[State["RightColumnRef"] = 5] = "RightColumnRef";
7451
+ /**
7452
+ * Expecting the right part of a full row range
7453
+ * e.g. "A", "A1"
7454
+ */
7455
+ State[State["RightRowRef"] = 6] = "RightRowRef";
7456
+ /**
7457
+ * Final state. A range has been matched
7458
+ */
7459
+ State[State["Found"] = 7] = "Found";
7460
+ })(State || (State = {}));
7461
+ const goTo = (state, guard = () => true) => [
7462
+ {
7463
+ goTo: state,
7464
+ guard,
7465
+ },
7466
+ ];
7467
+ const goToMulti = (state, guard = () => true) => ({
7468
+ goTo: state,
7469
+ guard,
7470
+ });
7471
+ const machine = {
7472
+ [State.LeftRef]: {
7473
+ REFERENCE: goTo(State.Separator),
7474
+ NUMBER: goTo(State.FullRowSeparator),
7475
+ SYMBOL: [
7476
+ goToMulti(State.FullColumnSeparator, (token) => isColReference(token.value)),
7477
+ goToMulti(State.FullRowSeparator, (token) => isRowReference(token.value)),
7478
+ ],
7479
+ },
7480
+ [State.FullColumnSeparator]: {
7481
+ SPACE: goTo(State.FullColumnSeparator),
7482
+ OPERATOR: goTo(State.RightColumnRef, (token) => token.value === ":"),
7483
+ },
7484
+ [State.FullRowSeparator]: {
7485
+ SPACE: goTo(State.FullRowSeparator),
7486
+ OPERATOR: goTo(State.RightRowRef, (token) => token.value === ":"),
7487
+ },
7488
+ [State.Separator]: {
7489
+ SPACE: goTo(State.Separator),
7490
+ OPERATOR: goTo(State.RightRef, (token) => token.value === ":"),
7491
+ },
7492
+ [State.RightRef]: {
7493
+ SPACE: goTo(State.RightRef),
7494
+ NUMBER: goTo(State.Found),
7495
+ REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
7496
+ SYMBOL: goTo(State.Found, (token) => isColHeader(token.value) || isRowHeader(token.value)),
7497
+ },
7498
+ [State.RightColumnRef]: {
7499
+ SPACE: goTo(State.RightColumnRef),
7500
+ SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
7501
+ REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
7502
+ },
7503
+ [State.RightRowRef]: {
7504
+ SPACE: goTo(State.RightRowRef),
7505
+ NUMBER: goTo(State.Found),
7506
+ REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
7507
+ SYMBOL: goTo(State.Found, (token) => isRowHeader(token.value)),
7508
+ },
7509
+ [State.Found]: {},
7510
+ };
7511
+ /**
7512
+ * Check if the list of tokens starts with a sequence of tokens representing
7513
+ * a range.
7514
+ * If a range is found, the sequence is removed from the list and is returned
7515
+ * as a single token.
7516
+ */
7517
+ function matchReference(tokens) {
7518
+ let head = 0;
7519
+ let transitions = machine[State.LeftRef];
7520
+ let matchedTokens = "";
7521
+ while (transitions !== undefined) {
7522
+ const token = tokens[head++];
7523
+ if (!token) {
7524
+ return null;
7525
+ }
7526
+ const transition = transitions[token.type]?.find((transition) => transition.guard(token));
7527
+ const nextState = transition ? transition.goTo : undefined;
7528
+ switch (nextState) {
7529
+ case undefined:
7530
+ return null;
7531
+ case State.Found:
7532
+ matchedTokens += token.value;
7533
+ tokens.splice(0, head);
7534
+ return {
7535
+ type: "REFERENCE",
7536
+ value: matchedTokens,
7537
+ };
7538
+ default:
7539
+ transitions = machine[nextState];
7540
+ matchedTokens += token.value;
7541
+ break;
7542
+ }
7543
+ }
7544
+ return null;
7545
+ }
7546
+ /**
7547
+ * Take the result of the tokenizer and transform it to be usable in the
7548
+ * manipulations of range
7549
+ *
7550
+ * @param formula
7551
+ */
7552
+ function rangeTokenize(formula, locale = DEFAULT_LOCALE) {
7553
+ const tokens = tokenize(formula, locale);
7554
+ const result = [];
7555
+ while (tokens.length) {
7556
+ result.push(matchReference(tokens) || tokens.shift());
7557
+ }
7558
+ return result;
7559
+ }
7560
+
7503
7561
  const functionRegex = /[a-zA-Z0-9\_]+(\.[a-zA-Z0-9\_]+)*/;
7504
7562
  const UNARY_OPERATORS_PREFIX = ["-", "+"];
7505
7563
  const UNARY_OPERATORS_POSTFIX = ["%"];
@@ -7648,7 +7706,7 @@ function parseExpression(tokens, parent_priority = 0) {
7648
7706
  * Parse an expression (as a string) into an AST.
7649
7707
  */
7650
7708
  function parse(str) {
7651
- return parseTokens(tokenize(str));
7709
+ return parseTokens(rangeTokenize(str));
7652
7710
  }
7653
7711
  function parseTokens(tokens) {
7654
7712
  tokens = tokens.filter((x) => x.type !== "SPACE");
@@ -7784,146 +7842,6 @@ function rightOperandToFormula(operationAST) {
7784
7842
  return needParenthesis ? `(${astToFormula(rightOperation)})` : astToFormula(rightOperation);
7785
7843
  }
7786
7844
 
7787
- var State;
7788
- (function (State) {
7789
- /**
7790
- * Initial state.
7791
- * Expecting any reference for the left part of a range
7792
- * e.g. "A1", "1", "A", "Sheet1!A1", "Sheet1!A"
7793
- */
7794
- State[State["LeftRef"] = 0] = "LeftRef";
7795
- /**
7796
- * Expecting any reference for the right part of a range
7797
- * e.g. "A1", "1", "A", "Sheet1!A1", "Sheet1!A"
7798
- */
7799
- State[State["RightRef"] = 1] = "RightRef";
7800
- /**
7801
- * Expecting the separator without any constraint on the right part
7802
- */
7803
- State[State["Separator"] = 2] = "Separator";
7804
- /**
7805
- * Expecting the separator for a full column range
7806
- */
7807
- State[State["FullColumnSeparator"] = 3] = "FullColumnSeparator";
7808
- /**
7809
- * Expecting the separator for a full row range
7810
- */
7811
- State[State["FullRowSeparator"] = 4] = "FullRowSeparator";
7812
- /**
7813
- * Expecting the right part of a full column range
7814
- * e.g. "1", "A1"
7815
- */
7816
- State[State["RightColumnRef"] = 5] = "RightColumnRef";
7817
- /**
7818
- * Expecting the right part of a full row range
7819
- * e.g. "A", "A1"
7820
- */
7821
- State[State["RightRowRef"] = 6] = "RightRowRef";
7822
- /**
7823
- * Final state. A range has been matched
7824
- */
7825
- State[State["Found"] = 7] = "Found";
7826
- })(State || (State = {}));
7827
- const goTo = (state, guard = () => true) => [
7828
- {
7829
- goTo: state,
7830
- guard,
7831
- },
7832
- ];
7833
- const goToMulti = (state, guard = () => true) => ({
7834
- goTo: state,
7835
- guard,
7836
- });
7837
- const machine = {
7838
- [State.LeftRef]: {
7839
- REFERENCE: goTo(State.Separator),
7840
- NUMBER: goTo(State.FullRowSeparator),
7841
- SYMBOL: [
7842
- goToMulti(State.FullColumnSeparator, (token) => isColReference(token.value)),
7843
- goToMulti(State.FullRowSeparator, (token) => isRowReference(token.value)),
7844
- ],
7845
- },
7846
- [State.FullColumnSeparator]: {
7847
- SPACE: goTo(State.FullColumnSeparator),
7848
- OPERATOR: goTo(State.RightColumnRef, (token) => token.value === ":"),
7849
- },
7850
- [State.FullRowSeparator]: {
7851
- SPACE: goTo(State.FullRowSeparator),
7852
- OPERATOR: goTo(State.RightRowRef, (token) => token.value === ":"),
7853
- },
7854
- [State.Separator]: {
7855
- SPACE: goTo(State.Separator),
7856
- OPERATOR: goTo(State.RightRef, (token) => token.value === ":"),
7857
- },
7858
- [State.RightRef]: {
7859
- SPACE: goTo(State.RightRef),
7860
- NUMBER: goTo(State.Found),
7861
- REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
7862
- SYMBOL: goTo(State.Found, (token) => isColHeader(token.value) || isRowHeader(token.value)),
7863
- },
7864
- [State.RightColumnRef]: {
7865
- SPACE: goTo(State.RightColumnRef),
7866
- SYMBOL: goTo(State.Found, (token) => isColHeader(token.value)),
7867
- REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
7868
- },
7869
- [State.RightRowRef]: {
7870
- SPACE: goTo(State.RightRowRef),
7871
- NUMBER: goTo(State.Found),
7872
- REFERENCE: goTo(State.Found, (token) => isSingleCellReference(token.value)),
7873
- SYMBOL: goTo(State.Found, (token) => isRowHeader(token.value)),
7874
- },
7875
- [State.Found]: {},
7876
- };
7877
- /**
7878
- * Check if the list of tokens starts with a sequence of tokens representing
7879
- * a range.
7880
- * If a range is found, the sequence is removed from the list and is returned
7881
- * as a single token.
7882
- */
7883
- function matchReference(tokens) {
7884
- let head = 0;
7885
- let transitions = machine[State.LeftRef];
7886
- let matchedTokens = "";
7887
- while (transitions !== undefined) {
7888
- const token = tokens[head++];
7889
- if (!token) {
7890
- return null;
7891
- }
7892
- const transition = transitions[token.type]?.find((transition) => transition.guard(token));
7893
- const nextState = transition ? transition.goTo : undefined;
7894
- switch (nextState) {
7895
- case undefined:
7896
- return null;
7897
- case State.Found:
7898
- matchedTokens += token.value;
7899
- tokens.splice(0, head);
7900
- return {
7901
- type: "REFERENCE",
7902
- value: matchedTokens,
7903
- };
7904
- default:
7905
- transitions = machine[nextState];
7906
- matchedTokens += token.value;
7907
- break;
7908
- }
7909
- }
7910
- return null;
7911
- }
7912
- /**
7913
- * Take the result of the tokenizer and transform it to be usable in the
7914
- * manipulations of range
7915
- *
7916
- * @param formula
7917
- */
7918
- function rangeTokenize(formula, locale = DEFAULT_LOCALE) {
7919
- const tokens = tokenize(formula, locale);
7920
- const result = [];
7921
- while (tokens.length) {
7922
- result.push(matchReference(tokens) || tokens.shift());
7923
- }
7924
- return result;
7925
- }
7926
-
7927
7845
  /**
7928
7846
  * Add the following information on tokens:
7929
7847
  * - length
@@ -8154,8 +8072,8 @@ function detectLink(value) {
8154
8072
 
8155
8073
  function evaluateLiteral(literalCell, localeFormat) {
8156
8074
  const value = localeFormat.format === PLAIN_TEXT_FORMAT ? literalCell.content : literalCell.parsedValue;
8157
- const fPayload = { value, format: localeFormat.format };
8158
- return createEvaluatedCell(fPayload, localeFormat.locale);
8075
+ const functionResult = { value, format: localeFormat.format };
8076
+ return createEvaluatedCell(functionResult, localeFormat.locale);
8159
8077
  }
8160
8078
  function parseLiteral(content, locale) {
8161
8079
  if (content.startsWith("=")) {
@@ -8176,13 +8094,13 @@ function parseLiteral(content, locale) {
8176
8094
  }
8177
8095
  return content;
8178
8096
  }
8179
- function createEvaluatedCell(fPayload, locale = DEFAULT_LOCALE, cell) {
8180
- const link = detectLink(fPayload.value);
8097
+ function createEvaluatedCell(functionResult, locale = DEFAULT_LOCALE, cell) {
8098
+ const link = detectLink(functionResult.value);
8181
8099
  if (!link) {
8182
- return _createEvaluatedCell(fPayload, locale, cell);
8100
+ return _createEvaluatedCell(functionResult, locale, cell);
8183
8101
  }
8184
8102
  const value = parseLiteral(link.label, locale);
8185
- const format = fPayload.format ||
8103
+ const format = functionResult.format ||
8186
8104
  (typeof value === "number"
8187
8105
  ? detectDateFormat(link.label, locale) || detectNumberFormat(link.label)
8188
8106
  : undefined);
@@ -8195,8 +8113,8 @@ function createEvaluatedCell(fPayload, locale = DEFAULT_LOCALE, cell) {
8195
8113
  link,
8196
8114
  };
8197
8115
  }
8198
- function _createEvaluatedCell(fPayload, locale, cell) {
8199
- let { value, format, message } = fPayload;
8116
+ function _createEvaluatedCell(functionResult, locale, cell) {
8117
+ let { value, format, message } = functionResult;
8200
8118
  format = cell?.format || format;
8201
8119
  const formattedValue = formatValue(value, { format, locale });
8202
8120
  if (isEvaluationError(value)) {
@@ -8484,9 +8402,11 @@ const ChartTerms = {
8484
8402
  BackgroundColor: _t("Background color"),
8485
8403
  StackedBarChart: _t("Stacked bar chart"),
8486
8404
  StackedLineChart: _t("Stacked line chart"),
8405
+ StackedAreaChart: _t("Stacked area chart"),
8487
8406
  CumulativeData: _t("Cumulative data"),
8488
8407
  TreatLabelsAsText: _t("Treat labels as text"),
8489
8408
  AggregatedChart: _t("Aggregate"),
8409
+ ShowValues: _t("Show values"),
8490
8410
  Errors: {
8491
8411
  Unexpected: _t("The chart definition is invalid for an unknown reason"),
8492
8412
  // BASIC CHART ERRORS (LINE | BAR | PIE)
@@ -10328,162 +10248,6 @@ class ComposerFocusStore extends SpreadsheetStore {
10328
10248
  }
10329
10249
  }
10330
10250
 
10331
- /** This is a chartJS plugin that will draw connector lines between the bars of a Waterfall chart */
10332
- const waterfallLinesPlugin = {
10333
- id: "waterfallLinesPlugin",
10334
- beforeDraw(chart, args, options) {
10335
- if (!options.showConnectorLines) {
10336
- return;
10337
- }
10338
- // Note: private properties are not in the typing of chartJS (and some of the existing types are missing properties)
10339
- // so we don't type anything in this file
10340
- const drawData = chart._metasets?.[0]?.data;
10341
- if (!drawData) {
10342
- return;
10343
- }
10344
- const ctx = chart.ctx;
10345
- ctx.save();
10346
- ctx.setLineDash([3, 2]);
10347
- for (let i = 0; i < drawData.length; i++) {
10348
- const bar = drawData[i];
10349
- if (bar.height === 0) {
10350
- continue;
10351
- }
10352
- const nextBar = getNextNonEmptyBar(drawData, i);
10353
- if (!nextBar) {
10354
- break;
10355
- }
10356
- const rect = getBarElementRect(bar);
10357
- const nextBarRect = getBarElementRect(nextBar);
10358
- const rawBarValues = bar.$context.raw;
10359
- const value = rawBarValues[1] - rawBarValues[0];
10360
- const lineY = Math.round(value < 0 ? rect.bottom - 1 : rect.top);
10361
- const lineStart = Math.round(rect.right);
10362
- const lineEnd = Math.round(nextBarRect.left);
10363
- ctx.strokeStyle = "#999";
10364
- ctx.beginPath();
10365
- ctx.moveTo(lineStart + 1, lineY + 0.5);
10366
- ctx.lineTo(lineEnd, lineY + 0.5);
10367
- ctx.stroke();
10368
- }
10369
- ctx.restore();
10370
- },
10371
- };
10372
- function getBarElementRect(bar) {
10373
- const flipped = bar.base < bar.y; // Bar are flipped for negative values in the dataset
10374
- return {
10375
- left: bar.x - bar.width / 2,
10376
- right: bar.x + bar.width / 2,
10377
- bottom: flipped ? bar.base + bar.height : bar.y + bar.height,
10378
- top: flipped ? bar.base : bar.y,
10379
- };
10380
- }
10381
- function getNextNonEmptyBar(bars, startIndex) {
10382
- return bars.find((bar, i) => i > startIndex && bar.height !== 0);
10383
- }
10384
-
10385
- // @ts-ignore
10386
- window.Chart?.register(waterfallLinesPlugin);
10387
- class ChartJsComponent extends owl.Component {
10388
- static template = "o-spreadsheet-ChartJsComponent";
10389
- static props = {
10390
- figure: Object,
10391
- };
10392
- canvas = owl.useRef("graphContainer");
10393
- chart;
10394
- currentRuntime;
10395
- get background() {
10396
- return this.chartRuntime.background;
10397
- }
10398
- get canvasStyle() {
10399
- return `background-color: ${this.background}`;
10400
- }
10401
- get chartRuntime() {
10402
- const runtime = this.env.model.getters.getChartRuntime(this.props.figure.id);
10403
- if (!("chartJsConfig" in runtime)) {
10404
- throw new Error("Unsupported chart runtime");
10405
- }
10406
- return runtime;
10407
- }
10408
- setup() {
10409
- owl.onMounted(() => {
10410
- const runtime = this.chartRuntime;
10411
- this.currentRuntime = runtime;
10412
- // Note: chartJS modify the runtime in place, so it's important to give it a copy
10413
- this.createChart(deepCopy(runtime.chartJsConfig));
10414
- });
10415
- owl.onWillUnmount(() => this.chart?.destroy());
10416
- owl.useEffect(() => {
10417
- const runtime = this.chartRuntime;
10418
- if (!deepEquals(runtime, this.currentRuntime, "ignoreFunctions")) {
10419
- if (runtime.chartJsConfig.type !== this.currentRuntime.chartJsConfig.type) {
10420
- this.chart?.destroy();
10421
- this.createChart(deepCopy(runtime.chartJsConfig));
10422
- }
10423
- else {
10424
- this.updateChartJs(deepCopy(runtime));
10425
- }
10426
- this.currentRuntime = runtime;
10427
- }
10428
- });
10429
- }
10430
- createChart(chartData) {
10431
- const canvas = this.canvas.el;
10432
- const ctx = canvas.getContext("2d");
10433
- // @ts-ignore
10434
- this.chart = new window.Chart(ctx, chartData);
10435
- }
10436
- updateChartJs(chartRuntime) {
10437
- const chartData = chartRuntime.chartJsConfig;
10438
- if (chartData.data && chartData.data.datasets) {
10439
- this.chart.data = chartData.data;
10440
- if (chartData.options?.plugins?.title) {
10441
- this.chart.config.options.plugins.title = chartData.options.plugins.title;
10442
- }
10443
- }
10444
- else {
10445
- this.chart.data.datasets = [];
10446
- }
10447
- this.chart.config.options = chartData.options;
10448
- this.chart.update();
10449
- }
10450
- }
10451
-
10452
- /**
10453
- * AbstractChart is the class from which every Chart should inherit.
10454
- * The role of this class is to maintain the state of each chart.
10455
- */
10456
- class AbstractChart {
10457
- sheetId;
10458
- title;
10459
- getters;
10460
- constructor(definition, sheetId, getters) {
10461
- this.title = definition.title;
10462
- this.sheetId = sheetId;
10463
- this.getters = getters;
10464
- }
10465
- /**
10466
- * Validate the chart definition given as arguments. This function will be
10467
- * called from allowDispatch function
10468
- */
10469
- static validateChartDefinition(validator, definition) {
10470
- throw new Error("This method should be implemented by sub class");
10471
- }
10472
- /**
10473
- * Get a new chart definition transformed with the executed command. This
10474
- * functions will be called during operational transform process
10475
- */
10476
- static transformDefinition(definition, executed) {
10477
- throw new Error("This method should be implemented by sub class");
10478
- }
10479
- /**
10480
- * Get an empty definition based on the given context
10481
- */
10482
- static getDefinitionFromContextCreation(context) {
10483
- throw new Error("This method should be implemented by sub class");
10484
- }
10485
- }
10486
-
10487
10251
  /**
10488
10252
  * This file contains helpers that are common to different charts (mainly
10489
10253
  * line, bar and pie charts)
@@ -10806,6 +10570,238 @@ function getDefinedAxis(definition) {
10806
10570
  return { useLeftAxis, useRightAxis };
10807
10571
  }
10808
10572
 
10573
+ /** This is a chartJS plugin that will draw the values of each data next to the point/bar/pie slice */
10574
+ const chartShowValuesPlugin = {
10575
+ id: "chartShowValuesPlugin",
10576
+ afterDatasetsDraw(chart, args, options) {
10577
+ if (!options.showValues) {
10578
+ return;
10579
+ }
10580
+ const drawData = chart._metasets?.[0]?.data;
10581
+ if (!drawData) {
10582
+ return;
10583
+ }
10584
+ const ctx = chart.ctx;
10585
+ ctx.save();
10586
+ ctx.textAlign = "center";
10587
+ ctx.textBaseline = "middle";
10588
+ ctx.fillStyle = chartFontColor(options.background);
10589
+ ctx.strokeStyle = chartFontColor(ctx.fillStyle);
10590
+ chart._metasets.forEach(function (dataset) {
10591
+ switch (dataset.type) {
10592
+ case "doughnut":
10593
+ case "pie": {
10594
+ for (let i = 0; i < dataset._parsed.length; i++) {
10595
+ const bar = dataset.data[i];
10596
+ const { startAngle, endAngle, innerRadius, outerRadius } = bar;
10597
+ const midAngle = (startAngle + endAngle) / 2;
10598
+ const midRadius = (innerRadius + outerRadius) / 2;
10599
+ const x = bar.x + midRadius * Math.cos(midAngle);
10600
+ const y = bar.y + midRadius * Math.sin(midAngle) + 7;
10601
+ ctx.fillStyle = chartFontColor(bar.options.backgroundColor);
10602
+ ctx.strokeStyle = chartFontColor(ctx.fillStyle);
10603
+ const value = options.callback(dataset._parsed[i]);
10604
+ ctx.strokeText(value, x, y);
10605
+ ctx.fillText(value, x, y);
10606
+ }
10607
+ break;
10608
+ }
10609
+ case "bar":
10610
+ case "line": {
10611
+ const yOffset = dataset.type === "bar" && !options.horizontal ? 0 : 3;
10612
+ for (let i = 0; i < dataset._parsed.length; i++) {
10613
+ const point = dataset.data[i];
10614
+ const value = options.horizontal ? dataset._parsed[i].x : dataset._parsed[i].y;
10615
+ const displayedValue = options.callback(value - 0);
10616
+ let xPosition = 0, yPosition = 0;
10617
+ if (options.horizontal) {
10618
+ yPosition = point.y;
10619
+ if (value < 0) {
10620
+ ctx.textAlign = "right";
10621
+ xPosition = point.x - yOffset;
10622
+ }
10623
+ else {
10624
+ ctx.textAlign = "left";
10625
+ xPosition = point.x + yOffset;
10626
+ }
10627
+ }
10628
+ else {
10629
+ xPosition = point.x;
10630
+ if (value < 0) {
10631
+ ctx.textBaseline = "top";
10632
+ yPosition = point.y + yOffset;
10633
+ }
10634
+ else {
10635
+ ctx.textBaseline = "bottom";
10636
+ yPosition = point.y - yOffset;
10637
+ }
10638
+ }
10639
+ ctx.strokeText(displayedValue, xPosition, yPosition);
10640
+ ctx.fillText(displayedValue, xPosition, yPosition);
10641
+ }
10642
+ break;
10643
+ }
10644
+ }
10645
+ });
10646
+ ctx.restore();
10647
+ },
10648
+ };
10649
+
10650
+ /** This is a chartJS plugin that will draw connector lines between the bars of a Waterfall chart */
10651
+ const waterfallLinesPlugin = {
10652
+ id: "waterfallLinesPlugin",
10653
+ beforeDraw(chart, args, options) {
10654
+ if (!options.showConnectorLines) {
10655
+ return;
10656
+ }
10657
+ // Note: private properties are not in the typing of chartJS (and some of the existing types are missing properties)
10658
+ // so we don't type anything in this file
10659
+ const drawData = chart._metasets?.[0]?.data;
10660
+ if (!drawData) {
10661
+ return;
10662
+ }
10663
+ const ctx = chart.ctx;
10664
+ ctx.save();
10665
+ ctx.setLineDash([3, 2]);
10666
+ for (let i = 0; i < drawData.length; i++) {
10667
+ const bar = drawData[i];
10668
+ if (bar.height === 0) {
10669
+ continue;
10670
+ }
10671
+ const nextBar = getNextNonEmptyBar(drawData, i);
10672
+ if (!nextBar) {
10673
+ break;
10674
+ }
10675
+ const rect = getBarElementRect(bar);
10676
+ const nextBarRect = getBarElementRect(nextBar);
10677
+ const rawBarValues = bar.$context.raw;
10678
+ const value = rawBarValues[1] - rawBarValues[0];
10679
+ const lineY = Math.round(value < 0 ? rect.bottom - 1 : rect.top);
10680
+ const lineStart = Math.round(rect.right);
10681
+ const lineEnd = Math.round(nextBarRect.left);
10682
+ ctx.strokeStyle = "#999";
10683
+ ctx.beginPath();
10684
+ ctx.moveTo(lineStart + 1, lineY + 0.5);
10685
+ ctx.lineTo(lineEnd, lineY + 0.5);
10686
+ ctx.stroke();
10687
+ }
10688
+ ctx.restore();
10689
+ },
10690
+ };
10691
+ function getBarElementRect(bar) {
10692
+ const flipped = bar.base < bar.y; // Bar are flipped for negative values in the dataset
10693
+ return {
10694
+ left: bar.x - bar.width / 2,
10695
+ right: bar.x + bar.width / 2,
10696
+ bottom: flipped ? bar.base + bar.height : bar.y + bar.height,
10697
+ top: flipped ? bar.base : bar.y,
10698
+ };
10699
+ }
10700
+ function getNextNonEmptyBar(bars, startIndex) {
10701
+ return bars.find((bar, i) => i > startIndex && bar.height !== 0);
10702
+ }
10703
+
10704
+ window.Chart?.register(waterfallLinesPlugin);
10705
+ window.Chart?.register(chartShowValuesPlugin);
10706
+ class ChartJsComponent extends owl.Component {
10707
+ static template = "o-spreadsheet-ChartJsComponent";
10708
+ static props = {
10709
+ figure: Object,
10710
+ };
10711
+ canvas = owl.useRef("graphContainer");
10712
+ chart;
10713
+ currentRuntime;
10714
+ get background() {
10715
+ return this.chartRuntime.background;
10716
+ }
10717
+ get canvasStyle() {
10718
+ return `background-color: ${this.background}`;
10719
+ }
10720
+ get chartRuntime() {
10721
+ const runtime = this.env.model.getters.getChartRuntime(this.props.figure.id);
10722
+ if (!("chartJsConfig" in runtime)) {
10723
+ throw new Error("Unsupported chart runtime");
10724
+ }
10725
+ return runtime;
10726
+ }
10727
+ setup() {
10728
+ owl.onMounted(() => {
10729
+ const runtime = this.chartRuntime;
10730
+ this.currentRuntime = runtime;
10731
+ // Note: chartJS modify the runtime in place, so it's important to give it a copy
10732
+ this.createChart(deepCopy(runtime.chartJsConfig));
10733
+ });
10734
+ owl.onWillUnmount(() => this.chart?.destroy());
10735
+ owl.useEffect(() => {
10736
+ const runtime = this.chartRuntime;
10737
+ if (runtime !== this.currentRuntime) {
10738
+ if (runtime.chartJsConfig.type !== this.currentRuntime.chartJsConfig.type) {
10739
+ this.chart?.destroy();
10740
+ this.createChart(deepCopy(runtime.chartJsConfig));
10741
+ }
10742
+ else {
10743
+ this.updateChartJs(deepCopy(runtime));
10744
+ }
10745
+ this.currentRuntime = runtime;
10746
+ }
10747
+ });
10748
+ }
10749
+ createChart(chartData) {
10750
+ const canvas = this.canvas.el;
10751
+ const ctx = canvas.getContext("2d");
10752
+ this.chart = new window.Chart(ctx, chartData);
10753
+ }
10754
+ updateChartJs(chartRuntime) {
10755
+ const chartData = chartRuntime.chartJsConfig;
10756
+ if (chartData.data && chartData.data.datasets) {
10757
+ this.chart.data = chartData.data;
10758
+ if (chartData.options?.plugins?.title) {
10759
+ this.chart.config.options.plugins.title = chartData.options.plugins.title;
10760
+ }
10761
+ }
10762
+ else {
10763
+ this.chart.data.datasets = [];
10764
+ }
10765
+ this.chart.config.options = chartData.options;
10766
+ this.chart.update();
10767
+ }
10768
+ }
10769
+
10770
+ /**
10771
+ * AbstractChart is the class from which every Chart should inherit.
10772
+ * The role of this class is to maintain the state of each chart.
10773
+ */
10774
+ class AbstractChart {
10775
+ sheetId;
10776
+ title;
10777
+ getters;
10778
+ constructor(definition, sheetId, getters) {
10779
+ this.title = definition.title;
10780
+ this.sheetId = sheetId;
10781
+ this.getters = getters;
10782
+ }
10783
+ /**
10784
+ * Validate the chart definition given as arguments. This function will be
10785
+ * called from allowDispatch function
10786
+ */
10787
+ static validateChartDefinition(validator, definition) {
10788
+ throw new Error("This method should be implemented by sub class");
10789
+ }
10790
+ /**
10791
+ * Get a new chart definition transformed with the executed command. This
10792
+ * functions will be called during operational transform process
10793
+ */
10794
+ static transformDefinition(definition, executed) {
10795
+ throw new Error("This method should be implemented by sub class");
10796
+ }
10797
+ /**
10798
+ * Get an empty definition based on the given context
10799
+ */
10800
+ static getDefinitionFromContextCreation(context) {
10801
+ throw new Error("This method should be implemented by sub class");
10802
+ }
10803
+ }
10804
+
10809
10805
  function getBaselineText(baseline, keyValue, baselineMode, humanize, locale) {
10810
10806
  if (!baseline) {
10811
10807
  return "";
@@ -18844,7 +18840,7 @@ const INDEX = {
18844
18840
  isExported: true,
18845
18841
  };
18846
18842
  // -----------------------------------------------------------------------------
18847
- // INDEX
18843
+ // INDIRECT
18848
18844
  // -----------------------------------------------------------------------------
18849
18845
  const INDIRECT = {
18850
18846
  description: _t("Returns the content of a cell, specified by a string."),
@@ -19182,7 +19178,7 @@ const PIVOT = {
19182
19178
  arg("include_column_titles (boolean, default=TRUE)", _t("Whether to include the column titles or not.")),
19183
19179
  arg("column_count (number, optional)", _t("number of columns")),
19184
19180
  ],
19185
- compute: function (pivotFormulaId, rowCount = { value: Number.MAX_VALUE }, includeTotal = { value: true }, includeColumnHeaders = { value: true }, columnCount = { value: Number.MAX_VALUE }) {
19181
+ compute: function (pivotFormulaId, rowCount = { value: 10000 }, includeTotal = { value: true }, includeColumnHeaders = { value: true }, columnCount = { value: Number.MAX_VALUE }) {
19186
19182
  const _pivotFormulaId = toString(pivotFormulaId);
19187
19183
  const _rowCount = toNumber(rowCount, this.locale);
19188
19184
  if (_rowCount < 0) {
@@ -19239,6 +19235,79 @@ const PIVOT = {
19239
19235
  return result;
19240
19236
  },
19241
19237
  };
19238
+ //--------------------------------------------------------------------------
19239
+ // OFFSET
19240
+ //--------------------------------------------------------------------------
19241
+ const OFFSET = {
19242
+ description: _t("Returns a range reference shifted by a specified number of rows and columns from a starting cell reference."),
19243
+ args: [
19244
+ arg("cell_reference (meta)", _t("The starting point from which to count the offset rows and columns.")),
19245
+ arg("offset_rows (number)", _t("The number of rows to offset by.")),
19246
+ arg("offset_columns (number)", _t("The number of columns to offset by.")),
19247
+ arg("height (number, default='height of cell_reference')", _t("The number of rows of the range to return starting at the offset target.")),
19248
+ arg("width (number, default='width of cell_reference')", _t("The number of columns of the range to return starting at the offset target.")),
19249
+ ],
19250
+ compute: function (cellReference, offsetRows, offsetColumns, height, width) {
19251
+ if (isEvaluationError(cellReference?.value)) {
19252
+ return cellReference;
19253
+ }
19254
+ const _cellReference = cellReference?.value;
19255
+ if (!_cellReference) {
19256
+ throw new Error("In this context, the function OFFSET needs to have a cell or range in parameter.");
19257
+ }
19258
+ const zone = toZone(_cellReference);
19259
+ let offsetHeight = zone.bottom - zone.top + 1;
19260
+ let offsetWidth = zone.right - zone.left + 1;
19261
+ if (height) {
19262
+ const _height = toNumber(height, this.locale);
19263
+ assertPositive(_t("Height value is %(_height)s. It should be greater than or equal to 1.", { _height }), _height);
19264
+ offsetHeight = _height;
19265
+ }
19266
+ if (width) {
19267
+ const _width = toNumber(width, this.locale);
19268
+ assertPositive(_t("Width value is %(_width)s. It should be greater than or equal to 1.", { _width }), _width);
19269
+ offsetWidth = _width;
19270
+ }
19271
+ const { sheetName } = splitReference(_cellReference);
19272
+ const sheetId = (sheetName && this.getters.getSheetIdByName(sheetName)) || this.getters.getActiveSheetId();
19273
+ const _offsetRows = toNumber(offsetRows, this.locale);
19274
+ const _offsetColumns = toNumber(offsetColumns, this.locale);
19275
+ let originPosition;
19276
+ const __originCellXC = this.__originCellXC?.();
19277
+ if (__originCellXC) {
19278
+ const cellZone = toZone(__originCellXC);
19279
+ originPosition = {
19280
+ sheetId: this.__originSheetId,
19281
+ col: cellZone.left,
19282
+ row: cellZone.top,
19283
+ };
19284
+ this.updateDependencies?.(originPosition);
19285
+ }
19286
+ const startingCol = zone.left + _offsetColumns;
19287
+ const startingRow = zone.top + _offsetRows;
19288
+ if (startingCol < 0 || startingRow < 0) {
19289
+ return new InvalidReferenceError(_t("OFFSET evaluates to an out of bounds range."));
19290
+ }
19291
+ const dependencyZone = {
19292
+ left: startingCol,
19293
+ top: startingRow,
19294
+ right: startingCol + offsetWidth - 1,
19295
+ bottom: startingRow + offsetHeight - 1,
19296
+ };
19297
+ const range = this.getters.getRangeFromZone(this.__originSheetId, dependencyZone);
19298
+ if (range.invalidXc || range.invalidSheetName) {
19299
+ return new InvalidReferenceError();
19300
+ }
19301
+ if (originPosition) {
19302
+ this.addDependencies?.(originPosition, [range]);
19303
+ }
19304
+ return generateMatrix(offsetWidth, offsetHeight, (col, row) => this.getters.getEvaluatedCell({
19305
+ sheetId,
19306
+ col: startingCol + col,
19307
+ row: startingRow + row,
19308
+ }));
19309
+ },
19310
+ };
19242
19311
 
19243
19312
  var lookup = /*#__PURE__*/Object.freeze({
19244
19313
  __proto__: null,
@@ -19250,6 +19319,7 @@ var lookup = /*#__PURE__*/Object.freeze({
19250
19319
  INDIRECT: INDIRECT,
19251
19320
  LOOKUP: LOOKUP,
19252
19321
  MATCH: MATCH,
19322
+ OFFSET: OFFSET,
19253
19323
  PIVOT: PIVOT,
19254
19324
  PIVOT_HEADER: PIVOT_HEADER,
19255
19325
  PIVOT_VALUE: PIVOT_VALUE,
@@ -19532,6 +19602,320 @@ var operators = /*#__PURE__*/Object.freeze({
19532
19602
  UPLUS: UPLUS
19533
19603
  });
19534
19604
 
19605
+ const transformFromFactor = (factor) => ({
19606
+ transform: (x) => x * factor,
19607
+ inverseTransform: (x) => x / factor,
19608
+ });
19609
+ const standard = { transform: (x) => x, inverseTransform: (x) => x };
19610
+ const ANG2M = 1e-10;
19611
+ const IN2M = 0.0254;
19612
+ const PICAPT2M = IN2M / 72;
19613
+ const FT2M = 0.3048;
19614
+ const YD2M = 0.9144;
19615
+ const MI2M = 1609.34;
19616
+ const NMI2M = 1852;
19617
+ const LY2M = 9.46073047258e15;
19618
+ const UNITS = {
19619
+ // WEIGHT UNITs : Standard = gramme
19620
+ g: { ...standard, category: "weight" },
19621
+ u: { ...transformFromFactor(1.66053e-24), category: "weight" },
19622
+ grain: { ...transformFromFactor(0.0647989), category: "weight" },
19623
+ ozm: { ...transformFromFactor(28.3495), category: "weight" },
19624
+ lbm: { ...transformFromFactor(453.592), category: "weight" },
19625
+ stone: { ...transformFromFactor(6350.29), category: "weight" },
19626
+ sg: { ...transformFromFactor(14593.90294), category: "weight" },
19627
+ cwt: { ...transformFromFactor(45359.237), category: "weight" },
19628
+ uk_cwt: { ...transformFromFactor(50802.3), category: "weight" },
19629
+ ton: { ...transformFromFactor(907184.74), category: "weight" },
19630
+ uk_ton: { ...transformFromFactor(1016046.9), category: "weight" },
19631
+ // DISTANCE UNITS : Standard = meter
19632
+ m: { ...standard, category: "distance" },
19633
+ km: { ...transformFromFactor(1000), category: "distance" },
19634
+ ang: { ...transformFromFactor(ANG2M), category: "distance" },
19635
+ Picapt: { ...transformFromFactor(PICAPT2M), category: "distance" },
19636
+ pica: { ...transformFromFactor(IN2M / 6), category: "distance" },
19637
+ in: { ...transformFromFactor(IN2M), category: "distance" },
19638
+ ft: { ...transformFromFactor(FT2M), category: "distance" },
19639
+ yd: { ...transformFromFactor(YD2M), category: "distance" },
19640
+ ell: { ...transformFromFactor(1.143), category: "distance" },
19641
+ mi: { ...transformFromFactor(MI2M), category: "distance" },
19642
+ survey_mi: { ...transformFromFactor(1609.34), category: "distance" },
19643
+ Nmi: { ...transformFromFactor(NMI2M), category: "distance" },
19644
+ ly: { ...transformFromFactor(LY2M), category: "distance" },
19645
+ parsec: { ...transformFromFactor(3.0856775814914e16), category: "distance" },
19646
+ // TIME UNITS : Standard = second
19647
+ sec: { ...standard, category: "time" },
19648
+ min: { ...transformFromFactor(60), category: "time" },
19649
+ hr: { ...transformFromFactor(3600), category: "time" },
19650
+ day: { ...transformFromFactor(86400), category: "time" },
19651
+ yr: { ...transformFromFactor(31556952), category: "time" },
19652
+ // PRESSURE UNITS : Standard = Pascal
19653
+ Pa: { ...standard, category: "pressure" },
19654
+ bar: { ...transformFromFactor(100000), category: "pressure" },
19655
+ mmHg: { ...transformFromFactor(133.322), category: "pressure" },
19656
+ Torr: { ...transformFromFactor(133.322), category: "pressure" },
19657
+ psi: { ...transformFromFactor(6894.76), category: "pressure" },
19658
+ atm: { ...transformFromFactor(101325), category: "pressure" },
19659
+ // FORCE UNITS : Standard = Newton
19660
+ N: { ...standard, category: "force" },
19661
+ dyn: { ...transformFromFactor(1e-5), category: "force" },
19662
+ pond: { ...transformFromFactor(0.00980665), category: "force" },
19663
+ lbf: { ...transformFromFactor(4.44822), category: "force" },
19664
+ // ENERGY UNITS : Standard = Joule
19665
+ J: { ...standard, category: "energy" },
19666
+ eV: { ...transformFromFactor(1.60218e-19), category: "energy" },
19667
+ e: { ...transformFromFactor(1e-7), category: "energy" },
19668
+ flb: { ...transformFromFactor(1.3558179483), category: "energy" },
19669
+ c: { ...transformFromFactor(4.184), category: "energy" },
19670
+ cal: { ...transformFromFactor(4.1868), category: "energy" },
19671
+ BTU: { ...transformFromFactor(1055.06), category: "energy" },
19672
+ Wh: { ...transformFromFactor(3600), category: "energy" },
19673
+ HPh: { ...transformFromFactor(2684520), category: "energy" },
19674
+ // POWER UNITS : Standard = Watt
19675
+ W: { ...standard, category: "power" },
19676
+ PS: { ...transformFromFactor(735.499), category: "power" },
19677
+ HP: { ...transformFromFactor(745.7), category: "power" },
19678
+ // MAGNETISM UNITS : Standard = Tesla
19679
+ T: { ...standard, category: "magnetism" },
19680
+ ga: { ...transformFromFactor(1e-4), category: "magnetism" },
19681
+ // TEMPERATURE UNITS : Standard = Kelvin
19682
+ K: { ...standard, category: "temperature" },
19683
+ C: {
19684
+ transform: (T) => T + 273.15,
19685
+ inverseTransform: (T) => T - 273.15,
19686
+ category: "temperature",
19687
+ },
19688
+ F: {
19689
+ transform: (T) => ((T - 32) * 5) / 9 + 273.15,
19690
+ inverseTransform: (T) => ((T - 273.15) * 9) / 5 + 32,
19691
+ category: "temperature",
19692
+ },
19693
+ Rank: { ...transformFromFactor(5 / 9), category: "temperature" },
19694
+ Reau: {
19695
+ transform: (T) => T * 1.25 + 273.15,
19696
+ inverseTransform: (T) => (T - 273.15) / 1.25,
19697
+ category: "temperature",
19698
+ },
19699
+ // VOLUME UNITS : Standard = cubic meter
19700
+ "m^3": { ...standard, category: "volume", order: 3 },
19701
+ "ang^3": { ...transformFromFactor(Math.pow(ANG2M, 3)), category: "volume", order: 3 },
19702
+ "Picapt^3": { ...transformFromFactor(Math.pow(PICAPT2M, 3)), category: "volume", order: 3 },
19703
+ tsp: { ...transformFromFactor(4.92892e-6), category: "volume" },
19704
+ tspm: { ...transformFromFactor(5e-6), category: "volume" },
19705
+ tbs: { ...transformFromFactor(1.4786764825785619e-5), category: "volume" },
19706
+ "in^3": { ...transformFromFactor(Math.pow(IN2M, 3)), category: "volume", order: 3 },
19707
+ oz: { ...transformFromFactor(2.95735295625e-5), category: "volume" },
19708
+ cup: { ...transformFromFactor(0.000237), category: "volume" },
19709
+ pt: { ...transformFromFactor(0.0004731765), category: "volume" },
19710
+ uk_pt: { ...transformFromFactor(0.000568261), category: "volume" },
19711
+ qt: { ...transformFromFactor(0.0009463529), category: "volume" },
19712
+ l: { ...transformFromFactor(1e-3), category: "volume" },
19713
+ uk_qt: { ...transformFromFactor(0.0011365225), category: "volume" },
19714
+ gal: { ...transformFromFactor(0.0037854118), category: "volume" },
19715
+ uk_gal: { ...transformFromFactor(0.00454609), category: "volume" },
19716
+ "ft^3": { ...transformFromFactor(Math.pow(FT2M, 3)), category: "volume", order: 3 },
19717
+ bushel: { ...transformFromFactor(0.0352390704), category: "volume" },
19718
+ barrel: { ...transformFromFactor(0.158987295), category: "volume" },
19719
+ "yd^3": { ...transformFromFactor(Math.pow(YD2M, 3)), category: "volume", order: 3 },
19720
+ MTON: { ...transformFromFactor(1.13267386368), category: "volume" },
19721
+ GRT: { ...transformFromFactor(2.83168), category: "volume" },
19722
+ "mi^3": { ...transformFromFactor(Math.pow(MI2M, 3)), category: "volume", order: 3 },
19723
+ "Nmi^3": { ...transformFromFactor(Math.pow(NMI2M, 3)), category: "volume", order: 3 },
19724
+ "ly^3": { ...transformFromFactor(Math.pow(LY2M, 3)), category: "volume", order: 3 },
19725
+ // AREA UNITS : Standard = square meter
19726
+ "m^2": { ...standard, category: "area", order: 2 },
19727
+ "ang^2": { ...transformFromFactor(Math.pow(ANG2M, 2)), category: "area", order: 2 },
19728
+ "Picapt^2": { ...transformFromFactor(Math.pow(PICAPT2M, 2)), category: "area", order: 2 },
19729
+ "in^2": { ...transformFromFactor(Math.pow(IN2M, 2)), category: "area", order: 2 },
19730
+ "ft^2": { ...transformFromFactor(Math.pow(FT2M, 2)), category: "area", order: 2 },
19731
+ "yd^2": { ...transformFromFactor(Math.pow(YD2M, 2)), category: "area", order: 2 },
19732
+ ar: { ...transformFromFactor(100), category: "area" },
19733
+ Morgen: { ...transformFromFactor(2500), category: "area" },
19734
+ uk_acre: { ...transformFromFactor(4046.8564224), category: "area" },
19735
+ us_acre: { ...transformFromFactor(4046.8726098743), category: "area" },
19736
+ ha: { ...transformFromFactor(1e4), category: "area" },
19737
+ "mi^2": { ...transformFromFactor(Math.pow(MI2M, 2)), category: "area", order: 2 },
19738
+ "Nmi^2": { ...transformFromFactor(Math.pow(NMI2M, 2)), category: "area", order: 2 },
19739
+ "ly^2": { ...transformFromFactor(Math.pow(LY2M, 2)), category: "area", order: 2 },
19740
+ // INFORMATION UNITS : Standard = bit
19741
+ bit: { ...standard, category: "information" },
19742
+ byte: { ...transformFromFactor(8), category: "information" },
19743
+ // SPEED UNITS : Standard = m/s
19744
+ "m/s": { ...standard, category: "speed" },
19745
+ "m/hr": { ...transformFromFactor(1 / 3600), category: "speed" },
19746
+ "km/hr": { ...transformFromFactor(1 / 3.6), category: "speed" },
19747
+ mph: { ...transformFromFactor(0.44704), category: "speed" },
19748
+ kn: { ...transformFromFactor(0.5144444444), category: "speed" },
19749
+ admkn: { ...transformFromFactor(0.5147733333), category: "speed" },
19750
+ };
19751
+ const UNITS_ALIASES = {
19752
+ shweight: "cwt",
19753
+ lcwt: "uk_cwt",
19754
+ hweight: "uk_cwt",
19755
+ LTON: "uk_ton",
19756
+ brton: "uk_ton",
19757
+ pc: "parsec",
19758
+ Pica: "Picapt",
19759
+ d: "day",
19760
+ mn: "min",
19761
+ s: "sec",
19762
+ p: "Pa",
19763
+ at: "atm",
19764
+ dy: "dyn",
19765
+ ev: "eV",
19766
+ hh: "HPh",
19767
+ wh: "Wh",
19768
+ btu: "BTU",
19769
+ h: "HP",
19770
+ cel: "C",
19771
+ fah: "F",
19772
+ kel: "K",
19773
+ us_pt: "pt",
19774
+ L: "l",
19775
+ lt: "l",
19776
+ ang3: "ang^3",
19777
+ ft3: "ft^3",
19778
+ in3: "in^3",
19779
+ ly3: "ly^3",
19780
+ m3: "m^3",
19781
+ mi3: "mi^3",
19782
+ yd3: "yd^3",
19783
+ Nmi3: "Nmi^3",
19784
+ Picapt3: "Picapt^3",
19785
+ "Pica^3": "Picapt^3",
19786
+ Pica3: "Picapt^3",
19787
+ regton: "GRT",
19788
+ ang2: "ang^2",
19789
+ ft2: "ft^2",
19790
+ in2: "in^2",
19791
+ ly2: "ly^2",
19792
+ m2: "m^2",
19793
+ mi2: "mi^2",
19794
+ Nmi2: "Nmi^2",
19795
+ Picapt2: "Picapt^2",
19796
+ "Pica^2": "Picapt^2",
19797
+ Pica2: "Picapt^2",
19798
+ yd2: "yd^2",
19799
+ "m/h": "m/hr",
19800
+ "m/sec": "m/s",
19801
+ };
19802
+ const UNIT_PREFIXES = {
19803
+ "": 1,
19804
+ Y: 1e24,
19805
+ Z: 1e21,
19806
+ E: 1e18,
19807
+ P: 1e15,
19808
+ T: 1e12,
19809
+ G: 1e9,
19810
+ M: 1e6,
19811
+ k: 1e3,
19812
+ h: 1e2,
19813
+ da: 1e1,
19814
+ e: 1e1,
19815
+ d: 1e-1,
19816
+ c: 1e-2,
19817
+ m: 1e-3,
19818
+ u: 1e-6,
19819
+ n: 1e-9,
19820
+ p: 1e-12,
19821
+ f: 1e-15,
19822
+ a: 1e-18,
19823
+ z: 1e-21,
19824
+ y: 1e-21,
19825
+ Yi: Math.pow(2, 80),
19826
+ Zi: Math.pow(2, 70),
19827
+ Ei: Math.pow(2, 60),
19828
+ Pi: Math.pow(2, 50),
19829
+ Ti: Math.pow(2, 40),
19830
+ Gi: Math.pow(2, 30),
19831
+ Mi: Math.pow(2, 20),
19832
+ ki: Math.pow(2, 10),
19833
+ };
19834
+ const TRANSLATED_CATEGORIES = {
19835
+ weight: _t("Weight"),
19836
+ distance: _t("Distance"),
19837
+ time: _t("Time"),
19838
+ pressure: _t("Pressure"),
19839
+ force: _t("Force"),
19840
+ energy: _t("Energy"),
19841
+ power: _t("Power"),
19842
+ magnetism: _t("Magnetism"),
19843
+ temperature: _t("Temperature"),
19844
+ volume: _t("Volume"),
19845
+ area: _t("Area"),
19846
+ information: _t("Information"),
19847
+ speed: _t("Speed"),
19848
+ };
19849
+ function getTranslatedCategory(key) {
19850
+ return TRANSLATED_CATEGORIES[key] ?? "";
19851
+ }
19852
+ function getTransformation(key) {
19853
+ for (const [prefix, value] of Object.entries(UNIT_PREFIXES)) {
19854
+ if (prefix && !key.startsWith(prefix))
19855
+ continue;
19856
+ const _key = key.slice(prefix.length);
19857
+ let conversion = UNITS[_key];
19858
+ if (!conversion && UNITS_ALIASES[_key]) {
19859
+ conversion = UNITS[UNITS_ALIASES[_key]];
19860
+ }
19861
+ if (conversion) {
19862
+ return {
19863
+ ...conversion,
19864
+ factor: conversion.order ? Math.pow(value, conversion.order) : value,
19865
+ };
19866
+ }
19867
+ }
19868
+ return;
19869
+ }
19870
+
19871
+ // -----------------------------------------------------------------------------
19872
+ // CONVERT
19873
+ // -----------------------------------------------------------------------------
19874
+ const CONVERT = {
19875
+ description: _t("Converts a numeric value to a different unit of measure."),
19876
+ args: [
19877
+ arg("value (number)", _t("the numeric value in start_unit to convert to end_unit")),
19878
+ arg("start_unit (string)", _t("The starting unit, the unit currently assigned to value")),
19879
+ arg("end_unit (string)", _t("The unit of measure into which to convert value")),
19880
+ ],
19881
+ compute: function (value, startUnit, endUnit) {
19882
+ const _value = toNumber(value, this.locale);
19883
+ const _startUnit = toString(startUnit);
19884
+ const _endUnit = toString(endUnit);
19885
+ const startConversion = getTransformation(_startUnit);
19886
+ const endConversion = getTransformation(_endUnit);
19887
+ if (!startConversion) {
19888
+ return {
19889
+ value: CellErrorType.GenericError,
19890
+ message: _t("Invalid units of measure ('%s')", _startUnit),
19891
+ };
19892
+ }
19893
+ if (!endConversion) {
19894
+ return {
19895
+ value: CellErrorType.GenericError,
19896
+ message: _t("Invalid units of measure ('%s')", _endUnit),
19897
+ };
19898
+ }
19899
+ if (startConversion.category !== endConversion.category) {
19900
+ return {
19901
+ value: CellErrorType.GenericError,
19902
+ message: _t("Incompatible units of measure ('%s' vs '%s')", getTranslatedCategory(startConversion.category), getTranslatedCategory(endConversion.category)),
19903
+ };
19904
+ }
19905
+ return {
19906
+ value: endConversion.inverseTransform(startConversion.factor * startConversion.transform(_value)) /
19907
+ endConversion.factor,
19908
+ format: value?.format,
19909
+ };
19910
+ },
19911
+ isExported: true,
19912
+ };
19913
+
19914
+ var parser = /*#__PURE__*/Object.freeze({
19915
+ __proto__: null,
19916
+ CONVERT: CONVERT
19917
+ });
19918
+
19535
19919
  const DEFAULT_STARTING_AT = 1;
19536
19920
  /** Regex matching all the words in a string */
19537
19921
  const wordRegex = /[A-Za-zÀ-ÖØ-öø-ÿ]+/g;
@@ -19949,6 +20333,7 @@ const categories = [
19949
20333
  { name: _t("Text"), functions: text },
19950
20334
  { name: _t("Engineering"), functions: engineering },
19951
20335
  { name: _t("Web"), functions: web },
20336
+ { name: _t("Parser"), functions: parser },
19952
20337
  ];
19953
20338
  const functionNameRegex = /^[A-Z0-9\_\.]+$/;
19954
20339
  class FunctionRegistry extends Registry {
@@ -19960,41 +20345,127 @@ class FunctionRegistry extends Registry {
19960
20345
  }
19961
20346
  const descr = addMetaInfoFromArg(addDescr);
19962
20347
  validateArguments(descr.args);
19963
- this.mapping[name] = addErrorHandling(addResultHandling(addInputHandling(descr), name), name);
20348
+ this.mapping[name] = createComputeFunction(descr, name);
19964
20349
  super.add(name, descr);
19965
20350
  return this;
19966
20351
  }
19967
20352
  }
19968
- function addInputHandling(descr) {
19969
- function computeWithInputHandling(...args) {
20353
+ const functionRegistry = new FunctionRegistry();
20354
+ for (let category of categories) {
20355
+ const fns = category.functions;
20356
+ for (let name in fns) {
20357
+ const addDescr = fns[name];
20358
+ addDescr.category = addDescr.category || category.name;
20359
+ name = name.replace(/_/g, ".");
20360
+ functionRegistry.add(name, { isExported: false, ...addDescr });
20361
+ }
20362
+ }
20363
+ const notAvailableError = new NotAvailableError(_t("Array arguments to [[FUNCTION_NAME]] are of different size."));
20364
+ function createComputeFunction(descr, functionName) {
20365
+ function runtimeCompute(...args) {
20366
+ try {
20367
+ return vectorizedCompute.apply(this, args);
20368
+ }
20369
+ catch (e) {
20370
+ return handleError(e, functionName);
20371
+ }
20372
+ }
20373
+ function vectorizedCompute(...args) {
20374
+ let countVectorizableCol = 1;
20375
+ let countVectorizableRow = 1;
20376
+ let vectorizableColLimit = Infinity;
20377
+ let vectorizableRowLimit = Infinity;
20378
+ let vectorArgsType = undefined;
20379
+ //#region Compute vectorisation limits
19970
20380
  for (let i = 0; i < args.length; i++) {
19971
20381
  const argDefinition = descr.args[descr.getArgToFocus(i + 1) - 1];
19972
20382
  const arg = args[i];
19973
20383
  if (isMatrix(arg) && !argDefinition.acceptMatrix) {
19974
- if (arg.length !== 1 || arg[0].length !== 1) {
19975
- throw new EvaluationError(_t("Function [[FUNCTION_NAME]] expects the parameter '%s' to be a single value or a single cell reference, not a range.", argDefinition.name));
20384
+ // if argDefinition does not accept a matrix but arg is still a matrix
20385
+ // --> triggers the arguments vectorization
20386
+ const nColumns = arg.length;
20387
+ const nRows = arg[0].length;
20388
+ if (nColumns !== 1 || nRows !== 1) {
20389
+ vectorArgsType ??= new Array(args.length);
20390
+ if (nColumns !== 1 && nRows !== 1) {
20391
+ vectorArgsType[i] = "matrix";
20392
+ countVectorizableCol = Math.max(countVectorizableCol, nColumns);
20393
+ countVectorizableRow = Math.max(countVectorizableRow, nRows);
20394
+ vectorizableColLimit = Math.min(vectorizableColLimit, nColumns);
20395
+ vectorizableRowLimit = Math.min(vectorizableRowLimit, nRows);
20396
+ }
20397
+ else if (nColumns !== 1) {
20398
+ vectorArgsType[i] = "horizontal";
20399
+ countVectorizableCol = Math.max(countVectorizableCol, nColumns);
20400
+ vectorizableColLimit = Math.min(vectorizableColLimit, nColumns);
20401
+ }
20402
+ else if (nRows !== 1) {
20403
+ vectorArgsType[i] = "vertical";
20404
+ countVectorizableRow = Math.max(countVectorizableRow, nRows);
20405
+ vectorizableRowLimit = Math.min(vectorizableRowLimit, nRows);
20406
+ }
20407
+ }
20408
+ else {
20409
+ args[i] = arg[0][0];
19976
20410
  }
19977
- args[i] = arg[0][0];
19978
20411
  }
19979
20412
  if (!isMatrix(arg) && argDefinition.acceptMatrixOnly) {
19980
20413
  throw new BadExpressionError(_t("Function [[FUNCTION_NAME]] expects the parameter '%s' to be reference to a cell or range.", (i + 1).toString()));
19981
20414
  }
19982
20415
  }
19983
- return descr.compute.apply(this, args);
20416
+ //#endregion
20417
+ if (countVectorizableCol === 1 && countVectorizableRow === 1) {
20418
+ // either this function is not vectorized or it ends up with a 1x1 dimension
20419
+ return computeFunctionToObject.apply(this, args);
20420
+ }
20421
+ const getArgOffset = (i, j) => args.map((arg, index) => {
20422
+ switch (vectorArgsType?.[index]) {
20423
+ case "matrix":
20424
+ return arg[i][j];
20425
+ case "horizontal":
20426
+ return arg[i][0];
20427
+ case "vertical":
20428
+ return arg[0][j];
20429
+ case undefined:
20430
+ return arg;
20431
+ }
20432
+ });
20433
+ return generateMatrix(countVectorizableCol, countVectorizableRow, (col, row) => {
20434
+ if (col > vectorizableColLimit - 1 || row > vectorizableRowLimit - 1) {
20435
+ return notAvailableError;
20436
+ }
20437
+ const singleCellComputeResult = computeFunctionToObject.apply(this, getArgOffset(col, row));
20438
+ // In the case where the user tries to vectorize arguments of an array formula, we will get an
20439
+ // array for every combination of the vectorized arguments, which will lead to a 3D matrix and
20440
+ // we won't be able to return the values.
20441
+ // In this case, we keep the first element of each spreading part, just as Excel does, and
20442
+ // create an array with these parts.
20443
+ // For exemple, we have MUNIT(x) that return an unitary matrix of x*x. If we use it with a
20444
+ // range, like MUNIT(A1:A2), we will get two unitary matrices (one for the value in A1 and one
20445
+ // for the value in A2). In this case, we will simply take the first value of each matrix and
20446
+ // return the array [First value of MUNIT(A1), First value of MUNIT(A2)].
20447
+ return isMatrix(singleCellComputeResult)
20448
+ ? singleCellComputeResult[0][0]
20449
+ : singleCellComputeResult;
20450
+ });
19984
20451
  }
19985
- return computeWithInputHandling;
19986
- }
19987
- function addErrorHandling(compute, functionName) {
19988
- return function (...args) {
19989
- try {
19990
- return compute.apply(this, args);
20452
+ function computeFunctionToObject(...args) {
20453
+ const result = descr.compute.apply(this, args);
20454
+ if (!isMatrix(result)) {
20455
+ if (typeof result === "object" && result !== null && "value" in result) {
20456
+ replaceFunctionNamePlaceholder(result, functionName);
20457
+ return result;
20458
+ }
20459
+ return { value: result };
19991
20460
  }
19992
- catch (e) {
19993
- return handleError(e, functionName);
20461
+ if (typeof result[0][0] === "object" && result[0][0] !== null && "value" in result[0][0]) {
20462
+ matrixForEach(result, (result) => replaceFunctionNamePlaceholder(result, functionName));
20463
+ return result;
19994
20464
  }
19995
- };
20465
+ return matrixMap(result, (row) => ({ value: row }));
20466
+ }
20467
+ return runtimeCompute;
19996
20468
  }
19997
- const implementationErrorMessage = _t("An unexpected error occurred. Submit a support ticket at odoo.com/help.");
19998
20469
  function handleError(e, functionName) {
19999
20470
  // the error could be an user error (instance of EvaluationError)
20000
20471
  // or a javascript error (instance of Error)
@@ -20013,42 +20484,16 @@ function hasStringValue(obj) {
20013
20484
  return (obj?.value !== undefined &&
20014
20485
  typeof obj.value === "string");
20015
20486
  }
20016
- function hasStringMessage(obj) {
20017
- return (obj?.message !== undefined &&
20018
- typeof obj.message === "string");
20019
- }
20020
- function addResultHandling(compute, functionName) {
20021
- return function computeWithResultHandling(...args) {
20022
- const result = compute.apply(this, args);
20023
- if (!isMatrix(result)) {
20024
- if (typeof result === "object" && result !== null && "value" in result) {
20025
- replaceFunctionNamePlaceholder(result, functionName);
20026
- return result;
20027
- }
20028
- return { value: result };
20029
- }
20030
- if (typeof result[0][0] === "object" && result[0][0] !== null && "value" in result[0][0]) {
20031
- matrixForEach(result, (result) => replaceFunctionNamePlaceholder(result, functionName));
20032
- return result;
20033
- }
20034
- return matrixMap(result, (row) => ({ value: row }));
20035
- };
20036
- }
20037
- function replaceFunctionNamePlaceholder(fPayload, functionName) {
20487
+ function replaceFunctionNamePlaceholder(functionResult, functionName) {
20038
20488
  // for performance reasons: change in place and only if needed
20039
- if (fPayload.message?.includes("[[FUNCTION_NAME]]")) {
20040
- fPayload.message = fPayload.message.replace("[[FUNCTION_NAME]]", functionName);
20489
+ if (functionResult.message?.includes("[[FUNCTION_NAME]]")) {
20490
+ functionResult.message = functionResult.message.replace("[[FUNCTION_NAME]]", functionName);
20041
20491
  }
20042
20492
  }
20043
- const functionRegistry = new FunctionRegistry();
20044
- for (let category of categories) {
20045
- const fns = category.functions;
20046
- for (let name in fns) {
20047
- const addDescr = fns[name];
20048
- addDescr.category = addDescr.category || category.name;
20049
- name = name.replace(/_/g, ".");
20050
- functionRegistry.add(name, { isExported: false, ...addDescr });
20051
- }
20493
+ const implementationErrorMessage = _t("An unexpected error occurred. Submit a support ticket at odoo.com/help.");
20494
+ function hasStringMessage(obj) {
20495
+ return (obj?.message !== undefined &&
20496
+ typeof obj.message === "string");
20052
20497
  }
20053
20498
 
20054
20499
  autoCompleteProviders.add("functions", {
@@ -21497,6 +21942,7 @@ function indentCode(code) {
21497
21942
 
21498
21943
  const functions$1 = functionRegistry.content;
21499
21944
  const OPERATOR_MAP = {
21945
+ // export for test
21500
21946
  "=": "EQ",
21501
21947
  "+": "ADD",
21502
21948
  "-": "MINUS",
@@ -21511,6 +21957,7 @@ const OPERATOR_MAP = {
21511
21957
  "&": "CONCATENATE",
21512
21958
  };
21513
21959
  const UNARY_OPERATOR_MAP = {
21960
+ // export for test
21514
21961
  "-": "UMINUS",
21515
21962
  "+": "UPLUS",
21516
21963
  "%": "UNARY.PERCENT",
@@ -21581,7 +22028,7 @@ function compileTokens(tokens) {
21581
22028
  }
21582
22029
  /**
21583
22030
  * This function compiles all the information extracted by the parser into an
21584
- * executable code for the evaluation of the cells content. It uses a cash to
22031
+ * executable code for the evaluation of the cells content. It uses a cache to
21585
22032
  * not reevaluate identical code structures.
21586
22033
  *
21587
22034
  * The function is sensitive to parameter “isMeta”. This
@@ -22799,8 +23246,7 @@ function truncateLabel(label) {
22799
23246
  /**
22800
23247
  * Get a default chart js configuration
22801
23248
  */
22802
- function getDefaultChartJsRuntime(chart, labels, fontColor, args) {
22803
- const { format, locale, truncateLabels, horizontalChart } = args;
23249
+ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, truncateLabels = true, horizontalChart, }) {
22804
23250
  const chartTitle = chart.title.text ? chart.title : { ...chart.title, content: "" };
22805
23251
  const options = {
22806
23252
  // https://www.chartjs.org/docs/latest/general/responsive.html
@@ -22949,14 +23395,17 @@ function getChartDatasetValues(getters, dataSets) {
22949
23395
  }
22950
23396
  return datasetValues;
22951
23397
  }
22952
- /** See https://www.chartjs.org/docs/latest/charts/area.html#filling-modes */
22953
- function getFillingMode(index) {
22954
- if (index === 0) {
23398
+ /**
23399
+ * If the chart is a stacked area chart, we want to fill until the next dataset.
23400
+ * If the chart is a simple area chart, we want to fill until the origin (bottom axis).
23401
+ *
23402
+ * See https://www.chartjs.org/docs/latest/charts/area.html#filling-modes
23403
+ */
23404
+ function getFillingMode(index, stackedChart) {
23405
+ if (!stackedChart) {
22955
23406
  return "origin";
22956
23407
  }
22957
- else {
22958
- return index - 1;
22959
- }
23408
+ return index === 0 ? "origin" : "-1";
22960
23409
  }
22961
23410
  function chartToImage(runtime, figure, type) {
22962
23411
  // wrap the canvas in a div with a fixed size because chart.js would
@@ -22973,7 +23422,6 @@ function chartToImage(runtime, figure, type) {
22973
23422
  if ("chartJsConfig" in runtime) {
22974
23423
  const config = deepCopy(runtime.chartJsConfig);
22975
23424
  config.plugins = [backgroundColorChartJSPlugin];
22976
- // @ts-ignore
22977
23425
  const chart = new window.Chart(canvas, config);
22978
23426
  const imgContent = chart.toBase64Image();
22979
23427
  chart.destroy();
@@ -23023,6 +23471,7 @@ class BarChart extends AbstractChart {
23023
23471
  dataSetDesign;
23024
23472
  axesDesign;
23025
23473
  horizontal;
23474
+ showValues;
23026
23475
  constructor(definition, sheetId, getters) {
23027
23476
  super(definition, sheetId, getters);
23028
23477
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -23035,6 +23484,7 @@ class BarChart extends AbstractChart {
23035
23484
  this.dataSetDesign = definition.dataSets;
23036
23485
  this.axesDesign = definition.axesDesign;
23037
23486
  this.horizontal = definition.horizontal;
23487
+ this.showValues = definition.showValues;
23038
23488
  }
23039
23489
  static transformDefinition(definition, executed) {
23040
23490
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -23054,6 +23504,7 @@ class BarChart extends AbstractChart {
23054
23504
  type: "bar",
23055
23505
  labelRange: context.auxiliaryRange || undefined,
23056
23506
  axesDesign: context.axesDesign,
23507
+ showValues: context.showValues,
23057
23508
  };
23058
23509
  }
23059
23510
  getContextCreation() {
@@ -23107,6 +23558,7 @@ class BarChart extends AbstractChart {
23107
23558
  aggregated: this.aggregated,
23108
23559
  axesDesign: this.axesDesign,
23109
23560
  horizontal: this.horizontal,
23561
+ showValues: this.showValues,
23110
23562
  };
23111
23563
  }
23112
23564
  getDefinitionForExcel() {
@@ -23156,22 +23608,23 @@ function getBarConfiguration(chart, labels, localeFormat) {
23156
23608
  padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
23157
23609
  };
23158
23610
  config.options.indexAxis = chart.horizontal ? "y" : "x";
23611
+ const formatCallback = (value) => {
23612
+ value = Number(value);
23613
+ if (isNaN(value))
23614
+ return value;
23615
+ const { locale, format } = localeFormat;
23616
+ return formatValue(value, {
23617
+ locale,
23618
+ format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23619
+ });
23620
+ };
23159
23621
  config.options.scales = {};
23160
23622
  const labelsAxis = { ticks: { padding: 5, color: fontColor } };
23161
23623
  const valuesAxis = {
23162
23624
  beginAtZero: true, // the origin of the y axis is always zero
23163
23625
  ticks: {
23164
23626
  color: fontColor,
23165
- callback: (value) => {
23166
- value = Number(value);
23167
- if (isNaN(value))
23168
- return value;
23169
- const { locale, format } = localeFormat;
23170
- return formatValue(value, {
23171
- locale,
23172
- format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23173
- });
23174
- },
23627
+ callback: formatCallback,
23175
23628
  },
23176
23629
  };
23177
23630
  const xAxis = chart.horizontal ? valuesAxis : labelsAxis;
@@ -23204,6 +23657,12 @@ function getBarConfiguration(chart, labels, localeFormat) {
23204
23657
  config.options.scales.y1.stacked = true;
23205
23658
  }
23206
23659
  }
23660
+ config.options.plugins.chartShowValuesPlugin = {
23661
+ showValues: chart.showValues,
23662
+ background: chart.background,
23663
+ horizontal: chart.horizontal,
23664
+ callback: formatCallback,
23665
+ };
23207
23666
  return config;
23208
23667
  }
23209
23668
  function createBarChartRuntime(chart, getters) {
@@ -23251,467 +23710,6 @@ function createBarChartRuntime(chart, getters) {
23251
23710
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
23252
23711
  }
23253
23712
 
23254
- class ComboChart extends AbstractChart {
23255
- dataSets;
23256
- labelRange;
23257
- background;
23258
- legendPosition;
23259
- aggregated;
23260
- dataSetsHaveTitle;
23261
- dataSetDesign;
23262
- axesDesign;
23263
- type = "combo";
23264
- constructor(definition, sheetId, getters) {
23265
- super(definition, sheetId, getters);
23266
- this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
23267
- this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
23268
- this.background = definition.background;
23269
- this.legendPosition = definition.legendPosition;
23270
- this.aggregated = definition.aggregated;
23271
- this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
23272
- this.dataSetDesign = definition.dataSets;
23273
- this.axesDesign = definition.axesDesign;
23274
- }
23275
- static transformDefinition(definition, executed) {
23276
- return transformChartDefinitionWithDataSetsWithZone(definition, executed);
23277
- }
23278
- static validateChartDefinition(validator, definition) {
23279
- return validator.checkValidations(definition, checkDataset, checkLabelRange);
23280
- }
23281
- getContextCreation() {
23282
- const range = [];
23283
- for (const [i, dataSet] of this.dataSets.entries()) {
23284
- range.push({
23285
- ...this.dataSetDesign?.[i],
23286
- dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
23287
- });
23288
- }
23289
- return {
23290
- ...this,
23291
- range,
23292
- auxiliaryRange: this.labelRange
23293
- ? this.getters.getRangeString(this.labelRange, this.sheetId)
23294
- : undefined,
23295
- };
23296
- }
23297
- getDefinition() {
23298
- return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
23299
- }
23300
- getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
23301
- const ranges = [];
23302
- for (const [i, dataSet] of dataSets.entries()) {
23303
- ranges.push({
23304
- ...this.dataSetDesign?.[i],
23305
- dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
23306
- });
23307
- }
23308
- return {
23309
- type: "combo",
23310
- dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
23311
- background: this.background,
23312
- dataSets: ranges,
23313
- legendPosition: this.legendPosition,
23314
- labelRange: labelRange
23315
- ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
23316
- : undefined,
23317
- title: this.title,
23318
- aggregated: this.aggregated,
23319
- axesDesign: this.axesDesign,
23320
- };
23321
- }
23322
- getDefinitionForExcel() {
23323
- // Excel does not support aggregating labels
23324
- if (this.aggregated) {
23325
- return undefined;
23326
- }
23327
- const dataSets = this.dataSets
23328
- .map((ds) => toExcelDataset(this.getters, ds))
23329
- .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
23330
- const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
23331
- const definition = this.getDefinition();
23332
- return {
23333
- ...definition,
23334
- backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
23335
- fontColor: toXlsxHexColor(chartFontColor(this.background)),
23336
- dataSets,
23337
- labelRange,
23338
- verticalAxis: getDefinedAxis(definition),
23339
- };
23340
- }
23341
- updateRanges(applyChange) {
23342
- const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
23343
- if (!isStale) {
23344
- return this;
23345
- }
23346
- const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
23347
- return new ComboChart(definition, this.sheetId, this.getters);
23348
- }
23349
- static getDefinitionFromContextCreation(context) {
23350
- return {
23351
- background: context.background,
23352
- dataSets: context.range ?? [],
23353
- dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
23354
- aggregated: context.aggregated,
23355
- legendPosition: context.legendPosition ?? "top",
23356
- title: context.title || { text: "" },
23357
- labelRange: context.auxiliaryRange || undefined,
23358
- type: "combo",
23359
- axesDesign: context.axesDesign,
23360
- };
23361
- }
23362
- copyForSheetId(sheetId) {
23363
- const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
23364
- const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
23365
- const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
23366
- return new ComboChart(definition, sheetId, this.getters);
23367
- }
23368
- copyInSheetId(sheetId) {
23369
- const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
23370
- return new ComboChart(definition, sheetId, this.getters);
23371
- }
23372
- }
23373
- function createComboChartRuntime(chart, getters) {
23374
- const mainDataSetFormat = chart.dataSets.length
23375
- ? getChartDatasetFormat(getters, [chart.dataSets[0]])
23376
- : undefined;
23377
- const lineDataSetsFormat = getChartDatasetFormat(getters, chart.dataSets.slice(1));
23378
- const locale = getters.getLocale();
23379
- const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
23380
- let labels = labelValues.formattedValues;
23381
- let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
23382
- if (chart.dataSetsHaveTitle &&
23383
- dataSetsValues[0] &&
23384
- labels.length > dataSetsValues[0].data.length) {
23385
- labels.shift();
23386
- }
23387
- ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
23388
- if (chart.aggregated) {
23389
- ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
23390
- }
23391
- const localeFormat = { format: mainDataSetFormat, locale };
23392
- const fontColor = chartFontColor(chart.background);
23393
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
23394
- const legend = {
23395
- labels: { color: fontColor },
23396
- reverse: true,
23397
- };
23398
- if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === "none") {
23399
- legend.display = false;
23400
- }
23401
- else {
23402
- legend.position = chart.legendPosition;
23403
- }
23404
- config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
23405
- config.options.layout = {
23406
- padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
23407
- };
23408
- config.options.scales = {
23409
- x: {
23410
- ticks: {
23411
- padding: 5,
23412
- color: fontColor,
23413
- },
23414
- title: getChartAxisTitleRuntime(chart.axesDesign?.x),
23415
- },
23416
- };
23417
- const formatCallback = (format) => {
23418
- return (value) => {
23419
- value = Number(value);
23420
- if (isNaN(value))
23421
- return value;
23422
- const { locale } = localeFormat;
23423
- return formatValue(value, {
23424
- locale,
23425
- format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23426
- });
23427
- };
23428
- };
23429
- const leftVerticalAxis = {
23430
- beginAtZero: true, // the origin of the y axis is always zero
23431
- ticks: {
23432
- color: fontColor,
23433
- callback: formatCallback(mainDataSetFormat),
23434
- },
23435
- };
23436
- const rightVerticalAxis = {
23437
- beginAtZero: true, // the origin of the y axis is always zero
23438
- ticks: {
23439
- color: fontColor,
23440
- callback: formatCallback(lineDataSetsFormat),
23441
- },
23442
- };
23443
- const definition = chart.getDefinition();
23444
- const { useLeftAxis, useRightAxis } = getDefinedAxis(definition);
23445
- if (useLeftAxis) {
23446
- config.options.scales.y = {
23447
- ...leftVerticalAxis,
23448
- position: "left",
23449
- title: getChartAxisTitleRuntime(chart.axesDesign?.y),
23450
- };
23451
- }
23452
- if (useRightAxis) {
23453
- config.options.scales.y1 = {
23454
- ...rightVerticalAxis,
23455
- position: "right",
23456
- grid: {
23457
- display: false,
23458
- },
23459
- title: getChartAxisTitleRuntime(chart.axesDesign?.y1),
23460
- };
23461
- }
23462
- const colors = new ColorGenerator();
23463
- for (let [index, { label, data }] of dataSetsValues.entries()) {
23464
- const design = definition.dataSets[index];
23465
- const color = colors.next();
23466
- const dataset = {
23467
- label: design?.label ?? label,
23468
- data,
23469
- borderColor: design?.backgroundColor ?? color,
23470
- backgroundColor: design.backgroundColor ?? color,
23471
- yAxisID: design?.yAxisId ?? "y",
23472
- type: index === 0 ? "bar" : "line",
23473
- order: -index,
23474
- };
23475
- config.data.datasets.push(dataset);
23476
- }
23477
- return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
23478
- }
23479
-
23480
- function isDataRangeValid(definition) {
23481
- return definition.dataRange && !rangeReference.test(definition.dataRange)
23482
- ? "InvalidGaugeDataRange" /* CommandResult.InvalidGaugeDataRange */
23483
- : "Success" /* CommandResult.Success */;
23484
- }
23485
- function checkRangeLimits(check, batchValidations) {
23486
- return batchValidations((definition) => {
23487
- if (definition.sectionRule) {
23488
- return check(definition.sectionRule.rangeMin, "rangeMin");
23489
- }
23490
- return "Success" /* CommandResult.Success */;
23491
- }, (definition) => {
23492
- if (definition.sectionRule) {
23493
- return check(definition.sectionRule.rangeMax, "rangeMax");
23494
- }
23495
- return "Success" /* CommandResult.Success */;
23496
- });
23497
- }
23498
- function checkInflectionPointsValue(check, batchValidations) {
23499
- return batchValidations((definition) => {
23500
- if (definition.sectionRule) {
23501
- return check(definition.sectionRule.lowerInflectionPoint.value, "lowerInflectionPointValue");
23502
- }
23503
- return "Success" /* CommandResult.Success */;
23504
- }, (definition) => {
23505
- if (definition.sectionRule) {
23506
- return check(definition.sectionRule.upperInflectionPoint.value, "upperInflectionPointValue");
23507
- }
23508
- return "Success" /* CommandResult.Success */;
23509
- });
23510
- }
23511
- function checkRangeMinBiggerThanRangeMax(definition) {
23512
- if (definition.sectionRule) {
23513
- if (Number(definition.sectionRule.rangeMin) >= Number(definition.sectionRule.rangeMax)) {
23514
- return "GaugeRangeMinBiggerThanRangeMax" /* CommandResult.GaugeRangeMinBiggerThanRangeMax */;
23515
- }
23516
- }
23517
- return "Success" /* CommandResult.Success */;
23518
- }
23519
- function checkEmpty(value, valueName) {
23520
- if (value === "") {
23521
- switch (valueName) {
23522
- case "rangeMin":
23523
- return "EmptyGaugeRangeMin" /* CommandResult.EmptyGaugeRangeMin */;
23524
- case "rangeMax":
23525
- return "EmptyGaugeRangeMax" /* CommandResult.EmptyGaugeRangeMax */;
23526
- }
23527
- }
23528
- return "Success" /* CommandResult.Success */;
23529
- }
23530
- function checkNaN(value, valueName) {
23531
- if (isNaN(value)) {
23532
- switch (valueName) {
23533
- case "rangeMin":
23534
- return "GaugeRangeMinNaN" /* CommandResult.GaugeRangeMinNaN */;
23535
- case "rangeMax":
23536
- return "GaugeRangeMaxNaN" /* CommandResult.GaugeRangeMaxNaN */;
23537
- case "lowerInflectionPointValue":
23538
- return "GaugeLowerInflectionPointNaN" /* CommandResult.GaugeLowerInflectionPointNaN */;
23539
- case "upperInflectionPointValue":
23540
- return "GaugeUpperInflectionPointNaN" /* CommandResult.GaugeUpperInflectionPointNaN */;
23541
- }
23542
- }
23543
- return "Success" /* CommandResult.Success */;
23544
- }
23545
- class GaugeChart extends AbstractChart {
23546
- dataRange;
23547
- sectionRule;
23548
- background;
23549
- type = "gauge";
23550
- constructor(definition, sheetId, getters) {
23551
- super(definition, sheetId, getters);
23552
- this.dataRange = createValidRange(this.getters, this.sheetId, definition.dataRange);
23553
- this.sectionRule = definition.sectionRule;
23554
- this.background = definition.background;
23555
- }
23556
- static validateChartDefinition(validator, definition) {
23557
- return validator.checkValidations(definition, isDataRangeValid, validator.chainValidations(checkRangeLimits(checkEmpty, validator.batchValidations), checkRangeLimits(checkNaN, validator.batchValidations), checkRangeMinBiggerThanRangeMax), validator.chainValidations(checkInflectionPointsValue(checkNaN, validator.batchValidations)));
23558
- }
23559
- static transformDefinition(definition, executed) {
23560
- let dataRangeZone;
23561
- if (definition.dataRange) {
23562
- dataRangeZone = transformZone(toUnboundedZone(definition.dataRange), executed);
23563
- }
23564
- return {
23565
- ...definition,
23566
- dataRange: dataRangeZone ? zoneToXc(dataRangeZone) : undefined,
23567
- };
23568
- }
23569
- static getDefinitionFromContextCreation(context) {
23570
- return {
23571
- background: context.background,
23572
- title: context.title || { text: "" },
23573
- type: "gauge",
23574
- dataRange: context.range ? context.range[0].dataRange : undefined,
23575
- sectionRule: {
23576
- colors: {
23577
- lowerColor: DEFAULT_GAUGE_LOWER_COLOR,
23578
- middleColor: DEFAULT_GAUGE_MIDDLE_COLOR,
23579
- upperColor: DEFAULT_GAUGE_UPPER_COLOR,
23580
- },
23581
- rangeMin: "0",
23582
- rangeMax: "100",
23583
- lowerInflectionPoint: {
23584
- type: "percentage",
23585
- value: "15",
23586
- },
23587
- upperInflectionPoint: {
23588
- type: "percentage",
23589
- value: "40",
23590
- },
23591
- },
23592
- };
23593
- }
23594
- copyForSheetId(sheetId) {
23595
- const dataRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.dataRange);
23596
- const definition = this.getDefinitionWithSpecificRanges(dataRange, sheetId);
23597
- return new GaugeChart(definition, sheetId, this.getters);
23598
- }
23599
- copyInSheetId(sheetId) {
23600
- const definition = this.getDefinitionWithSpecificRanges(this.dataRange, sheetId);
23601
- return new GaugeChart(definition, sheetId, this.getters);
23602
- }
23603
- getDefinition() {
23604
- return this.getDefinitionWithSpecificRanges(this.dataRange);
23605
- }
23606
- getDefinitionWithSpecificRanges(dataRange, targetSheetId) {
23607
- return {
23608
- background: this.background,
23609
- sectionRule: this.sectionRule,
23610
- title: this.title,
23611
- type: "gauge",
23612
- dataRange: dataRange
23613
- ? this.getters.getRangeString(dataRange, targetSheetId || this.sheetId)
23614
- : undefined,
23615
- };
23616
- }
23617
- getDefinitionForExcel() {
23618
- // This kind of graph is not exportable in Excel
23619
- return undefined;
23620
- }
23621
- getContextCreation() {
23622
- return {
23623
- ...this,
23624
- range: this.dataRange
23625
- ? [{ dataRange: this.getters.getRangeString(this.dataRange, this.sheetId) }]
23626
- : undefined,
23627
- };
23628
- }
23629
- updateRanges(applyChange) {
23630
- const range = adaptChartRange(this.dataRange, applyChange);
23631
- if (this.dataRange === range) {
23632
- return this;
23633
- }
23634
- const definition = this.getDefinitionWithSpecificRanges(range);
23635
- return new GaugeChart(definition, this.sheetId, this.getters);
23636
- }
23637
- }
23638
- function createGaugeChartRuntime(chart, getters) {
23639
- const locale = getters.getLocale();
23640
- const chartColors = chart.sectionRule.colors;
23641
- let gaugeValue = undefined;
23642
- let formattedValue = undefined;
23643
- let format = undefined;
23644
- const dataRange = chart.dataRange;
23645
- if (dataRange !== undefined) {
23646
- const cell = getters.getEvaluatedCell({
23647
- sheetId: dataRange.sheetId,
23648
- col: dataRange.zone.left,
23649
- row: dataRange.zone.top,
23650
- });
23651
- if (cell.type === CellValueType.number) {
23652
- gaugeValue = cell.value;
23653
- formattedValue = cell.formattedValue;
23654
- format = cell.format;
23655
- }
23656
- }
23657
- const minValue = Number(chart.sectionRule.rangeMin);
23658
- const maxValue = Number(chart.sectionRule.rangeMax);
23659
- const lowerPoint = chart.sectionRule.lowerInflectionPoint;
23660
- const upperPoint = chart.sectionRule.upperInflectionPoint;
23661
- const lowerPointValue = getSectionThresholdValue(lowerPoint, minValue, maxValue);
23662
- const upperPointValue = getSectionThresholdValue(upperPoint, minValue, maxValue);
23663
- const inflectionValues = [];
23664
- const colors = [];
23665
- if (lowerPointValue !== undefined) {
23666
- inflectionValues.push({
23667
- value: lowerPointValue,
23668
- label: formatValue(lowerPointValue, { locale, format }),
23669
- });
23670
- colors.push(chartColors.lowerColor);
23671
- }
23672
- if (upperPointValue !== undefined && upperPointValue !== lowerPointValue) {
23673
- inflectionValues.push({
23674
- value: upperPointValue,
23675
- label: formatValue(upperPointValue, { locale, format }),
23676
- });
23677
- colors.push(chartColors.middleColor);
23678
- }
23679
- if (upperPointValue !== undefined &&
23680
- lowerPointValue !== undefined &&
23681
- lowerPointValue > upperPointValue) {
23682
- inflectionValues.reverse();
23683
- colors.reverse();
23684
- }
23685
- colors.push(chartColors.upperColor);
23686
- return {
23687
- background: getters.getStyleOfSingleCellChart(chart.background, dataRange).background,
23688
- title: chart.title ?? { text: "" },
23689
- minValue: {
23690
- value: minValue,
23691
- label: formatValue(minValue, { locale, format }),
23692
- },
23693
- maxValue: {
23694
- value: maxValue,
23695
- label: formatValue(maxValue, { locale, format }),
23696
- },
23697
- gaugeValue: gaugeValue !== undefined && formattedValue
23698
- ? { value: gaugeValue, label: formattedValue }
23699
- : undefined,
23700
- inflectionValues,
23701
- colors,
23702
- };
23703
- }
23704
- function getSectionThresholdValue(threshold, minValue, maxValue) {
23705
- if (threshold.value === "" || isNaN(Number(threshold.value))) {
23706
- return undefined;
23707
- }
23708
- const numberValue = Number(threshold.value);
23709
- const value = threshold.type === "number"
23710
- ? numberValue
23711
- : minValue + ((maxValue - minValue) * numberValue) / 100;
23712
- return clip(value, minValue, maxValue);
23713
- }
23714
-
23715
23713
  const UNIT_LENGTH = {
23716
23714
  second: 1000,
23717
23715
  minute: 1000 * 60,
@@ -23894,13 +23892,11 @@ function canBeLinearChart(labelRange, getters) {
23894
23892
  }
23895
23893
  let missingTimeAdapterAlreadyWarned = false;
23896
23894
  function isLuxonTimeAdapterInstalled() {
23897
- // @ts-ignore
23898
23895
  if (!window.Chart) {
23899
23896
  return false;
23900
23897
  }
23901
23898
  // @ts-ignore
23902
23899
  const adapter = new window.Chart._adapters._date({});
23903
- // @ts-ignore
23904
23900
  const isInstalled = adapter._id === "luxon";
23905
23901
  if (!isInstalled && !missingTimeAdapterAlreadyWarned) {
23906
23902
  missingTimeAdapterAlreadyWarned = true;
@@ -23917,9 +23913,7 @@ function getLineOrScatterConfiguration(chart, labels, options) {
23917
23913
  generateLabels(chart) {
23918
23914
  // color the legend labels with the dataset color, without any transparency
23919
23915
  const { data } = chart;
23920
- /** @ts-ignore */
23921
- const labels = window.Chart.defaults.plugins.legend.labels
23922
- .generateLabels(chart);
23916
+ const labels = window.Chart.defaults.plugins.legend.labels.generateLabels(chart);
23923
23917
  for (const [index, label] of labels.entries()) {
23924
23918
  label.fillStyle = data.datasets[index].borderColor;
23925
23919
  }
@@ -23946,151 +23940,628 @@ function getLineOrScatterConfiguration(chart, labels, options) {
23946
23940
  title: getChartAxisTitleRuntime(chart.axesDesign?.x),
23947
23941
  },
23948
23942
  };
23949
- const yAxis = {
23943
+ const formatCallback = (value) => {
23944
+ value = Number(value);
23945
+ if (isNaN(value))
23946
+ return value;
23947
+ const { locale, format } = options;
23948
+ return formatValue(value, {
23949
+ locale,
23950
+ format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23951
+ });
23952
+ };
23953
+ const yAxis = {
23954
+ beginAtZero: true, // the origin of the y axis is always zero
23955
+ ticks: {
23956
+ color: fontColor,
23957
+ callback: formatCallback,
23958
+ },
23959
+ };
23960
+ const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
23961
+ if (useLeftAxis) {
23962
+ config.options.scales.y = {
23963
+ ...yAxis,
23964
+ position: "left",
23965
+ title: getChartAxisTitleRuntime(chart.axesDesign?.y),
23966
+ };
23967
+ }
23968
+ if (useRightAxis) {
23969
+ config.options.scales.y1 = {
23970
+ ...yAxis,
23971
+ position: "right",
23972
+ title: getChartAxisTitleRuntime(chart.axesDesign?.y1),
23973
+ };
23974
+ }
23975
+ if ("stacked" in chart && chart.stacked) {
23976
+ if (useLeftAxis) {
23977
+ // @ts-ignore chart.js type is broken
23978
+ config.options.scales.y.stacked = true;
23979
+ }
23980
+ if (useRightAxis) {
23981
+ // @ts-ignore chart.js type is broken
23982
+ config.options.scales.y1.stacked = true;
23983
+ }
23984
+ }
23985
+ config.options.plugins.chartShowValuesPlugin = {
23986
+ showValues: chart.showValues,
23987
+ background: chart.background,
23988
+ callback: formatCallback,
23989
+ };
23990
+ return config;
23991
+ }
23992
+ function createLineOrScatterChartRuntime(chart, getters) {
23993
+ const axisType = getChartAxisType(chart, getters);
23994
+ const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
23995
+ let labels = axisType === "linear" ? labelValues.values : labelValues.formattedValues;
23996
+ let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
23997
+ if (chart.dataSetsHaveTitle &&
23998
+ dataSetsValues[0] &&
23999
+ labels.length > dataSetsValues[0].data.length) {
24000
+ labels.shift();
24001
+ }
24002
+ ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
24003
+ if (axisType === "time") {
24004
+ ({ labels, dataSetsValues } = fixEmptyLabelsForDateCharts(labels, dataSetsValues));
24005
+ }
24006
+ if (chart.aggregated) {
24007
+ ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
24008
+ }
24009
+ const locale = getters.getLocale();
24010
+ const truncateLabels = axisType === "category";
24011
+ const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
24012
+ const options = { format: dataSetFormat, locale, truncateLabels };
24013
+ const config = getLineOrScatterConfiguration(chart, labels, options);
24014
+ const labelFormat = getChartLabelFormat(getters, chart.labelRange);
24015
+ if (axisType === "time") {
24016
+ const axis = {
24017
+ type: "time",
24018
+ time: getChartTimeOptions(labels, labelFormat, locale),
24019
+ };
24020
+ Object.assign(config.options.scales.x, axis);
24021
+ config.options.scales.x.ticks.maxTicksLimit = 15;
24022
+ }
24023
+ else if (axisType === "linear") {
24024
+ config.options.scales.x.type = "linear";
24025
+ config.options.scales.x.ticks.callback = (value) => formatValue(value, { format: labelFormat, locale });
24026
+ config.options.plugins.tooltip.callbacks.title = (tooltipItem) => {
24027
+ return formatValue(tooltipItem[0].parsed.x || tooltipItem[0].label, {
24028
+ locale,
24029
+ format: labelFormat,
24030
+ });
24031
+ };
24032
+ }
24033
+ const areaChart = "fillArea" in chart ? chart.fillArea : false;
24034
+ const stackedChart = "stacked" in chart ? chart.stacked : false;
24035
+ const cumulative = "cumulative" in chart ? chart.cumulative : false;
24036
+ const colors = new ColorGenerator();
24037
+ const definition = chart.getDefinition();
24038
+ for (let [index, { label, data }] of dataSetsValues.entries()) {
24039
+ const color = colors.next();
24040
+ let backgroundRGBA = colorToRGBA(color);
24041
+ if (areaChart) {
24042
+ backgroundRGBA.a = LINE_FILL_TRANSPARENCY;
24043
+ }
24044
+ if (cumulative) {
24045
+ let accumulator = 0;
24046
+ data = data.map((value) => {
24047
+ if (!isNaN(value)) {
24048
+ accumulator += parseFloat(value);
24049
+ return accumulator;
24050
+ }
24051
+ return value;
24052
+ });
24053
+ }
24054
+ if (["linear", "time"].includes(axisType)) {
24055
+ // Replace empty string labels by undefined to make sure chartJS doesn't decide that "" is the same as 0
24056
+ data = data.map((y, index) => ({ x: labels[index] || undefined, y }));
24057
+ }
24058
+ const backgroundColor = rgbaToHex(backgroundRGBA);
24059
+ const dataset = {
24060
+ label,
24061
+ data,
24062
+ tension: 0, // 0 -> render straight lines, which is much faster
24063
+ borderColor: color,
24064
+ backgroundColor,
24065
+ pointBackgroundColor: color,
24066
+ fill: areaChart ? getFillingMode(index, stackedChart) : false,
24067
+ };
24068
+ config.data.datasets.push(dataset);
24069
+ }
24070
+ for (const [index, dataset] of config.data.datasets.entries()) {
24071
+ if (definition.dataSets?.[index]?.backgroundColor) {
24072
+ const color = definition.dataSets[index].backgroundColor;
24073
+ dataset.backgroundColor = color;
24074
+ dataset.borderColor = color;
24075
+ //@ts-ignore
24076
+ dataset.pointBackgroundColor = color;
24077
+ }
24078
+ if (definition.dataSets?.[index]?.label) {
24079
+ const label = definition.dataSets[index].label;
24080
+ dataset.label = label;
24081
+ }
24082
+ if (definition.dataSets?.[index]?.yAxisId) {
24083
+ dataset["yAxisID"] = definition.dataSets[index].yAxisId;
24084
+ }
24085
+ }
24086
+ return {
24087
+ chartJsConfig: config,
24088
+ background: chart.background || BACKGROUND_CHART_COLOR,
24089
+ dataSetsValues,
24090
+ labelValues,
24091
+ dataSetFormat,
24092
+ labelFormat,
24093
+ };
24094
+ }
24095
+
24096
+ class ComboChart extends AbstractChart {
24097
+ dataSets;
24098
+ labelRange;
24099
+ background;
24100
+ legendPosition;
24101
+ aggregated;
24102
+ dataSetsHaveTitle;
24103
+ dataSetDesign;
24104
+ axesDesign;
24105
+ type = "combo";
24106
+ showValues;
24107
+ constructor(definition, sheetId, getters) {
24108
+ super(definition, sheetId, getters);
24109
+ this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
24110
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
24111
+ this.background = definition.background;
24112
+ this.legendPosition = definition.legendPosition;
24113
+ this.aggregated = definition.aggregated;
24114
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24115
+ this.dataSetDesign = definition.dataSets;
24116
+ this.axesDesign = definition.axesDesign;
24117
+ this.showValues = definition.showValues;
24118
+ }
24119
+ static transformDefinition(definition, executed) {
24120
+ return transformChartDefinitionWithDataSetsWithZone(definition, executed);
24121
+ }
24122
+ static validateChartDefinition(validator, definition) {
24123
+ return validator.checkValidations(definition, checkDataset, checkLabelRange);
24124
+ }
24125
+ getContextCreation() {
24126
+ const range = [];
24127
+ for (const [i, dataSet] of this.dataSets.entries()) {
24128
+ range.push({
24129
+ ...this.dataSetDesign?.[i],
24130
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
24131
+ });
24132
+ }
24133
+ return {
24134
+ ...this,
24135
+ range,
24136
+ auxiliaryRange: this.labelRange
24137
+ ? this.getters.getRangeString(this.labelRange, this.sheetId)
24138
+ : undefined,
24139
+ };
24140
+ }
24141
+ getDefinition() {
24142
+ return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
24143
+ }
24144
+ getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
24145
+ const ranges = [];
24146
+ for (const [i, dataSet] of dataSets.entries()) {
24147
+ ranges.push({
24148
+ ...this.dataSetDesign?.[i],
24149
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
24150
+ });
24151
+ }
24152
+ return {
24153
+ type: "combo",
24154
+ dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
24155
+ background: this.background,
24156
+ dataSets: ranges,
24157
+ legendPosition: this.legendPosition,
24158
+ labelRange: labelRange
24159
+ ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
24160
+ : undefined,
24161
+ title: this.title,
24162
+ aggregated: this.aggregated,
24163
+ axesDesign: this.axesDesign,
24164
+ showValues: this.showValues,
24165
+ };
24166
+ }
24167
+ getDefinitionForExcel() {
24168
+ // Excel does not support aggregating labels
24169
+ if (this.aggregated) {
24170
+ return undefined;
24171
+ }
24172
+ const dataSets = this.dataSets
24173
+ .map((ds) => toExcelDataset(this.getters, ds))
24174
+ .filter((ds) => ds.range !== "" && ds.range !== CellErrorType.InvalidReference);
24175
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
24176
+ const definition = this.getDefinition();
24177
+ return {
24178
+ ...definition,
24179
+ backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
24180
+ fontColor: toXlsxHexColor(chartFontColor(this.background)),
24181
+ dataSets,
24182
+ labelRange,
24183
+ verticalAxis: getDefinedAxis(definition),
24184
+ };
24185
+ }
24186
+ updateRanges(applyChange) {
24187
+ const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
24188
+ if (!isStale) {
24189
+ return this;
24190
+ }
24191
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
24192
+ return new ComboChart(definition, this.sheetId, this.getters);
24193
+ }
24194
+ static getDefinitionFromContextCreation(context) {
24195
+ return {
24196
+ background: context.background,
24197
+ dataSets: context.range ?? [],
24198
+ dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
24199
+ aggregated: context.aggregated,
24200
+ legendPosition: context.legendPosition ?? "top",
24201
+ title: context.title || { text: "" },
24202
+ labelRange: context.auxiliaryRange || undefined,
24203
+ type: "combo",
24204
+ axesDesign: context.axesDesign,
24205
+ showValues: context.showValues,
24206
+ };
24207
+ }
24208
+ copyForSheetId(sheetId) {
24209
+ const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
24210
+ const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
24211
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
24212
+ return new ComboChart(definition, sheetId, this.getters);
24213
+ }
24214
+ copyInSheetId(sheetId) {
24215
+ const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
24216
+ return new ComboChart(definition, sheetId, this.getters);
24217
+ }
24218
+ }
24219
+ function createComboChartRuntime(chart, getters) {
24220
+ const mainDataSetFormat = chart.dataSets.length
24221
+ ? getChartDatasetFormat(getters, [chart.dataSets[0]])
24222
+ : undefined;
24223
+ const lineDataSetsFormat = getChartDatasetFormat(getters, chart.dataSets.slice(1));
24224
+ const locale = getters.getLocale();
24225
+ const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
24226
+ let labels = labelValues.formattedValues;
24227
+ let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
24228
+ if (chart.dataSetsHaveTitle &&
24229
+ dataSetsValues[0] &&
24230
+ labels.length > dataSetsValues[0].data.length) {
24231
+ labels.shift();
24232
+ }
24233
+ ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
24234
+ if (chart.aggregated) {
24235
+ ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
24236
+ }
24237
+ const localeFormat = { format: mainDataSetFormat, locale };
24238
+ const fontColor = chartFontColor(chart.background);
24239
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
24240
+ const legend = {
24241
+ labels: { color: fontColor },
24242
+ reverse: true,
24243
+ };
24244
+ if ((!chart.labelRange && chart.dataSets.length === 1) || chart.legendPosition === "none") {
24245
+ legend.display = false;
24246
+ }
24247
+ else {
24248
+ legend.position = chart.legendPosition;
24249
+ }
24250
+ config.options.plugins.legend = { ...config.options.plugins?.legend, ...legend };
24251
+ config.options.layout = {
24252
+ padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
24253
+ };
24254
+ config.options.scales = {
24255
+ x: {
24256
+ ticks: {
24257
+ padding: 5,
24258
+ color: fontColor,
24259
+ },
24260
+ title: getChartAxisTitleRuntime(chart.axesDesign?.x),
24261
+ },
24262
+ };
24263
+ const formatCallback = (format) => {
24264
+ return (value) => {
24265
+ value = Number(value);
24266
+ if (isNaN(value))
24267
+ return value;
24268
+ const { locale } = localeFormat;
24269
+ return formatValue(value, {
24270
+ locale,
24271
+ format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
24272
+ });
24273
+ };
24274
+ };
24275
+ const leftVerticalAxis = {
24276
+ beginAtZero: true, // the origin of the y axis is always zero
24277
+ ticks: {
24278
+ color: fontColor,
24279
+ callback: formatCallback(mainDataSetFormat),
24280
+ },
24281
+ };
24282
+ const rightVerticalAxis = {
23950
24283
  beginAtZero: true, // the origin of the y axis is always zero
23951
24284
  ticks: {
23952
24285
  color: fontColor,
23953
- callback: (value) => {
23954
- value = Number(value);
23955
- if (isNaN(value))
23956
- return value;
23957
- const { locale, format } = options;
23958
- return formatValue(value, {
23959
- locale,
23960
- format: !format && Math.abs(value) >= 1000 ? "#,##" : format,
23961
- });
23962
- },
24286
+ callback: formatCallback(lineDataSetsFormat),
23963
24287
  },
23964
24288
  };
23965
- const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
24289
+ const definition = chart.getDefinition();
24290
+ const { useLeftAxis, useRightAxis } = getDefinedAxis(definition);
23966
24291
  if (useLeftAxis) {
23967
24292
  config.options.scales.y = {
23968
- ...yAxis,
24293
+ ...leftVerticalAxis,
23969
24294
  position: "left",
23970
24295
  title: getChartAxisTitleRuntime(chart.axesDesign?.y),
23971
24296
  };
23972
24297
  }
23973
24298
  if (useRightAxis) {
23974
24299
  config.options.scales.y1 = {
23975
- ...yAxis,
24300
+ ...rightVerticalAxis,
23976
24301
  position: "right",
24302
+ grid: {
24303
+ display: false,
24304
+ },
23977
24305
  title: getChartAxisTitleRuntime(chart.axesDesign?.y1),
23978
24306
  };
23979
24307
  }
23980
- if ("stacked" in chart && chart.stacked) {
23981
- if (useLeftAxis) {
23982
- // @ts-ignore chart.js type is broken
23983
- config.options.scales.y.stacked = true;
24308
+ config.options.plugins.chartShowValuesPlugin = {
24309
+ showValues: chart.showValues,
24310
+ background: chart.background,
24311
+ callback: formatCallback(mainDataSetFormat),
24312
+ };
24313
+ const colors = new ColorGenerator();
24314
+ for (let [index, { label, data }] of dataSetsValues.entries()) {
24315
+ const design = definition.dataSets[index];
24316
+ const color = colors.next();
24317
+ const dataset = {
24318
+ label: design?.label ?? label,
24319
+ data,
24320
+ borderColor: design?.backgroundColor ?? color,
24321
+ backgroundColor: design.backgroundColor ?? color,
24322
+ yAxisID: design?.yAxisId ?? "y",
24323
+ type: index === 0 ? "bar" : "line",
24324
+ order: -index,
24325
+ };
24326
+ config.data.datasets.push(dataset);
24327
+ }
24328
+ return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
24329
+ }
24330
+
24331
+ function isDataRangeValid(definition) {
24332
+ return definition.dataRange && !rangeReference.test(definition.dataRange)
24333
+ ? "InvalidGaugeDataRange" /* CommandResult.InvalidGaugeDataRange */
24334
+ : "Success" /* CommandResult.Success */;
24335
+ }
24336
+ function checkRangeLimits(check, batchValidations) {
24337
+ return batchValidations((definition) => {
24338
+ if (definition.sectionRule) {
24339
+ return check(definition.sectionRule.rangeMin, "rangeMin");
23984
24340
  }
23985
- if (useRightAxis) {
23986
- // @ts-ignore chart.js type is broken
23987
- config.options.scales.y1.stacked = true;
24341
+ return "Success" /* CommandResult.Success */;
24342
+ }, (definition) => {
24343
+ if (definition.sectionRule) {
24344
+ return check(definition.sectionRule.rangeMax, "rangeMax");
24345
+ }
24346
+ return "Success" /* CommandResult.Success */;
24347
+ });
24348
+ }
24349
+ function checkInflectionPointsValue(check, batchValidations) {
24350
+ return batchValidations((definition) => {
24351
+ if (definition.sectionRule) {
24352
+ return check(definition.sectionRule.lowerInflectionPoint.value, "lowerInflectionPointValue");
24353
+ }
24354
+ return "Success" /* CommandResult.Success */;
24355
+ }, (definition) => {
24356
+ if (definition.sectionRule) {
24357
+ return check(definition.sectionRule.upperInflectionPoint.value, "upperInflectionPointValue");
24358
+ }
24359
+ return "Success" /* CommandResult.Success */;
24360
+ });
24361
+ }
24362
+ function checkRangeMinBiggerThanRangeMax(definition) {
24363
+ if (definition.sectionRule) {
24364
+ if (Number(definition.sectionRule.rangeMin) >= Number(definition.sectionRule.rangeMax)) {
24365
+ return "GaugeRangeMinBiggerThanRangeMax" /* CommandResult.GaugeRangeMinBiggerThanRangeMax */;
23988
24366
  }
23989
24367
  }
23990
- return config;
24368
+ return "Success" /* CommandResult.Success */;
23991
24369
  }
23992
- function createLineOrScatterChartRuntime(chart, getters) {
23993
- const axisType = getChartAxisType(chart, getters);
23994
- const labelValues = getChartLabelValues(getters, chart.dataSets, chart.labelRange);
23995
- let labels = axisType === "linear" ? labelValues.values : labelValues.formattedValues;
23996
- let dataSetsValues = getChartDatasetValues(getters, chart.dataSets);
23997
- if (chart.dataSetsHaveTitle &&
23998
- dataSetsValues[0] &&
23999
- labels.length > dataSetsValues[0].data.length) {
24000
- labels.shift();
24370
+ function checkEmpty(value, valueName) {
24371
+ if (value === "") {
24372
+ switch (valueName) {
24373
+ case "rangeMin":
24374
+ return "EmptyGaugeRangeMin" /* CommandResult.EmptyGaugeRangeMin */;
24375
+ case "rangeMax":
24376
+ return "EmptyGaugeRangeMax" /* CommandResult.EmptyGaugeRangeMax */;
24377
+ }
24001
24378
  }
24002
- ({ labels, dataSetsValues } = filterEmptyDataPoints(labels, dataSetsValues));
24003
- if (axisType === "time") {
24004
- ({ labels, dataSetsValues } = fixEmptyLabelsForDateCharts(labels, dataSetsValues));
24379
+ return "Success" /* CommandResult.Success */;
24380
+ }
24381
+ function checkNaN(value, valueName) {
24382
+ if (isNaN(value)) {
24383
+ switch (valueName) {
24384
+ case "rangeMin":
24385
+ return "GaugeRangeMinNaN" /* CommandResult.GaugeRangeMinNaN */;
24386
+ case "rangeMax":
24387
+ return "GaugeRangeMaxNaN" /* CommandResult.GaugeRangeMaxNaN */;
24388
+ case "lowerInflectionPointValue":
24389
+ return "GaugeLowerInflectionPointNaN" /* CommandResult.GaugeLowerInflectionPointNaN */;
24390
+ case "upperInflectionPointValue":
24391
+ return "GaugeUpperInflectionPointNaN" /* CommandResult.GaugeUpperInflectionPointNaN */;
24392
+ }
24005
24393
  }
24006
- if (chart.aggregated) {
24007
- ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
24394
+ return "Success" /* CommandResult.Success */;
24395
+ }
24396
+ class GaugeChart extends AbstractChart {
24397
+ dataRange;
24398
+ sectionRule;
24399
+ background;
24400
+ type = "gauge";
24401
+ constructor(definition, sheetId, getters) {
24402
+ super(definition, sheetId, getters);
24403
+ this.dataRange = createValidRange(this.getters, this.sheetId, definition.dataRange);
24404
+ this.sectionRule = definition.sectionRule;
24405
+ this.background = definition.background;
24008
24406
  }
24009
- const locale = getters.getLocale();
24010
- const truncateLabels = axisType === "category";
24011
- const dataSetFormat = getChartDatasetFormat(getters, chart.dataSets);
24012
- const options = { format: dataSetFormat, locale, truncateLabels };
24013
- const config = getLineOrScatterConfiguration(chart, labels, options);
24014
- const labelFormat = getChartLabelFormat(getters, chart.labelRange);
24015
- if (axisType === "time") {
24016
- const axis = {
24017
- type: "time",
24018
- time: getChartTimeOptions(labels, labelFormat, locale),
24407
+ static validateChartDefinition(validator, definition) {
24408
+ return validator.checkValidations(definition, isDataRangeValid, validator.chainValidations(checkRangeLimits(checkEmpty, validator.batchValidations), checkRangeLimits(checkNaN, validator.batchValidations), checkRangeMinBiggerThanRangeMax), validator.chainValidations(checkInflectionPointsValue(checkNaN, validator.batchValidations)));
24409
+ }
24410
+ static transformDefinition(definition, executed) {
24411
+ let dataRangeZone;
24412
+ if (definition.dataRange) {
24413
+ dataRangeZone = transformZone(toUnboundedZone(definition.dataRange), executed);
24414
+ }
24415
+ return {
24416
+ ...definition,
24417
+ dataRange: dataRangeZone ? zoneToXc(dataRangeZone) : undefined,
24019
24418
  };
24020
- Object.assign(config.options.scales.x, axis);
24021
- config.options.scales.x.ticks.maxTicksLimit = 15;
24022
24419
  }
24023
- else if (axisType === "linear") {
24024
- config.options.scales.x.type = "linear";
24025
- config.options.scales.x.ticks.callback = (value) => formatValue(value, { format: labelFormat, locale });
24026
- config.options.plugins.tooltip.callbacks.title = (tooltipItem) => {
24027
- return formatValue(tooltipItem[0].parsed.x || tooltipItem[0].label, {
24028
- locale,
24029
- format: labelFormat,
24030
- });
24420
+ static getDefinitionFromContextCreation(context) {
24421
+ return {
24422
+ background: context.background,
24423
+ title: context.title || { text: "" },
24424
+ type: "gauge",
24425
+ dataRange: context.range ? context.range[0].dataRange : undefined,
24426
+ sectionRule: {
24427
+ colors: {
24428
+ lowerColor: DEFAULT_GAUGE_LOWER_COLOR,
24429
+ middleColor: DEFAULT_GAUGE_MIDDLE_COLOR,
24430
+ upperColor: DEFAULT_GAUGE_UPPER_COLOR,
24431
+ },
24432
+ rangeMin: "0",
24433
+ rangeMax: "100",
24434
+ lowerInflectionPoint: {
24435
+ type: "percentage",
24436
+ value: "15",
24437
+ },
24438
+ upperInflectionPoint: {
24439
+ type: "percentage",
24440
+ value: "40",
24441
+ },
24442
+ },
24031
24443
  };
24032
24444
  }
24033
- const stacked = "stacked" in chart ? chart.stacked : false;
24034
- const cumulative = "cumulative" in chart ? chart.cumulative : false;
24035
- const colors = new ColorGenerator();
24036
- const definition = chart.getDefinition();
24037
- for (let [index, { label, data }] of dataSetsValues.entries()) {
24038
- if (["linear", "time"].includes(axisType)) {
24039
- // Replace empty string labels by undefined to make sure chartJS doesn't decide that "" is the same as 0
24040
- data = data.map((y, index) => ({ x: labels[index] || undefined, y }));
24041
- }
24042
- const color = colors.next();
24043
- let backgroundRGBA = colorToRGBA(color);
24044
- if (stacked) {
24045
- backgroundRGBA.a = LINE_FILL_TRANSPARENCY;
24046
- }
24047
- if (cumulative) {
24048
- let accumulator = 0;
24049
- data = data.map((value) => {
24050
- if (!isNaN(value)) {
24051
- accumulator += parseFloat(value);
24052
- return accumulator;
24053
- }
24054
- return value;
24055
- });
24056
- }
24057
- const backgroundColor = rgbaToHex(backgroundRGBA);
24058
- const dataset = {
24059
- label,
24060
- data,
24061
- tension: 0, // 0 -> render straight lines, which is much faster
24062
- borderColor: color,
24063
- backgroundColor,
24064
- pointBackgroundColor: color,
24065
- fill: stacked ? getFillingMode(index) : false,
24445
+ copyForSheetId(sheetId) {
24446
+ const dataRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.dataRange);
24447
+ const definition = this.getDefinitionWithSpecificRanges(dataRange, sheetId);
24448
+ return new GaugeChart(definition, sheetId, this.getters);
24449
+ }
24450
+ copyInSheetId(sheetId) {
24451
+ const definition = this.getDefinitionWithSpecificRanges(this.dataRange, sheetId);
24452
+ return new GaugeChart(definition, sheetId, this.getters);
24453
+ }
24454
+ getDefinition() {
24455
+ return this.getDefinitionWithSpecificRanges(this.dataRange);
24456
+ }
24457
+ getDefinitionWithSpecificRanges(dataRange, targetSheetId) {
24458
+ return {
24459
+ background: this.background,
24460
+ sectionRule: this.sectionRule,
24461
+ title: this.title,
24462
+ type: "gauge",
24463
+ dataRange: dataRange
24464
+ ? this.getters.getRangeString(dataRange, targetSheetId || this.sheetId)
24465
+ : undefined,
24066
24466
  };
24067
- config.data.datasets.push(dataset);
24068
24467
  }
24069
- for (const [index, dataset] of config.data.datasets.entries()) {
24070
- if (definition.dataSets?.[index]?.backgroundColor) {
24071
- const color = definition.dataSets[index].backgroundColor;
24072
- dataset.backgroundColor = color;
24073
- dataset.borderColor = color;
24074
- //@ts-ignore
24075
- dataset.pointBackgroundColor = color;
24076
- }
24077
- if (definition.dataSets?.[index]?.label) {
24078
- const label = definition.dataSets[index].label;
24079
- dataset.label = label;
24468
+ getDefinitionForExcel() {
24469
+ // This kind of graph is not exportable in Excel
24470
+ return undefined;
24471
+ }
24472
+ getContextCreation() {
24473
+ return {
24474
+ ...this,
24475
+ range: this.dataRange
24476
+ ? [{ dataRange: this.getters.getRangeString(this.dataRange, this.sheetId) }]
24477
+ : undefined,
24478
+ };
24479
+ }
24480
+ updateRanges(applyChange) {
24481
+ const range = adaptChartRange(this.dataRange, applyChange);
24482
+ if (this.dataRange === range) {
24483
+ return this;
24080
24484
  }
24081
- if (definition.dataSets?.[index]?.yAxisId) {
24082
- dataset["yAxisID"] = definition.dataSets[index].yAxisId;
24485
+ const definition = this.getDefinitionWithSpecificRanges(range);
24486
+ return new GaugeChart(definition, this.sheetId, this.getters);
24487
+ }
24488
+ }
24489
+ function createGaugeChartRuntime(chart, getters) {
24490
+ const locale = getters.getLocale();
24491
+ const chartColors = chart.sectionRule.colors;
24492
+ let gaugeValue = undefined;
24493
+ let formattedValue = undefined;
24494
+ let format = undefined;
24495
+ const dataRange = chart.dataRange;
24496
+ if (dataRange !== undefined) {
24497
+ const cell = getters.getEvaluatedCell({
24498
+ sheetId: dataRange.sheetId,
24499
+ col: dataRange.zone.left,
24500
+ row: dataRange.zone.top,
24501
+ });
24502
+ if (cell.type === CellValueType.number) {
24503
+ gaugeValue = cell.value;
24504
+ formattedValue = cell.formattedValue;
24505
+ format = cell.format;
24083
24506
  }
24084
24507
  }
24508
+ const minValue = Number(chart.sectionRule.rangeMin);
24509
+ const maxValue = Number(chart.sectionRule.rangeMax);
24510
+ const lowerPoint = chart.sectionRule.lowerInflectionPoint;
24511
+ const upperPoint = chart.sectionRule.upperInflectionPoint;
24512
+ const lowerPointValue = getSectionThresholdValue(lowerPoint, minValue, maxValue);
24513
+ const upperPointValue = getSectionThresholdValue(upperPoint, minValue, maxValue);
24514
+ const inflectionValues = [];
24515
+ const colors = [];
24516
+ if (lowerPointValue !== undefined) {
24517
+ inflectionValues.push({
24518
+ value: lowerPointValue,
24519
+ label: formatValue(lowerPointValue, { locale, format }),
24520
+ });
24521
+ colors.push(chartColors.lowerColor);
24522
+ }
24523
+ if (upperPointValue !== undefined && upperPointValue !== lowerPointValue) {
24524
+ inflectionValues.push({
24525
+ value: upperPointValue,
24526
+ label: formatValue(upperPointValue, { locale, format }),
24527
+ });
24528
+ colors.push(chartColors.middleColor);
24529
+ }
24530
+ if (upperPointValue !== undefined &&
24531
+ lowerPointValue !== undefined &&
24532
+ lowerPointValue > upperPointValue) {
24533
+ inflectionValues.reverse();
24534
+ colors.reverse();
24535
+ }
24536
+ colors.push(chartColors.upperColor);
24085
24537
  return {
24086
- chartJsConfig: config,
24087
- background: chart.background || BACKGROUND_CHART_COLOR,
24088
- dataSetsValues,
24089
- labelValues,
24090
- dataSetFormat,
24091
- labelFormat,
24538
+ background: getters.getStyleOfSingleCellChart(chart.background, dataRange).background,
24539
+ title: chart.title ?? { text: "" },
24540
+ minValue: {
24541
+ value: minValue,
24542
+ label: formatValue(minValue, { locale, format }),
24543
+ },
24544
+ maxValue: {
24545
+ value: maxValue,
24546
+ label: formatValue(maxValue, { locale, format }),
24547
+ },
24548
+ gaugeValue: gaugeValue !== undefined && formattedValue
24549
+ ? { value: gaugeValue, label: formattedValue }
24550
+ : undefined,
24551
+ inflectionValues,
24552
+ colors,
24092
24553
  };
24093
24554
  }
24555
+ function getSectionThresholdValue(threshold, minValue, maxValue) {
24556
+ if (threshold.value === "" || isNaN(Number(threshold.value))) {
24557
+ return undefined;
24558
+ }
24559
+ const numberValue = Number(threshold.value);
24560
+ const value = threshold.type === "number"
24561
+ ? numberValue
24562
+ : minValue + ((maxValue - minValue) * numberValue) / 100;
24563
+ return clip(value, minValue, maxValue);
24564
+ }
24094
24565
 
24095
24566
  class LineChart extends AbstractChart {
24096
24567
  dataSets;
@@ -24105,6 +24576,8 @@ class LineChart extends AbstractChart {
24105
24576
  cumulative;
24106
24577
  dataSetDesign;
24107
24578
  axesDesign;
24579
+ fillArea;
24580
+ showValues;
24108
24581
  constructor(definition, sheetId, getters) {
24109
24582
  super(definition, sheetId, getters);
24110
24583
  this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -24118,6 +24591,8 @@ class LineChart extends AbstractChart {
24118
24591
  this.cumulative = definition.cumulative;
24119
24592
  this.dataSetDesign = definition.dataSets;
24120
24593
  this.axesDesign = definition.axesDesign;
24594
+ this.fillArea = definition.fillArea;
24595
+ this.showValues = definition.showValues;
24121
24596
  }
24122
24597
  static validateChartDefinition(validator, definition) {
24123
24598
  return validator.checkValidations(definition, checkDataset, checkLabelRange);
@@ -24139,6 +24614,8 @@ class LineChart extends AbstractChart {
24139
24614
  aggregated: context.aggregated ?? false,
24140
24615
  cumulative: context.cumulative ?? false,
24141
24616
  axesDesign: context.axesDesign,
24617
+ fillArea: context.fillArea,
24618
+ showValues: context.showValues,
24142
24619
  };
24143
24620
  }
24144
24621
  getDefinition() {
@@ -24167,6 +24644,8 @@ class LineChart extends AbstractChart {
24167
24644
  aggregated: this.aggregated,
24168
24645
  cumulative: this.cumulative,
24169
24646
  axesDesign: this.axesDesign,
24647
+ fillArea: this.fillArea,
24648
+ showValues: this.showValues,
24170
24649
  };
24171
24650
  }
24172
24651
  getContextCreation() {
@@ -24222,10 +24701,6 @@ class LineChart extends AbstractChart {
24222
24701
  return new LineChart(definition, sheetId, this.getters);
24223
24702
  }
24224
24703
  }
24225
- function createLineChartRuntime(chart, getters) {
24226
- const { chartJsConfig, background } = createLineOrScatterChartRuntime(chart, getters);
24227
- return { chartJsConfig, background };
24228
- }
24229
24704
 
24230
24705
  class PieChart extends AbstractChart {
24231
24706
  dataSets;
@@ -24236,6 +24711,7 @@ class PieChart extends AbstractChart {
24236
24711
  aggregated;
24237
24712
  dataSetsHaveTitle;
24238
24713
  isDoughnut;
24714
+ showValues;
24239
24715
  constructor(definition, sheetId, getters) {
24240
24716
  super(definition, sheetId, getters);
24241
24717
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -24245,6 +24721,7 @@ class PieChart extends AbstractChart {
24245
24721
  this.aggregated = definition.aggregated;
24246
24722
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24247
24723
  this.isDoughnut = definition.isDoughnut;
24724
+ this.showValues = definition.showValues;
24248
24725
  }
24249
24726
  static transformDefinition(definition, executed) {
24250
24727
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -24263,6 +24740,7 @@ class PieChart extends AbstractChart {
24263
24740
  labelRange: context.auxiliaryRange || undefined,
24264
24741
  aggregated: context.aggregated ?? false,
24265
24742
  isDoughnut: false,
24743
+ showValues: context.showValues,
24266
24744
  };
24267
24745
  }
24268
24746
  getDefinition() {
@@ -24294,6 +24772,7 @@ class PieChart extends AbstractChart {
24294
24772
  title: this.title,
24295
24773
  aggregated: this.aggregated,
24296
24774
  isDoughnut: this.isDoughnut,
24775
+ showValues: this.showValues,
24297
24776
  };
24298
24777
  }
24299
24778
  copyForSheetId(sheetId) {
@@ -24361,6 +24840,7 @@ function getPieConfiguration(chart, labels, localeFormat) {
24361
24840
  const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
24362
24841
  return xLabel ? `${xLabel}: ${yLabelStr} (${percentage}%)` : `${yLabelStr} (${percentage}%)`;
24363
24842
  };
24843
+ config.options.plugins.chartShowValuesPlugin = { showValues: chart.showValues };
24364
24844
  return config;
24365
24845
  }
24366
24846
  function getPieColors(colors, dataSetsValues) {
@@ -24446,6 +24926,7 @@ class PyramidChart extends AbstractChart {
24446
24926
  axesDesign;
24447
24927
  horizontal = true;
24448
24928
  stacked = true;
24929
+ showValues;
24449
24930
  constructor(definition, sheetId, getters) {
24450
24931
  super(definition, sheetId, getters);
24451
24932
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle).slice(0, 2);
@@ -24456,6 +24937,7 @@ class PyramidChart extends AbstractChart {
24456
24937
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24457
24938
  this.dataSetDesign = definition.dataSets;
24458
24939
  this.axesDesign = definition.axesDesign;
24940
+ this.showValues = definition.showValues;
24459
24941
  }
24460
24942
  static transformDefinition(definition, executed) {
24461
24943
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -24476,6 +24958,7 @@ class PyramidChart extends AbstractChart {
24476
24958
  axesDesign: context.axesDesign,
24477
24959
  horizontal: true,
24478
24960
  stacked: true,
24961
+ showValues: context.showValues,
24479
24962
  };
24480
24963
  }
24481
24964
  getContextCreation() {
@@ -24529,6 +25012,7 @@ class PyramidChart extends AbstractChart {
24529
25012
  axesDesign: this.axesDesign,
24530
25013
  horizontal: true,
24531
25014
  stacked: true,
25015
+ showValues: this.showValues,
24532
25016
  };
24533
25017
  }
24534
25018
  getDefinitionForExcel() {
@@ -24563,6 +25047,8 @@ function createPyramidChartRuntime(chart, getters) {
24563
25047
  const tooltipItem = { ...item, parsed: { y: item.parsed.y, x: Math.abs(item.parsed.x) } };
24564
25048
  return tooltipLabelCallback(tooltipItem);
24565
25049
  };
25050
+ const callback = config.options.plugins.chartShowValuesPlugin.callback;
25051
+ config.options.plugins.chartShowValuesPlugin.callback = (x) => callback(Math.abs(x));
24566
25052
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
24567
25053
  }
24568
25054
 
@@ -24577,6 +25063,7 @@ class ScatterChart extends AbstractChart {
24577
25063
  dataSetsHaveTitle;
24578
25064
  dataSetDesign;
24579
25065
  axesDesign;
25066
+ showValues;
24580
25067
  constructor(definition, sheetId, getters) {
24581
25068
  super(definition, sheetId, getters);
24582
25069
  this.dataSets = createDataSets(this.getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -24588,6 +25075,7 @@ class ScatterChart extends AbstractChart {
24588
25075
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24589
25076
  this.dataSetDesign = definition.dataSets;
24590
25077
  this.axesDesign = definition.axesDesign;
25078
+ this.showValues = definition.showValues;
24591
25079
  }
24592
25080
  static validateChartDefinition(validator, definition) {
24593
25081
  return validator.checkValidations(definition, checkDataset, checkLabelRange);
@@ -24607,6 +25095,7 @@ class ScatterChart extends AbstractChart {
24607
25095
  labelRange: context.auxiliaryRange || undefined,
24608
25096
  aggregated: context.aggregated ?? false,
24609
25097
  axesDesign: context.axesDesign,
25098
+ showValues: context.showValues,
24610
25099
  };
24611
25100
  }
24612
25101
  getDefinition() {
@@ -24633,6 +25122,7 @@ class ScatterChart extends AbstractChart {
24633
25122
  labelsAsText: this.labelsAsText,
24634
25123
  aggregated: this.aggregated,
24635
25124
  axesDesign: this.axesDesign,
25125
+ showValues: this.showValues,
24636
25126
  };
24637
25127
  }
24638
25128
  getContextCreation() {
@@ -24733,6 +25223,7 @@ class WaterfallChart extends AbstractChart {
24733
25223
  subTotalValuesColor;
24734
25224
  dataSetDesign;
24735
25225
  axesDesign;
25226
+ showValues;
24736
25227
  constructor(definition, sheetId, getters) {
24737
25228
  super(definition, sheetId, getters);
24738
25229
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -24750,6 +25241,7 @@ class WaterfallChart extends AbstractChart {
24750
25241
  this.firstValueAsSubtotal = definition.firstValueAsSubtotal;
24751
25242
  this.dataSetDesign = definition.dataSets;
24752
25243
  this.axesDesign = definition.axesDesign;
25244
+ this.showValues = definition.showValues;
24753
25245
  }
24754
25246
  static transformDefinition(definition, executed) {
24755
25247
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -24772,6 +25264,7 @@ class WaterfallChart extends AbstractChart {
24772
25264
  showConnectorLines: context.showConnectorLines ?? true,
24773
25265
  firstValueAsSubtotal: context.firstValueAsSubtotal ?? false,
24774
25266
  axesDesign: context.axesDesign,
25267
+ showValues: context.showValues,
24775
25268
  };
24776
25269
  }
24777
25270
  getContextCreation() {
@@ -24830,6 +25323,7 @@ class WaterfallChart extends AbstractChart {
24830
25323
  subTotalValuesColor: this.subTotalValuesColor,
24831
25324
  firstValueAsSubtotal: this.firstValueAsSubtotal,
24832
25325
  axesDesign: this.axesDesign,
25326
+ showValues: this.showValues,
24833
25327
  };
24834
25328
  }
24835
25329
  getDefinitionForExcel() {
@@ -24927,6 +25421,10 @@ function getWaterfallConfiguration(chart, labels, dataSeriesLabels, localeFormat
24927
25421
  },
24928
25422
  };
24929
25423
  config.options.plugins.waterfallLinesPlugin = { showConnectorLines: chart.showConnectorLines };
25424
+ config.options.plugins.chartShowValuesPlugin = {
25425
+ showValues: chart.showValues,
25426
+ background: chart.background,
25427
+ };
24930
25428
  return config;
24931
25429
  }
24932
25430
  function createWaterfallChartRuntime(chart, getters) {
@@ -25019,7 +25517,7 @@ chartRegistry.add("combo", {
25019
25517
  chartRegistry.add("line", {
25020
25518
  match: (type) => type === "line",
25021
25519
  createChart: (definition, sheetId, getters) => new LineChart(definition, sheetId, getters),
25022
- getChartRuntime: createLineChartRuntime,
25520
+ getChartRuntime: createLineOrScatterChartRuntime,
25023
25521
  validateChartDefinition: LineChart.validateChartDefinition,
25024
25522
  transformDefinition: LineChart.transformDefinition,
25025
25523
  getChartDefinitionFromContextCreation: LineChart.getDefinitionFromContextCreation,
@@ -25093,27 +25591,46 @@ const chartCategories = {
25093
25591
  line: _t("Line"),
25094
25592
  column: _t("Column"),
25095
25593
  bar: _t("Bar"),
25594
+ area: _t("Area"),
25096
25595
  pie: _t("Pie"),
25097
25596
  misc: _t("Miscellaneous"),
25098
25597
  };
25099
25598
  const chartSubtypeRegistry = new Registry();
25100
25599
  chartSubtypeRegistry
25101
25600
  .add("line", {
25102
- matcher: (definition) => definition.type === "line" && !definition.stacked,
25601
+ matcher: (definition) => definition.type === "line" && !definition.stacked && !definition.fillArea,
25103
25602
  displayName: _t("Line"),
25104
25603
  chartType: "line",
25105
25604
  chartSubtype: "line",
25106
- subtypeDefinition: { stacked: false },
25605
+ subtypeDefinition: { stacked: false, fillArea: false },
25107
25606
  category: "line",
25108
25607
  preview: "o-spreadsheet-ChartPreview.LINE_CHART",
25109
25608
  })
25110
25609
  .add("stacked_line", {
25111
- matcher: (definition) => definition.type === "line" && definition.stacked,
25610
+ matcher: (definition) => definition.type === "line" && !definition.fillArea && !!definition.stacked,
25112
25611
  displayName: _t("Stacked Line"),
25113
25612
  chartType: "line",
25114
25613
  chartSubtype: "stacked_line",
25115
- subtypeDefinition: { stacked: true },
25614
+ subtypeDefinition: { stacked: true, fillArea: false },
25116
25615
  category: "line",
25616
+ preview: "o-spreadsheet-ChartPreview.STACKED_LINE_CHART",
25617
+ })
25618
+ .add("area", {
25619
+ matcher: (definition) => definition.type === "line" && !definition.stacked && !!definition.fillArea,
25620
+ displayName: _t("Area"),
25621
+ chartType: "line",
25622
+ chartSubtype: "area",
25623
+ subtypeDefinition: { stacked: false, fillArea: true },
25624
+ category: "area",
25625
+ preview: "o-spreadsheet-ChartPreview.AREA_CHART",
25626
+ })
25627
+ .add("stacked_area", {
25628
+ matcher: (definition) => definition.type === "line" && definition.stacked && !!definition.fillArea,
25629
+ displayName: _t("Stacked Area"),
25630
+ chartType: "line",
25631
+ chartSubtype: "stacked_area",
25632
+ subtypeDefinition: { stacked: true, fillArea: true },
25633
+ category: "area",
25117
25634
  preview: "o-spreadsheet-ChartPreview.STACKED_AREA_CHART",
25118
25635
  })
25119
25636
  .add("scatter", {
@@ -25537,7 +26054,7 @@ class MenuItemRegistry extends Registry {
25537
26054
  * @param path Path of items to add this subitem
25538
26055
  * @param value Subitem to add
25539
26056
  */
25540
- addChild(key, path, value) {
26057
+ addChild(key, path, value, options = { force: false }) {
25541
26058
  if (typeof value !== "function" && value.id === undefined) {
25542
26059
  value.id = key;
25543
26060
  }
@@ -25559,6 +26076,19 @@ class MenuItemRegistry extends Registry {
25559
26076
  if (!node.children) {
25560
26077
  node.children = [];
25561
26078
  }
26079
+ const children = node.children;
26080
+ if (!children || typeof children === "function") {
26081
+ throw new Error(`${path} is either not a node or it's dynamically computed`);
26082
+ }
26083
+ if ("id" in value) {
26084
+ const valueIndex = children.findIndex((elt) => "id" in elt && elt.id === value.id);
26085
+ if (valueIndex > -1) {
26086
+ if (!options.force)
26087
+ throw new Error(`A child with the id "${value.id}" already exists.`);
26088
+ node.children.splice(valueIndex, 1, value);
26089
+ return this;
26090
+ }
26091
+ }
25562
26092
  node.children.push(value);
25563
26093
  return this;
25564
26094
  }
@@ -27851,6 +28381,23 @@ const CREATE_PIVOT = (env) => {
27851
28381
  env.openSidePanel("PivotSidePanel", { pivotId });
27852
28382
  }
27853
28383
  };
28384
+ const REINSERT_PIVOT_CHILDREN = (env) => env.model.getters.getPivotIds().map((pivotId, index) => ({
28385
+ id: `reinsert_pivot_${env.model.getters.getPivotFormulaId(pivotId)}`,
28386
+ name: env.model.getters.getPivotDisplayName(pivotId),
28387
+ sequence: index,
28388
+ execute: (env) => {
28389
+ const zone = env.model.getters.getSelectedZone();
28390
+ const table = env.model.getters.getPivot(pivotId).getTableStructure().export();
28391
+ env.model.dispatch("INSERT_PIVOT_WITH_TABLE", {
28392
+ pivotId,
28393
+ table,
28394
+ col: zone.left,
28395
+ row: zone.top,
28396
+ sheetId: env.model.getters.getActiveSheetId(),
28397
+ });
28398
+ env.model.dispatch("REFRESH_PIVOT", { id: pivotId });
28399
+ },
28400
+ }));
27854
28401
  //------------------------------------------------------------------------------
27855
28402
  // Image
27856
28403
  //------------------------------------------------------------------------------
@@ -28715,12 +29262,21 @@ const splitToColumns = {
28715
29262
  isEnabled: (env) => env.model.getters.isSingleColSelected(),
28716
29263
  icon: "o-spreadsheet-Icon.SPLIT_TEXT",
28717
29264
  };
29265
+ const reinsertPivotMenu = {
29266
+ id: "reinsert_pivot",
29267
+ name: _t("Re-insert pivot"),
29268
+ sequence: 1020,
29269
+ icon: "o-spreadsheet-Icon.INSERT_PIVOT",
29270
+ children: [REINSERT_PIVOT_CHILDREN],
29271
+ isVisible: (env) => env.model.getters.getPivotIds().length > 0,
29272
+ };
28718
29273
 
28719
29274
  var ACTION_DATA = /*#__PURE__*/Object.freeze({
28720
29275
  __proto__: null,
28721
29276
  createRemoveFilter: createRemoveFilter,
28722
29277
  createRemoveFilterTool: createRemoveFilterTool,
28723
29278
  dataCleanup: dataCleanup,
29279
+ reinsertPivotMenu: reinsertPivotMenu,
28724
29280
  removeDuplicates: removeDuplicates,
28725
29281
  sortAscending: sortAscending,
28726
29282
  sortDescending: sortDescending,
@@ -30201,7 +30757,8 @@ topbarMenuRegistry
30201
30757
  : undefined,
30202
30758
  };
30203
30759
  });
30204
- });
30760
+ })
30761
+ .addChild("reinsert_pivot", ["data"], reinsertPivotMenu);
30205
30762
 
30206
30763
  class OTRegistry extends Registry {
30207
30764
  /**
@@ -31893,6 +32450,7 @@ class ChartWithAxisDesignPanel extends owl.Component {
31893
32450
  Section,
31894
32451
  AxisDesignEditor,
31895
32452
  RoundColorPicker,
32453
+ Checkbox,
31896
32454
  };
31897
32455
  static props = {
31898
32456
  figureId: String,
@@ -31985,6 +32543,12 @@ class ChartWithAxisDesignPanel extends owl.Component {
31985
32543
  const dataSets = this.props.definition.dataSets;
31986
32544
  return dataSets[this.state.index]?.label || this.getDataSeries()[this.state.index];
31987
32545
  }
32546
+ get showValuesLabel() {
32547
+ return ChartTerms.ShowValues;
32548
+ }
32549
+ updateShowValues(showValues) {
32550
+ this.props.updateChart(this.props.figureId, { showValues });
32551
+ }
31988
32552
  }
31989
32553
 
31990
32554
  class GaugeChartConfigPanel extends owl.Component {
@@ -32134,6 +32698,10 @@ class LineConfigPanel extends GenericChartConfigPanel {
32134
32698
  }
32135
32699
  return false;
32136
32700
  }
32701
+ get stackedLabel() {
32702
+ const definition = this.props.definition;
32703
+ return definition.fillArea ? this.chartTerms.StackedAreaChart : this.chartTerms.StackedBarChart;
32704
+ }
32137
32705
  getLabelRangeOptions() {
32138
32706
  const options = super.getLabelRangeOptions();
32139
32707
  if (this.canTreatLabelsAsText) {
@@ -32173,6 +32741,7 @@ class PieChartDesignPanel extends owl.Component {
32173
32741
  static components = {
32174
32742
  GeneralDesignEditor,
32175
32743
  Section,
32744
+ Checkbox,
32176
32745
  };
32177
32746
  static props = {
32178
32747
  figureId: String,
@@ -32185,6 +32754,9 @@ class PieChartDesignPanel extends owl.Component {
32185
32754
  legendPosition: ev.target.value,
32186
32755
  });
32187
32756
  }
32757
+ get showValuesLabel() {
32758
+ return ChartTerms.ShowValues;
32759
+ }
32188
32760
  }
32189
32761
 
32190
32762
  class ScatterConfigPanel extends GenericChartConfigPanel {
@@ -32379,6 +32951,12 @@ class WaterfallChartDesignPanel extends owl.Component {
32379
32951
  verticalAxisPosition: ev.target.value,
32380
32952
  });
32381
32953
  }
32954
+ get showValuesLabel() {
32955
+ return ChartTerms.ShowValues;
32956
+ }
32957
+ updateShowValues(showValues) {
32958
+ this.props.updateChart(this.props.figureId, { showValues });
32959
+ }
32382
32960
  }
32383
32961
 
32384
32962
  const chartSidePanelComponentRegistry = new Registry();
@@ -32639,6 +33217,11 @@ class ChartPanel extends owl.Component {
32639
33217
  }
32640
33218
  }
32641
33219
 
33220
+ /**
33221
+ * Registry to draw icons on cells
33222
+ */
33223
+ const iconsOnCellRegistry = new Registry();
33224
+
32642
33225
  css /* scss */ `
32643
33226
  .o-spreadsheet {
32644
33227
  .o-icon {
@@ -32674,49 +33257,47 @@ const FROWN = '<svg class="o-cf-icon frown" width="10" height="10" focusable="fa
32674
33257
  const GREEN_DOT = '<svg class="o-cf-icon green-dot" width="10" height="10" focusable="false" viewBox="0 0 512 512"><path fill="#6AA84F" d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8z"></path></svg>';
32675
33258
  const YELLOW_DOT = '<svg class="o-cf-icon yellow-dot" width="10" height="10" focusable="false" viewBox="0 0 512 512"><path fill="#F0AD4E" d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8z"></path></svg>';
32676
33259
  const RED_DOT = '<svg class="o-cf-icon red-dot" width="10" height="10" focusable="false" viewBox="0 0 512 512"><path fill="#E06666" d="M256 8C119 8 8 119 8 256s111 248 248 248 248-111 248-248S393 8 256 8z"></path></svg>';
32677
- function loadIconImage(svg) {
33260
+ function getIconSrc(svg) {
32678
33261
  /** We have to add xmlns, as it's not added by owl in the canvas */
32679
33262
  svg = `<svg xmlns="http://www.w3.org/2000/svg" ${svg.slice(4)}`;
32680
- const image = new Image();
32681
- image.src = "data:image/svg+xml; charset=utf8, " + encodeURIComponent(svg);
32682
- return image;
33263
+ return "data:image/svg+xml; charset=utf8, " + encodeURIComponent(svg);
32683
33264
  }
32684
33265
  const ICONS = {
32685
33266
  arrowGood: {
32686
33267
  template: "ARROW_UP",
32687
- img: loadIconImage(ARROW_UP),
33268
+ img: getIconSrc(ARROW_UP),
32688
33269
  },
32689
33270
  arrowNeutral: {
32690
33271
  template: "ARROW_RIGHT",
32691
- img: loadIconImage(ARROW_RIGHT),
33272
+ img: getIconSrc(ARROW_RIGHT),
32692
33273
  },
32693
33274
  arrowBad: {
32694
33275
  template: "ARROW_DOWN",
32695
- img: loadIconImage(ARROW_DOWN),
33276
+ img: getIconSrc(ARROW_DOWN),
32696
33277
  },
32697
33278
  smileyGood: {
32698
33279
  template: "SMILE",
32699
- img: loadIconImage(SMILE),
33280
+ img: getIconSrc(SMILE),
32700
33281
  },
32701
33282
  smileyNeutral: {
32702
33283
  template: "MEH",
32703
- img: loadIconImage(MEH),
33284
+ img: getIconSrc(MEH),
32704
33285
  },
32705
33286
  smileyBad: {
32706
33287
  template: "FROWN",
32707
- img: loadIconImage(FROWN),
33288
+ img: getIconSrc(FROWN),
32708
33289
  },
32709
33290
  dotGood: {
32710
33291
  template: "GREEN_DOT",
32711
- img: loadIconImage(GREEN_DOT),
33292
+ img: getIconSrc(GREEN_DOT),
32712
33293
  },
32713
33294
  dotNeutral: {
32714
33295
  template: "YELLOW_DOT",
32715
- img: loadIconImage(YELLOW_DOT),
33296
+ img: getIconSrc(YELLOW_DOT),
32716
33297
  },
32717
33298
  dotBad: {
32718
33299
  template: "RED_DOT",
32719
- img: loadIconImage(RED_DOT),
33300
+ img: getIconSrc(RED_DOT),
32720
33301
  },
32721
33302
  };
32722
33303
  const ICON_SETS = {
@@ -32736,6 +33317,12 @@ const ICON_SETS = {
32736
33317
  bad: "dotBad",
32737
33318
  },
32738
33319
  };
33320
+ iconsOnCellRegistry.add("conditional_formatting", (getters, position) => {
33321
+ const icon = getters.getConditionalIcon(position);
33322
+ if (icon) {
33323
+ return ICONS[icon].img;
33324
+ }
33325
+ });
32739
33326
 
32740
33327
  css /* scss */ `
32741
33328
  .o-icon-picker {
@@ -33084,21 +33671,25 @@ function useHoveredElement(ref) {
33084
33671
 
33085
33672
  function useHighlightsOnHover(ref, highlightProvider) {
33086
33673
  const hoverState = useHoveredElement(ref);
33087
- const stores = useStoreProvider();
33088
33674
  useHighlights({
33089
33675
  get highlights() {
33090
33676
  return hoverState.hovered ? highlightProvider.highlights : [];
33091
33677
  },
33092
33678
  });
33093
- owl.useEffect(() => {
33094
- stores.trigger("store-updated");
33095
- }, () => [hoverState.hovered]);
33096
33679
  }
33097
33680
  function useHighlights(highlightProvider) {
33681
+ const stores = useStoreProvider();
33098
33682
  const store = useLocalStore(HighlightStore);
33099
33683
  owl.onMounted(() => {
33100
33684
  store.register(highlightProvider);
33101
33685
  });
33686
+ let currentHighlights = highlightProvider.highlights;
33687
+ owl.useEffect((highlights) => {
33688
+ if (!deepEquals(highlights, currentHighlights)) {
33689
+ currentHighlights = highlights;
33690
+ stores.trigger("store-updated");
33691
+ }
33692
+ }, () => [highlightProvider.highlights]);
33102
33693
  }
33103
33694
 
33104
33695
  css /* scss */ `
@@ -34570,6 +35161,8 @@ class FindAndReplaceStore extends SpreadsheetStore {
34570
35161
  isSearchDirty = false;
34571
35162
  initialShowFormulaState;
34572
35163
  preserveSelectedMatchIndex = false;
35164
+ irreplaceableMatchCount = 0;
35165
+ notificationStore = this.get(NotificationStore);
34573
35166
  // fixme: why do we make selectedMatchIndex on top of a selected
34574
35167
  // property in the matches?
34575
35168
  selectedMatchIndex = null;
@@ -34579,7 +35172,7 @@ class FindAndReplaceStore extends SpreadsheetStore {
34579
35172
  matchCase: false,
34580
35173
  exactMatch: false,
34581
35174
  searchFormulas: false,
34582
- searchScope: "allSheets",
35175
+ searchScope: "activeSheet",
34583
35176
  specificRange: undefined,
34584
35177
  };
34585
35178
  updateSearchContent = debounce(this._updateSearchContent.bind(this), 200);
@@ -34646,6 +35239,11 @@ class FindAndReplaceStore extends SpreadsheetStore {
34646
35239
  for (const match of cmd.matches) {
34647
35240
  this.replaceMatch(match, cmd.searchString, cmd.replaceWith, cmd.searchOptions);
34648
35241
  }
35242
+ if (this.irreplaceableMatchCount > 0) {
35243
+ this.showReplaceWarningMessage(cmd.matches.length, this.irreplaceableMatchCount);
35244
+ }
35245
+ this.irreplaceableMatchCount = 0;
35246
+ break;
34649
35247
  }
34650
35248
  }
34651
35249
  finalize() {
@@ -34827,12 +35425,36 @@ class FindAndReplaceStore extends SpreadsheetStore {
34827
35425
  searchOptions: this.searchOptions,
34828
35426
  });
34829
35427
  }
35428
+ /**
35429
+ * Show a warning message based on the number of matches replaced and irreplaceable.
35430
+ */
35431
+ showReplaceWarningMessage(totalMatches, irreplaceableMatches) {
35432
+ const replaceableMatches = totalMatches - irreplaceableMatches;
35433
+ if (replaceableMatches === 0) {
35434
+ this.notificationStore.notifyUser({
35435
+ type: "warning",
35436
+ sticky: false,
35437
+ text: _t("Match(es) cannot be replaced as they are part of a formula."),
35438
+ });
35439
+ }
35440
+ else {
35441
+ this.notificationStore.notifyUser({
35442
+ type: "warning",
35443
+ sticky: false,
35444
+ text: _t("%(replaceable_count)s match(es) replaced. %(irreplaceable_count)s match(es) cannot be replaced as they are part of a formula.", {
35445
+ replaceable_count: replaceableMatches,
35446
+ irreplaceable_count: irreplaceableMatches,
35447
+ }),
35448
+ });
35449
+ }
35450
+ }
34830
35451
  replaceMatch(selectedMatch, searchString, replaceWith, searchOptions) {
34831
35452
  const cell = this.getters.getCell(selectedMatch);
34832
35453
  if (!cell?.content) {
34833
35454
  return;
34834
35455
  }
34835
35456
  if (cell?.isFormula && !searchOptions.searchFormulas) {
35457
+ this.irreplaceableMatchCount++;
34836
35458
  return;
34837
35459
  }
34838
35460
  const searchRegex = getSearchRegex(searchString, searchOptions);
@@ -35509,9 +36131,11 @@ class PivotTitleSection extends owl.Component {
35509
36131
  }
35510
36132
  duplicatePivot() {
35511
36133
  const newPivotId = this.env.model.uuidGenerator.uuidv4();
35512
- const result = this.env.model.dispatch("DUPLICATE_PIVOT", {
36134
+ const newSheetId = this.env.model.uuidGenerator.uuidv4();
36135
+ const result = this.env.model.dispatch("DUPLICATE_PIVOT_IN_NEW_SHEET", {
35513
36136
  pivotId: this.props.pivotId,
35514
36137
  newPivotId,
36138
+ newSheetId,
35515
36139
  });
35516
36140
  const text = result.isSuccessful ? _t("Pivot duplicated.") : _t("Pivot duplication failed");
35517
36141
  const type = result.isSuccessful ? "success" : "danger";
@@ -36046,7 +36670,7 @@ function createDate(dimension, value, locale) {
36046
36670
  if (typeof value === "number" || typeof value === "string") {
36047
36671
  const date = toJsDate(value, locale);
36048
36672
  switch (granularity) {
36049
- case "year_number":
36673
+ case "year":
36050
36674
  number = date.getFullYear();
36051
36675
  break;
36052
36676
  case "quarter_number":
@@ -36074,7 +36698,7 @@ function createDate(dimension, value, locale) {
36074
36698
  * This map is used to cache the different values of a pivot date value
36075
36699
  * 43_831 -> 01/01/2012
36076
36700
  * Example: {
36077
- * year_number: {
36701
+ * year: {
36078
36702
  * set: { 43_831 },
36079
36703
  * values: { '43_831': 2012 }
36080
36704
  * },
@@ -36101,7 +36725,7 @@ function createDate(dimension, value, locale) {
36101
36725
  * }
36102
36726
  */
36103
36727
  const MAP_VALUE_DIMENSION_DATE = {
36104
- year_number: {
36728
+ year: {
36105
36729
  set: new Set(),
36106
36730
  values: {},
36107
36731
  },
@@ -36486,7 +37110,7 @@ pivotRegistry.add("SPREADSHEET", {
36486
37110
  externalData: false,
36487
37111
  onIterationEndEvaluation: (pivot) => pivot.markAsDirtyForEvaluation(),
36488
37112
  granularities: [
36489
- "year_number",
37113
+ "year",
36490
37114
  "quarter_number",
36491
37115
  "month_number",
36492
37116
  "iso_week_number",
@@ -36896,7 +37520,13 @@ class SettingsPanel extends owl.Component {
36896
37520
  }
36897
37521
  async loadLocales() {
36898
37522
  this.loadedLocales = (await this.env.loadLocales())
36899
- .filter(isValidLocale)
37523
+ .filter((locale) => {
37524
+ const isValid = isValidLocale(locale);
37525
+ if (!isValid) {
37526
+ console.warn(`Invalid locale: ${locale["code"]} ${locale}`);
37527
+ }
37528
+ return isValid;
37529
+ })
36900
37530
  .sort((a, b) => a.name.localeCompare(b.name));
36901
37531
  }
36902
37532
  get numberFormatPreview() {
@@ -37955,6 +38585,9 @@ class TopBarComponentRegistry extends Registry {
37955
38585
  const component = { ...value, id: this.uuidGenerator.uuidv4() };
37956
38586
  return super.add(name, component);
37957
38587
  }
38588
+ getAllOrdered() {
38589
+ return this.getAll().sort((a, b) => a.sequence - b.sequence);
38590
+ }
37958
38591
  }
37959
38592
  const topbarComponentRegistry = new TopBarComponentRegistry();
37960
38593
 
@@ -40843,21 +41476,22 @@ class GridRenderer {
40843
41476
  isError: (cell.type === CellValueType.error && !!cell.message) ||
40844
41477
  this.getters.isDataValidationInvalid(position),
40845
41478
  };
40846
- if (cell.type === CellValueType.empty || this.getters.isCellValidCheckbox(position)) {
40847
- return box;
40848
- }
40849
- /** Icon CF */
40850
- const cfIcon = this.getters.getConditionalIcon(position);
41479
+ /** Icon */
41480
+ const iconSrc = this.getters.getCellIconSrc(position);
40851
41481
  const fontSizePX = computeTextFontSizeInPixels(box.style);
40852
- const iconBoxWidth = cfIcon ? MIN_CF_ICON_MARGIN + fontSizePX : 0;
40853
- if (cfIcon) {
41482
+ const iconBoxWidth = iconSrc ? MIN_CF_ICON_MARGIN + fontSizePX : 0;
41483
+ if (iconSrc) {
41484
+ const imageHtmlElement = loadIconImage(iconSrc);
40854
41485
  box.image = {
40855
41486
  type: "icon",
40856
41487
  size: fontSizePX,
40857
41488
  clipIcon: { x: box.x, y: box.y, width: Math.min(iconBoxWidth, width), height },
40858
- image: ICONS[cfIcon].img,
41489
+ image: imageHtmlElement,
40859
41490
  };
40860
41491
  }
41492
+ if (cell.type === CellValueType.empty || this.getters.isCellValidCheckbox(position)) {
41493
+ return box;
41494
+ }
40861
41495
  /** Filter Header or data validation icon */
40862
41496
  box.hasIcon = this.getters.doesCellHaveGridIcon(position);
40863
41497
  const headerIconWidth = box.hasIcon ? GRID_ICON_EDGE_LENGTH + GRID_ICON_MARGIN : 0;
@@ -40876,7 +41510,7 @@ class GridRenderer {
40876
41510
  };
40877
41511
  /** ClipRect */
40878
41512
  const isOverflowing = contentWidth > width || fontSizePX > height;
40879
- if (cfIcon || box.hasIcon) {
41513
+ if (iconSrc || box.hasIcon) {
40880
41514
  box.clipRect = {
40881
41515
  x: box.x + iconBoxWidth,
40882
41516
  y: box.y,
@@ -40987,6 +41621,11 @@ class GridRenderer {
40987
41621
  return boxes;
40988
41622
  }
40989
41623
  }
41624
+ const loadIconImage = memoize(function loadIconImage(src) {
41625
+ const image = new Image();
41626
+ image.src = src;
41627
+ return image;
41628
+ });
40990
41629
 
40991
41630
  function useGridDrawing(refName, model, canvasSize) {
40992
41631
  const canvasRef = owl.useRef(refName);
@@ -42059,6 +42698,7 @@ class Grid extends owl.Component {
42059
42698
  return;
42060
42699
  }
42061
42700
  if (clipboardData.types.indexOf(ClipboardMIMEType.PlainText) > -1) {
42701
+ ev.preventDefault();
42062
42702
  const content = clipboardData.getData(ClipboardMIMEType.PlainText);
42063
42703
  const target = this.env.model.getters.getSelectedZones();
42064
42704
  const clipboardString = this.env.model.getters.getClipboardTextContent();
@@ -42325,6 +42965,8 @@ const NON_RETROCOMPATIBLE_FUNCTIONS = [
42325
42965
  "BITOR",
42326
42966
  "BITRSHIFT",
42327
42967
  "BITXOR",
42968
+ "BYCOL",
42969
+ "BYROW",
42328
42970
  "CEILING.MATH",
42329
42971
  "CEILING.PRECISE",
42330
42972
  "CHISQ.DIST",
@@ -42332,6 +42974,8 @@ const NON_RETROCOMPATIBLE_FUNCTIONS = [
42332
42974
  "CHISQ.INV",
42333
42975
  "CHISQ.INV.RT",
42334
42976
  "CHISQ.TEST",
42977
+ "CHOOSECOLS",
42978
+ "CHOOSEROWS",
42335
42979
  "COMBINA",
42336
42980
  "CONCAT",
42337
42981
  "CONFIDENCE.NORM",
@@ -42344,14 +42988,17 @@ const NON_RETROCOMPATIBLE_FUNCTIONS = [
42344
42988
  "CSCH",
42345
42989
  "DAYS",
42346
42990
  "DECIMAL",
42991
+ "DROP",
42347
42992
  "ERF.PRECISE",
42348
42993
  "ERFC.PRECISE",
42994
+ "EXPAND",
42349
42995
  "EXPON.DIST",
42350
42996
  "F.DIST",
42351
42997
  "F.DIST.RT",
42352
42998
  "F.INV",
42353
42999
  "F.INV.RT",
42354
43000
  "F.TEST",
43001
+ "FIELDVALUE",
42355
43002
  "FILTERXML",
42356
43003
  "FLOOR.MATH",
42357
43004
  "FLOOR.PRECISE",
@@ -42366,6 +43013,7 @@ const NON_RETROCOMPATIBLE_FUNCTIONS = [
42366
43013
  "GAMMA.INV",
42367
43014
  "GAMMALN.PRECISE",
42368
43015
  "GAUSS",
43016
+ "HSTACK",
42369
43017
  "HYPGEOM.DIST",
42370
43018
  "IFNA",
42371
43019
  "IFS",
@@ -42378,9 +43026,14 @@ const NON_RETROCOMPATIBLE_FUNCTIONS = [
42378
43026
  "IMSINH",
42379
43027
  "IMTAN",
42380
43028
  "ISFORMULA",
43029
+ "ISOMITTED",
42381
43030
  "ISOWEEKNUM",
43031
+ "LAMBDA",
43032
+ "LET",
42382
43033
  "LOGNORM.DIST",
42383
43034
  "LOGNORM.INV",
43035
+ "MAKEARRAY",
43036
+ "MAP",
42384
43037
  "MAXIFS",
42385
43038
  "MINIFS",
42386
43039
  "MODE.MULT",
@@ -42400,17 +43053,26 @@ const NON_RETROCOMPATIBLE_FUNCTIONS = [
42400
43053
  "PERMUTATIONA",
42401
43054
  "PHI",
42402
43055
  "POISSON.DIST",
43056
+ "PQSOURCE",
43057
+ "PYTHON_STR",
43058
+ "PYTHON_TYPE",
43059
+ "PYTHON_TYPENAME",
42403
43060
  "QUARTILE.EXC",
42404
43061
  "QUARTILE.INC",
42405
43062
  "QUERYSTRING",
43063
+ "RANDARRAY",
42406
43064
  "RANK.AVG",
42407
43065
  "RANK.EQ",
43066
+ "REDUCE",
42408
43067
  "RRI",
43068
+ "SCAN",
42409
43069
  "SEC",
42410
43070
  "SECH",
43071
+ "SEQUENCE",
42411
43072
  "SHEET",
42412
43073
  "SHEETS",
42413
43074
  "SKEW.P",
43075
+ "SORTBY",
42414
43076
  "STDEV.P",
42415
43077
  "STDEV.S",
42416
43078
  "SWITCH",
@@ -42420,13 +43082,24 @@ const NON_RETROCOMPATIBLE_FUNCTIONS = [
42420
43082
  "T.INV",
42421
43083
  "T.INV.2T",
42422
43084
  "T.TEST",
43085
+ "TAKE",
43086
+ "TEXTAFTER",
43087
+ "TEXTBEFORE",
42423
43088
  "TEXTJOIN",
43089
+ "TEXTSPLIT",
43090
+ "TOCOL",
43091
+ "TOROW",
42424
43092
  "UNICHAR",
42425
43093
  "UNICODE",
43094
+ "UNIQUE",
42426
43095
  "VAR.P",
42427
43096
  "VAR.S",
43097
+ "VSTACK",
42428
43098
  "WEBSERVICE",
42429
43099
  "WEIBULL.DIST",
43100
+ "WRAPCOLS",
43101
+ "WRAPROWS",
43102
+ "XLOOKUP",
42430
43103
  "XOR",
42431
43104
  "Z.TEST",
42432
43105
  ];
@@ -45610,7 +46283,7 @@ function getRelationFile(file, xmls) {
45610
46283
  return relsFile;
45611
46284
  }
45612
46285
 
45613
- const EXCEL_IMPORT_VERSION = 16;
46286
+ const EXCEL_IMPORT_VERSION = 17;
45614
46287
  class XlsxReader {
45615
46288
  warningManager;
45616
46289
  xmls;
@@ -45742,7 +46415,7 @@ function normalizeV9(formula) {
45742
46415
  * a breaking change is made in the way the state is handled, and an upgrade
45743
46416
  * function should be defined
45744
46417
  */
45745
- const CURRENT_VERSION = 16;
46418
+ const CURRENT_VERSION = 17;
45746
46419
  const INITIAL_SHEET_ID = "Sheet1";
45747
46420
  /**
45748
46421
  * This function tries to load anything that could look like a valid
@@ -46689,6 +47362,21 @@ class BordersPlugin extends CorePlugin {
46689
47362
  return [];
46690
47363
  return Object.keys(sheetBorders).map((index) => parseInt(index, 10));
46691
47364
  }
47365
+ /**
47366
+ * Get all the rows which contains at least a border
47367
+ */
47368
+ getRowsWithBorders(sheetId) {
47369
+ const sheetBorders = this.borders[sheetId]?.filter(isDefined);
47370
+ if (!sheetBorders)
47371
+ return [];
47372
+ const rowsWithBorders = new Set();
47373
+ for (const rowBorders of sheetBorders) {
47374
+ for (const rowBorder in rowBorders) {
47375
+ rowsWithBorders.add(parseInt(rowBorder, 10));
47376
+ }
47377
+ }
47378
+ return Array.from(rowsWithBorders);
47379
+ }
46692
47380
  /**
46693
47381
  * Get the range of all the rows in the sheet
46694
47382
  */
@@ -46738,7 +47426,7 @@ class BordersPlugin extends CorePlugin {
46738
47426
  destructive: false,
46739
47427
  });
46740
47428
  }
46741
- this.getRowsRange(sheetId)
47429
+ this.getRowsWithBorders(sheetId)
46742
47430
  .filter((row) => row >= start)
46743
47431
  .sort((a, b) => (delta < 0 ? a - b : b - a)) // start by the end when moving up
46744
47432
  .forEach((row) => {
@@ -49064,10 +49752,12 @@ class MergePlugin extends CorePlugin {
49064
49752
  if (!sheetMap)
49065
49753
  return [];
49066
49754
  const mergeIds = new Set();
49067
- for (const { col, row } of positions(zone)) {
49068
- const mergeId = sheetMap[col]?.[row];
49069
- if (mergeId) {
49070
- mergeIds.add(mergeId);
49755
+ for (let col = zone.left; col <= zone.right; col++) {
49756
+ for (let row = zone.top; row <= zone.bottom; row++) {
49757
+ const mergeId = sheetMap[col]?.[row];
49758
+ if (mergeId) {
49759
+ mergeIds.add(mergeId);
49760
+ }
49071
49761
  }
49072
49762
  }
49073
49763
  return Array.from(mergeIds)
@@ -52935,7 +53625,13 @@ class FormulaDependencyGraph {
52935
53625
  const queue = Array.from(ranges).reverse();
52936
53626
  while (queue.length > 0) {
52937
53627
  const range = queue.pop();
52938
- visited.addMany(positions(range.zone).map((position) => ({ sheetId: range.sheetId, ...position })));
53628
+ const zone = range.zone;
53629
+ const sheetId = range.sheetId;
53630
+ for (let col = zone.left; col <= zone.right; col++) {
53631
+ for (let row = zone.top; row <= zone.bottom; row++) {
53632
+ visited.add({ sheetId, col, row });
53633
+ }
53634
+ }
52939
53635
  const impactedPositions = this.rTree.search(range).map((dep) => dep.data);
52940
53636
  const nextInQueue = {};
52941
53637
  for (const position of impactedPositions) {
@@ -52951,7 +53647,16 @@ class FormulaDependencyGraph {
52951
53647
  queue.push(...zones.map((zone) => ({ sheetId, zone })));
52952
53648
  }
52953
53649
  }
52954
- visited.deleteMany(ranges.flatMap((r) => positions(r.zone).map((position) => ({ sheetId: r.sheetId, ...position }))));
53650
+ // remove initial ranges
53651
+ for (const range of ranges) {
53652
+ const zone = range.zone;
53653
+ const sheetId = range.sheetId;
53654
+ for (let col = zone.left; col <= zone.right; col++) {
53655
+ for (let row = zone.top; row <= zone.bottom; row++) {
53656
+ visited.delete({ sheetId, col, row });
53657
+ }
53658
+ }
53659
+ }
52955
53660
  return visited;
52956
53661
  }
52957
53662
  }
@@ -53082,7 +53787,7 @@ class PositionSet {
53082
53787
  return this.sheets[position.sheetId].getValue(position) === 1;
53083
53788
  }
53084
53789
  clear() {
53085
- const insertions = this.insertions;
53790
+ const insertions = [...this];
53086
53791
  this.insertions = [];
53087
53792
  for (const sheetId in this.sheets) {
53088
53793
  this.sheets[sheetId].clear();
@@ -53426,6 +54131,7 @@ class Evaluator {
53426
54131
  }
53427
54132
  finally {
53428
54133
  this.cellsBeingComputed.delete(cellId);
54134
+ this.nextPositionsToUpdate.delete(position);
53429
54135
  }
53430
54136
  }
53431
54137
  computeAndSave(position) {
@@ -53452,8 +54158,33 @@ class Evaluator {
53452
54158
  forEachSpreadPositionInMatrix(nbColumns, nbRows,
53453
54159
  // thanks to the isMatrix check above, we know that formulaReturn is MatrixFunctionReturn
53454
54160
  this.spreadValues(formulaPosition, formulaReturn));
54161
+ this.invalidatePositionsDependingOnSpread(formulaPosition, nbColumns, nbRows);
53455
54162
  return createEvaluatedCell(nullValueToZeroValue(formulaReturn[0][0]), this.getters.getLocale(), cellData);
53456
54163
  }
54164
+ invalidatePositionsDependingOnSpread(arrayFormulaPosition, nbColumns, nbRows) {
54165
+ // the result matrix is split in 2 zones to exclude the array formula position
54166
+ const top = arrayFormulaPosition.row;
54167
+ const left = arrayFormulaPosition.col;
54168
+ const bottom = top + nbRows - 1;
54169
+ const leftColumnZone = {
54170
+ top: top + 1,
54171
+ bottom,
54172
+ left,
54173
+ right: left,
54174
+ };
54175
+ const rightPartZone = {
54176
+ top,
54177
+ bottom,
54178
+ left: left + 1,
54179
+ right: left + nbColumns - 1,
54180
+ };
54181
+ const sheetId = arrayFormulaPosition.sheetId;
54182
+ const invalidatedPositions = this.formulaDependencies().getCellsDependingOn([
54183
+ { sheetId, zone: rightPartZone },
54184
+ { sheetId, zone: leftColumnZone },
54185
+ ]);
54186
+ this.nextPositionsToUpdate.addMany(invalidatedPositions);
54187
+ }
53457
54188
  assertSheetHasEnoughSpaceToSpreadFormulaResult({ sheetId, col, row }, matrixResult) {
53458
54189
  const numberOfCols = this.getters.getNumberCols(sheetId);
53459
54190
  const numberOfRows = this.getters.getNumberRows(sheetId);
@@ -53484,14 +54215,15 @@ class Evaluator {
53484
54215
  }
53485
54216
  updateSpreadRelation({ sheetId, col, row, }) {
53486
54217
  const arrayFormulaPosition = { sheetId, col, row };
53487
- return (i, j) => {
54218
+ const updateSpreadRelation = (i, j) => {
53488
54219
  const position = { sheetId, col: i + col, row: j + row };
53489
54220
  this.spreadingRelations.addRelation({ resultPosition: position, arrayFormulaPosition });
53490
54221
  };
54222
+ return updateSpreadRelation;
53491
54223
  }
53492
54224
  checkCollision(formulaPosition) {
53493
54225
  const { sheetId, col, row } = formulaPosition;
53494
- return (i, j) => {
54226
+ const checkCollision = (i, j) => {
53495
54227
  const position = { sheetId: sheetId, col: i + col, row: j + row };
53496
54228
  const rawCell = this.getters.getCell(position);
53497
54229
  if (rawCell?.content ||
@@ -53501,17 +54233,16 @@ class Evaluator {
53501
54233
  }
53502
54234
  this.blockedArrayFormulas.delete(formulaPosition);
53503
54235
  };
54236
+ return checkCollision;
53504
54237
  }
53505
54238
  spreadValues({ sheetId, col, row }, matrixResult) {
53506
- return (i, j) => {
54239
+ const spreadValues = (i, j) => {
53507
54240
  const position = { sheetId, col: i + col, row: j + row };
53508
54241
  const cell = this.getters.getCell(position);
53509
54242
  const evaluatedCell = createEvaluatedCell(nullValueToZeroValue(matrixResult[i][j]), this.getters.getLocale(), cell);
53510
54243
  this.evaluatedCells.set(position, evaluatedCell);
53511
- // check if formula dependencies present in the spread zone
53512
- // if so, they need to be recomputed
53513
- this.nextPositionsToUpdate.addMany(this.getCellsDependingOn([position]));
53514
54244
  };
54245
+ return spreadValues;
53515
54246
  }
53516
54247
  invalidateSpreading(position) {
53517
54248
  if (!this.spreadingRelations.isArrayFormula(position)) {
@@ -53566,12 +54297,12 @@ function forEachSpreadPositionInMatrix(nbColumns, nbRows, callback) {
53566
54297
  * rather than appearing empty. This indicates that the
53567
54298
  * cell is the result of a non-empty content.
53568
54299
  */
53569
- function nullValueToZeroValue(fPayload) {
53570
- if (fPayload.value === null || fPayload.value === undefined) {
53571
- // 'fPayload.value === undefined' is supposed to never happen, it's a safety net for javascript use
53572
- return { ...fPayload, value: 0 };
54300
+ function nullValueToZeroValue(functionResult) {
54301
+ if (functionResult.value === null || functionResult.value === undefined) {
54302
+ // 'functionResult.value === undefined' is supposed to never happen, it's a safety net for javascript use
54303
+ return { ...functionResult, value: 0 };
53573
54304
  }
53574
- return fPayload;
54305
+ return functionResult;
53575
54306
  }
53576
54307
  function updateEvalContextAndExecute(compiledFormula, compilationParams, sheetId, cellId) {
53577
54308
  compilationParams.evalContext.__originCellXC = lazy(() => {
@@ -57139,6 +57870,12 @@ class InsertPivotPlugin extends UIPlugin {
57139
57870
  case "INSERT_NEW_PIVOT":
57140
57871
  this.insertNewPivot(cmd.pivotId, cmd.newSheetId);
57141
57872
  break;
57873
+ case "DUPLICATE_PIVOT_IN_NEW_SHEET":
57874
+ this.duplicatePivotInNewSheet(cmd.pivotId, cmd.newPivotId, cmd.newSheetId);
57875
+ break;
57876
+ case "INSERT_PIVOT_WITH_TABLE":
57877
+ this.insertPivotWithTable(cmd.sheetId, cmd.col, cmd.row, cmd.pivotId, cmd.table);
57878
+ break;
57142
57879
  }
57143
57880
  }
57144
57881
  insertNewPivot(pivotId, sheetId) {
@@ -57164,7 +57901,7 @@ class InsertPivotPlugin extends UIPlugin {
57164
57901
  const formulaId = this.getters.getPivotFormulaId(pivotId);
57165
57902
  this.dispatch("CREATE_SHEET", {
57166
57903
  sheetId,
57167
- name: _t("Pivot #%s", formulaId),
57904
+ name: _t("Pivot #%(formulaId)s", { formulaId }),
57168
57905
  position,
57169
57906
  });
57170
57907
  this.dispatch("ACTIVATE_SHEET", {
@@ -57178,6 +57915,92 @@ class InsertPivotPlugin extends UIPlugin {
57178
57915
  content: `=PIVOT(${formulaId})`,
57179
57916
  });
57180
57917
  }
57918
+ duplicatePivotInNewSheet(pivotId, newPivotId, newSheetId) {
57919
+ this.dispatch("DUPLICATE_PIVOT", {
57920
+ pivotId,
57921
+ newPivotId,
57922
+ });
57923
+ const activeSheetId = this.getters.getActiveSheetId();
57924
+ const position = this.getters.getSheetIds().indexOf(activeSheetId) + 1;
57925
+ const formulaId = this.getters.getPivotFormulaId(newPivotId);
57926
+ const newPivotName = this.getters.getPivotName(newPivotId);
57927
+ this.dispatch("CREATE_SHEET", {
57928
+ sheetId: newSheetId,
57929
+ name: this.getPivotDuplicateSheetName(_t("%(newPivotName)s (Pivot #%(formulaId)s)", {
57930
+ newPivotName,
57931
+ formulaId,
57932
+ })),
57933
+ position,
57934
+ });
57935
+ this.dispatch("ACTIVATE_SHEET", { sheetIdFrom: activeSheetId, sheetIdTo: newSheetId });
57936
+ this.dispatch("UPDATE_CELL", {
57937
+ sheetId: newSheetId,
57938
+ col: 0,
57939
+ row: 0,
57940
+ content: `=PIVOT(${formulaId})`,
57941
+ });
57942
+ }
57943
+ getPivotDuplicateSheetName(pivotName) {
57944
+ let i = 1;
57945
+ const names = this.getters.getSheetIds().map((id) => this.getters.getSheetName(id));
57946
+ let name = pivotName;
57947
+ while (names.includes(name)) {
57948
+ name = `${pivotName} (${i})`;
57949
+ i++;
57950
+ }
57951
+ return name;
57952
+ }
57953
+ insertPivotWithTable(sheetId, col, row, pivotId, table) {
57954
+ const { cols, rows, measures, fieldsType } = table;
57955
+ const pivotTable = new SpreadsheetPivotTable(cols, rows, measures, fieldsType || {});
57956
+ this.resizeSheet(sheetId, col, row, pivotTable);
57957
+ const pivotFormulaId = this.getters.getPivotFormulaId(pivotId);
57958
+ this.dispatch("UPDATE_CELL", {
57959
+ sheetId,
57960
+ col,
57961
+ row,
57962
+ content: `=PIVOT("${pivotFormulaId}")`,
57963
+ });
57964
+ const zone = {
57965
+ left: col,
57966
+ right: col,
57967
+ top: row,
57968
+ bottom: row,
57969
+ };
57970
+ const numberOfHeaders = pivotTable.columns.length - 1;
57971
+ this.dispatch("CREATE_TABLE", {
57972
+ tableType: "dynamic",
57973
+ sheetId,
57974
+ ranges: [this.getters.getRangeDataFromZone(sheetId, zone)],
57975
+ config: { ...PIVOT_TABLE_CONFIG, numberOfHeaders },
57976
+ });
57977
+ }
57978
+ resizeSheet(sheetId, col, row, table) {
57979
+ const colLimit = table.getNumberOfDataColumns() + 1; // +1 for the Top-Left
57980
+ const numberCols = this.getters.getNumberCols(sheetId);
57981
+ const deltaCol = numberCols - col;
57982
+ if (deltaCol < colLimit) {
57983
+ this.dispatch("ADD_COLUMNS_ROWS", {
57984
+ dimension: "COL",
57985
+ base: numberCols - 1,
57986
+ sheetId: sheetId,
57987
+ quantity: colLimit - deltaCol,
57988
+ position: "after",
57989
+ });
57990
+ }
57991
+ const rowLimit = table.columns.length + table.rows.length;
57992
+ const numberRows = this.getters.getNumberRows(sheetId);
57993
+ const deltaRow = numberRows - row;
57994
+ if (deltaRow < rowLimit) {
57995
+ this.dispatch("ADD_COLUMNS_ROWS", {
57996
+ dimension: "ROW",
57997
+ base: numberRows - 1,
57998
+ sheetId: sheetId,
57999
+ quantity: rowLimit - deltaRow,
58000
+ position: "after",
58001
+ });
58002
+ }
58003
+ }
57181
58004
  }
57182
58005
 
57183
58006
  class SortPlugin extends UIPlugin {
@@ -57358,6 +58181,7 @@ class SheetUIPlugin extends UIPlugin {
57358
58181
  static getters = [
57359
58182
  "doesCellHaveGridIcon",
57360
58183
  "getCellWidth",
58184
+ "getCellIconSrc",
57361
58185
  "getTextWidth",
57362
58186
  "getCellText",
57363
58187
  "getCellMultiLineText",
@@ -57408,7 +58232,7 @@ class SheetUIPlugin extends UIPlugin {
57408
58232
  const multiLineText = splitTextToWidth(this.ctx, content, style, undefined);
57409
58233
  contentWidth += Math.max(...multiLineText.map((line) => computeTextWidth(this.ctx, line, style)));
57410
58234
  }
57411
- const icon = this.getters.getConditionalIcon(position);
58235
+ const icon = this.getters.getCellIconSrc(position);
57412
58236
  if (icon) {
57413
58237
  contentWidth += computeIconWidth(style);
57414
58238
  }
@@ -57425,6 +58249,16 @@ class SheetUIPlugin extends UIPlugin {
57425
58249
  }
57426
58250
  return contentWidth;
57427
58251
  }
58252
+ getCellIconSrc(position) {
58253
+ const callbacks = iconsOnCellRegistry.getAll();
58254
+ for (const callback of callbacks) {
58255
+ const imageSrc = callback(this.getters, position);
58256
+ if (imageSrc) {
58257
+ return imageSrc;
58258
+ }
58259
+ }
58260
+ return undefined;
58261
+ }
57428
58262
  getTextWidth(text, style) {
57429
58263
  return computeTextWidth(this.ctx, text, style);
57430
58264
  }
@@ -61200,7 +62034,7 @@ class BottomBarSheet extends owl.Component {
61200
62034
  this.DOMFocusableElementStore.focus();
61201
62035
  }
61202
62036
  }
61203
- onClickSheetName(ev) {
62037
+ onMouseEventSheetName(ev) {
61204
62038
  if (this.state.isEditing)
61205
62039
  ev.stopPropagation();
61206
62040
  }
@@ -62928,7 +63762,7 @@ class TopBar extends owl.Component {
62928
63762
  }
62929
63763
  get topbarComponents() {
62930
63764
  return topbarComponentRegistry
62931
- .getAll()
63765
+ .getAllOrdered()
62932
63766
  .filter((item) => !item.isVisible || item.isVisible(this.env));
62933
63767
  }
62934
63768
  onExternalClick(ev) {
@@ -67341,6 +68175,7 @@ const registries = {
67341
68175
  linkMenuRegistry,
67342
68176
  functionRegistry,
67343
68177
  featurePluginRegistry,
68178
+ iconsOnCellRegistry,
67344
68179
  statefulUIPluginRegistry,
67345
68180
  coreViewsPluginRegistry,
67346
68181
  corePluginRegistry,
@@ -67558,6 +68393,6 @@ exports.tokenColors = tokenColors;
67558
68393
  exports.tokenize = tokenize;
67559
68394
 
67560
68395
 
67561
- __info__.version = "17.4.0-alpha.9";
67562
- __info__.date = "2024-06-26T11:09:20.284Z";
67563
- __info__.hash = "526be20";
68396
+ __info__.version = "17.4.0";
68397
+ __info__.date = "2024-07-12T04:41:51.675Z";
68398
+ __info__.hash = "f8e8543";