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