@odoo/o-spreadsheet 17.4.0-alpha.4 → 17.4.0-alpha.6

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.
@@ -3,9 +3,9 @@
3
3
  /**
4
4
  * This file is generated by o-spreadsheet build tools. Do not edit it.
5
5
  * @see https://github.com/odoo/o-spreadsheet
6
- * @version 17.4.0-alpha.4
7
- * @date 2024-06-12T14:00:22.046Z
8
- * @hash cefb0e4
6
+ * @version 17.4.0-alpha.6
7
+ * @date 2024-06-19T13:46:27.157Z
8
+ * @hash a4f22e4
9
9
  */
10
10
 
11
11
  (function (exports, owl) {
@@ -5283,7 +5283,7 @@
5283
5283
  return text;
5284
5284
  }
5285
5285
  const ellipsis = "…";
5286
- const ellipsisWidth = computeCachedTextWidth(ctx, text);
5286
+ const ellipsisWidth = computeCachedTextWidth(ctx, ellipsis);
5287
5287
  if (width <= ellipsisWidth) {
5288
5288
  return text;
5289
5289
  }
@@ -6201,7 +6201,7 @@
6201
6201
  };
6202
6202
  }
6203
6203
  isPasteAllowed(sheetId, target, content, clipboardOptions) {
6204
- if (!("cells" in content)) {
6204
+ if (!content.cells) {
6205
6205
  return "Success" /* CommandResult.Success */;
6206
6206
  }
6207
6207
  if (clipboardOptions?.isCutOperation && clipboardOptions?.pasteOption !== undefined) {
@@ -6215,6 +6215,17 @@
6215
6215
  return "WrongPasteSelection" /* CommandResult.WrongPasteSelection */;
6216
6216
  }
6217
6217
  }
6218
+ const clipboardHeight = content.cells.length;
6219
+ const clipboardWidth = content.cells[0].length;
6220
+ for (const zone of getPasteZones(target, content.cells)) {
6221
+ if (this.getters.doesIntersectMerge(sheetId, zone)) {
6222
+ if (target.length > 1 ||
6223
+ !this.getters.isSingleCellOrMerge(sheetId, target[0]) ||
6224
+ clipboardHeight * clipboardWidth !== 1) {
6225
+ return "WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */;
6226
+ }
6227
+ }
6228
+ }
6218
6229
  return "Success" /* CommandResult.Success */;
6219
6230
  }
6220
6231
  /**
@@ -6454,13 +6465,22 @@
6454
6465
  }
6455
6466
  const { rowsIndexes, columnsIndexes } = data;
6456
6467
  const sheetId = data.sheetId;
6457
- const cellPositions = rowsIndexes.map((row) => columnsIndexes.map((col) => ({ col, row, sheetId })));
6458
- return {
6459
- cellPositions,
6460
- };
6468
+ const cfRules = [];
6469
+ for (const row of rowsIndexes) {
6470
+ const cfRuleInRow = [];
6471
+ for (const col of columnsIndexes) {
6472
+ const cfRules = Array.from(this.getters.getRulesByCell(sheetId, col, row));
6473
+ cfRuleInRow.push({
6474
+ position: { col, row, sheetId },
6475
+ rules: cfRules,
6476
+ });
6477
+ }
6478
+ cfRules.push(cfRuleInRow);
6479
+ }
6480
+ return { cfRules };
6461
6481
  }
6462
6482
  paste(target, clippedContent, options) {
6463
- if (!clippedContent?.cellPositions ||
6483
+ if (!clippedContent?.cfRules ||
6464
6484
  options?.pasteOption === "asValue" ||
6465
6485
  !("zones" in target) ||
6466
6486
  !target.zones.length) {
@@ -6469,7 +6489,7 @@
6469
6489
  const zones = target.zones;
6470
6490
  const sheetId = target.sheetId;
6471
6491
  if (!options?.isCutOperation) {
6472
- this.pasteFromCopy(sheetId, zones, clippedContent.cellPositions);
6492
+ this.pasteFromCopy(sheetId, zones, clippedContent.cfRules, options);
6473
6493
  }
6474
6494
  else {
6475
6495
  this.pasteFromCut(sheetId, zones, clippedContent);
@@ -6477,12 +6497,12 @@
6477
6497
  }
6478
6498
  pasteFromCut(sheetId, target, content) {
6479
6499
  const selection = target[0];
6480
- this.pasteZone(sheetId, selection.left, selection.top, content.cellPositions, {
6500
+ this.pasteZone(sheetId, selection.left, selection.top, content.cfRules, {
6481
6501
  isCutOperation: true,
6482
6502
  });
6483
6503
  }
6484
- pasteZone(sheetId, col, row, positions, clipboardOptions) {
6485
- for (const [r, rowCells] of positions.entries()) {
6504
+ pasteZone(sheetId, col, row, cfRules, clipboardOptions) {
6505
+ for (const [r, rowCells] of cfRules.entries()) {
6486
6506
  for (const [c, origin] of rowCells.entries()) {
6487
6507
  const position = { col: col + c, row: row + r, sheetId };
6488
6508
  this.pasteCf(origin, position, clipboardOptions?.isCutOperation);
@@ -6490,23 +6510,21 @@
6490
6510
  }
6491
6511
  }
6492
6512
  pasteCf(origin, target, isCutOperation) {
6493
- const zone = positionToZone(target);
6494
- for (const rule of this.getters.getConditionalFormats(origin.sheetId)) {
6495
- for (const range of rule.ranges) {
6496
- if (isInside(origin.col, origin.row, this.getters.getRangeFromSheetXC(origin.sheetId, range).zone)) {
6497
- const toRemoveZones = [];
6498
- if (isCutOperation) {
6499
- //remove from current rule
6500
- toRemoveZones.push(positionToZone(origin));
6501
- }
6502
- if (origin.sheetId === target.sheetId) {
6503
- this.adaptCFRules(origin.sheetId, rule, [zone], toRemoveZones);
6504
- }
6505
- else {
6506
- this.adaptCFRules(origin.sheetId, rule, [], toRemoveZones);
6507
- const cfToCopyTo = this.getCFToCopyTo(target.sheetId, rule);
6508
- this.adaptCFRules(target.sheetId, cfToCopyTo, [zone], []);
6509
- }
6513
+ if (origin?.rules && origin.rules.length > 0) {
6514
+ const zone = positionToZone(target);
6515
+ for (const rule of origin.rules) {
6516
+ const toRemoveZones = [];
6517
+ if (isCutOperation) {
6518
+ //remove from current rule
6519
+ toRemoveZones.push(positionToZone(origin.position));
6520
+ }
6521
+ if (origin.position.sheetId === target.sheetId) {
6522
+ this.adaptCFRules(origin.position.sheetId, rule, [zone], toRemoveZones);
6523
+ }
6524
+ else {
6525
+ this.adaptCFRules(origin.position.sheetId, rule, [], toRemoveZones);
6526
+ const cfToCopyTo = this.getCFToCopyTo(target.sheetId, rule);
6527
+ this.adaptCFRules(target.sheetId, cfToCopyTo, [zone], []);
6510
6528
  }
6511
6529
  }
6512
6530
  }
@@ -6549,13 +6567,20 @@
6549
6567
  }
6550
6568
  const { rowsIndexes, columnsIndexes } = data;
6551
6569
  const sheetId = data.sheetId;
6552
- const cellPositions = rowsIndexes.map((row) => columnsIndexes.map((col) => ({ col, row, sheetId })));
6553
- return {
6554
- cellPositions,
6555
- };
6570
+ const dvRules = [];
6571
+ for (const row of rowsIndexes) {
6572
+ const dvRuleInRow = [];
6573
+ for (const col of columnsIndexes) {
6574
+ const position = { sheetId, col, row };
6575
+ const rule = this.getters.getValidationRuleForCell(position);
6576
+ dvRuleInRow.push({ position, rule });
6577
+ }
6578
+ dvRules.push(dvRuleInRow);
6579
+ }
6580
+ return { dvRules };
6556
6581
  }
6557
6582
  paste(target, clippedContent, options) {
6558
- if (!clippedContent?.cellPositions) {
6583
+ if (!clippedContent?.dvRules) {
6559
6584
  return;
6560
6585
  }
6561
6586
  if (options?.pasteOption) {
@@ -6567,7 +6592,7 @@
6567
6592
  const zones = target.zones;
6568
6593
  const sheetId = target.sheetId;
6569
6594
  if (!options?.isCutOperation) {
6570
- this.pasteFromCopy(sheetId, zones, clippedContent.cellPositions);
6595
+ this.pasteFromCopy(sheetId, zones, clippedContent.dvRules);
6571
6596
  }
6572
6597
  else {
6573
6598
  this.pasteFromCut(sheetId, zones, clippedContent);
@@ -6575,12 +6600,12 @@
6575
6600
  }
6576
6601
  pasteFromCut(sheetId, target, content) {
6577
6602
  const selection = target[0];
6578
- this.pasteZone(sheetId, selection.left, selection.top, content.cellPositions, {
6603
+ this.pasteZone(sheetId, selection.left, selection.top, content.dvRules, {
6579
6604
  isCutOperation: true,
6580
6605
  });
6581
6606
  }
6582
- pasteZone(sheetId, col, row, positions, clipboardOptions) {
6583
- for (const [r, rowCells] of positions.entries()) {
6607
+ pasteZone(sheetId, col, row, dvRules, clipboardOptions) {
6608
+ for (const [r, rowCells] of dvRules.entries()) {
6584
6609
  for (const [c, origin] of rowCells.entries()) {
6585
6610
  const position = { col: col + c, row: row + r, sheetId };
6586
6611
  this.pasteDataValidation(origin, position, clipboardOptions?.isCutOperation);
@@ -6588,41 +6613,43 @@
6588
6613
  }
6589
6614
  }
6590
6615
  pasteDataValidation(origin, target, isCutOperation) {
6591
- const rule = this.getters.getValidationRuleForCell(origin);
6592
- if (!rule) {
6593
- const targetRule = this.getters.getValidationRuleForCell(target);
6594
- if (targetRule) {
6595
- // Remove the data validation rule on the target cell
6596
- this.adaptDataValidationRule(target.sheetId, targetRule, [], [positionToZone(target)]);
6597
- }
6598
- return;
6599
- }
6600
- const zone = positionToZone(target);
6601
- for (const range of rule.ranges) {
6602
- if (isInside(origin.col, origin.row, range.zone)) {
6603
- const toRemoveZone = [];
6604
- if (isCutOperation) {
6605
- toRemoveZone.push(positionToZone(origin));
6606
- }
6607
- if (origin.sheetId === target.sheetId) {
6608
- this.adaptDataValidationRule(origin.sheetId, rule, [zone], toRemoveZone);
6616
+ if (origin) {
6617
+ const zone = positionToZone(target);
6618
+ const rule = origin.rule;
6619
+ if (!rule) {
6620
+ const targetRule = this.getters.getValidationRuleForCell(target);
6621
+ if (targetRule) {
6622
+ // Remove the data validation rule on the target cell
6623
+ this.adaptDataValidationRule(target.sheetId, targetRule, [], [zone]);
6609
6624
  }
6610
- else {
6611
- this.adaptDataValidationRule(origin.sheetId, rule, [], toRemoveZone);
6612
- const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule);
6613
- this.adaptDataValidationRule(target.sheetId, copyToRule, [zone], []);
6625
+ return;
6626
+ }
6627
+ const toRemoveZone = [];
6628
+ if (isCutOperation) {
6629
+ toRemoveZone.push(positionToZone(origin.position));
6630
+ }
6631
+ if (origin.position.sheetId === target.sheetId) {
6632
+ const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule, false);
6633
+ this.adaptDataValidationRule(origin.position.sheetId, copyToRule, [zone], toRemoveZone);
6634
+ }
6635
+ else {
6636
+ const originRule = this.getters.getValidationRuleForCell(origin.position);
6637
+ if (originRule) {
6638
+ this.adaptDataValidationRule(origin.position.sheetId, originRule, [], toRemoveZone);
6614
6639
  }
6640
+ const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule);
6641
+ this.adaptDataValidationRule(target.sheetId, copyToRule, [zone], []);
6615
6642
  }
6616
6643
  }
6617
6644
  }
6618
- getDataValidationRuleToCopyTo(targetSheetId, originRule) {
6645
+ getDataValidationRuleToCopyTo(targetSheetId, originRule, newId = true) {
6619
6646
  const ruleInTargetSheet = this.getters
6620
6647
  .getDataValidationRules(targetSheetId)
6621
6648
  .find((rule) => deepEquals(originRule.criterion, rule.criterion) &&
6622
6649
  originRule.isBlocking === rule.isBlocking);
6623
6650
  return ruleInTargetSheet
6624
6651
  ? ruleInTargetSheet
6625
- : { ...originRule, id: this.uuidGenerator.uuidv4(), ranges: [] };
6652
+ : { ...originRule, id: newId ? this.uuidGenerator.uuidv4() : originRule.id, ranges: [] };
6626
6653
  }
6627
6654
  /**
6628
6655
  * Add or remove XCs to a given data validation rule.
@@ -6714,69 +6741,63 @@
6714
6741
  }
6715
6742
 
6716
6743
  class MergeClipboardHandler extends AbstractCellClipboardHandler {
6717
- isPasteAllowed(sheetId, target, content) {
6718
- if (!("cells" in content)) {
6719
- return "Success" /* CommandResult.Success */;
6744
+ copy(data) {
6745
+ if (!data.zones.length) {
6746
+ return;
6720
6747
  }
6721
- const clipboardHeight = content.cells.length;
6722
- const clipboardWidth = content.cells[0].length;
6723
- for (const zone of getPasteZones(target, content.cells)) {
6724
- if (this.getters.doesIntersectMerge(sheetId, zone)) {
6725
- if (target.length > 1 ||
6726
- !this.getters.isSingleCellOrMerge(sheetId, target[0]) ||
6727
- clipboardHeight * clipboardWidth !== 1) {
6728
- return "WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */;
6729
- }
6748
+ const sheetId = this.getters.getActiveSheetId();
6749
+ const { rowsIndexes, columnsIndexes } = data;
6750
+ const merges = [];
6751
+ for (const row of rowsIndexes) {
6752
+ const mergesInRow = [];
6753
+ for (const col of columnsIndexes) {
6754
+ const position = { col, row, sheetId };
6755
+ mergesInRow.push(this.getters.getMerge(position));
6730
6756
  }
6757
+ merges.push(mergesInRow);
6731
6758
  }
6732
- return "Success" /* CommandResult.Success */;
6759
+ return { merges };
6733
6760
  }
6734
6761
  /**
6735
6762
  * Paste the clipboard content in the given target
6736
6763
  */
6737
6764
  paste(target, content, options) {
6738
- if (options?.isCutOperation || !("zones" in target) || !target.zones.length) {
6765
+ if (!content.merges ||
6766
+ options?.isCutOperation ||
6767
+ !("zones" in target) ||
6768
+ !target.zones.length) {
6739
6769
  return;
6740
6770
  }
6741
- this.pasteFromCopy(target.sheetId, target.zones, content.cells, options);
6771
+ this.pasteFromCopy(target.sheetId, target.zones, content.merges, options);
6742
6772
  }
6743
- pasteZone(sheetId, col, row, cells) {
6744
- for (const [r, rowCells] of cells.entries()) {
6745
- for (const [c, origin] of rowCells.entries()) {
6746
- if (!origin.position) {
6747
- continue;
6748
- }
6773
+ pasteZone(sheetId, col, row, merges) {
6774
+ for (const [r, rowMerges] of merges.entries()) {
6775
+ for (const [c, originMerge] of rowMerges.entries()) {
6749
6776
  const position = { col: col + c, row: row + r, sheetId };
6750
- this.pasteMergeIfExist(origin.position, position);
6777
+ this.pasteMerge(originMerge, position);
6751
6778
  }
6752
6779
  }
6753
6780
  }
6754
- /**
6755
- * If the origin position given is the top left of a merge, merge the target
6756
- * position.
6757
- */
6758
- pasteMergeIfExist(origin, target) {
6759
- let { sheetId, col, row } = origin;
6760
- const { col: mainCellColOrigin, row: mainCellRowOrigin } = this.getters.getMainCellPosition(origin);
6761
- if (mainCellColOrigin === col && mainCellRowOrigin === row) {
6762
- const merge = this.getters.getMerge(origin);
6763
- if (!merge) {
6764
- return;
6765
- }
6766
- ({ sheetId, col, row } = target);
6767
- this.dispatch("ADD_MERGE", {
6768
- sheetId,
6769
- force: true,
6770
- target: [
6771
- {
6772
- left: col,
6773
- top: row,
6774
- right: col + merge.right - merge.left,
6775
- bottom: row + merge.bottom - merge.top,
6776
- },
6777
- ],
6778
- });
6781
+ pasteMerge(originMerge, target) {
6782
+ if (!originMerge) {
6783
+ return;
6784
+ }
6785
+ if (this.getters.isInMerge(target)) {
6786
+ return;
6779
6787
  }
6788
+ const { sheetId, col, row } = target;
6789
+ this.dispatch("ADD_MERGE", {
6790
+ sheetId,
6791
+ force: true,
6792
+ target: [
6793
+ {
6794
+ left: col,
6795
+ top: row,
6796
+ right: col + originMerge.right - originMerge.left,
6797
+ bottom: row + originMerge.bottom - originMerge.top,
6798
+ },
6799
+ ],
6800
+ });
6780
6801
  }
6781
6802
  }
6782
6803
 
@@ -7756,6 +7777,486 @@
7756
7777
  };
7757
7778
  }
7758
7779
 
7780
+ function boolAnd(args) {
7781
+ let foundBoolean = false;
7782
+ let acc = true;
7783
+ conditionalVisitBoolean(args, (arg) => {
7784
+ foundBoolean = true;
7785
+ acc = acc && arg;
7786
+ return acc;
7787
+ });
7788
+ return {
7789
+ foundBoolean,
7790
+ result: acc,
7791
+ };
7792
+ }
7793
+ function boolOr(args) {
7794
+ let foundBoolean = false;
7795
+ let acc = false;
7796
+ conditionalVisitBoolean(args, (arg) => {
7797
+ foundBoolean = true;
7798
+ acc = acc || arg;
7799
+ return !acc;
7800
+ });
7801
+ return {
7802
+ foundBoolean,
7803
+ result: acc,
7804
+ };
7805
+ }
7806
+
7807
+ function sum(values, locale) {
7808
+ return reduceNumbers(values, (acc, a) => acc + a, 0, locale);
7809
+ }
7810
+ function countUnique(args) {
7811
+ return reduceAny(args, (acc, a) => (isDataNonEmpty(a) ? acc.add(a?.value) : acc), new Set()).size;
7812
+ }
7813
+
7814
+ function assertSameNumberOfElements(...args) {
7815
+ const dims = args[0].length;
7816
+ args.forEach((arg, i) => assert(() => arg.length === dims, _t("[[FUNCTION_NAME]] has mismatched dimensions for argument %s (%s vs %s).", i.toString(), dims.toString(), arg.length.toString())));
7817
+ }
7818
+ function average(values, locale) {
7819
+ let count = 0;
7820
+ const sum = reduceNumbers(values, (acc, a) => {
7821
+ count += 1;
7822
+ return acc + a;
7823
+ }, 0, locale);
7824
+ assertNotZero(count);
7825
+ return sum / count;
7826
+ }
7827
+ function countNumbers(values, locale) {
7828
+ let count = 0;
7829
+ for (let n of values) {
7830
+ if (isMatrix(n)) {
7831
+ for (let i of n) {
7832
+ for (let j of i) {
7833
+ if (typeof j.value === "number") {
7834
+ count += 1;
7835
+ }
7836
+ }
7837
+ }
7838
+ }
7839
+ else {
7840
+ const value = n?.value;
7841
+ if (!isEvaluationError(value) &&
7842
+ (typeof value !== "string" || isNumber(value, locale) || parseDateTime(value, locale))) {
7843
+ count += 1;
7844
+ }
7845
+ }
7846
+ }
7847
+ return count;
7848
+ }
7849
+ function countAny(values) {
7850
+ return reduceAny(values, (acc, a) => (a !== undefined && a.value !== null ? acc + 1 : acc), 0);
7851
+ }
7852
+ function max(values, locale) {
7853
+ const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, locale);
7854
+ return result === -Infinity ? 0 : result;
7855
+ }
7856
+ function min(values, locale) {
7857
+ const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
7858
+ return result === Infinity ? 0 : result;
7859
+ }
7860
+
7861
+ const pivotTimeAdapterRegistry = new Registry();
7862
+ function pivotTimeAdapter(granularity) {
7863
+ return pivotTimeAdapterRegistry.get(granularity);
7864
+ }
7865
+ /**
7866
+ * The Time Adapter: Managing Time Periods for Pivot Functions
7867
+ *
7868
+ * Overview:
7869
+ * A time adapter is responsible for managing time periods associated with pivot functions.
7870
+ * Each type of period (day, week, month, quarter, etc.) has its own dedicated adapter.
7871
+ * The adapter's primary role is to normalize period values between spreadsheet functions,
7872
+ * and the pivot.
7873
+ * By normalizing the period value, it can be stored consistently in the pivot.
7874
+ *
7875
+ * Normalization Process:
7876
+ * When working with functions in the spreadsheet, the time adapter normalizes
7877
+ * the provided period to facilitate accurate lookup of values in the pivot.
7878
+ * For instance, if the spreadsheet function represents a day period as a number generated
7879
+ * by the DATE function (DATE(2023, 12, 25)), the time adapter will normalize it accordingly.
7880
+ *
7881
+ */
7882
+ /**
7883
+ * Normalized value: "12/25/2023"
7884
+ *
7885
+ * Note: Those two format are equivalent:
7886
+ * - "MM/dd/yyyy" (luxon format)
7887
+ * - "mm/dd/yyyy" (spreadsheet format)
7888
+ **/
7889
+ const dayAdapter = {
7890
+ normalizeFunctionValue(value) {
7891
+ const date = toNumber(value, DEFAULT_LOCALE);
7892
+ return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/dd/yyyy" });
7893
+ },
7894
+ getFormat(locale) {
7895
+ return (locale ?? DEFAULT_LOCALE).dateFormat;
7896
+ },
7897
+ formatValue(normalizedValue, locale) {
7898
+ locale = locale ?? DEFAULT_LOCALE;
7899
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7900
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7901
+ },
7902
+ toCellValue(normalizedValue) {
7903
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7904
+ },
7905
+ };
7906
+ /**
7907
+ * normalizes day of month number
7908
+ */
7909
+ const dayOfMonthAdapter = {
7910
+ normalizeFunctionValue(value) {
7911
+ const day = toNumber(value, DEFAULT_LOCALE);
7912
+ if (day < 1 || day > 31) {
7913
+ throw new EvaluationError(_t("%s is not a valid day of month (it should be a number between 1 and 31)", day));
7914
+ }
7915
+ return day;
7916
+ },
7917
+ getFormat() {
7918
+ return "0";
7919
+ },
7920
+ formatValue(normalizedValue, locale) {
7921
+ locale = locale ?? DEFAULT_LOCALE;
7922
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7923
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7924
+ },
7925
+ toCellValue(normalizedValue) {
7926
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7927
+ },
7928
+ };
7929
+ /**
7930
+ * Normalized value: "2/2023" for week 2 of 2023
7931
+ */
7932
+ const weekAdapter = {
7933
+ normalizeFunctionValue(value) {
7934
+ const [week, year] = value.split("/");
7935
+ return `${Number(week)}/${Number(year)}`;
7936
+ },
7937
+ getFormat() {
7938
+ return undefined;
7939
+ },
7940
+ formatValue(normalizedValue) {
7941
+ const [week, year] = normalizedValue.split("/");
7942
+ return _t("W%(week)s %(year)s", { week, year });
7943
+ },
7944
+ toCellValue(normalizedValue) {
7945
+ return this.formatValue(normalizedValue);
7946
+ },
7947
+ };
7948
+ /**
7949
+ * normalizes iso week number
7950
+ */
7951
+ const isoWeekNumberAdapter = {
7952
+ normalizeFunctionValue(value) {
7953
+ const isoWeek = toNumber(value, DEFAULT_LOCALE);
7954
+ if (isoWeek < 0 || isoWeek > 53) {
7955
+ throw new EvaluationError(_t("%s is not a valid week (it should be a number between 0 and 53)", isoWeek));
7956
+ }
7957
+ return isoWeek;
7958
+ },
7959
+ getFormat() {
7960
+ return "0";
7961
+ },
7962
+ formatValue(normalizedValue, locale) {
7963
+ locale = locale ?? DEFAULT_LOCALE;
7964
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7965
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7966
+ },
7967
+ toCellValue(normalizedValue) {
7968
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7969
+ },
7970
+ };
7971
+ /**
7972
+ * normalized month value is a string formatted as "MM/yyyy" (luxon format)
7973
+ * e.g. "01/2020" for January 2020
7974
+ */
7975
+ const monthAdapter = {
7976
+ normalizeFunctionValue(value) {
7977
+ const date = toNumber(value, DEFAULT_LOCALE);
7978
+ return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
7979
+ },
7980
+ getFormat() {
7981
+ return "mmmm yyyy";
7982
+ },
7983
+ formatValue(normalizedValue, locale) {
7984
+ locale = locale ?? DEFAULT_LOCALE;
7985
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7986
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7987
+ },
7988
+ toCellValue(normalizedValue) {
7989
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7990
+ },
7991
+ };
7992
+ /**
7993
+ * normalizes month number
7994
+ */
7995
+ const monthNumberAdapter = {
7996
+ normalizeFunctionValue(value) {
7997
+ const month = toNumber(value, DEFAULT_LOCALE);
7998
+ if (month < 1 || month > 12) {
7999
+ throw new EvaluationError(_t("%s is not a valid month (it should be a number between 1 and 12)", month));
8000
+ }
8001
+ return month;
8002
+ },
8003
+ getFormat() {
8004
+ return "0";
8005
+ },
8006
+ formatValue(normalizedValue, locale) {
8007
+ locale = locale ?? DEFAULT_LOCALE;
8008
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
8009
+ return formatValue(value, { locale, format: this.getFormat(locale) });
8010
+ },
8011
+ toCellValue(normalizedValue) {
8012
+ return MONTHS[toNumber(normalizedValue, DEFAULT_LOCALE) - 1].toString();
8013
+ },
8014
+ };
8015
+ /**
8016
+ * normalized quarter value is "quarter/year"
8017
+ * e.g. "1/2020" for Q1 2020
8018
+ */
8019
+ const quarterAdapter = {
8020
+ normalizeFunctionValue(value) {
8021
+ const [quarter, year] = value.split("/");
8022
+ return `${quarter}/${year}`;
8023
+ },
8024
+ getFormat() {
8025
+ return undefined;
8026
+ },
8027
+ formatValue(normalizedValue) {
8028
+ const [quarter, year] = normalizedValue.split("/");
8029
+ return _t("Q%(quarter)s %(year)s", { quarter, year });
8030
+ },
8031
+ toCellValue(normalizedValue) {
8032
+ return this.formatValue(normalizedValue);
8033
+ },
8034
+ };
8035
+ /**
8036
+ * normalizes quarter number
8037
+ */
8038
+ const quarterNumberAdapter = {
8039
+ normalizeFunctionValue(value) {
8040
+ const quarter = toNumber(value, DEFAULT_LOCALE);
8041
+ if (quarter < 1 || quarter > 4) {
8042
+ throw new EvaluationError(_t("%s is not a valid quarter (it should be a number between 1 and 4)", quarter));
8043
+ }
8044
+ return quarter;
8045
+ },
8046
+ getFormat() {
8047
+ return "0";
8048
+ },
8049
+ formatValue(normalizedValue, locale) {
8050
+ locale = locale ?? DEFAULT_LOCALE;
8051
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
8052
+ return formatValue(value, { locale, format: this.getFormat(locale) });
8053
+ },
8054
+ toCellValue(normalizedValue) {
8055
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
8056
+ },
8057
+ };
8058
+ const yearAdapter = {
8059
+ normalizeFunctionValue(value) {
8060
+ return toNumber(value, DEFAULT_LOCALE);
8061
+ },
8062
+ getFormat() {
8063
+ return "0";
8064
+ },
8065
+ formatValue(normalizedValue, locale) {
8066
+ locale = locale ?? DEFAULT_LOCALE;
8067
+ return formatValue(normalizedValue, { locale, format: "0" });
8068
+ },
8069
+ toCellValue(normalizedValue) {
8070
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
8071
+ },
8072
+ };
8073
+ pivotTimeAdapterRegistry
8074
+ .add("day", dayAdapter)
8075
+ .add("week", weekAdapter)
8076
+ .add("month", monthAdapter)
8077
+ .add("quarter", quarterAdapter)
8078
+ .add("year", yearAdapter)
8079
+ .add("day_of_month", dayOfMonthAdapter)
8080
+ .add("iso_week_number", isoWeekNumberAdapter)
8081
+ .add("month_number", monthNumberAdapter)
8082
+ .add("quarter_number", quarterNumberAdapter)
8083
+ .add("year_number", yearAdapter);
8084
+
8085
+ const AGGREGATOR_NAMES = {
8086
+ count: _t("Count"),
8087
+ count_distinct: _t("Count Distinct"),
8088
+ bool_and: _t("Boolean And"),
8089
+ bool_or: _t("Boolean Or"),
8090
+ max: _t("Maximum"),
8091
+ min: _t("Minimum"),
8092
+ avg: _t("Average"),
8093
+ sum: _t("Sum"),
8094
+ };
8095
+ const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8096
+ const AGGREGATORS_BY_FIELD_TYPE = {
8097
+ integer: NUMBER_CHAR_AGGREGATORS,
8098
+ char: NUMBER_CHAR_AGGREGATORS,
8099
+ boolean: ["count_distinct", "count", "bool_and", "bool_or"],
8100
+ };
8101
+ const AGGREGATORS = {};
8102
+ for (const type in AGGREGATORS_BY_FIELD_TYPE) {
8103
+ AGGREGATORS[type] = {};
8104
+ for (const aggregator of AGGREGATORS_BY_FIELD_TYPE[type]) {
8105
+ AGGREGATORS[type][aggregator] = AGGREGATOR_NAMES[aggregator];
8106
+ }
8107
+ }
8108
+ const AGGREGATORS_FN = {
8109
+ count: {
8110
+ fn: (args) => countAny([args]),
8111
+ format: () => "0",
8112
+ },
8113
+ count_distinct: {
8114
+ fn: (args) => countUnique([args]),
8115
+ format: () => "0",
8116
+ },
8117
+ bool_and: {
8118
+ fn: (args) => boolAnd([args]).result,
8119
+ format: () => undefined,
8120
+ },
8121
+ bool_or: {
8122
+ fn: (args) => boolOr([args]).result,
8123
+ format: () => undefined,
8124
+ },
8125
+ max: {
8126
+ fn: (args, locale) => max([args], locale),
8127
+ format: inferFormat,
8128
+ },
8129
+ min: {
8130
+ fn: (args, locale) => min([args], locale),
8131
+ format: inferFormat,
8132
+ },
8133
+ avg: {
8134
+ fn: (args, locale) => average([args], locale),
8135
+ format: inferFormat,
8136
+ },
8137
+ sum: {
8138
+ fn: (args, locale) => sum([args], locale),
8139
+ format: inferFormat,
8140
+ },
8141
+ };
8142
+ function makePivotFormulaFromPivotCell(pivotFormulaId, pivotCell) {
8143
+ switch (pivotCell.type) {
8144
+ case "HEADER":
8145
+ return makePivotFormula("PIVOT.HEADER", [pivotFormulaId, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
8146
+ case "MEASURE_HEADER":
8147
+ return makePivotFormula("PIVOT.HEADER", [pivotFormulaId, ...flatPivotDomain(pivotCell.domain), "measure", pivotCell.measure].filter(isDefined));
8148
+ case "VALUE":
8149
+ return makePivotFormula("PIVOT.VALUE", [pivotFormulaId, pivotCell.measure, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
8150
+ case "EMPTY":
8151
+ return "";
8152
+ }
8153
+ }
8154
+ /**
8155
+ * Build a pivot formula expression
8156
+ */
8157
+ function makePivotFormula(formula, args) {
8158
+ return `=${formula}(${args
8159
+ .map((arg) => {
8160
+ const stringIsNumber = typeof arg == "string" && !isNaN(Number(arg)) && Number(arg).toString() === arg;
8161
+ const convertToNumber = typeof arg == "number" || stringIsNumber;
8162
+ return convertToNumber ? `${arg}` : `"${arg.toString().replace(/"/g, '\\"')}"`;
8163
+ })
8164
+ .join(",")})`;
8165
+ }
8166
+ /**
8167
+ * Given an object of form {"1": {...}, "2": {...}, ...} get the maximum ID used
8168
+ * in this object
8169
+ * If the object has no keys, return 0
8170
+ *
8171
+ */
8172
+ function getMaxObjectId(o) {
8173
+ const keys = Object.keys(o);
8174
+ if (!keys.length) {
8175
+ return 0;
8176
+ }
8177
+ const nums = keys.map((id) => parseInt(id, 10));
8178
+ const max = Math.max(...nums);
8179
+ return max;
8180
+ }
8181
+ const ALL_PERIODS = {
8182
+ year: _t("Year"),
8183
+ quarter: _t("Quarter"),
8184
+ month: _t("Month"),
8185
+ week: _t("Week"),
8186
+ day: _t("Day"),
8187
+ year_number: _t("Year"),
8188
+ quarter_number: _t("Quarter"),
8189
+ month_number: _t("Month"),
8190
+ iso_week_number: _t("Week"),
8191
+ day_of_month: _t("Day of Month"),
8192
+ };
8193
+ const DATE_FIELDS = ["date", "datetime"];
8194
+ /**
8195
+ * Parse a dimension string into a pivot dimension definition.
8196
+ * e.g "create_date:month" => { name: "create_date", granularity: "month" }
8197
+ */
8198
+ function parseDimension(dimension) {
8199
+ const [name, granularity] = dimension.split(":");
8200
+ if (granularity) {
8201
+ return { name, granularity };
8202
+ }
8203
+ return { name };
8204
+ }
8205
+ function isDateField(field) {
8206
+ return DATE_FIELDS.includes(field.type);
8207
+ }
8208
+ function toPivotDomain(domainStr) {
8209
+ if (domainStr.length % 2 !== 0) {
8210
+ throw new Error("Invalid domain: odd number of elements");
8211
+ }
8212
+ const domain = [];
8213
+ for (let i = 0; i < domainStr.length - 1; i += 2) {
8214
+ domain.push({ field: domainStr[i], value: domainStr[i + 1] });
8215
+ }
8216
+ return domain;
8217
+ }
8218
+ function flatPivotDomain(domain) {
8219
+ return domain.flatMap((arg) => [arg.field, arg.value]);
8220
+ }
8221
+ /**
8222
+ * Parses the value defining a pivot group in a PIVOT formula
8223
+ * e.g. given the following formula PIVOT.VALUE("1", "stage_id", "42", "status", "won"),
8224
+ * the two group values are "42" and "won".
8225
+ */
8226
+ function toNormalizedPivotValue(dimension, groupValue) {
8227
+ if (groupValue === null || groupValue === "null") {
8228
+ return null;
8229
+ }
8230
+ const groupValueString = typeof groupValue === "boolean"
8231
+ ? toString(groupValue).toLocaleLowerCase()
8232
+ : toString(groupValue);
8233
+ if (!pivotNormalizationValueRegistry.contains(dimension.type)) {
8234
+ throw new EvaluationError(_t("Field %(field)s is not supported because of its type (%(type)s)", {
8235
+ field: dimension.displayName,
8236
+ type: dimension.type,
8237
+ }));
8238
+ }
8239
+ // represents a field which is not set (=False server side)
8240
+ if (groupValueString === "false") {
8241
+ return false;
8242
+ }
8243
+ const normalizer = pivotNormalizationValueRegistry.get(dimension.type);
8244
+ return normalizer(groupValueString, dimension.granularity);
8245
+ }
8246
+ function normalizeDateTime(value, granularity) {
8247
+ if (!granularity) {
8248
+ throw "";
8249
+ }
8250
+ return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
8251
+ }
8252
+ const pivotNormalizationValueRegistry = new Registry();
8253
+ pivotNormalizationValueRegistry
8254
+ .add("date", normalizeDateTime)
8255
+ .add("datetime", normalizeDateTime)
8256
+ .add("integer", (value) => toNumber(value, DEFAULT_LOCALE))
8257
+ .add("boolean", (value) => toBoolean(value))
8258
+ .add("char", (value) => toString(value));
8259
+
7759
8260
  /**
7760
8261
  * Change the reference types inside the given token, if the token represent a range or a cell
7761
8262
  *
@@ -7954,6 +8455,11 @@
7954
8455
  const ChartTerms = {
7955
8456
  Series: _t("Series"),
7956
8457
  BackgroundColor: _t("Background color"),
8458
+ StackedBarChart: _t("Stacked bar chart"),
8459
+ StackedLineChart: _t("Stacked line chart"),
8460
+ CumulativeData: _t("Cumulative data"),
8461
+ TreatLabelsAsText: _t("Treat labels as text"),
8462
+ AggregatedChart: _t("Aggregate"),
7957
8463
  Errors: {
7958
8464
  Unexpected: _t("The chart definition is invalid for an unknown reason"),
7959
8465
  // BASIC CHART ERRORS (LINE | BAR | PIE)
@@ -8886,6 +9392,7 @@ stores.inject(MyMetaStore, storeInstance);
8886
9392
  }
8887
9393
  const color = highlight.color || HIGHLIGHT_COLOR;
8888
9394
  const { ctx } = renderingContext;
9395
+ ctx.save();
8889
9396
  if (!highlight.noBorder) {
8890
9397
  if (highlight.dashed) {
8891
9398
  ctx.setLineDash([5, 3]);
@@ -8905,6 +9412,7 @@ stores.inject(MyMetaStore, storeInstance);
8905
9412
  ctx.fillStyle = setColorAlpha(toHex(color), highlight.fillAlpha ?? 0.12);
8906
9413
  ctx.fillRect(x, y, width, height);
8907
9414
  }
9415
+ ctx.restore();
8908
9416
  }
8909
9417
 
8910
9418
  class HighlightStore extends SpreadsheetStore {
@@ -9435,8 +9943,20 @@ stores.inject(MyMetaStore, storeInstance);
9435
9943
  replaceSelectedRange(zone) {
9436
9944
  const ref = this.getZoneReference(zone);
9437
9945
  const currentToken = this.tokenAtCursor;
9438
- const start = currentToken?.type === "REFERENCE" ? currentToken.start : this.selectionStart;
9439
- this.replaceText(ref, start, this.selectionEnd);
9946
+ let replaceStart = this.selectionStart;
9947
+ if (currentToken?.type === "REFERENCE") {
9948
+ replaceStart = currentToken.start;
9949
+ }
9950
+ else if (currentToken?.type === "RIGHT_PAREN") {
9951
+ // match left parenthesis
9952
+ const leftParenthesisIndex = this.currentTokens.findIndex((token) => token.type === "LEFT_PAREN" && token.parenIndex === currentToken.parenIndex);
9953
+ const functionToken = this.currentTokens[leftParenthesisIndex - 1];
9954
+ if (functionToken === undefined) {
9955
+ return;
9956
+ }
9957
+ replaceStart = functionToken.start;
9958
+ }
9959
+ this.replaceText(ref, replaceStart, this.selectionEnd);
9440
9960
  }
9441
9961
  /**
9442
9962
  * Replace the reference of the old zone by the new one.
@@ -9456,10 +9976,8 @@ stores.inject(MyMetaStore, storeInstance);
9456
9976
  const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
9457
9977
  return isEqual(this.getters.expandZone(activeSheetId, refRange.zone), oldZone);
9458
9978
  });
9459
- // this function assumes that the previous range is always found because
9460
- // it's called when changing a highlight, which exists by definition
9461
9979
  if (!previousRefToken) {
9462
- throw new Error("Previous range not found");
9980
+ return;
9463
9981
  }
9464
9982
  const previousRange = this.getters.getRangeFromSheetXC(activeSheetId, previousRefToken.value);
9465
9983
  this.selectionStart = previousRefToken.start;
@@ -9471,6 +9989,17 @@ stores.inject(MyMetaStore, storeInstance);
9471
9989
  getZoneReference(zone) {
9472
9990
  const inputSheetId = this.currentEditedCell.sheetId;
9473
9991
  const sheetId = this.getters.getActiveSheetId();
9992
+ if (zone.top === zone.bottom && zone.left === zone.right) {
9993
+ const position = { sheetId, col: zone.left, row: zone.top };
9994
+ const pivotId = this.getters.getPivotIdFromPosition(position);
9995
+ const pivotCell = this.getters.getPivotCellFromPosition(position);
9996
+ const cell = this.getters.getCell(position);
9997
+ if (pivotId && pivotCell.type !== "EMPTY" && !cell?.isFormula) {
9998
+ const formulaPivotId = this.getters.getPivotFormulaId(pivotId);
9999
+ const formula = makePivotFormulaFromPivotCell(formulaPivotId, pivotCell);
10000
+ return formula.slice(1); // strip leading =
10001
+ }
10002
+ }
9474
10003
  const range = this.getters.getRangeFromZone(sheetId, zone);
9475
10004
  return this.getters.getSelectionRangeString(range, inputSheetId);
9476
10005
  }
@@ -9573,19 +10102,35 @@ stores.inject(MyMetaStore, storeInstance);
9573
10102
  const colorIndex = this.colorIndexByRange[rangeString];
9574
10103
  return colors$1[colorIndex % colors$1.length];
9575
10104
  };
9576
- return this.getReferencedRanges().map((range) => {
10105
+ const highlights = [];
10106
+ for (const range of this.getReferencedRanges()) {
9577
10107
  const rangeString = this.getters.getRangeString(range, editionSheetId);
9578
10108
  const { numberOfRows, numberOfCols } = zoneToDimension(range.zone);
9579
10109
  const zone = numberOfRows * numberOfCols === 1
9580
10110
  ? this.getters.expandZone(range.sheetId, range.zone)
9581
10111
  : range.zone;
9582
- return {
10112
+ highlights.push({
9583
10113
  zone,
9584
10114
  color: rangeColor(rangeString),
9585
10115
  sheetId: range.sheetId,
9586
10116
  interactive: true,
9587
- };
9588
- });
10117
+ });
10118
+ }
10119
+ const activeSheetId = this.getters.getActiveSheetId();
10120
+ const selectionZone = this.model.selection.getAnchor().zone;
10121
+ const isSelectionHightlighted = highlights.find((highlight) => highlight.sheetId === activeSheetId && isEqual(highlight.zone, selectionZone));
10122
+ if (this.editionMode === "selecting" && !isSelectionHightlighted) {
10123
+ highlights.push({
10124
+ zone: selectionZone,
10125
+ color: "#445566",
10126
+ sheetId: activeSheetId,
10127
+ dashed: true,
10128
+ interactive: false,
10129
+ noFill: true,
10130
+ thinLine: true,
10131
+ });
10132
+ }
10133
+ return highlights;
9589
10134
  }
9590
10135
  /**
9591
10136
  * Return ranges currently referenced in the composer
@@ -9844,8 +10389,14 @@ stores.inject(MyMetaStore, storeInstance);
9844
10389
  owl.useEffect(() => {
9845
10390
  const runtime = this.chartRuntime;
9846
10391
  if (!deepEquals(runtime, this.currentRuntime, "ignoreFunctions")) {
10392
+ if (runtime.chartJsConfig.type !== this.currentRuntime.chartJsConfig.type) {
10393
+ this.chart?.destroy();
10394
+ this.createChart(deepCopy(runtime.chartJsConfig));
10395
+ }
10396
+ else {
10397
+ this.updateChartJs(deepCopy(runtime));
10398
+ }
9847
10399
  this.currentRuntime = runtime;
9848
- this.updateChartJs(deepCopy(runtime));
9849
10400
  }
9850
10401
  });
9851
10402
  }
@@ -10213,6 +10764,9 @@ stores.inject(MyMetaStore, storeInstance);
10213
10764
  }
10214
10765
  function getDefinedAxis(definition) {
10215
10766
  let useLeftAxis = false, useRightAxis = false;
10767
+ if ("horizontal" in definition && definition.horizontal) {
10768
+ return { useLeftAxis: true, useRightAxis: false };
10769
+ }
10216
10770
  for (const design of definition.dataSets || []) {
10217
10771
  if (design.yAxisId === "y1") {
10218
10772
  useRightAxis = true;
@@ -10423,6 +10977,7 @@ stores.inject(MyMetaStore, storeInstance);
10423
10977
  function drawScoreChart(structure, canvas) {
10424
10978
  const ctx = canvas.getContext("2d");
10425
10979
  canvas.width = structure.canvas.width;
10980
+ const availableWidth = canvas.width - DEFAULT_CHART_PADDING;
10426
10981
  canvas.height = structure.canvas.height;
10427
10982
  ctx.fillStyle = structure.canvas.backgroundColor;
10428
10983
  ctx.fillRect(0, 0, structure.canvas.width, structure.canvas.height);
@@ -10431,7 +10986,7 @@ stores.inject(MyMetaStore, storeInstance);
10431
10986
  ctx.fillStyle = structure.title.style.color;
10432
10987
  const baseline = ctx.textBaseline;
10433
10988
  ctx.textBaseline = "middle";
10434
- ctx.fillText(clipTextWithEllipsis(ctx, structure.title.text, canvas.width - structure.title.position.x), structure.title.position.x, structure.title.position.y);
10989
+ ctx.fillText(clipTextWithEllipsis(ctx, structure.title.text, availableWidth - structure.title.position.x), structure.title.position.x, structure.title.position.y);
10435
10990
  ctx.textBaseline = baseline;
10436
10991
  }
10437
10992
  if (structure.baseline) {
@@ -10521,13 +11076,16 @@ stores.inject(MyMetaStore, storeInstance);
10521
11076
  return {
10522
11077
  title: {
10523
11078
  ...chart.title,
11079
+ // chart titles are extracted from .json files and they are translated at runtime here
10524
11080
  text: _t(chart.title.text ?? ""),
10525
11081
  },
10526
11082
  keyValue: formattedKeyValue,
10527
11083
  baselineDisplay,
10528
11084
  baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
10529
11085
  baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
10530
- baselineDescr: chart.baselineMode !== "progress" && chart.baselineDescr ? _t(chart.baselineDescr) : "",
11086
+ baselineDescr: chart.baselineMode !== "progress" && chart.baselineDescr
11087
+ ? _t(chart.baselineDescr) // descriptions are extracted from .json files and they are translated at runtime here
11088
+ : "",
10531
11089
  fontColor,
10532
11090
  background,
10533
11091
  baselineStyle: chart.baselineMode !== "percentage" && chart.baselineMode !== "progress" && baseline
@@ -10556,7 +11114,7 @@ stores.inject(MyMetaStore, storeInstance);
10556
11114
  /* Sizes of boxes containing the texts, in percentage of the Chart size */
10557
11115
  const KEY_BOX_HEIGHT_RATIO = 0.8;
10558
11116
  /* Padding at the border of the chart */
10559
- const CHART_PADDING = DEFAULT_CHART_PADDING;
11117
+ const CHART_PADDING = 10;
10560
11118
  const BOTTOM_PADDING_RATIO = 0.05;
10561
11119
  /**
10562
11120
  * Line height (in em)
@@ -10706,6 +11264,7 @@ stores.inject(MyMetaStore, storeInstance);
10706
11264
  position: {
10707
11265
  x: (this.width - keyWidth) / 2,
10708
11266
  y: this.height * (0.5 - BOTTOM_PADDING_RATIO * 2) +
11267
+ CHART_PADDING / 2 +
10709
11268
  (titleHeight + keyHeight / (this.baseline || this.baselineDescr ? 2 : 1.2)) / 2,
10710
11269
  },
10711
11270
  };
@@ -10765,7 +11324,8 @@ stores.inject(MyMetaStore, storeInstance);
10765
11324
  const remainingWidth = maxLineWidth - baselineValueWidth;
10766
11325
  let baselineDescrFontSize = getFontSizeMatchingWidth(remainingWidth, baselineValueFontSize, (fontSize) => computeTextWidth(this.context, this.baselineDescr, { fontSize }));
10767
11326
  let isBaselineSplit = false;
10768
- if (baselineDescrFontSize < baselineValueFontSize / 2.5) {
11327
+ if (baselineDescrFontSize < baselineValueFontSize / 2.5 &&
11328
+ this.baselineDescr.trim().includes(" ")) {
10769
11329
  isBaselineSplit = true;
10770
11330
  baselineDescrFontSize = Math.floor(baselineValueFontSize / 2.5);
10771
11331
  for (const line of splitTextInTwoLines(this.baselineDescr)) {
@@ -10814,7 +11374,7 @@ stores.inject(MyMetaStore, storeInstance);
10814
11374
  /** Get the height of the chart minus all the vertical paddings */
10815
11375
  getDrawableHeight() {
10816
11376
  const verticalPadding = CHART_PADDING + this.height * BOTTOM_PADDING_RATIO;
10817
- let availableHeight = this.height - 2 * verticalPadding;
11377
+ let availableHeight = this.height - verticalPadding;
10818
11378
  availableHeight -= this.title ? DEFAULT_CHART_FONT_SIZE * LINE_HEIGHT : 0;
10819
11379
  return availableHeight;
10820
11380
  }
@@ -10856,6 +11416,11 @@ stores.inject(MyMetaStore, storeInstance);
10856
11416
  get runtime() {
10857
11417
  return this.env.model.getters.getChartRuntime(this.props.figure.id);
10858
11418
  }
11419
+ get title() {
11420
+ const title = this.env.model.getters.getChartDefinition(this.props.figure.id).title.text ?? "";
11421
+ // chart titles are extracted from .json files and they are translated at runtime here
11422
+ return _t(title);
11423
+ }
10859
11424
  setup() {
10860
11425
  owl.useEffect(this.createChart.bind(this), () => {
10861
11426
  const canvas = this.canvas.el;
@@ -11769,13 +12334,6 @@ stores.inject(MyMetaStore, storeInstance);
11769
12334
  FORMAT_LARGE_NUMBER: FORMAT_LARGE_NUMBER
11770
12335
  });
11771
12336
 
11772
- function sum(values, locale) {
11773
- return reduceNumbers(values, (acc, a) => acc + a, 0, locale);
11774
- }
11775
- function countUnique(args) {
11776
- return reduceAny(args, (acc, a) => (isDataNonEmpty(a) ? acc.add(a?.value) : acc), new Set()).size;
11777
- }
11778
-
11779
12337
  const DEFAULT_FACTOR = 1;
11780
12338
  const DEFAULT_MODE = 0;
11781
12339
  const DEFAULT_PLACES = 0;
@@ -12889,53 +13447,6 @@ stores.inject(MyMetaStore, storeInstance);
12889
13447
  TRUNC: TRUNC
12890
13448
  });
12891
13449
 
12892
- function assertSameNumberOfElements(...args) {
12893
- const dims = args[0].length;
12894
- args.forEach((arg, i) => assert(() => arg.length === dims, _t("[[FUNCTION_NAME]] has mismatched dimensions for argument %s (%s vs %s).", i.toString(), dims.toString(), arg.length.toString())));
12895
- }
12896
- function average(values, locale) {
12897
- let count = 0;
12898
- const sum = reduceNumbers(values, (acc, a) => {
12899
- count += 1;
12900
- return acc + a;
12901
- }, 0, locale);
12902
- assertNotZero(count);
12903
- return sum / count;
12904
- }
12905
- function countNumbers(values, locale) {
12906
- let count = 0;
12907
- for (let n of values) {
12908
- if (isMatrix(n)) {
12909
- for (let i of n) {
12910
- for (let j of i) {
12911
- if (typeof j.value === "number") {
12912
- count += 1;
12913
- }
12914
- }
12915
- }
12916
- }
12917
- else {
12918
- const value = n?.value;
12919
- if (!isEvaluationError(value) &&
12920
- (typeof value !== "string" || isNumber(value, locale) || parseDateTime(value, locale))) {
12921
- count += 1;
12922
- }
12923
- }
12924
- }
12925
- return count;
12926
- }
12927
- function countAny(values) {
12928
- return reduceAny(values, (acc, a) => (a !== undefined && a.value !== null ? acc + 1 : acc), 0);
12929
- }
12930
- function max(values, locale) {
12931
- const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, locale);
12932
- return result === -Infinity ? 0 : result;
12933
- }
12934
- function min(values, locale) {
12935
- const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
12936
- return result === Infinity ? 0 : result;
12937
- }
12938
-
12939
13450
  function filterAndFlatData(dataY, dataX) {
12940
13451
  const _flatDataY = [];
12941
13452
  const _flatDataX = [];
@@ -13491,7 +14002,7 @@ stores.inject(MyMetaStore, storeInstance);
13491
14002
  // LINEST
13492
14003
  // -----------------------------------------------------------------------------
13493
14004
  const LINEST = {
13494
- description: _t("Compute the intercept of the linear regression."),
14005
+ description: _t("Given partial data about a linear trend, calculates various parameters about the ideal linear trend using the least-squares method."),
13495
14006
  args: [
13496
14007
  arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
13497
14008
  arg("data_x (range<number>, default={1;2;3;...})", _t("The range representing the array or matrix of independent data.")),
@@ -13507,7 +14018,7 @@ stores.inject(MyMetaStore, storeInstance);
13507
14018
  // LOGEST
13508
14019
  // -----------------------------------------------------------------------------
13509
14020
  const LOGEST = {
13510
- description: _t("Compute the intercept of the linear regression."),
14021
+ description: _t("Given partial data about an exponential growth curve, calculates various parameters about the best fit ideal exponential growth curve."),
13511
14022
  args: [
13512
14023
  arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
13513
14024
  arg("data_x (range<number>, optional, default={1;2;3;...})", _t("The range representing the array or matrix of independent data.")),
@@ -17910,33 +18421,6 @@ stores.inject(MyMetaStore, storeInstance);
17910
18421
  NA: NA
17911
18422
  });
17912
18423
 
17913
- function boolAnd(args) {
17914
- let foundBoolean = false;
17915
- let acc = true;
17916
- conditionalVisitBoolean(args, (arg) => {
17917
- foundBoolean = true;
17918
- acc = acc && arg;
17919
- return acc;
17920
- });
17921
- return {
17922
- foundBoolean,
17923
- result: acc,
17924
- };
17925
- }
17926
- function boolOr(args) {
17927
- let foundBoolean = false;
17928
- let acc = false;
17929
- conditionalVisitBoolean(args, (arg) => {
17930
- foundBoolean = true;
17931
- acc = acc || arg;
17932
- return !acc;
17933
- });
17934
- return {
17935
- foundBoolean,
17936
- result: acc,
17937
- };
17938
- }
17939
-
17940
18424
  // -----------------------------------------------------------------------------
17941
18425
  // AND
17942
18426
  // -----------------------------------------------------------------------------
@@ -18134,393 +18618,6 @@ stores.inject(MyMetaStore, storeInstance);
18134
18618
  XOR: XOR
18135
18619
  });
18136
18620
 
18137
- const pivotTimeAdapterRegistry = new Registry();
18138
- function pivotTimeAdapter(granularity) {
18139
- return pivotTimeAdapterRegistry.get(granularity);
18140
- }
18141
- /**
18142
- * The Time Adapter: Managing Time Periods for Pivot Functions
18143
- *
18144
- * Overview:
18145
- * A time adapter is responsible for managing time periods associated with pivot functions.
18146
- * Each type of period (day, week, month, quarter, etc.) has its own dedicated adapter.
18147
- * The adapter's primary role is to normalize period values between spreadsheet functions,
18148
- * and the pivot.
18149
- * By normalizing the period value, it can be stored consistently in the pivot.
18150
- *
18151
- * Normalization Process:
18152
- * When working with functions in the spreadsheet, the time adapter normalizes
18153
- * the provided period to facilitate accurate lookup of values in the pivot.
18154
- * For instance, if the spreadsheet function represents a day period as a number generated
18155
- * by the DATE function (DATE(2023, 12, 25)), the time adapter will normalize it accordingly.
18156
- *
18157
- */
18158
- /**
18159
- * Normalized value: "12/25/2023"
18160
- *
18161
- * Note: Those two format are equivalent:
18162
- * - "MM/dd/yyyy" (luxon format)
18163
- * - "mm/dd/yyyy" (spreadsheet format)
18164
- **/
18165
- const dayAdapter = {
18166
- normalizeFunctionValue(value) {
18167
- const date = toNumber(value, DEFAULT_LOCALE);
18168
- return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/dd/yyyy" });
18169
- },
18170
- getFormat(locale) {
18171
- return (locale ?? DEFAULT_LOCALE).dateFormat;
18172
- },
18173
- formatValue(normalizedValue, locale) {
18174
- locale = locale ?? DEFAULT_LOCALE;
18175
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18176
- return formatValue(value, { locale, format: this.getFormat(locale) });
18177
- },
18178
- toCellValue(normalizedValue) {
18179
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18180
- },
18181
- };
18182
- /**
18183
- * normalizes day of month number
18184
- */
18185
- const dayOfMonthAdapter = {
18186
- normalizeFunctionValue(value) {
18187
- const day = toNumber(value, DEFAULT_LOCALE);
18188
- if (day < 1 || day > 31) {
18189
- throw new EvaluationError(_t("%s is not a valid day of month (it should be a number between 1 and 31)", day));
18190
- }
18191
- return day;
18192
- },
18193
- getFormat() {
18194
- return "0";
18195
- },
18196
- formatValue(normalizedValue, locale) {
18197
- locale = locale ?? DEFAULT_LOCALE;
18198
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18199
- return formatValue(value, { locale, format: this.getFormat(locale) });
18200
- },
18201
- toCellValue(normalizedValue) {
18202
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18203
- },
18204
- };
18205
- /**
18206
- * Normalized value: "2/2023" for week 2 of 2023
18207
- */
18208
- const weekAdapter = {
18209
- normalizeFunctionValue(value) {
18210
- const [week, year] = value.split("/");
18211
- return `${Number(week)}/${Number(year)}`;
18212
- },
18213
- getFormat() {
18214
- return undefined;
18215
- },
18216
- formatValue(normalizedValue) {
18217
- const [week, year] = normalizedValue.split("/");
18218
- return _t("W%(week)s %(year)s", { week, year });
18219
- },
18220
- toCellValue(normalizedValue) {
18221
- return this.formatValue(normalizedValue);
18222
- },
18223
- };
18224
- /**
18225
- * normalizes iso week number
18226
- */
18227
- const isoWeekNumberAdapter = {
18228
- normalizeFunctionValue(value) {
18229
- const isoWeek = toNumber(value, DEFAULT_LOCALE);
18230
- if (isoWeek < 0 || isoWeek > 53) {
18231
- throw new EvaluationError(_t("%s is not a valid week (it should be a number between 0 and 53)", isoWeek));
18232
- }
18233
- return isoWeek;
18234
- },
18235
- getFormat() {
18236
- return "0";
18237
- },
18238
- formatValue(normalizedValue, locale) {
18239
- locale = locale ?? DEFAULT_LOCALE;
18240
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18241
- return formatValue(value, { locale, format: this.getFormat(locale) });
18242
- },
18243
- toCellValue(normalizedValue) {
18244
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18245
- },
18246
- };
18247
- /**
18248
- * normalized month value is a string formatted as "MM/yyyy" (luxon format)
18249
- * e.g. "01/2020" for January 2020
18250
- */
18251
- const monthAdapter = {
18252
- normalizeFunctionValue(value) {
18253
- const date = toNumber(value, DEFAULT_LOCALE);
18254
- return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
18255
- },
18256
- getFormat() {
18257
- return "mmmm yyyy";
18258
- },
18259
- formatValue(normalizedValue, locale) {
18260
- locale = locale ?? DEFAULT_LOCALE;
18261
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18262
- return formatValue(value, { locale, format: this.getFormat(locale) });
18263
- },
18264
- toCellValue(normalizedValue) {
18265
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18266
- },
18267
- };
18268
- /**
18269
- * normalizes month number
18270
- */
18271
- const monthNumberAdapter = {
18272
- normalizeFunctionValue(value) {
18273
- const month = toNumber(value, DEFAULT_LOCALE);
18274
- if (month < 1 || month > 12) {
18275
- throw new EvaluationError(_t("%s is not a valid month (it should be a number between 1 and 12)", month));
18276
- }
18277
- return month;
18278
- },
18279
- getFormat() {
18280
- return "0";
18281
- },
18282
- formatValue(normalizedValue, locale) {
18283
- locale = locale ?? DEFAULT_LOCALE;
18284
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18285
- return formatValue(value, { locale, format: this.getFormat(locale) });
18286
- },
18287
- toCellValue(normalizedValue) {
18288
- return MONTHS[toNumber(normalizedValue, DEFAULT_LOCALE) - 1].toString();
18289
- },
18290
- };
18291
- /**
18292
- * normalized quarter value is "quarter/year"
18293
- * e.g. "1/2020" for Q1 2020
18294
- */
18295
- const quarterAdapter = {
18296
- normalizeFunctionValue(value) {
18297
- const [quarter, year] = value.split("/");
18298
- return `${quarter}/${year}`;
18299
- },
18300
- getFormat() {
18301
- return undefined;
18302
- },
18303
- formatValue(normalizedValue) {
18304
- const [quarter, year] = normalizedValue.split("/");
18305
- return _t("Q%(quarter)s %(year)s", { quarter, year });
18306
- },
18307
- toCellValue(normalizedValue) {
18308
- return this.formatValue(normalizedValue);
18309
- },
18310
- };
18311
- /**
18312
- * normalizes quarter number
18313
- */
18314
- const quarterNumberAdapter = {
18315
- normalizeFunctionValue(value) {
18316
- const quarter = toNumber(value, DEFAULT_LOCALE);
18317
- if (quarter < 1 || quarter > 4) {
18318
- throw new EvaluationError(_t("%s is not a valid quarter (it should be a number between 1 and 4)", quarter));
18319
- }
18320
- return quarter;
18321
- },
18322
- getFormat() {
18323
- return "0";
18324
- },
18325
- formatValue(normalizedValue, locale) {
18326
- locale = locale ?? DEFAULT_LOCALE;
18327
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18328
- return formatValue(value, { locale, format: this.getFormat(locale) });
18329
- },
18330
- toCellValue(normalizedValue) {
18331
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18332
- },
18333
- };
18334
- const yearAdapter = {
18335
- normalizeFunctionValue(value) {
18336
- return toNumber(value, DEFAULT_LOCALE);
18337
- },
18338
- getFormat() {
18339
- return "0";
18340
- },
18341
- formatValue(normalizedValue, locale) {
18342
- locale = locale ?? DEFAULT_LOCALE;
18343
- return formatValue(normalizedValue, { locale, format: "0" });
18344
- },
18345
- toCellValue(normalizedValue) {
18346
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18347
- },
18348
- };
18349
- pivotTimeAdapterRegistry
18350
- .add("day", dayAdapter)
18351
- .add("week", weekAdapter)
18352
- .add("month", monthAdapter)
18353
- .add("quarter", quarterAdapter)
18354
- .add("year", yearAdapter)
18355
- .add("day_of_month", dayOfMonthAdapter)
18356
- .add("iso_week_number", isoWeekNumberAdapter)
18357
- .add("month_number", monthNumberAdapter)
18358
- .add("quarter_number", quarterNumberAdapter)
18359
- .add("year_number", yearAdapter);
18360
-
18361
- const AGGREGATOR_NAMES = {
18362
- count: _t("Count"),
18363
- count_distinct: _t("Count Distinct"),
18364
- bool_and: _t("Boolean And"),
18365
- bool_or: _t("Boolean Or"),
18366
- max: _t("Maximum"),
18367
- min: _t("Minimum"),
18368
- avg: _t("Average"),
18369
- sum: _t("Sum"),
18370
- };
18371
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
18372
- const AGGREGATORS_BY_FIELD_TYPE = {
18373
- integer: NUMBER_CHAR_AGGREGATORS,
18374
- char: NUMBER_CHAR_AGGREGATORS,
18375
- boolean: ["count_distinct", "count", "bool_and", "bool_or"],
18376
- };
18377
- const AGGREGATORS = {};
18378
- for (const type in AGGREGATORS_BY_FIELD_TYPE) {
18379
- AGGREGATORS[type] = {};
18380
- for (const aggregator of AGGREGATORS_BY_FIELD_TYPE[type]) {
18381
- AGGREGATORS[type][aggregator] = AGGREGATOR_NAMES[aggregator];
18382
- }
18383
- }
18384
- const AGGREGATORS_FN = {
18385
- count: {
18386
- fn: (args) => countAny([args]),
18387
- format: () => "0",
18388
- },
18389
- count_distinct: {
18390
- fn: (args) => countUnique([args]),
18391
- format: () => "0",
18392
- },
18393
- bool_and: {
18394
- fn: (args) => boolAnd([args]).result,
18395
- format: () => undefined,
18396
- },
18397
- bool_or: {
18398
- fn: (args) => boolOr([args]).result,
18399
- format: () => undefined,
18400
- },
18401
- max: {
18402
- fn: (args, locale) => max([args], locale),
18403
- format: inferFormat,
18404
- },
18405
- min: {
18406
- fn: (args, locale) => min([args], locale),
18407
- format: inferFormat,
18408
- },
18409
- avg: {
18410
- fn: (args, locale) => average([args], locale),
18411
- format: inferFormat,
18412
- },
18413
- sum: {
18414
- fn: (args, locale) => sum([args], locale),
18415
- format: inferFormat,
18416
- },
18417
- };
18418
- /**
18419
- * Build a pivot formula expression
18420
- */
18421
- function makePivotFormula(formula, args) {
18422
- return `=${formula}(${args
18423
- .map((arg) => {
18424
- const stringIsNumber = typeof arg == "string" && !isNaN(Number(arg)) && Number(arg).toString() === arg;
18425
- const convertToNumber = typeof arg == "number" || stringIsNumber;
18426
- return convertToNumber ? `${arg}` : `"${arg.toString().replace(/"/g, '\\"')}"`;
18427
- })
18428
- .join(",")})`;
18429
- }
18430
- /**
18431
- * Given an object of form {"1": {...}, "2": {...}, ...} get the maximum ID used
18432
- * in this object
18433
- * If the object has no keys, return 0
18434
- *
18435
- */
18436
- function getMaxObjectId(o) {
18437
- const keys = Object.keys(o);
18438
- if (!keys.length) {
18439
- return 0;
18440
- }
18441
- const nums = keys.map((id) => parseInt(id, 10));
18442
- const max = Math.max(...nums);
18443
- return max;
18444
- }
18445
- const ALL_PERIODS = {
18446
- year: _t("Year"),
18447
- quarter: _t("Quarter"),
18448
- month: _t("Month"),
18449
- week: _t("Week"),
18450
- day: _t("Day"),
18451
- year_number: _t("Year"),
18452
- quarter_number: _t("Quarter"),
18453
- month_number: _t("Month"),
18454
- iso_week_number: _t("Week"),
18455
- day_of_month: _t("Day of Month"),
18456
- };
18457
- const DATE_FIELDS = ["date", "datetime"];
18458
- /**
18459
- * Parse a dimension string into a pivot dimension definition.
18460
- * e.g "create_date:month" => { name: "create_date", granularity: "month" }
18461
- */
18462
- function parseDimension(dimension) {
18463
- const [name, granularity] = dimension.split(":");
18464
- if (granularity) {
18465
- return { name, granularity };
18466
- }
18467
- return { name };
18468
- }
18469
- function isDateField(field) {
18470
- return DATE_FIELDS.includes(field.type);
18471
- }
18472
- function toPivotDomain(domainStr) {
18473
- if (domainStr.length % 2 !== 0) {
18474
- throw new Error("Invalid domain: odd number of elements");
18475
- }
18476
- const domain = [];
18477
- for (let i = 0; i < domainStr.length - 1; i += 2) {
18478
- domain.push({ field: domainStr[i], value: domainStr[i + 1] });
18479
- }
18480
- return domain;
18481
- }
18482
- function flatPivotDomain(domain) {
18483
- return domain.flatMap((arg) => [arg.field, arg.value]);
18484
- }
18485
- /**
18486
- * Parses the value defining a pivot group in a PIVOT formula
18487
- * e.g. given the following formula PIVOT.VALUE("1", "stage_id", "42", "status", "won"),
18488
- * the two group values are "42" and "won".
18489
- */
18490
- function toNormalizedPivotValue(dimension, groupValue) {
18491
- if (groupValue === null || groupValue === "null") {
18492
- return null;
18493
- }
18494
- const groupValueString = typeof groupValue === "boolean"
18495
- ? toString(groupValue).toLocaleLowerCase()
18496
- : toString(groupValue);
18497
- if (!pivotNormalizationValueRegistry.contains(dimension.type)) {
18498
- throw new EvaluationError(_t("Field %(field)s is not supported because of its type (%(type)s)", {
18499
- field: dimension.displayName,
18500
- type: dimension.type,
18501
- }));
18502
- }
18503
- // represents a field which is not set (=False server side)
18504
- if (groupValueString === "false") {
18505
- return false;
18506
- }
18507
- const normalizer = pivotNormalizationValueRegistry.get(dimension.type);
18508
- return normalizer(groupValueString, dimension.granularity);
18509
- }
18510
- function normalizeDateTime(value, granularity) {
18511
- if (!granularity) {
18512
- throw "";
18513
- }
18514
- return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
18515
- }
18516
- const pivotNormalizationValueRegistry = new Registry();
18517
- pivotNormalizationValueRegistry
18518
- .add("date", normalizeDateTime)
18519
- .add("datetime", normalizeDateTime)
18520
- .add("integer", (value) => toNumber(value, DEFAULT_LOCALE))
18521
- .add("boolean", (value) => toBoolean(value))
18522
- .add("char", (value) => toString(value));
18523
-
18524
18621
  /**
18525
18622
  * Get the pivot ID from the formula pivot ID.
18526
18623
  */
@@ -19097,14 +19194,10 @@ stores.inject(MyMetaStore, storeInstance);
19097
19194
  result[col].push({ value: "" });
19098
19195
  break;
19099
19196
  case "HEADER":
19100
- const domain = pivotCell.domain;
19101
- const lastNode = domain.at(-1);
19102
- if (lastNode?.field === "measure") {
19103
- result[col].push(pivot.getPivotMeasureValue(toString(lastNode.value), domain));
19104
- }
19105
- else {
19106
- result[col].push(pivot.getPivotHeaderValueAndFormat(domain));
19107
- }
19197
+ result[col].push(pivot.getPivotHeaderValueAndFormat(pivotCell.domain));
19198
+ break;
19199
+ case "MEASURE_HEADER":
19200
+ result[col].push(pivot.getPivotMeasureValue(pivotCell.measure, pivotCell.domain));
19108
19201
  break;
19109
19202
  case "VALUE":
19110
19203
  result[col].push(pivot.getPivotCellValueAndFormat(pivotCell.measure, pivotCell.domain));
@@ -20143,6 +20236,8 @@ stores.inject(MyMetaStore, storeInstance);
20143
20236
  * a child element.
20144
20237
  */
20145
20238
  function isChildEvent(parent, ev) {
20239
+ if (!parent)
20240
+ return false;
20146
20241
  return !!ev.target && parent.contains(ev.target);
20147
20242
  }
20148
20243
  function gridOverlayPosition() {
@@ -22682,7 +22777,8 @@ stores.inject(MyMetaStore, storeInstance);
22682
22777
  /**
22683
22778
  * Get a default chart js configuration
22684
22779
  */
22685
- function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, truncateLabels }) {
22780
+ function getDefaultChartJsRuntime(chart, labels, fontColor, args) {
22781
+ const { format, locale, truncateLabels, horizontalChart } = args;
22686
22782
  const chartTitle = chart.title.text ? chart.title : { ...chart.title, content: "" };
22687
22783
  const options = {
22688
22784
  // https://www.chartjs.org/docs/latest/general/responsive.html
@@ -22726,8 +22822,11 @@ stores.inject(MyMetaStore, storeInstance);
22726
22822
  callbacks: {
22727
22823
  label: function (tooltipItem) {
22728
22824
  const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
22729
- // tooltipItem.parsed.y can be an object or a number for pie charts
22730
- const yLabel = tooltipItem.parsed.y ?? tooltipItem.parsed;
22825
+ // tooltipItem.parsed can be an object or a number for pie charts
22826
+ let yLabel = horizontalChart ? tooltipItem.parsed.x : tooltipItem.parsed.y;
22827
+ if (!yLabel) {
22828
+ yLabel = tooltipItem.parsed;
22829
+ }
22731
22830
  const toolTipFormat = !format && Math.abs(yLabel) >= 1000 ? "#,##" : format;
22732
22831
  const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
22733
22832
  return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
@@ -22901,6 +23000,7 @@ stores.inject(MyMetaStore, storeInstance);
22901
23000
  dataSetsHaveTitle;
22902
23001
  dataSetDesign;
22903
23002
  axesDesign;
23003
+ horizontal;
22904
23004
  constructor(definition, sheetId, getters) {
22905
23005
  super(definition, sheetId, getters);
22906
23006
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -22912,6 +23012,7 @@ stores.inject(MyMetaStore, storeInstance);
22912
23012
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
22913
23013
  this.dataSetDesign = definition.dataSets;
22914
23014
  this.axesDesign = definition.axesDesign;
23015
+ this.horizontal = definition.horizontal;
22915
23016
  }
22916
23017
  static transformDefinition(definition, executed) {
22917
23018
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -22983,6 +23084,7 @@ stores.inject(MyMetaStore, storeInstance);
22983
23084
  stacked: this.stacked,
22984
23085
  aggregated: this.aggregated,
22985
23086
  axesDesign: this.axesDesign,
23087
+ horizontal: this.horizontal,
22986
23088
  };
22987
23089
  }
22988
23090
  getDefinitionForExcel() {
@@ -23014,7 +23116,10 @@ stores.inject(MyMetaStore, storeInstance);
23014
23116
  }
23015
23117
  function getBarConfiguration(chart, labels, localeFormat) {
23016
23118
  const fontColor = chartFontColor(chart.background);
23017
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
23119
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, {
23120
+ ...localeFormat,
23121
+ horizontalChart: chart.horizontal,
23122
+ });
23018
23123
  const legend = {
23019
23124
  labels: { color: fontColor },
23020
23125
  };
@@ -23028,16 +23133,10 @@ stores.inject(MyMetaStore, storeInstance);
23028
23133
  config.options.layout = {
23029
23134
  padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
23030
23135
  };
23031
- config.options.scales = {
23032
- x: {
23033
- ticks: {
23034
- padding: 5,
23035
- color: fontColor,
23036
- },
23037
- title: getChartAxisTitleRuntime(chart.axesDesign?.x),
23038
- },
23039
- };
23040
- const yAxis = {
23136
+ config.options.indexAxis = chart.horizontal ? "y" : "x";
23137
+ config.options.scales = {};
23138
+ const labelsAxis = { ticks: { padding: 5, color: fontColor } };
23139
+ const valuesAxis = {
23041
23140
  beginAtZero: true, // the origin of the y axis is always zero
23042
23141
  ticks: {
23043
23142
  color: fontColor,
@@ -23053,7 +23152,10 @@ stores.inject(MyMetaStore, storeInstance);
23053
23152
  },
23054
23153
  },
23055
23154
  };
23155
+ const xAxis = chart.horizontal ? valuesAxis : labelsAxis;
23156
+ const yAxis = chart.horizontal ? labelsAxis : valuesAxis;
23056
23157
  const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
23158
+ config.options.scales.x = { ...xAxis, title: getChartAxisTitleRuntime(chart.axesDesign?.x) };
23057
23159
  if (useLeftAxis) {
23058
23160
  config.options.scales.y = {
23059
23161
  ...yAxis,
@@ -23120,7 +23222,7 @@ stores.inject(MyMetaStore, storeInstance);
23120
23222
  const label = definition.dataSets[index].label;
23121
23223
  dataset.label = label;
23122
23224
  }
23123
- if (definition.dataSets?.[index]?.yAxisId) {
23225
+ if (definition.dataSets?.[index]?.yAxisId && !chart.horizontal) {
23124
23226
  dataset["yAxisID"] = definition.dataSets[index].yAxisId;
23125
23227
  }
23126
23228
  }
@@ -24111,6 +24213,7 @@ stores.inject(MyMetaStore, storeInstance);
24111
24213
  type = "pie";
24112
24214
  aggregated;
24113
24215
  dataSetsHaveTitle;
24216
+ isDoughnut;
24114
24217
  constructor(definition, sheetId, getters) {
24115
24218
  super(definition, sheetId, getters);
24116
24219
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -24119,6 +24222,7 @@ stores.inject(MyMetaStore, storeInstance);
24119
24222
  this.legendPosition = definition.legendPosition;
24120
24223
  this.aggregated = definition.aggregated;
24121
24224
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24225
+ this.isDoughnut = definition.isDoughnut;
24122
24226
  }
24123
24227
  static transformDefinition(definition, executed) {
24124
24228
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -24136,6 +24240,7 @@ stores.inject(MyMetaStore, storeInstance);
24136
24240
  type: "pie",
24137
24241
  labelRange: context.auxiliaryRange || undefined,
24138
24242
  aggregated: context.aggregated ?? false,
24243
+ isDoughnut: false,
24139
24244
  };
24140
24245
  }
24141
24246
  getDefinition() {
@@ -24166,6 +24271,7 @@ stores.inject(MyMetaStore, storeInstance);
24166
24271
  : undefined,
24167
24272
  title: this.title,
24168
24273
  aggregated: this.aggregated,
24274
+ isDoughnut: this.isDoughnut,
24169
24275
  };
24170
24276
  }
24171
24277
  copyForSheetId(sheetId) {
@@ -24300,6 +24406,141 @@ stores.inject(MyMetaStore, storeInstance);
24300
24406
  };
24301
24407
  config.data.datasets.push(dataset);
24302
24408
  }
24409
+ if (chart.isDoughnut) {
24410
+ config.type = "doughnut";
24411
+ }
24412
+ return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
24413
+ }
24414
+
24415
+ class PyramidChart extends AbstractChart {
24416
+ dataSets;
24417
+ labelRange;
24418
+ background;
24419
+ legendPosition;
24420
+ aggregated;
24421
+ type = "pyramid";
24422
+ dataSetsHaveTitle;
24423
+ dataSetDesign;
24424
+ axesDesign;
24425
+ horizontal = true;
24426
+ stacked = true;
24427
+ constructor(definition, sheetId, getters) {
24428
+ super(definition, sheetId, getters);
24429
+ this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle).slice(0, 2);
24430
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
24431
+ this.background = definition.background;
24432
+ this.legendPosition = definition.legendPosition;
24433
+ this.aggregated = definition.aggregated;
24434
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24435
+ this.dataSetDesign = definition.dataSets;
24436
+ this.axesDesign = definition.axesDesign;
24437
+ }
24438
+ static transformDefinition(definition, executed) {
24439
+ return transformChartDefinitionWithDataSetsWithZone(definition, executed);
24440
+ }
24441
+ static validateChartDefinition(validator, definition) {
24442
+ return validator.checkValidations(definition, checkDataset, checkLabelRange);
24443
+ }
24444
+ static getDefinitionFromContextCreation(context) {
24445
+ return {
24446
+ background: context.background,
24447
+ dataSets: context.range ?? [],
24448
+ dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
24449
+ aggregated: context.aggregated ?? false,
24450
+ legendPosition: context.legendPosition ?? "top",
24451
+ title: context.title || { text: "" },
24452
+ type: "pyramid",
24453
+ labelRange: context.auxiliaryRange || undefined,
24454
+ axesDesign: context.axesDesign,
24455
+ horizontal: true,
24456
+ stacked: true,
24457
+ };
24458
+ }
24459
+ getContextCreation() {
24460
+ const range = [];
24461
+ for (const [i, dataSet] of this.dataSets.entries()) {
24462
+ range.push({
24463
+ ...this.dataSetDesign?.[i],
24464
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
24465
+ });
24466
+ }
24467
+ return {
24468
+ ...this,
24469
+ range,
24470
+ auxiliaryRange: this.labelRange
24471
+ ? this.getters.getRangeString(this.labelRange, this.sheetId)
24472
+ : undefined,
24473
+ };
24474
+ }
24475
+ copyForSheetId(sheetId) {
24476
+ const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
24477
+ const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
24478
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
24479
+ return new PyramidChart(definition, sheetId, this.getters);
24480
+ }
24481
+ copyInSheetId(sheetId) {
24482
+ const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
24483
+ return new PyramidChart(definition, sheetId, this.getters);
24484
+ }
24485
+ getDefinition() {
24486
+ return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
24487
+ }
24488
+ getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
24489
+ const ranges = [];
24490
+ for (const [i, dataSet] of dataSets.entries()) {
24491
+ ranges.push({
24492
+ ...this.dataSetDesign?.[i],
24493
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
24494
+ });
24495
+ }
24496
+ return {
24497
+ type: "pyramid",
24498
+ dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
24499
+ background: this.background,
24500
+ dataSets: ranges,
24501
+ legendPosition: this.legendPosition,
24502
+ labelRange: labelRange
24503
+ ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
24504
+ : undefined,
24505
+ title: this.title,
24506
+ aggregated: this.aggregated,
24507
+ axesDesign: this.axesDesign,
24508
+ horizontal: true,
24509
+ stacked: true,
24510
+ };
24511
+ }
24512
+ getDefinitionForExcel() {
24513
+ return undefined;
24514
+ }
24515
+ updateRanges(applyChange) {
24516
+ const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
24517
+ if (!isStale) {
24518
+ return this;
24519
+ }
24520
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
24521
+ return new PyramidChart(definition, this.sheetId, this.getters);
24522
+ }
24523
+ }
24524
+ function createPyramidChartRuntime(chart, getters) {
24525
+ const barDef = { ...chart.getDefinition(), type: "bar" };
24526
+ const barChart = new BarChart(barDef, chart.sheetId, getters);
24527
+ const barRuntime = createBarChartRuntime(barChart, getters);
24528
+ const config = barRuntime.chartJsConfig;
24529
+ let datasets = config.data?.datasets;
24530
+ if (datasets && datasets[0]) {
24531
+ datasets[0].data = datasets[0].data.map((value) => (value > 0 ? value : 0));
24532
+ }
24533
+ if (datasets && datasets[1]) {
24534
+ datasets[1].data = datasets[1].data.map((value) => (value > 0 ? -value : 0));
24535
+ }
24536
+ const scales = config.options.scales;
24537
+ const scalesXCallback = scales.x.ticks.callback;
24538
+ scales.x.ticks.callback = (value) => scalesXCallback(Math.abs(value));
24539
+ const tooltipLabelCallback = config.options.plugins.tooltip.callbacks.label;
24540
+ config.options.plugins.tooltip.callbacks.label = (item) => {
24541
+ const tooltipItem = { ...item, parsed: { y: item.parsed.y, x: Math.abs(item.parsed.x) } };
24542
+ return tooltipLabelCallback(tooltipItem);
24543
+ };
24303
24544
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
24304
24545
  }
24305
24546
 
@@ -24742,7 +24983,6 @@ stores.inject(MyMetaStore, storeInstance);
24742
24983
  validateChartDefinition: BarChart.validateChartDefinition,
24743
24984
  transformDefinition: BarChart.transformDefinition,
24744
24985
  getChartDefinitionFromContextCreation: BarChart.getDefinitionFromContextCreation,
24745
- name: _t("Bar"),
24746
24986
  sequence: 10,
24747
24987
  });
24748
24988
  chartRegistry.add("combo", {
@@ -24752,7 +24992,6 @@ stores.inject(MyMetaStore, storeInstance);
24752
24992
  validateChartDefinition: ComboChart.validateChartDefinition,
24753
24993
  transformDefinition: ComboChart.transformDefinition,
24754
24994
  getChartDefinitionFromContextCreation: ComboChart.getDefinitionFromContextCreation,
24755
- name: _t("Combo"),
24756
24995
  sequence: 15,
24757
24996
  });
24758
24997
  chartRegistry.add("line", {
@@ -24762,7 +25001,6 @@ stores.inject(MyMetaStore, storeInstance);
24762
25001
  validateChartDefinition: LineChart.validateChartDefinition,
24763
25002
  transformDefinition: LineChart.transformDefinition,
24764
25003
  getChartDefinitionFromContextCreation: LineChart.getDefinitionFromContextCreation,
24765
- name: _t("Line"),
24766
25004
  sequence: 20,
24767
25005
  });
24768
25006
  chartRegistry.add("pie", {
@@ -24772,7 +25010,6 @@ stores.inject(MyMetaStore, storeInstance);
24772
25010
  validateChartDefinition: PieChart.validateChartDefinition,
24773
25011
  transformDefinition: PieChart.transformDefinition,
24774
25012
  getChartDefinitionFromContextCreation: PieChart.getDefinitionFromContextCreation,
24775
- name: _t("Pie"),
24776
25013
  sequence: 30,
24777
25014
  });
24778
25015
  chartRegistry.add("scorecard", {
@@ -24782,7 +25019,6 @@ stores.inject(MyMetaStore, storeInstance);
24782
25019
  validateChartDefinition: ScorecardChart$1.validateChartDefinition,
24783
25020
  transformDefinition: ScorecardChart$1.transformDefinition,
24784
25021
  getChartDefinitionFromContextCreation: ScorecardChart$1.getDefinitionFromContextCreation,
24785
- name: _t("Scorecard"),
24786
25022
  sequence: 40,
24787
25023
  });
24788
25024
  chartRegistry.add("gauge", {
@@ -24792,7 +25028,6 @@ stores.inject(MyMetaStore, storeInstance);
24792
25028
  validateChartDefinition: GaugeChart.validateChartDefinition,
24793
25029
  transformDefinition: GaugeChart.transformDefinition,
24794
25030
  getChartDefinitionFromContextCreation: GaugeChart.getDefinitionFromContextCreation,
24795
- name: _t("Gauge"),
24796
25031
  sequence: 50,
24797
25032
  });
24798
25033
  chartRegistry.add("scatter", {
@@ -24802,7 +25037,6 @@ stores.inject(MyMetaStore, storeInstance);
24802
25037
  validateChartDefinition: ScatterChart.validateChartDefinition,
24803
25038
  transformDefinition: ScatterChart.transformDefinition,
24804
25039
  getChartDefinitionFromContextCreation: ScatterChart.getDefinitionFromContextCreation,
24805
- name: _t("Scatter"),
24806
25040
  sequence: 60,
24807
25041
  });
24808
25042
  chartRegistry.add("waterfall", {
@@ -24812,9 +25046,17 @@ stores.inject(MyMetaStore, storeInstance);
24812
25046
  validateChartDefinition: WaterfallChart.validateChartDefinition,
24813
25047
  transformDefinition: WaterfallChart.transformDefinition,
24814
25048
  getChartDefinitionFromContextCreation: WaterfallChart.getDefinitionFromContextCreation,
24815
- name: _t("Waterfall"),
24816
25049
  sequence: 70,
24817
25050
  });
25051
+ chartRegistry.add("pyramid", {
25052
+ match: (type) => type === "pyramid",
25053
+ createChart: (definition, sheetId, getters) => new PyramidChart(definition, sheetId, getters),
25054
+ getChartRuntime: createPyramidChartRuntime,
25055
+ validateChartDefinition: PyramidChart.validateChartDefinition,
25056
+ transformDefinition: PyramidChart.transformDefinition,
25057
+ getChartDefinitionFromContextCreation: PyramidChart.getDefinitionFromContextCreation,
25058
+ sequence: 80,
25059
+ });
24818
25060
  const chartComponentRegistry = new Registry();
24819
25061
  chartComponentRegistry.add("line", ChartJsComponent);
24820
25062
  chartComponentRegistry.add("bar", ChartJsComponent);
@@ -24824,6 +25066,130 @@ stores.inject(MyMetaStore, storeInstance);
24824
25066
  chartComponentRegistry.add("scatter", ChartJsComponent);
24825
25067
  chartComponentRegistry.add("scorecard", ScorecardChart);
24826
25068
  chartComponentRegistry.add("waterfall", ChartJsComponent);
25069
+ chartComponentRegistry.add("pyramid", ChartJsComponent);
25070
+ const chartCategories = {
25071
+ line: _t("Line"),
25072
+ column: _t("Column"),
25073
+ bar: _t("Bar"),
25074
+ pie: _t("Pie"),
25075
+ misc: _t("Miscellaneous"),
25076
+ };
25077
+ const chartSubtypeRegistry = new Registry();
25078
+ chartSubtypeRegistry
25079
+ .add("line", {
25080
+ matcher: (definition) => definition.type === "line" && !definition.stacked,
25081
+ displayName: _t("Line"),
25082
+ chartType: "line",
25083
+ chartSubtype: "line",
25084
+ subtypeDefinition: { stacked: false },
25085
+ category: "line",
25086
+ preview: "o-spreadsheet-ChartPreview.LINE_CHART",
25087
+ })
25088
+ .add("stacked_line", {
25089
+ matcher: (definition) => definition.type === "line" && definition.stacked,
25090
+ displayName: _t("Stacked Line"),
25091
+ chartType: "line",
25092
+ chartSubtype: "stacked_line",
25093
+ subtypeDefinition: { stacked: true },
25094
+ category: "line",
25095
+ preview: "o-spreadsheet-ChartPreview.STACKED_AREA_CHART",
25096
+ })
25097
+ .add("scatter", {
25098
+ displayName: _t("Scatter"),
25099
+ chartType: "scatter",
25100
+ chartSubtype: "scatter",
25101
+ category: "misc",
25102
+ preview: "o-spreadsheet-ChartPreview.SCATTER_CHART",
25103
+ })
25104
+ .add("column", {
25105
+ matcher: (definition) => definition.type === "bar" && !definition.stacked && !definition.horizontal,
25106
+ displayName: _t("Column"),
25107
+ chartType: "bar",
25108
+ chartSubtype: "column",
25109
+ subtypeDefinition: { stacked: false, horizontal: false },
25110
+ category: "column",
25111
+ preview: "o-spreadsheet-ChartPreview.COLUMN_CHART",
25112
+ })
25113
+ .add("stacked_column", {
25114
+ matcher: (definition) => definition.type === "bar" && definition.stacked && !definition.horizontal,
25115
+ displayName: _t("Stacked Column"),
25116
+ chartType: "bar",
25117
+ chartSubtype: "stacked_column",
25118
+ subtypeDefinition: { stacked: true, horizontal: false },
25119
+ category: "column",
25120
+ preview: "o-spreadsheet-ChartPreview.STACKED_COLUMN_CHART",
25121
+ })
25122
+ .add("bar", {
25123
+ matcher: (definition) => definition.type === "bar" && !definition.stacked && !!definition.horizontal,
25124
+ displayName: _t("Bar"),
25125
+ chartType: "bar",
25126
+ chartSubtype: "bar",
25127
+ subtypeDefinition: { horizontal: true, stacked: false },
25128
+ category: "bar",
25129
+ preview: "o-spreadsheet-ChartPreview.BAR_CHART",
25130
+ })
25131
+ .add("stacked_bar", {
25132
+ matcher: (definition) => definition.type === "bar" && definition.stacked && !!definition.horizontal,
25133
+ displayName: _t("Stacked Bar"),
25134
+ chartType: "bar",
25135
+ chartSubtype: "stacked_bar",
25136
+ subtypeDefinition: { horizontal: true, stacked: true },
25137
+ category: "bar",
25138
+ preview: "o-spreadsheet-ChartPreview.STACKED_BAR_CHART",
25139
+ })
25140
+ .add("combo", {
25141
+ displayName: _t("Combo"),
25142
+ chartSubtype: "combo",
25143
+ chartType: "combo",
25144
+ category: "line",
25145
+ preview: "o-spreadsheet-ChartPreview.COMBO_CHART",
25146
+ })
25147
+ .add("pie", {
25148
+ matcher: (definition) => definition.type === "pie" && !definition.isDoughnut,
25149
+ displayName: _t("Pie"),
25150
+ chartSubtype: "pie",
25151
+ chartType: "pie",
25152
+ subtypeDefinition: { isDoughnut: false },
25153
+ category: "pie",
25154
+ preview: "o-spreadsheet-ChartPreview.PIE_CHART",
25155
+ })
25156
+ .add("doughnut", {
25157
+ matcher: (definition) => definition.type === "pie" && !!definition.isDoughnut,
25158
+ displayName: _t("Doughnut"),
25159
+ chartSubtype: "doughnut",
25160
+ chartType: "pie",
25161
+ subtypeDefinition: { isDoughnut: true },
25162
+ category: "pie",
25163
+ preview: "o-spreadsheet-ChartPreview.DOUGHNUT_CHART",
25164
+ })
25165
+ .add("gauge", {
25166
+ displayName: _t("Gauge"),
25167
+ chartSubtype: "gauge",
25168
+ chartType: "gauge",
25169
+ category: "misc",
25170
+ preview: "o-spreadsheet-ChartPreview.GAUGE_CHART",
25171
+ })
25172
+ .add("scorecard", {
25173
+ displayName: _t("Scorecard"),
25174
+ chartSubtype: "scorecard",
25175
+ chartType: "scorecard",
25176
+ category: "misc",
25177
+ preview: "o-spreadsheet-ChartPreview.SCORECARD_CHART",
25178
+ })
25179
+ .add("waterfall", {
25180
+ displayName: _t("Waterfall"),
25181
+ chartSubtype: "waterfall",
25182
+ chartType: "waterfall",
25183
+ category: "misc",
25184
+ preview: "o-spreadsheet-ChartPreview.WATERFALL_CHART",
25185
+ })
25186
+ .add("pyramid", {
25187
+ displayName: _t("Population Pyramid"),
25188
+ chartSubtype: "pyramid",
25189
+ chartType: "pyramid",
25190
+ category: "misc",
25191
+ preview: "o-spreadsheet-ChartPreview.POPULATION_PYRAMID_CHART",
25192
+ });
24827
25193
 
24828
25194
  /**
24829
25195
  * Registry intended to support usual currencies. It is mainly used to create
@@ -26680,20 +27046,6 @@ stores.inject(MyMetaStore, storeInstance);
26680
27046
  }
26681
27047
  return transformation.transformDefinition(definition, executed);
26682
27048
  }
26683
- /**
26684
- * Get an empty definition based on the given context and the given type
26685
- */
26686
- function getChartDefinitionFromContextCreation(context, type) {
26687
- const chartClass = chartRegistry.get(type);
26688
- return chartClass.getChartDefinitionFromContextCreation(context);
26689
- }
26690
- function getChartTypes() {
26691
- const result = {};
26692
- for (const key of chartRegistry.getKeys()) {
26693
- result[key] = chartRegistry.get(key).name;
26694
- }
26695
- return result;
26696
- }
26697
27049
  /**
26698
27050
  * Return a "smart" chart definition in the given zone. The definition is "smart" because it will
26699
27051
  * use the best type of chart to display the data of the zone.
@@ -28153,6 +28505,40 @@ stores.inject(MyMetaStore, storeInstance);
28153
28505
  },
28154
28506
  icon: "o-spreadsheet-Icon.PIVOT",
28155
28507
  };
28508
+ const FIX_FORMULAS = {
28509
+ name: _t("Convert to individual formulas"),
28510
+ execute(env) {
28511
+ const position = env.model.getters.getActivePosition();
28512
+ const cell = env.model.getters.getCorrespondingFormulaCell(position);
28513
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
28514
+ if (!cell || !pivotId) {
28515
+ return;
28516
+ }
28517
+ const { sheetId, col, row } = env.model.getters.getCellPosition(cell.id);
28518
+ const pivot = env.model.getters.getPivot(pivotId);
28519
+ pivot.init();
28520
+ if (!pivot.isValid()) {
28521
+ return;
28522
+ }
28523
+ env.model.dispatch("INSERT_PIVOT", {
28524
+ sheetId,
28525
+ col,
28526
+ row,
28527
+ pivotId,
28528
+ table: pivot.getTableStructure().export(),
28529
+ });
28530
+ },
28531
+ isVisible: (env) => {
28532
+ const position = env.model.getters.getActivePosition();
28533
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
28534
+ if (!pivotId) {
28535
+ return false;
28536
+ }
28537
+ const pivot = env.model.getters.getPivot(pivotId);
28538
+ return pivot.isValid() && env.model.getters.isSpillPivotFormula(position);
28539
+ },
28540
+ icon: "o-spreadsheet-Icon.PIVOT",
28541
+ };
28156
28542
 
28157
28543
  //------------------------------------------------------------------------------
28158
28544
  // Context Menu Registry
@@ -28251,6 +28637,10 @@ stores.inject(MyMetaStore, storeInstance);
28251
28637
  name: INSERT_LINK_NAME,
28252
28638
  sequence: 150,
28253
28639
  separator: true,
28640
+ })
28641
+ .add("pivot_fix_formulas", {
28642
+ ...FIX_FORMULAS,
28643
+ sequence: 155,
28254
28644
  })
28255
28645
  .add("pivot_properties", {
28256
28646
  ...pivotProperties,
@@ -30473,6 +30863,7 @@ stores.inject(MyMetaStore, storeInstance);
30473
30863
  });
30474
30864
  dataSeriesRanges = [];
30475
30865
  labelRange;
30866
+ chartTerms = ChartTerms;
30476
30867
  setup() {
30477
30868
  this.dataSeriesRanges = this.props.definition.dataSets;
30478
30869
  this.labelRange = this.props.definition.labelRange;
@@ -30497,7 +30888,7 @@ stores.inject(MyMetaStore, storeInstance);
30497
30888
  return [
30498
30889
  {
30499
30890
  name: "aggregated",
30500
- label: _t("Aggregate"),
30891
+ label: this.chartTerms.AggregatedChart,
30501
30892
  value: this.props.definition.aggregated ?? false,
30502
30893
  onChange: this.onUpdateAggregated.bind(this),
30503
30894
  },
@@ -30573,9 +30964,6 @@ stores.inject(MyMetaStore, storeInstance);
30573
30964
 
30574
30965
  class BarConfigPanel extends GenericChartConfigPanel {
30575
30966
  static template = "o-spreadsheet-BarConfigPanel";
30576
- get stackedLabel() {
30577
- return _t("Stacked barchart");
30578
- }
30579
30967
  onUpdateStacked(stacked) {
30580
30968
  this.props.updateChart(this.props.figureId, {
30581
30969
  stacked,
@@ -31565,6 +31953,9 @@ stores.inject(MyMetaStore, storeInstance);
31565
31953
  return "left";
31566
31954
  return dataSets[this.state.index].yAxisId === "y1" ? "right" : "left";
31567
31955
  }
31956
+ get canHaveTwoVerticalAxis() {
31957
+ return "horizontal" in this.props.definition ? !this.props.definition.horizontal : true;
31958
+ }
31568
31959
  updateDataSeriesLabel(ev) {
31569
31960
  const label = ev.target.value;
31570
31961
  const dataSets = this.props.definition.dataSets;
@@ -31679,14 +32070,6 @@ stores.inject(MyMetaStore, storeInstance);
31679
32070
  const cancelledReasons = [...(this.state.sectionRuleDispatchResult?.reasons || [])];
31680
32071
  return cancelledReasons.map((error) => ChartTerms.Errors[error] || ChartTerms.Errors.Unexpected);
31681
32072
  }
31682
- updateBackgroundColor(color) {
31683
- this.props.updateChart(this.props.figureId, {
31684
- background: color,
31685
- });
31686
- }
31687
- updateTitle(content) {
31688
- this.props.updateChart(this.props.figureId, { title: { text: content } });
31689
- }
31690
32073
  isRangeMinInvalid() {
31691
32074
  return !!(this.state.sectionRuleDispatchResult?.isCancelledBecause("EmptyGaugeRangeMin" /* CommandResult.EmptyGaugeRangeMin */) ||
31692
32075
  this.state.sectionRuleDispatchResult?.isCancelledBecause("GaugeRangeMinNaN" /* CommandResult.GaugeRangeMinNaN */) ||
@@ -31726,9 +32109,6 @@ stores.inject(MyMetaStore, storeInstance);
31726
32109
  sectionRule,
31727
32110
  });
31728
32111
  }
31729
- get backgroundColorTitle() {
31730
- return ChartTerms.BackgroundColor;
31731
- }
31732
32112
  }
31733
32113
 
31734
32114
  class LineConfigPanel extends GenericChartConfigPanel {
@@ -31740,19 +32120,13 @@ stores.inject(MyMetaStore, storeInstance);
31740
32120
  }
31741
32121
  return false;
31742
32122
  }
31743
- get stackedLabel() {
31744
- return _t("Stacked linechart");
31745
- }
31746
- get cumulativeLabel() {
31747
- return _t("Cumulative data");
31748
- }
31749
32123
  getLabelRangeOptions() {
31750
32124
  const options = super.getLabelRangeOptions();
31751
32125
  if (this.canTreatLabelsAsText) {
31752
32126
  options.push({
31753
32127
  name: "labelsAsText",
31754
32128
  value: this.props.definition.labelsAsText,
31755
- label: _t("Treat labels as text"),
32129
+ label: this.chartTerms.TreatLabelsAsText,
31756
32130
  onChange: this.onUpdateLabelsAsText.bind(this),
31757
32131
  });
31758
32132
  }
@@ -31819,7 +32193,7 @@ stores.inject(MyMetaStore, storeInstance);
31819
32193
  options.push({
31820
32194
  name: "labelsAsText",
31821
32195
  value: this.props.definition.labelsAsText,
31822
- label: _t("Treat labels as text"),
32196
+ label: this.chartTerms.TreatLabelsAsText,
31823
32197
  onChange: this.onUpdateLabelsAsText.bind(this),
31824
32198
  });
31825
32199
  }
@@ -31911,9 +32285,6 @@ stores.inject(MyMetaStore, storeInstance);
31911
32285
  get humanizeNumbersLabel() {
31912
32286
  return _t("Humanize numbers");
31913
32287
  }
31914
- updateTitle(content) {
31915
- this.props.updateChart(this.props.figureId, { title: { text: content } });
31916
- }
31917
32288
  updateHumanizeNumbers(humanize) {
31918
32289
  this.props.updateChart(this.props.figureId, { humanize });
31919
32290
  }
@@ -31936,9 +32307,6 @@ stores.inject(MyMetaStore, storeInstance);
31936
32307
  break;
31937
32308
  }
31938
32309
  }
31939
- get backgroundColorTitle() {
31940
- return ChartTerms.BackgroundColor;
31941
- }
31942
32310
  }
31943
32311
 
31944
32312
  class WaterfallChartDesignPanel extends owl.Component {
@@ -32032,8 +32400,106 @@ stores.inject(MyMetaStore, storeInstance);
32032
32400
  .add("waterfall", {
32033
32401
  configuration: GenericChartConfigPanel,
32034
32402
  design: WaterfallChartDesignPanel,
32403
+ })
32404
+ .add("pyramid", {
32405
+ configuration: GenericChartConfigPanel,
32406
+ design: ChartWithAxisDesignPanel,
32035
32407
  });
32036
32408
 
32409
+ css /* scss */ `
32410
+ .o-section .o-type-selector {
32411
+ height: 30px;
32412
+ padding-left: 30px;
32413
+ }
32414
+ .o-type-selector-preview {
32415
+ left: 5px;
32416
+ top: 3px;
32417
+ .o-chart-preview {
32418
+ width: 24px;
32419
+ height: 24px;
32420
+ }
32421
+ }
32422
+
32423
+ .o-popover .o-chart-select-popover {
32424
+ box-sizing: border-box;
32425
+ background: #fff;
32426
+ .o-chart-type-item {
32427
+ cursor: pointer;
32428
+ padding: 3px 6px;
32429
+ margin: 1px 2px;
32430
+ &.selected,
32431
+ &:hover {
32432
+ background: #f5f5f5;
32433
+ border: 1px solid #ccc;
32434
+ padding: 2px 5px;
32435
+ }
32436
+ .o-chart-preview {
32437
+ width: 48px;
32438
+ height: 48px;
32439
+ }
32440
+ }
32441
+ }
32442
+ `;
32443
+ class ChartTypePicker extends owl.Component {
32444
+ static template = "o-spreadsheet-ChartTypePicker";
32445
+ static components = { Section, Popover };
32446
+ static props = { figureId: String, chartPanelStore: Object };
32447
+ categories = chartCategories;
32448
+ chartTypeByCategories = {};
32449
+ popoverRef = owl.useRef("popoverRef");
32450
+ selectRef = owl.useRef("selectRef");
32451
+ state = owl.useState({ popoverProps: undefined, popoverStyle: "" });
32452
+ setup() {
32453
+ owl.useExternalListener(window, "pointerdown", this.onExternalClick, { capture: true });
32454
+ for (const subtypeProperties of chartSubtypeRegistry.getAll()) {
32455
+ if (this.chartTypeByCategories[subtypeProperties.category]) {
32456
+ this.chartTypeByCategories[subtypeProperties.category].push(subtypeProperties);
32457
+ }
32458
+ else {
32459
+ this.chartTypeByCategories[subtypeProperties.category] = [subtypeProperties];
32460
+ }
32461
+ }
32462
+ }
32463
+ onExternalClick(ev) {
32464
+ if (isChildEvent(this.popoverRef.el?.parentElement, ev) ||
32465
+ isChildEvent(this.selectRef.el, ev)) {
32466
+ return;
32467
+ }
32468
+ this.closePopover();
32469
+ }
32470
+ onTypeChange(type) {
32471
+ this.props.chartPanelStore.changeChartType(this.props.figureId, type);
32472
+ this.closePopover();
32473
+ }
32474
+ getChartDefinition(figureId) {
32475
+ return this.env.model.getters.getChartDefinition(figureId);
32476
+ }
32477
+ getSelectedChartSubtypeProperties() {
32478
+ const definition = this.getChartDefinition(this.props.figureId);
32479
+ const matchedChart = chartSubtypeRegistry
32480
+ .getAll()
32481
+ .find((c) => c.matcher?.(definition) || false);
32482
+ return matchedChart || chartSubtypeRegistry.get(definition.type);
32483
+ }
32484
+ onPointerDown(ev) {
32485
+ if (this.state.popoverProps) {
32486
+ this.closePopover();
32487
+ return;
32488
+ }
32489
+ const target = ev.currentTarget;
32490
+ const { bottom, right, width } = target.getBoundingClientRect();
32491
+ this.state.popoverProps = {
32492
+ anchorRect: { x: right, y: bottom, width: 0, height: 0 },
32493
+ positioning: "TopRight",
32494
+ verticalOffset: 0,
32495
+ };
32496
+ this.state.popoverStyle = cssPropertiesToCss({ width: `${width}px` });
32497
+ }
32498
+ closePopover() {
32499
+ this.state.popoverProps = undefined;
32500
+ }
32501
+ }
32502
+
32037
32503
  class MainChartPanelStore extends SpreadsheetStore {
32038
32504
  mutators = ["activatePanel", "changeChartType"];
32039
32505
  panel = "configuration";
@@ -32041,7 +32507,7 @@ stores.inject(MyMetaStore, storeInstance);
32041
32507
  activatePanel(panel) {
32042
32508
  this.panel = panel;
32043
32509
  }
32044
- changeChartType(figureId, type) {
32510
+ changeChartType(figureId, newDisplayType) {
32045
32511
  this.creationContext = {
32046
32512
  ...this.creationContext,
32047
32513
  ...this.getters.getContextCreationChart(figureId),
@@ -32050,13 +32516,25 @@ stores.inject(MyMetaStore, storeInstance);
32050
32516
  if (!sheetId) {
32051
32517
  return;
32052
32518
  }
32053
- const definition = getChartDefinitionFromContextCreation(this.creationContext, type);
32519
+ const definition = this.getChartDefinitionFromContextCreation(figureId, newDisplayType);
32054
32520
  this.model.dispatch("UPDATE_CHART", {
32055
32521
  definition,
32056
32522
  id: figureId,
32057
32523
  sheetId,
32058
32524
  });
32059
32525
  }
32526
+ getChartDefinitionFromContextCreation(figureId, newDisplayType) {
32527
+ const newChartInfo = chartSubtypeRegistry.get(newDisplayType);
32528
+ const ChartClass = chartRegistry.get(newChartInfo.chartType);
32529
+ const contextCreation = {
32530
+ ...this.creationContext,
32531
+ ...this.getters.getContextCreationChart(figureId),
32532
+ };
32533
+ return {
32534
+ ...ChartClass.getChartDefinitionFromContextCreation(contextCreation),
32535
+ ...newChartInfo.subtypeDefinition,
32536
+ };
32537
+ }
32060
32538
  }
32061
32539
 
32062
32540
  css /* scss */ `
@@ -32085,7 +32563,7 @@ stores.inject(MyMetaStore, storeInstance);
32085
32563
  `;
32086
32564
  class ChartPanel extends owl.Component {
32087
32565
  static template = "o-spreadsheet-ChartPanel";
32088
- static components = { Section };
32566
+ static components = { Section, ChartTypePicker };
32089
32567
  static props = { onCloseSidePanel: Function, figureId: String };
32090
32568
  store;
32091
32569
  get figureId() {
@@ -32145,9 +32623,6 @@ stores.inject(MyMetaStore, storeInstance);
32145
32623
  getChartDefinition(figureId) {
32146
32624
  return this.env.model.getters.getChartDefinition(figureId);
32147
32625
  }
32148
- get chartTypes() {
32149
- return getChartTypes();
32150
- }
32151
32626
  }
32152
32627
 
32153
32628
  css /* scss */ `
@@ -35221,9 +35696,17 @@ stores.inject(MyMetaStore, storeInstance);
35221
35696
  }
35222
35697
  getPivotCell(col, row, includeTotal = true) {
35223
35698
  const colHeadersHeight = this.columns.length;
35224
- if (row <= colHeadersHeight - 1) {
35699
+ if (col > 0 && row === colHeadersHeight - 1) {
35225
35700
  const domain = this.getColHeaderDomain(col, row);
35226
- return domain ? { type: "HEADER", domain } : { type: "EMPTY" };
35701
+ if (!domain) {
35702
+ return EMPTY_PIVOT_CELL;
35703
+ }
35704
+ const measure = domain.at(-1)?.value.toString() || "";
35705
+ return { type: "MEASURE_HEADER", domain: domain.slice(0, -1), measure };
35706
+ }
35707
+ else if (row <= colHeadersHeight - 1) {
35708
+ const domain = this.getColHeaderDomain(col, row);
35709
+ return domain ? { type: "HEADER", domain } : EMPTY_PIVOT_CELL;
35227
35710
  }
35228
35711
  else if (col === 0) {
35229
35712
  const rowIndex = row - colHeadersHeight;
@@ -35233,7 +35716,7 @@ stores.inject(MyMetaStore, storeInstance);
35233
35716
  else {
35234
35717
  const rowIndex = row - colHeadersHeight;
35235
35718
  if (!includeTotal && this.isTotalRow(rowIndex)) {
35236
- return { type: "EMPTY" };
35719
+ return EMPTY_PIVOT_CELL;
35237
35720
  }
35238
35721
  const domain = [...this.getRowDomain(rowIndex), ...this.getColDomain(col)];
35239
35722
  const measure = this.getColMeasure(col);
@@ -35287,6 +35770,7 @@ stores.inject(MyMetaStore, storeInstance);
35287
35770
  };
35288
35771
  }
35289
35772
  }
35773
+ const EMPTY_PIVOT_CELL = { type: "EMPTY" };
35290
35774
 
35291
35775
  /**
35292
35776
  * This function converts a list of data entry into a spreadsheet pivot table.
@@ -35484,6 +35968,9 @@ stores.inject(MyMetaStore, storeInstance);
35484
35968
  if (!(granularity in MAP_VALUE_DIMENSION_DATE)) {
35485
35969
  throw new Error(`Unknown date granularity: ${granularity}`);
35486
35970
  }
35971
+ if (value === null) {
35972
+ return null;
35973
+ }
35487
35974
  if (!MAP_VALUE_DIMENSION_DATE[granularity].set.has(value)) {
35488
35975
  MAP_VALUE_DIMENSION_DATE[granularity].set.add(value);
35489
35976
  const date = toJsDate(value, locale);
@@ -35714,14 +36201,17 @@ stores.inject(MyMetaStore, storeInstance);
35714
36201
  if (dimension.type === "date") {
35715
36202
  const adapter = pivotTimeAdapter(dimension.granularity);
35716
36203
  return {
35717
- value: adapter.toCellValue(toNormalizedPivotValue(dimension, lastNode.value)),
36204
+ value: lastNode.value !== "null"
36205
+ ? adapter.toCellValue(toNormalizedPivotValue(dimension, lastNode.value))
36206
+ : _t("(Undefined)"),
35718
36207
  format: adapter.getFormat(this.getters.getLocale()),
35719
36208
  };
35720
36209
  }
35721
36210
  if (!finalCell) {
35722
36211
  return { value: "" };
35723
36212
  }
35724
- if (finalCell.value === null) {
36213
+ // Value can be null but stringified (e.g. an empty date, as for now every date is stringified)
36214
+ if (finalCell.value === null || finalCell.value === `${null}`) {
35725
36215
  return { value: _t("(Undefined)") };
35726
36216
  }
35727
36217
  return {
@@ -35743,10 +36233,15 @@ stores.inject(MyMetaStore, storeInstance);
35743
36233
  if (!operator) {
35744
36234
  throw new Error(`Aggregator ${aggregator} does not exist`);
35745
36235
  }
35746
- return {
35747
- value: values.length ? operator.fn([values], this.getters.getLocale()) : "",
35748
- format: operator.format(values[0]),
35749
- };
36236
+ try {
36237
+ return {
36238
+ value: values.length ? operator.fn([values], this.getters.getLocale()) : "",
36239
+ format: operator.format(values[0]),
36240
+ };
36241
+ }
36242
+ catch (e) {
36243
+ return handleError(e, aggregator.toUpperCase());
36244
+ }
35750
36245
  }
35751
36246
  getPossibleFieldValues(dimension) {
35752
36247
  const values = [];
@@ -38620,7 +39115,7 @@ stores.inject(MyMetaStore, storeInstance);
38620
39115
  });
38621
39116
  }
38622
39117
  getContainerRect(container) {
38623
- const { width: viewWidth, height: viewHeight } = this.env.model.getters.getMainViewportRect();
39118
+ const { width: viewWidth, height: viewHeight } = this.env.model.getters.getSheetViewDimension();
38624
39119
  const { x: viewportX, y: viewportY } = this.env.model.getters.getMainViewportCoordinates();
38625
39120
  const x = ["bottomRight", "topRight"].includes(container) ? viewportX : 0;
38626
39121
  const width = viewWidth - x;
@@ -51113,30 +51608,14 @@ stores.inject(MyMetaStore, storeInstance);
51113
51608
  for (let col = 0; col < pivotCells.length; col++) {
51114
51609
  for (let row = 0; row < pivotCells[col].length; row++) {
51115
51610
  const pivotCell = pivotCells[col][row];
51116
- const cellPosition = {
51611
+ this.dispatch("UPDATE_CELL", {
51117
51612
  sheetId: position.sheetId,
51118
51613
  col: position.col + col,
51119
51614
  row: position.row + row,
51120
- };
51121
- this.addPivotFormula(cellPosition, formulaId, pivotCell);
51615
+ content: makePivotFormulaFromPivotCell(formulaId, pivotCell),
51616
+ });
51122
51617
  }
51123
51618
  }
51124
- const pivotZone = {
51125
- top: position.row,
51126
- bottom: position.row + pivotCells[0].length - 1,
51127
- left: position.col,
51128
- right: position.col + pivotCells.length - 1,
51129
- };
51130
- const numberOfHeaders = table.columns.length - 1;
51131
- const cmdContent = {
51132
- sheetId: position.sheetId,
51133
- ranges: [this.getters.getRangeDataFromZone(position.sheetId, pivotZone)],
51134
- config: { ...PIVOT_TABLE_CONFIG, numberOfHeaders },
51135
- tableType: "static",
51136
- };
51137
- if (this.canDispatch("CREATE_TABLE", cmdContent).isSuccessful) {
51138
- this.dispatch("CREATE_TABLE", cmdContent);
51139
- }
51140
51619
  }
51141
51620
  resizeSheet(sheetId, { col, row }, table) {
51142
51621
  const colLimit = table.getNumberOfDataColumns() + 1; // +1 for the Top-Left
@@ -51164,21 +51643,6 @@ stores.inject(MyMetaStore, storeInstance);
51164
51643
  });
51165
51644
  }
51166
51645
  }
51167
- addPivotFormula(position, formulaId, pivotCell) {
51168
- let content = undefined;
51169
- switch (pivotCell.type) {
51170
- case "HEADER":
51171
- content = makePivotFormula("PIVOT.HEADER", [formulaId, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
51172
- break;
51173
- case "VALUE":
51174
- content = makePivotFormula("PIVOT.VALUE", [formulaId, pivotCell.measure, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
51175
- break;
51176
- }
51177
- this.dispatch("UPDATE_CELL", {
51178
- ...position,
51179
- content,
51180
- });
51181
- }
51182
51646
  getPivotCore(pivotId) {
51183
51647
  const pivot = this.pivots[pivotId];
51184
51648
  if (!pivot) {
@@ -52843,7 +53307,6 @@ stores.inject(MyMetaStore, storeInstance);
52843
53307
  if (!this.blockedArrayFormulas.has(position)) {
52844
53308
  this.invalidateSpreading(position);
52845
53309
  }
52846
- this.spreadingRelations.removeNode(position);
52847
53310
  const cell = this.getters.getCell(position);
52848
53311
  if (cell === undefined) {
52849
53312
  return EMPTY_CELL;
@@ -52885,6 +53348,7 @@ stores.inject(MyMetaStore, storeInstance);
52885
53348
  this.assertSheetHasEnoughSpaceToSpreadFormulaResult(formulaPosition, formulaReturn);
52886
53349
  const nbColumns = formulaReturn.length;
52887
53350
  const nbRows = formulaReturn[0].length;
53351
+ this.spreadingRelations.removeNode(formulaPosition);
52888
53352
  forEachSpreadPositionInMatrix(nbColumns, nbRows, this.updateSpreadRelation(formulaPosition));
52889
53353
  this.assertNoMergedCellsInSpreadZone(formulaPosition, formulaReturn);
52890
53354
  forEachSpreadPositionInMatrix(nbColumns, nbRows, this.checkCollision(formulaPosition));
@@ -54412,9 +54876,10 @@ stores.inject(MyMetaStore, storeInstance);
54412
54876
  "getPivot",
54413
54877
  "getFirstPivotFunction",
54414
54878
  "getPivotIdFromPosition",
54415
- "getPivotDomainArgsFromPosition",
54879
+ "getPivotCellFromPosition",
54416
54880
  "isPivotUnused",
54417
54881
  "areDomainArgsFieldsValid",
54882
+ "isSpillPivotFormula",
54418
54883
  ];
54419
54884
  pivots = {};
54420
54885
  unusedPivots;
@@ -54493,6 +54958,14 @@ stores.inject(MyMetaStore, storeInstance);
54493
54958
  }
54494
54959
  return undefined;
54495
54960
  }
54961
+ isSpillPivotFormula(position) {
54962
+ const cell = this.getters.getCorrespondingFormulaCell(position);
54963
+ if (cell && cell.isFormula) {
54964
+ const pivotFunction = this.getFirstPivotFunction(cell.compiledFormula.tokens);
54965
+ return pivotFunction?.functionName === "PIVOT";
54966
+ }
54967
+ return false;
54968
+ }
54496
54969
  getFirstPivotFunction(tokens) {
54497
54970
  const pivotFunction = getFirstPivotFunction(tokens);
54498
54971
  if (!pivotFunction) {
@@ -54526,29 +54999,29 @@ stores.inject(MyMetaStore, storeInstance);
54526
54999
  * If the cell is the result of PIVOT, the result is the domain of the cell
54527
55000
  * as if it was the individual pivot formula
54528
55001
  */
54529
- getPivotDomainArgsFromPosition(position) {
55002
+ getPivotCellFromPosition(position) {
54530
55003
  const cell = this.getters.getCorrespondingFormulaCell(position);
54531
55004
  if (!cell || !cell.isFormula || getNumberOfPivotFunctions(cell.compiledFormula.tokens) === 0) {
54532
- return undefined;
55005
+ return EMPTY_PIVOT_CELL;
54533
55006
  }
54534
55007
  const mainPosition = this.getters.getCellPosition(cell.id);
54535
55008
  const result = this.getters.getFirstPivotFunction(cell.compiledFormula.tokens);
54536
55009
  if (!result) {
54537
- return undefined;
55010
+ return EMPTY_PIVOT_CELL;
54538
55011
  }
54539
55012
  const { functionName, args } = result;
54540
55013
  if (functionName === "PIVOT") {
54541
55014
  const formulaId = args[0];
54542
55015
  if (!formulaId) {
54543
- return undefined;
55016
+ return EMPTY_PIVOT_CELL;
54544
55017
  }
54545
55018
  const pivotId = this.getters.getPivotId(formulaId.toString());
54546
55019
  if (!pivotId) {
54547
- return undefined;
55020
+ return EMPTY_PIVOT_CELL;
54548
55021
  }
54549
55022
  const pivot = this.getPivot(pivotId);
54550
55023
  if (!pivot.isValid()) {
54551
- return undefined;
55024
+ return EMPTY_PIVOT_CELL;
54552
55025
  }
54553
55026
  const includeTotal = args[2] === false ? false : undefined;
54554
55027
  const includeColumnHeaders = args[3] === false ? false : undefined;
@@ -54557,22 +55030,28 @@ stores.inject(MyMetaStore, storeInstance);
54557
55030
  .getPivotCells(includeTotal, includeColumnHeaders);
54558
55031
  const pivotCol = position.col - mainPosition.col;
54559
55032
  const pivotRow = position.row - mainPosition.row;
54560
- const pivotCell = pivotCells[pivotCol][pivotRow];
54561
- if (pivotCell.type === "EMPTY") {
54562
- return undefined;
54563
- }
54564
- let domain = pivotCell.domain;
54565
- if (domain.at(-1)?.field === "measure") {
54566
- domain = domain.slice(0, -1);
54567
- }
54568
- return { domainArgs: domain, isHeader: pivotCell.type === "HEADER" };
55033
+ return pivotCells[pivotCol][pivotRow];
54569
55034
  }
54570
- let domain = toPivotDomain(args.slice(functionName === "PIVOT.VALUE" ? 2 : 1).map((x) => `${x}`));
54571
- if (domain.at(-1)?.field === "measure") {
54572
- domain = domain.slice(0, -1);
55035
+ if (functionName === "PIVOT.HEADER" && args.at(-2) === "measure") {
55036
+ const domain = toPivotDomain(args.slice(1, -2).map((x) => `${x}`));
55037
+ return {
55038
+ type: "MEASURE_HEADER",
55039
+ domain,
55040
+ measure: args.at(-1)?.toString() || "",
55041
+ };
54573
55042
  }
54574
- const isHeader = functionName === "PIVOT.HEADER";
54575
- return { domainArgs: domain, isHeader };
55043
+ else if (functionName === "PIVOT.HEADER") {
55044
+ return {
55045
+ type: "HEADER",
55046
+ domain: toPivotDomain(args.slice(1).map((x) => `${x}`)),
55047
+ };
55048
+ }
55049
+ const [measure, ...domainArgs] = args.slice(1);
55050
+ return {
55051
+ type: "VALUE",
55052
+ domain: toPivotDomain(domainArgs.map((x) => `${x}`)),
55053
+ measure: measure?.toString() || "",
55054
+ };
54576
55055
  }
54577
55056
  getPivot(pivotId) {
54578
55057
  return this.pivots[pivotId];
@@ -55908,6 +56387,9 @@ stores.inject(MyMetaStore, storeInstance);
55908
56387
  * Send a snapshot of the spreadsheet to the collaboration server
55909
56388
  */
55910
56389
  snapshot(data) {
56390
+ if (this.pendingMessages.length !== 0) {
56391
+ return;
56392
+ }
55911
56393
  const snapshotId = this.uuidGenerator.uuidv4();
55912
56394
  this.transportService.sendMessage({
55913
56395
  type: "SNAPSHOT",
@@ -63852,6 +64334,9 @@ stores.inject(MyMetaStore, storeInstance);
63852
64334
  getBackToDefault() {
63853
64335
  this.stream.getBackToDefault();
63854
64336
  }
64337
+ getAnchor() {
64338
+ return this.anchor;
64339
+ }
63855
64340
  modifyAnchor(anchor, mode, options) {
63856
64341
  const sheetId = this.getters.getActiveSheetId();
63857
64342
  anchor = {
@@ -66815,6 +67300,7 @@ stores.inject(MyMetaStore, storeInstance);
66815
67300
  chartSidePanelComponentRegistry,
66816
67301
  chartComponentRegistry,
66817
67302
  chartRegistry,
67303
+ chartSubtypeRegistry,
66818
67304
  topbarMenuRegistry,
66819
67305
  topbarComponentRegistry,
66820
67306
  clickableCellRegistry,
@@ -66923,6 +67409,7 @@ stores.inject(MyMetaStore, storeInstance);
66923
67409
  GaugeChartDesignPanel,
66924
67410
  ScorecardChartConfigPanel,
66925
67411
  ScorecardChartDesignPanel,
67412
+ ChartTypePicker,
66926
67413
  FigureComponent,
66927
67414
  Menu,
66928
67415
  Popover,
@@ -66972,6 +67459,7 @@ stores.inject(MyMetaStore, storeInstance);
66972
67459
  DEFAULT_LOCALE,
66973
67460
  HIGHLIGHT_COLOR,
66974
67461
  PIVOT_TABLE_CONFIG,
67462
+ ChartTerms,
66975
67463
  };
66976
67464
 
66977
67465
  exports.AbstractCellClipboardHandler = AbstractCellClipboardHandler;
@@ -67020,9 +67508,9 @@ stores.inject(MyMetaStore, storeInstance);
67020
67508
  exports.tokenize = tokenize;
67021
67509
 
67022
67510
 
67023
- __info__.version = "17.4.0-alpha.4";
67024
- __info__.date = "2024-06-12T14:00:22.046Z";
67025
- __info__.hash = "cefb0e4";
67511
+ __info__.version = "17.4.0-alpha.6";
67512
+ __info__.date = "2024-06-19T13:46:27.157Z";
67513
+ __info__.hash = "a4f22e4";
67026
67514
 
67027
67515
 
67028
67516
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);