@odoo/o-spreadsheet 17.4.0-alpha.5 → 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.5
7
- * @date 2024-06-14T10:01:40.605Z
8
- * @hash 9ceed96
6
+ * @version 17.4.0-alpha.6
7
+ * @date 2024-06-19T13:46:27.157Z
8
+ * @hash a4f22e4
9
9
  */
10
10
 
11
11
  'use strict';
@@ -6202,7 +6202,7 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
6202
6202
  };
6203
6203
  }
6204
6204
  isPasteAllowed(sheetId, target, content, clipboardOptions) {
6205
- if (!("cells" in content)) {
6205
+ if (!content.cells) {
6206
6206
  return "Success" /* CommandResult.Success */;
6207
6207
  }
6208
6208
  if (clipboardOptions?.isCutOperation && clipboardOptions?.pasteOption !== undefined) {
@@ -6216,6 +6216,17 @@ class CellClipboardHandler extends AbstractCellClipboardHandler {
6216
6216
  return "WrongPasteSelection" /* CommandResult.WrongPasteSelection */;
6217
6217
  }
6218
6218
  }
6219
+ const clipboardHeight = content.cells.length;
6220
+ const clipboardWidth = content.cells[0].length;
6221
+ for (const zone of getPasteZones(target, content.cells)) {
6222
+ if (this.getters.doesIntersectMerge(sheetId, zone)) {
6223
+ if (target.length > 1 ||
6224
+ !this.getters.isSingleCellOrMerge(sheetId, target[0]) ||
6225
+ clipboardHeight * clipboardWidth !== 1) {
6226
+ return "WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */;
6227
+ }
6228
+ }
6229
+ }
6219
6230
  return "Success" /* CommandResult.Success */;
6220
6231
  }
6221
6232
  /**
@@ -6455,13 +6466,22 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
6455
6466
  }
6456
6467
  const { rowsIndexes, columnsIndexes } = data;
6457
6468
  const sheetId = data.sheetId;
6458
- const cellPositions = rowsIndexes.map((row) => columnsIndexes.map((col) => ({ col, row, sheetId })));
6459
- return {
6460
- cellPositions,
6461
- };
6469
+ const cfRules = [];
6470
+ for (const row of rowsIndexes) {
6471
+ const cfRuleInRow = [];
6472
+ for (const col of columnsIndexes) {
6473
+ const cfRules = Array.from(this.getters.getRulesByCell(sheetId, col, row));
6474
+ cfRuleInRow.push({
6475
+ position: { col, row, sheetId },
6476
+ rules: cfRules,
6477
+ });
6478
+ }
6479
+ cfRules.push(cfRuleInRow);
6480
+ }
6481
+ return { cfRules };
6462
6482
  }
6463
6483
  paste(target, clippedContent, options) {
6464
- if (!clippedContent?.cellPositions ||
6484
+ if (!clippedContent?.cfRules ||
6465
6485
  options?.pasteOption === "asValue" ||
6466
6486
  !("zones" in target) ||
6467
6487
  !target.zones.length) {
@@ -6470,7 +6490,7 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
6470
6490
  const zones = target.zones;
6471
6491
  const sheetId = target.sheetId;
6472
6492
  if (!options?.isCutOperation) {
6473
- this.pasteFromCopy(sheetId, zones, clippedContent.cellPositions);
6493
+ this.pasteFromCopy(sheetId, zones, clippedContent.cfRules, options);
6474
6494
  }
6475
6495
  else {
6476
6496
  this.pasteFromCut(sheetId, zones, clippedContent);
@@ -6478,12 +6498,12 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
6478
6498
  }
6479
6499
  pasteFromCut(sheetId, target, content) {
6480
6500
  const selection = target[0];
6481
- this.pasteZone(sheetId, selection.left, selection.top, content.cellPositions, {
6501
+ this.pasteZone(sheetId, selection.left, selection.top, content.cfRules, {
6482
6502
  isCutOperation: true,
6483
6503
  });
6484
6504
  }
6485
- pasteZone(sheetId, col, row, positions, clipboardOptions) {
6486
- for (const [r, rowCells] of positions.entries()) {
6505
+ pasteZone(sheetId, col, row, cfRules, clipboardOptions) {
6506
+ for (const [r, rowCells] of cfRules.entries()) {
6487
6507
  for (const [c, origin] of rowCells.entries()) {
6488
6508
  const position = { col: col + c, row: row + r, sheetId };
6489
6509
  this.pasteCf(origin, position, clipboardOptions?.isCutOperation);
@@ -6491,23 +6511,21 @@ class ConditionalFormatClipboardHandler extends AbstractCellClipboardHandler {
6491
6511
  }
6492
6512
  }
6493
6513
  pasteCf(origin, target, isCutOperation) {
6494
- const zone = positionToZone(target);
6495
- for (const rule of this.getters.getConditionalFormats(origin.sheetId)) {
6496
- for (const range of rule.ranges) {
6497
- if (isInside(origin.col, origin.row, this.getters.getRangeFromSheetXC(origin.sheetId, range).zone)) {
6498
- const toRemoveZones = [];
6499
- if (isCutOperation) {
6500
- //remove from current rule
6501
- toRemoveZones.push(positionToZone(origin));
6502
- }
6503
- if (origin.sheetId === target.sheetId) {
6504
- this.adaptCFRules(origin.sheetId, rule, [zone], toRemoveZones);
6505
- }
6506
- else {
6507
- this.adaptCFRules(origin.sheetId, rule, [], toRemoveZones);
6508
- const cfToCopyTo = this.getCFToCopyTo(target.sheetId, rule);
6509
- this.adaptCFRules(target.sheetId, cfToCopyTo, [zone], []);
6510
- }
6514
+ if (origin?.rules && origin.rules.length > 0) {
6515
+ const zone = positionToZone(target);
6516
+ for (const rule of origin.rules) {
6517
+ const toRemoveZones = [];
6518
+ if (isCutOperation) {
6519
+ //remove from current rule
6520
+ toRemoveZones.push(positionToZone(origin.position));
6521
+ }
6522
+ if (origin.position.sheetId === target.sheetId) {
6523
+ this.adaptCFRules(origin.position.sheetId, rule, [zone], toRemoveZones);
6524
+ }
6525
+ else {
6526
+ this.adaptCFRules(origin.position.sheetId, rule, [], toRemoveZones);
6527
+ const cfToCopyTo = this.getCFToCopyTo(target.sheetId, rule);
6528
+ this.adaptCFRules(target.sheetId, cfToCopyTo, [zone], []);
6511
6529
  }
6512
6530
  }
6513
6531
  }
@@ -6550,13 +6568,20 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
6550
6568
  }
6551
6569
  const { rowsIndexes, columnsIndexes } = data;
6552
6570
  const sheetId = data.sheetId;
6553
- const cellPositions = rowsIndexes.map((row) => columnsIndexes.map((col) => ({ col, row, sheetId })));
6554
- return {
6555
- cellPositions,
6556
- };
6571
+ const dvRules = [];
6572
+ for (const row of rowsIndexes) {
6573
+ const dvRuleInRow = [];
6574
+ for (const col of columnsIndexes) {
6575
+ const position = { sheetId, col, row };
6576
+ const rule = this.getters.getValidationRuleForCell(position);
6577
+ dvRuleInRow.push({ position, rule });
6578
+ }
6579
+ dvRules.push(dvRuleInRow);
6580
+ }
6581
+ return { dvRules };
6557
6582
  }
6558
6583
  paste(target, clippedContent, options) {
6559
- if (!clippedContent?.cellPositions) {
6584
+ if (!clippedContent?.dvRules) {
6560
6585
  return;
6561
6586
  }
6562
6587
  if (options?.pasteOption) {
@@ -6568,7 +6593,7 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
6568
6593
  const zones = target.zones;
6569
6594
  const sheetId = target.sheetId;
6570
6595
  if (!options?.isCutOperation) {
6571
- this.pasteFromCopy(sheetId, zones, clippedContent.cellPositions);
6596
+ this.pasteFromCopy(sheetId, zones, clippedContent.dvRules);
6572
6597
  }
6573
6598
  else {
6574
6599
  this.pasteFromCut(sheetId, zones, clippedContent);
@@ -6576,12 +6601,12 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
6576
6601
  }
6577
6602
  pasteFromCut(sheetId, target, content) {
6578
6603
  const selection = target[0];
6579
- this.pasteZone(sheetId, selection.left, selection.top, content.cellPositions, {
6604
+ this.pasteZone(sheetId, selection.left, selection.top, content.dvRules, {
6580
6605
  isCutOperation: true,
6581
6606
  });
6582
6607
  }
6583
- pasteZone(sheetId, col, row, positions, clipboardOptions) {
6584
- for (const [r, rowCells] of positions.entries()) {
6608
+ pasteZone(sheetId, col, row, dvRules, clipboardOptions) {
6609
+ for (const [r, rowCells] of dvRules.entries()) {
6585
6610
  for (const [c, origin] of rowCells.entries()) {
6586
6611
  const position = { col: col + c, row: row + r, sheetId };
6587
6612
  this.pasteDataValidation(origin, position, clipboardOptions?.isCutOperation);
@@ -6589,41 +6614,43 @@ class DataValidationClipboardHandler extends AbstractCellClipboardHandler {
6589
6614
  }
6590
6615
  }
6591
6616
  pasteDataValidation(origin, target, isCutOperation) {
6592
- const rule = this.getters.getValidationRuleForCell(origin);
6593
- if (!rule) {
6594
- const targetRule = this.getters.getValidationRuleForCell(target);
6595
- if (targetRule) {
6596
- // Remove the data validation rule on the target cell
6597
- this.adaptDataValidationRule(target.sheetId, targetRule, [], [positionToZone(target)]);
6598
- }
6599
- return;
6600
- }
6601
- const zone = positionToZone(target);
6602
- for (const range of rule.ranges) {
6603
- if (isInside(origin.col, origin.row, range.zone)) {
6604
- const toRemoveZone = [];
6605
- if (isCutOperation) {
6606
- toRemoveZone.push(positionToZone(origin));
6607
- }
6608
- if (origin.sheetId === target.sheetId) {
6609
- this.adaptDataValidationRule(origin.sheetId, rule, [zone], toRemoveZone);
6617
+ if (origin) {
6618
+ const zone = positionToZone(target);
6619
+ const rule = origin.rule;
6620
+ if (!rule) {
6621
+ const targetRule = this.getters.getValidationRuleForCell(target);
6622
+ if (targetRule) {
6623
+ // Remove the data validation rule on the target cell
6624
+ this.adaptDataValidationRule(target.sheetId, targetRule, [], [zone]);
6610
6625
  }
6611
- else {
6612
- this.adaptDataValidationRule(origin.sheetId, rule, [], toRemoveZone);
6613
- const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule);
6614
- this.adaptDataValidationRule(target.sheetId, copyToRule, [zone], []);
6626
+ return;
6627
+ }
6628
+ const toRemoveZone = [];
6629
+ if (isCutOperation) {
6630
+ toRemoveZone.push(positionToZone(origin.position));
6631
+ }
6632
+ if (origin.position.sheetId === target.sheetId) {
6633
+ const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule, false);
6634
+ this.adaptDataValidationRule(origin.position.sheetId, copyToRule, [zone], toRemoveZone);
6635
+ }
6636
+ else {
6637
+ const originRule = this.getters.getValidationRuleForCell(origin.position);
6638
+ if (originRule) {
6639
+ this.adaptDataValidationRule(origin.position.sheetId, originRule, [], toRemoveZone);
6615
6640
  }
6641
+ const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule);
6642
+ this.adaptDataValidationRule(target.sheetId, copyToRule, [zone], []);
6616
6643
  }
6617
6644
  }
6618
6645
  }
6619
- getDataValidationRuleToCopyTo(targetSheetId, originRule) {
6646
+ getDataValidationRuleToCopyTo(targetSheetId, originRule, newId = true) {
6620
6647
  const ruleInTargetSheet = this.getters
6621
6648
  .getDataValidationRules(targetSheetId)
6622
6649
  .find((rule) => deepEquals(originRule.criterion, rule.criterion) &&
6623
6650
  originRule.isBlocking === rule.isBlocking);
6624
6651
  return ruleInTargetSheet
6625
6652
  ? ruleInTargetSheet
6626
- : { ...originRule, id: this.uuidGenerator.uuidv4(), ranges: [] };
6653
+ : { ...originRule, id: newId ? this.uuidGenerator.uuidv4() : originRule.id, ranges: [] };
6627
6654
  }
6628
6655
  /**
6629
6656
  * Add or remove XCs to a given data validation rule.
@@ -6715,69 +6742,63 @@ class ImageClipboardHandler extends AbstractFigureClipboardHandler {
6715
6742
  }
6716
6743
 
6717
6744
  class MergeClipboardHandler extends AbstractCellClipboardHandler {
6718
- isPasteAllowed(sheetId, target, content) {
6719
- if (!("cells" in content)) {
6720
- return "Success" /* CommandResult.Success */;
6745
+ copy(data) {
6746
+ if (!data.zones.length) {
6747
+ return;
6721
6748
  }
6722
- const clipboardHeight = content.cells.length;
6723
- const clipboardWidth = content.cells[0].length;
6724
- for (const zone of getPasteZones(target, content.cells)) {
6725
- if (this.getters.doesIntersectMerge(sheetId, zone)) {
6726
- if (target.length > 1 ||
6727
- !this.getters.isSingleCellOrMerge(sheetId, target[0]) ||
6728
- clipboardHeight * clipboardWidth !== 1) {
6729
- return "WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */;
6730
- }
6749
+ const sheetId = this.getters.getActiveSheetId();
6750
+ const { rowsIndexes, columnsIndexes } = data;
6751
+ const merges = [];
6752
+ for (const row of rowsIndexes) {
6753
+ const mergesInRow = [];
6754
+ for (const col of columnsIndexes) {
6755
+ const position = { col, row, sheetId };
6756
+ mergesInRow.push(this.getters.getMerge(position));
6731
6757
  }
6758
+ merges.push(mergesInRow);
6732
6759
  }
6733
- return "Success" /* CommandResult.Success */;
6760
+ return { merges };
6734
6761
  }
6735
6762
  /**
6736
6763
  * Paste the clipboard content in the given target
6737
6764
  */
6738
6765
  paste(target, content, options) {
6739
- if (options?.isCutOperation || !("zones" in target) || !target.zones.length) {
6766
+ if (!content.merges ||
6767
+ options?.isCutOperation ||
6768
+ !("zones" in target) ||
6769
+ !target.zones.length) {
6740
6770
  return;
6741
6771
  }
6742
- this.pasteFromCopy(target.sheetId, target.zones, content.cells, options);
6772
+ this.pasteFromCopy(target.sheetId, target.zones, content.merges, options);
6743
6773
  }
6744
- pasteZone(sheetId, col, row, cells) {
6745
- for (const [r, rowCells] of cells.entries()) {
6746
- for (const [c, origin] of rowCells.entries()) {
6747
- if (!origin.position) {
6748
- continue;
6749
- }
6774
+ pasteZone(sheetId, col, row, merges) {
6775
+ for (const [r, rowMerges] of merges.entries()) {
6776
+ for (const [c, originMerge] of rowMerges.entries()) {
6750
6777
  const position = { col: col + c, row: row + r, sheetId };
6751
- this.pasteMergeIfExist(origin.position, position);
6778
+ this.pasteMerge(originMerge, position);
6752
6779
  }
6753
6780
  }
6754
6781
  }
6755
- /**
6756
- * If the origin position given is the top left of a merge, merge the target
6757
- * position.
6758
- */
6759
- pasteMergeIfExist(origin, target) {
6760
- let { sheetId, col, row } = origin;
6761
- const { col: mainCellColOrigin, row: mainCellRowOrigin } = this.getters.getMainCellPosition(origin);
6762
- if (mainCellColOrigin === col && mainCellRowOrigin === row) {
6763
- const merge = this.getters.getMerge(origin);
6764
- if (!merge) {
6765
- return;
6766
- }
6767
- ({ sheetId, col, row } = target);
6768
- this.dispatch("ADD_MERGE", {
6769
- sheetId,
6770
- force: true,
6771
- target: [
6772
- {
6773
- left: col,
6774
- top: row,
6775
- right: col + merge.right - merge.left,
6776
- bottom: row + merge.bottom - merge.top,
6777
- },
6778
- ],
6779
- });
6782
+ pasteMerge(originMerge, target) {
6783
+ if (!originMerge) {
6784
+ return;
6785
+ }
6786
+ if (this.getters.isInMerge(target)) {
6787
+ return;
6780
6788
  }
6789
+ const { sheetId, col, row } = target;
6790
+ this.dispatch("ADD_MERGE", {
6791
+ sheetId,
6792
+ force: true,
6793
+ target: [
6794
+ {
6795
+ left: col,
6796
+ top: row,
6797
+ right: col + originMerge.right - originMerge.left,
6798
+ bottom: row + originMerge.bottom - originMerge.top,
6799
+ },
6800
+ ],
6801
+ });
6781
6802
  }
6782
6803
  }
6783
6804
 
@@ -7757,6 +7778,486 @@ function errorCell(value, message) {
7757
7778
  };
7758
7779
  }
7759
7780
 
7781
+ function boolAnd(args) {
7782
+ let foundBoolean = false;
7783
+ let acc = true;
7784
+ conditionalVisitBoolean(args, (arg) => {
7785
+ foundBoolean = true;
7786
+ acc = acc && arg;
7787
+ return acc;
7788
+ });
7789
+ return {
7790
+ foundBoolean,
7791
+ result: acc,
7792
+ };
7793
+ }
7794
+ function boolOr(args) {
7795
+ let foundBoolean = false;
7796
+ let acc = false;
7797
+ conditionalVisitBoolean(args, (arg) => {
7798
+ foundBoolean = true;
7799
+ acc = acc || arg;
7800
+ return !acc;
7801
+ });
7802
+ return {
7803
+ foundBoolean,
7804
+ result: acc,
7805
+ };
7806
+ }
7807
+
7808
+ function sum(values, locale) {
7809
+ return reduceNumbers(values, (acc, a) => acc + a, 0, locale);
7810
+ }
7811
+ function countUnique(args) {
7812
+ return reduceAny(args, (acc, a) => (isDataNonEmpty(a) ? acc.add(a?.value) : acc), new Set()).size;
7813
+ }
7814
+
7815
+ function assertSameNumberOfElements(...args) {
7816
+ const dims = args[0].length;
7817
+ 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())));
7818
+ }
7819
+ function average(values, locale) {
7820
+ let count = 0;
7821
+ const sum = reduceNumbers(values, (acc, a) => {
7822
+ count += 1;
7823
+ return acc + a;
7824
+ }, 0, locale);
7825
+ assertNotZero(count);
7826
+ return sum / count;
7827
+ }
7828
+ function countNumbers(values, locale) {
7829
+ let count = 0;
7830
+ for (let n of values) {
7831
+ if (isMatrix(n)) {
7832
+ for (let i of n) {
7833
+ for (let j of i) {
7834
+ if (typeof j.value === "number") {
7835
+ count += 1;
7836
+ }
7837
+ }
7838
+ }
7839
+ }
7840
+ else {
7841
+ const value = n?.value;
7842
+ if (!isEvaluationError(value) &&
7843
+ (typeof value !== "string" || isNumber(value, locale) || parseDateTime(value, locale))) {
7844
+ count += 1;
7845
+ }
7846
+ }
7847
+ }
7848
+ return count;
7849
+ }
7850
+ function countAny(values) {
7851
+ return reduceAny(values, (acc, a) => (a !== undefined && a.value !== null ? acc + 1 : acc), 0);
7852
+ }
7853
+ function max(values, locale) {
7854
+ const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, locale);
7855
+ return result === -Infinity ? 0 : result;
7856
+ }
7857
+ function min(values, locale) {
7858
+ const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
7859
+ return result === Infinity ? 0 : result;
7860
+ }
7861
+
7862
+ const pivotTimeAdapterRegistry = new Registry();
7863
+ function pivotTimeAdapter(granularity) {
7864
+ return pivotTimeAdapterRegistry.get(granularity);
7865
+ }
7866
+ /**
7867
+ * The Time Adapter: Managing Time Periods for Pivot Functions
7868
+ *
7869
+ * Overview:
7870
+ * A time adapter is responsible for managing time periods associated with pivot functions.
7871
+ * Each type of period (day, week, month, quarter, etc.) has its own dedicated adapter.
7872
+ * The adapter's primary role is to normalize period values between spreadsheet functions,
7873
+ * and the pivot.
7874
+ * By normalizing the period value, it can be stored consistently in the pivot.
7875
+ *
7876
+ * Normalization Process:
7877
+ * When working with functions in the spreadsheet, the time adapter normalizes
7878
+ * the provided period to facilitate accurate lookup of values in the pivot.
7879
+ * For instance, if the spreadsheet function represents a day period as a number generated
7880
+ * by the DATE function (DATE(2023, 12, 25)), the time adapter will normalize it accordingly.
7881
+ *
7882
+ */
7883
+ /**
7884
+ * Normalized value: "12/25/2023"
7885
+ *
7886
+ * Note: Those two format are equivalent:
7887
+ * - "MM/dd/yyyy" (luxon format)
7888
+ * - "mm/dd/yyyy" (spreadsheet format)
7889
+ **/
7890
+ const dayAdapter = {
7891
+ normalizeFunctionValue(value) {
7892
+ const date = toNumber(value, DEFAULT_LOCALE);
7893
+ return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/dd/yyyy" });
7894
+ },
7895
+ getFormat(locale) {
7896
+ return (locale ?? DEFAULT_LOCALE).dateFormat;
7897
+ },
7898
+ formatValue(normalizedValue, locale) {
7899
+ locale = locale ?? DEFAULT_LOCALE;
7900
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7901
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7902
+ },
7903
+ toCellValue(normalizedValue) {
7904
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7905
+ },
7906
+ };
7907
+ /**
7908
+ * normalizes day of month number
7909
+ */
7910
+ const dayOfMonthAdapter = {
7911
+ normalizeFunctionValue(value) {
7912
+ const day = toNumber(value, DEFAULT_LOCALE);
7913
+ if (day < 1 || day > 31) {
7914
+ throw new EvaluationError(_t("%s is not a valid day of month (it should be a number between 1 and 31)", day));
7915
+ }
7916
+ return day;
7917
+ },
7918
+ getFormat() {
7919
+ return "0";
7920
+ },
7921
+ formatValue(normalizedValue, locale) {
7922
+ locale = locale ?? DEFAULT_LOCALE;
7923
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7924
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7925
+ },
7926
+ toCellValue(normalizedValue) {
7927
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7928
+ },
7929
+ };
7930
+ /**
7931
+ * Normalized value: "2/2023" for week 2 of 2023
7932
+ */
7933
+ const weekAdapter = {
7934
+ normalizeFunctionValue(value) {
7935
+ const [week, year] = value.split("/");
7936
+ return `${Number(week)}/${Number(year)}`;
7937
+ },
7938
+ getFormat() {
7939
+ return undefined;
7940
+ },
7941
+ formatValue(normalizedValue) {
7942
+ const [week, year] = normalizedValue.split("/");
7943
+ return _t("W%(week)s %(year)s", { week, year });
7944
+ },
7945
+ toCellValue(normalizedValue) {
7946
+ return this.formatValue(normalizedValue);
7947
+ },
7948
+ };
7949
+ /**
7950
+ * normalizes iso week number
7951
+ */
7952
+ const isoWeekNumberAdapter = {
7953
+ normalizeFunctionValue(value) {
7954
+ const isoWeek = toNumber(value, DEFAULT_LOCALE);
7955
+ if (isoWeek < 0 || isoWeek > 53) {
7956
+ throw new EvaluationError(_t("%s is not a valid week (it should be a number between 0 and 53)", isoWeek));
7957
+ }
7958
+ return isoWeek;
7959
+ },
7960
+ getFormat() {
7961
+ return "0";
7962
+ },
7963
+ formatValue(normalizedValue, locale) {
7964
+ locale = locale ?? DEFAULT_LOCALE;
7965
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7966
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7967
+ },
7968
+ toCellValue(normalizedValue) {
7969
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7970
+ },
7971
+ };
7972
+ /**
7973
+ * normalized month value is a string formatted as "MM/yyyy" (luxon format)
7974
+ * e.g. "01/2020" for January 2020
7975
+ */
7976
+ const monthAdapter = {
7977
+ normalizeFunctionValue(value) {
7978
+ const date = toNumber(value, DEFAULT_LOCALE);
7979
+ return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
7980
+ },
7981
+ getFormat() {
7982
+ return "mmmm yyyy";
7983
+ },
7984
+ formatValue(normalizedValue, locale) {
7985
+ locale = locale ?? DEFAULT_LOCALE;
7986
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7987
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7988
+ },
7989
+ toCellValue(normalizedValue) {
7990
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7991
+ },
7992
+ };
7993
+ /**
7994
+ * normalizes month number
7995
+ */
7996
+ const monthNumberAdapter = {
7997
+ normalizeFunctionValue(value) {
7998
+ const month = toNumber(value, DEFAULT_LOCALE);
7999
+ if (month < 1 || month > 12) {
8000
+ throw new EvaluationError(_t("%s is not a valid month (it should be a number between 1 and 12)", month));
8001
+ }
8002
+ return month;
8003
+ },
8004
+ getFormat() {
8005
+ return "0";
8006
+ },
8007
+ formatValue(normalizedValue, locale) {
8008
+ locale = locale ?? DEFAULT_LOCALE;
8009
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
8010
+ return formatValue(value, { locale, format: this.getFormat(locale) });
8011
+ },
8012
+ toCellValue(normalizedValue) {
8013
+ return MONTHS[toNumber(normalizedValue, DEFAULT_LOCALE) - 1].toString();
8014
+ },
8015
+ };
8016
+ /**
8017
+ * normalized quarter value is "quarter/year"
8018
+ * e.g. "1/2020" for Q1 2020
8019
+ */
8020
+ const quarterAdapter = {
8021
+ normalizeFunctionValue(value) {
8022
+ const [quarter, year] = value.split("/");
8023
+ return `${quarter}/${year}`;
8024
+ },
8025
+ getFormat() {
8026
+ return undefined;
8027
+ },
8028
+ formatValue(normalizedValue) {
8029
+ const [quarter, year] = normalizedValue.split("/");
8030
+ return _t("Q%(quarter)s %(year)s", { quarter, year });
8031
+ },
8032
+ toCellValue(normalizedValue) {
8033
+ return this.formatValue(normalizedValue);
8034
+ },
8035
+ };
8036
+ /**
8037
+ * normalizes quarter number
8038
+ */
8039
+ const quarterNumberAdapter = {
8040
+ normalizeFunctionValue(value) {
8041
+ const quarter = toNumber(value, DEFAULT_LOCALE);
8042
+ if (quarter < 1 || quarter > 4) {
8043
+ throw new EvaluationError(_t("%s is not a valid quarter (it should be a number between 1 and 4)", quarter));
8044
+ }
8045
+ return quarter;
8046
+ },
8047
+ getFormat() {
8048
+ return "0";
8049
+ },
8050
+ formatValue(normalizedValue, locale) {
8051
+ locale = locale ?? DEFAULT_LOCALE;
8052
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
8053
+ return formatValue(value, { locale, format: this.getFormat(locale) });
8054
+ },
8055
+ toCellValue(normalizedValue) {
8056
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
8057
+ },
8058
+ };
8059
+ const yearAdapter = {
8060
+ normalizeFunctionValue(value) {
8061
+ return toNumber(value, DEFAULT_LOCALE);
8062
+ },
8063
+ getFormat() {
8064
+ return "0";
8065
+ },
8066
+ formatValue(normalizedValue, locale) {
8067
+ locale = locale ?? DEFAULT_LOCALE;
8068
+ return formatValue(normalizedValue, { locale, format: "0" });
8069
+ },
8070
+ toCellValue(normalizedValue) {
8071
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
8072
+ },
8073
+ };
8074
+ pivotTimeAdapterRegistry
8075
+ .add("day", dayAdapter)
8076
+ .add("week", weekAdapter)
8077
+ .add("month", monthAdapter)
8078
+ .add("quarter", quarterAdapter)
8079
+ .add("year", yearAdapter)
8080
+ .add("day_of_month", dayOfMonthAdapter)
8081
+ .add("iso_week_number", isoWeekNumberAdapter)
8082
+ .add("month_number", monthNumberAdapter)
8083
+ .add("quarter_number", quarterNumberAdapter)
8084
+ .add("year_number", yearAdapter);
8085
+
8086
+ const AGGREGATOR_NAMES = {
8087
+ count: _t("Count"),
8088
+ count_distinct: _t("Count Distinct"),
8089
+ bool_and: _t("Boolean And"),
8090
+ bool_or: _t("Boolean Or"),
8091
+ max: _t("Maximum"),
8092
+ min: _t("Minimum"),
8093
+ avg: _t("Average"),
8094
+ sum: _t("Sum"),
8095
+ };
8096
+ const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8097
+ const AGGREGATORS_BY_FIELD_TYPE = {
8098
+ integer: NUMBER_CHAR_AGGREGATORS,
8099
+ char: NUMBER_CHAR_AGGREGATORS,
8100
+ boolean: ["count_distinct", "count", "bool_and", "bool_or"],
8101
+ };
8102
+ const AGGREGATORS = {};
8103
+ for (const type in AGGREGATORS_BY_FIELD_TYPE) {
8104
+ AGGREGATORS[type] = {};
8105
+ for (const aggregator of AGGREGATORS_BY_FIELD_TYPE[type]) {
8106
+ AGGREGATORS[type][aggregator] = AGGREGATOR_NAMES[aggregator];
8107
+ }
8108
+ }
8109
+ const AGGREGATORS_FN = {
8110
+ count: {
8111
+ fn: (args) => countAny([args]),
8112
+ format: () => "0",
8113
+ },
8114
+ count_distinct: {
8115
+ fn: (args) => countUnique([args]),
8116
+ format: () => "0",
8117
+ },
8118
+ bool_and: {
8119
+ fn: (args) => boolAnd([args]).result,
8120
+ format: () => undefined,
8121
+ },
8122
+ bool_or: {
8123
+ fn: (args) => boolOr([args]).result,
8124
+ format: () => undefined,
8125
+ },
8126
+ max: {
8127
+ fn: (args, locale) => max([args], locale),
8128
+ format: inferFormat,
8129
+ },
8130
+ min: {
8131
+ fn: (args, locale) => min([args], locale),
8132
+ format: inferFormat,
8133
+ },
8134
+ avg: {
8135
+ fn: (args, locale) => average([args], locale),
8136
+ format: inferFormat,
8137
+ },
8138
+ sum: {
8139
+ fn: (args, locale) => sum([args], locale),
8140
+ format: inferFormat,
8141
+ },
8142
+ };
8143
+ function makePivotFormulaFromPivotCell(pivotFormulaId, pivotCell) {
8144
+ switch (pivotCell.type) {
8145
+ case "HEADER":
8146
+ return makePivotFormula("PIVOT.HEADER", [pivotFormulaId, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
8147
+ case "MEASURE_HEADER":
8148
+ return makePivotFormula("PIVOT.HEADER", [pivotFormulaId, ...flatPivotDomain(pivotCell.domain), "measure", pivotCell.measure].filter(isDefined));
8149
+ case "VALUE":
8150
+ return makePivotFormula("PIVOT.VALUE", [pivotFormulaId, pivotCell.measure, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
8151
+ case "EMPTY":
8152
+ return "";
8153
+ }
8154
+ }
8155
+ /**
8156
+ * Build a pivot formula expression
8157
+ */
8158
+ function makePivotFormula(formula, args) {
8159
+ return `=${formula}(${args
8160
+ .map((arg) => {
8161
+ const stringIsNumber = typeof arg == "string" && !isNaN(Number(arg)) && Number(arg).toString() === arg;
8162
+ const convertToNumber = typeof arg == "number" || stringIsNumber;
8163
+ return convertToNumber ? `${arg}` : `"${arg.toString().replace(/"/g, '\\"')}"`;
8164
+ })
8165
+ .join(",")})`;
8166
+ }
8167
+ /**
8168
+ * Given an object of form {"1": {...}, "2": {...}, ...} get the maximum ID used
8169
+ * in this object
8170
+ * If the object has no keys, return 0
8171
+ *
8172
+ */
8173
+ function getMaxObjectId(o) {
8174
+ const keys = Object.keys(o);
8175
+ if (!keys.length) {
8176
+ return 0;
8177
+ }
8178
+ const nums = keys.map((id) => parseInt(id, 10));
8179
+ const max = Math.max(...nums);
8180
+ return max;
8181
+ }
8182
+ const ALL_PERIODS = {
8183
+ year: _t("Year"),
8184
+ quarter: _t("Quarter"),
8185
+ month: _t("Month"),
8186
+ week: _t("Week"),
8187
+ day: _t("Day"),
8188
+ year_number: _t("Year"),
8189
+ quarter_number: _t("Quarter"),
8190
+ month_number: _t("Month"),
8191
+ iso_week_number: _t("Week"),
8192
+ day_of_month: _t("Day of Month"),
8193
+ };
8194
+ const DATE_FIELDS = ["date", "datetime"];
8195
+ /**
8196
+ * Parse a dimension string into a pivot dimension definition.
8197
+ * e.g "create_date:month" => { name: "create_date", granularity: "month" }
8198
+ */
8199
+ function parseDimension(dimension) {
8200
+ const [name, granularity] = dimension.split(":");
8201
+ if (granularity) {
8202
+ return { name, granularity };
8203
+ }
8204
+ return { name };
8205
+ }
8206
+ function isDateField(field) {
8207
+ return DATE_FIELDS.includes(field.type);
8208
+ }
8209
+ function toPivotDomain(domainStr) {
8210
+ if (domainStr.length % 2 !== 0) {
8211
+ throw new Error("Invalid domain: odd number of elements");
8212
+ }
8213
+ const domain = [];
8214
+ for (let i = 0; i < domainStr.length - 1; i += 2) {
8215
+ domain.push({ field: domainStr[i], value: domainStr[i + 1] });
8216
+ }
8217
+ return domain;
8218
+ }
8219
+ function flatPivotDomain(domain) {
8220
+ return domain.flatMap((arg) => [arg.field, arg.value]);
8221
+ }
8222
+ /**
8223
+ * Parses the value defining a pivot group in a PIVOT formula
8224
+ * e.g. given the following formula PIVOT.VALUE("1", "stage_id", "42", "status", "won"),
8225
+ * the two group values are "42" and "won".
8226
+ */
8227
+ function toNormalizedPivotValue(dimension, groupValue) {
8228
+ if (groupValue === null || groupValue === "null") {
8229
+ return null;
8230
+ }
8231
+ const groupValueString = typeof groupValue === "boolean"
8232
+ ? toString(groupValue).toLocaleLowerCase()
8233
+ : toString(groupValue);
8234
+ if (!pivotNormalizationValueRegistry.contains(dimension.type)) {
8235
+ throw new EvaluationError(_t("Field %(field)s is not supported because of its type (%(type)s)", {
8236
+ field: dimension.displayName,
8237
+ type: dimension.type,
8238
+ }));
8239
+ }
8240
+ // represents a field which is not set (=False server side)
8241
+ if (groupValueString === "false") {
8242
+ return false;
8243
+ }
8244
+ const normalizer = pivotNormalizationValueRegistry.get(dimension.type);
8245
+ return normalizer(groupValueString, dimension.granularity);
8246
+ }
8247
+ function normalizeDateTime(value, granularity) {
8248
+ if (!granularity) {
8249
+ throw "";
8250
+ }
8251
+ return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
8252
+ }
8253
+ const pivotNormalizationValueRegistry = new Registry();
8254
+ pivotNormalizationValueRegistry
8255
+ .add("date", normalizeDateTime)
8256
+ .add("datetime", normalizeDateTime)
8257
+ .add("integer", (value) => toNumber(value, DEFAULT_LOCALE))
8258
+ .add("boolean", (value) => toBoolean(value))
8259
+ .add("char", (value) => toString(value));
8260
+
7760
8261
  /**
7761
8262
  * Change the reference types inside the given token, if the token represent a range or a cell
7762
8263
  *
@@ -7955,6 +8456,11 @@ const CellIsOperators = {
7955
8456
  const ChartTerms = {
7956
8457
  Series: _t("Series"),
7957
8458
  BackgroundColor: _t("Background color"),
8459
+ StackedBarChart: _t("Stacked bar chart"),
8460
+ StackedLineChart: _t("Stacked line chart"),
8461
+ CumulativeData: _t("Cumulative data"),
8462
+ TreatLabelsAsText: _t("Treat labels as text"),
8463
+ AggregatedChart: _t("Aggregate"),
7958
8464
  Errors: {
7959
8465
  Unexpected: _t("The chart definition is invalid for an unknown reason"),
7960
8466
  // BASIC CHART ERRORS (LINE | BAR | PIE)
@@ -8887,6 +9393,7 @@ function drawHighlight(renderingContext, highlight, rect) {
8887
9393
  }
8888
9394
  const color = highlight.color || HIGHLIGHT_COLOR;
8889
9395
  const { ctx } = renderingContext;
9396
+ ctx.save();
8890
9397
  if (!highlight.noBorder) {
8891
9398
  if (highlight.dashed) {
8892
9399
  ctx.setLineDash([5, 3]);
@@ -8906,6 +9413,7 @@ function drawHighlight(renderingContext, highlight, rect) {
8906
9413
  ctx.fillStyle = setColorAlpha(toHex(color), highlight.fillAlpha ?? 0.12);
8907
9414
  ctx.fillRect(x, y, width, height);
8908
9415
  }
9416
+ ctx.restore();
8909
9417
  }
8910
9418
 
8911
9419
  class HighlightStore extends SpreadsheetStore {
@@ -9436,8 +9944,20 @@ class ComposerStore extends SpreadsheetStore {
9436
9944
  replaceSelectedRange(zone) {
9437
9945
  const ref = this.getZoneReference(zone);
9438
9946
  const currentToken = this.tokenAtCursor;
9439
- const start = currentToken?.type === "REFERENCE" ? currentToken.start : this.selectionStart;
9440
- this.replaceText(ref, start, this.selectionEnd);
9947
+ let replaceStart = this.selectionStart;
9948
+ if (currentToken?.type === "REFERENCE") {
9949
+ replaceStart = currentToken.start;
9950
+ }
9951
+ else if (currentToken?.type === "RIGHT_PAREN") {
9952
+ // match left parenthesis
9953
+ const leftParenthesisIndex = this.currentTokens.findIndex((token) => token.type === "LEFT_PAREN" && token.parenIndex === currentToken.parenIndex);
9954
+ const functionToken = this.currentTokens[leftParenthesisIndex - 1];
9955
+ if (functionToken === undefined) {
9956
+ return;
9957
+ }
9958
+ replaceStart = functionToken.start;
9959
+ }
9960
+ this.replaceText(ref, replaceStart, this.selectionEnd);
9441
9961
  }
9442
9962
  /**
9443
9963
  * Replace the reference of the old zone by the new one.
@@ -9457,10 +9977,8 @@ class ComposerStore extends SpreadsheetStore {
9457
9977
  const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
9458
9978
  return isEqual(this.getters.expandZone(activeSheetId, refRange.zone), oldZone);
9459
9979
  });
9460
- // this function assumes that the previous range is always found because
9461
- // it's called when changing a highlight, which exists by definition
9462
9980
  if (!previousRefToken) {
9463
- throw new Error("Previous range not found");
9981
+ return;
9464
9982
  }
9465
9983
  const previousRange = this.getters.getRangeFromSheetXC(activeSheetId, previousRefToken.value);
9466
9984
  this.selectionStart = previousRefToken.start;
@@ -9472,6 +9990,17 @@ class ComposerStore extends SpreadsheetStore {
9472
9990
  getZoneReference(zone) {
9473
9991
  const inputSheetId = this.currentEditedCell.sheetId;
9474
9992
  const sheetId = this.getters.getActiveSheetId();
9993
+ if (zone.top === zone.bottom && zone.left === zone.right) {
9994
+ const position = { sheetId, col: zone.left, row: zone.top };
9995
+ const pivotId = this.getters.getPivotIdFromPosition(position);
9996
+ const pivotCell = this.getters.getPivotCellFromPosition(position);
9997
+ const cell = this.getters.getCell(position);
9998
+ if (pivotId && pivotCell.type !== "EMPTY" && !cell?.isFormula) {
9999
+ const formulaPivotId = this.getters.getPivotFormulaId(pivotId);
10000
+ const formula = makePivotFormulaFromPivotCell(formulaPivotId, pivotCell);
10001
+ return formula.slice(1); // strip leading =
10002
+ }
10003
+ }
9475
10004
  const range = this.getters.getRangeFromZone(sheetId, zone);
9476
10005
  return this.getters.getSelectionRangeString(range, inputSheetId);
9477
10006
  }
@@ -9574,19 +10103,35 @@ class ComposerStore extends SpreadsheetStore {
9574
10103
  const colorIndex = this.colorIndexByRange[rangeString];
9575
10104
  return colors$1[colorIndex % colors$1.length];
9576
10105
  };
9577
- return this.getReferencedRanges().map((range) => {
10106
+ const highlights = [];
10107
+ for (const range of this.getReferencedRanges()) {
9578
10108
  const rangeString = this.getters.getRangeString(range, editionSheetId);
9579
10109
  const { numberOfRows, numberOfCols } = zoneToDimension(range.zone);
9580
10110
  const zone = numberOfRows * numberOfCols === 1
9581
10111
  ? this.getters.expandZone(range.sheetId, range.zone)
9582
10112
  : range.zone;
9583
- return {
10113
+ highlights.push({
9584
10114
  zone,
9585
10115
  color: rangeColor(rangeString),
9586
10116
  sheetId: range.sheetId,
9587
10117
  interactive: true,
9588
- };
9589
- });
10118
+ });
10119
+ }
10120
+ const activeSheetId = this.getters.getActiveSheetId();
10121
+ const selectionZone = this.model.selection.getAnchor().zone;
10122
+ const isSelectionHightlighted = highlights.find((highlight) => highlight.sheetId === activeSheetId && isEqual(highlight.zone, selectionZone));
10123
+ if (this.editionMode === "selecting" && !isSelectionHightlighted) {
10124
+ highlights.push({
10125
+ zone: selectionZone,
10126
+ color: "#445566",
10127
+ sheetId: activeSheetId,
10128
+ dashed: true,
10129
+ interactive: false,
10130
+ noFill: true,
10131
+ thinLine: true,
10132
+ });
10133
+ }
10134
+ return highlights;
9590
10135
  }
9591
10136
  /**
9592
10137
  * Return ranges currently referenced in the composer
@@ -9845,8 +10390,14 @@ class ChartJsComponent extends owl.Component {
9845
10390
  owl.useEffect(() => {
9846
10391
  const runtime = this.chartRuntime;
9847
10392
  if (!deepEquals(runtime, this.currentRuntime, "ignoreFunctions")) {
10393
+ if (runtime.chartJsConfig.type !== this.currentRuntime.chartJsConfig.type) {
10394
+ this.chart?.destroy();
10395
+ this.createChart(deepCopy(runtime.chartJsConfig));
10396
+ }
10397
+ else {
10398
+ this.updateChartJs(deepCopy(runtime));
10399
+ }
9848
10400
  this.currentRuntime = runtime;
9849
- this.updateChartJs(deepCopy(runtime));
9850
10401
  }
9851
10402
  });
9852
10403
  }
@@ -10214,6 +10765,9 @@ function getChartAxisTitleRuntime(design) {
10214
10765
  }
10215
10766
  function getDefinedAxis(definition) {
10216
10767
  let useLeftAxis = false, useRightAxis = false;
10768
+ if ("horizontal" in definition && definition.horizontal) {
10769
+ return { useLeftAxis: true, useRightAxis: false };
10770
+ }
10217
10771
  for (const design of definition.dataSets || []) {
10218
10772
  if (design.yAxisId === "y1") {
10219
10773
  useRightAxis = true;
@@ -11781,13 +12335,6 @@ var misc = /*#__PURE__*/Object.freeze({
11781
12335
  FORMAT_LARGE_NUMBER: FORMAT_LARGE_NUMBER
11782
12336
  });
11783
12337
 
11784
- function sum(values, locale) {
11785
- return reduceNumbers(values, (acc, a) => acc + a, 0, locale);
11786
- }
11787
- function countUnique(args) {
11788
- return reduceAny(args, (acc, a) => (isDataNonEmpty(a) ? acc.add(a?.value) : acc), new Set()).size;
11789
- }
11790
-
11791
12338
  const DEFAULT_FACTOR = 1;
11792
12339
  const DEFAULT_MODE = 0;
11793
12340
  const DEFAULT_PLACES = 0;
@@ -12901,53 +13448,6 @@ var math = /*#__PURE__*/Object.freeze({
12901
13448
  TRUNC: TRUNC
12902
13449
  });
12903
13450
 
12904
- function assertSameNumberOfElements(...args) {
12905
- const dims = args[0].length;
12906
- 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())));
12907
- }
12908
- function average(values, locale) {
12909
- let count = 0;
12910
- const sum = reduceNumbers(values, (acc, a) => {
12911
- count += 1;
12912
- return acc + a;
12913
- }, 0, locale);
12914
- assertNotZero(count);
12915
- return sum / count;
12916
- }
12917
- function countNumbers(values, locale) {
12918
- let count = 0;
12919
- for (let n of values) {
12920
- if (isMatrix(n)) {
12921
- for (let i of n) {
12922
- for (let j of i) {
12923
- if (typeof j.value === "number") {
12924
- count += 1;
12925
- }
12926
- }
12927
- }
12928
- }
12929
- else {
12930
- const value = n?.value;
12931
- if (!isEvaluationError(value) &&
12932
- (typeof value !== "string" || isNumber(value, locale) || parseDateTime(value, locale))) {
12933
- count += 1;
12934
- }
12935
- }
12936
- }
12937
- return count;
12938
- }
12939
- function countAny(values) {
12940
- return reduceAny(values, (acc, a) => (a !== undefined && a.value !== null ? acc + 1 : acc), 0);
12941
- }
12942
- function max(values, locale) {
12943
- const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, locale);
12944
- return result === -Infinity ? 0 : result;
12945
- }
12946
- function min(values, locale) {
12947
- const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
12948
- return result === Infinity ? 0 : result;
12949
- }
12950
-
12951
13451
  function filterAndFlatData(dataY, dataX) {
12952
13452
  const _flatDataY = [];
12953
13453
  const _flatDataX = [];
@@ -13503,7 +14003,7 @@ const LARGE = {
13503
14003
  // LINEST
13504
14004
  // -----------------------------------------------------------------------------
13505
14005
  const LINEST = {
13506
- description: _t("Compute the intercept of the linear regression."),
14006
+ description: _t("Given partial data about a linear trend, calculates various parameters about the ideal linear trend using the least-squares method."),
13507
14007
  args: [
13508
14008
  arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
13509
14009
  arg("data_x (range<number>, default={1;2;3;...})", _t("The range representing the array or matrix of independent data.")),
@@ -13519,7 +14019,7 @@ const LINEST = {
13519
14019
  // LOGEST
13520
14020
  // -----------------------------------------------------------------------------
13521
14021
  const LOGEST = {
13522
- description: _t("Compute the intercept of the linear regression."),
14022
+ description: _t("Given partial data about an exponential growth curve, calculates various parameters about the best fit ideal exponential growth curve."),
13523
14023
  args: [
13524
14024
  arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
13525
14025
  arg("data_x (range<number>, optional, default={1;2;3;...})", _t("The range representing the array or matrix of independent data.")),
@@ -17922,33 +18422,6 @@ var info = /*#__PURE__*/Object.freeze({
17922
18422
  NA: NA
17923
18423
  });
17924
18424
 
17925
- function boolAnd(args) {
17926
- let foundBoolean = false;
17927
- let acc = true;
17928
- conditionalVisitBoolean(args, (arg) => {
17929
- foundBoolean = true;
17930
- acc = acc && arg;
17931
- return acc;
17932
- });
17933
- return {
17934
- foundBoolean,
17935
- result: acc,
17936
- };
17937
- }
17938
- function boolOr(args) {
17939
- let foundBoolean = false;
17940
- let acc = false;
17941
- conditionalVisitBoolean(args, (arg) => {
17942
- foundBoolean = true;
17943
- acc = acc || arg;
17944
- return !acc;
17945
- });
17946
- return {
17947
- foundBoolean,
17948
- result: acc,
17949
- };
17950
- }
17951
-
17952
18425
  // -----------------------------------------------------------------------------
17953
18426
  // AND
17954
18427
  // -----------------------------------------------------------------------------
@@ -18146,393 +18619,6 @@ var logical = /*#__PURE__*/Object.freeze({
18146
18619
  XOR: XOR
18147
18620
  });
18148
18621
 
18149
- const pivotTimeAdapterRegistry = new Registry();
18150
- function pivotTimeAdapter(granularity) {
18151
- return pivotTimeAdapterRegistry.get(granularity);
18152
- }
18153
- /**
18154
- * The Time Adapter: Managing Time Periods for Pivot Functions
18155
- *
18156
- * Overview:
18157
- * A time adapter is responsible for managing time periods associated with pivot functions.
18158
- * Each type of period (day, week, month, quarter, etc.) has its own dedicated adapter.
18159
- * The adapter's primary role is to normalize period values between spreadsheet functions,
18160
- * and the pivot.
18161
- * By normalizing the period value, it can be stored consistently in the pivot.
18162
- *
18163
- * Normalization Process:
18164
- * When working with functions in the spreadsheet, the time adapter normalizes
18165
- * the provided period to facilitate accurate lookup of values in the pivot.
18166
- * For instance, if the spreadsheet function represents a day period as a number generated
18167
- * by the DATE function (DATE(2023, 12, 25)), the time adapter will normalize it accordingly.
18168
- *
18169
- */
18170
- /**
18171
- * Normalized value: "12/25/2023"
18172
- *
18173
- * Note: Those two format are equivalent:
18174
- * - "MM/dd/yyyy" (luxon format)
18175
- * - "mm/dd/yyyy" (spreadsheet format)
18176
- **/
18177
- const dayAdapter = {
18178
- normalizeFunctionValue(value) {
18179
- const date = toNumber(value, DEFAULT_LOCALE);
18180
- return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/dd/yyyy" });
18181
- },
18182
- getFormat(locale) {
18183
- return (locale ?? DEFAULT_LOCALE).dateFormat;
18184
- },
18185
- formatValue(normalizedValue, locale) {
18186
- locale = locale ?? DEFAULT_LOCALE;
18187
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18188
- return formatValue(value, { locale, format: this.getFormat(locale) });
18189
- },
18190
- toCellValue(normalizedValue) {
18191
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18192
- },
18193
- };
18194
- /**
18195
- * normalizes day of month number
18196
- */
18197
- const dayOfMonthAdapter = {
18198
- normalizeFunctionValue(value) {
18199
- const day = toNumber(value, DEFAULT_LOCALE);
18200
- if (day < 1 || day > 31) {
18201
- throw new EvaluationError(_t("%s is not a valid day of month (it should be a number between 1 and 31)", day));
18202
- }
18203
- return day;
18204
- },
18205
- getFormat() {
18206
- return "0";
18207
- },
18208
- formatValue(normalizedValue, locale) {
18209
- locale = locale ?? DEFAULT_LOCALE;
18210
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18211
- return formatValue(value, { locale, format: this.getFormat(locale) });
18212
- },
18213
- toCellValue(normalizedValue) {
18214
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18215
- },
18216
- };
18217
- /**
18218
- * Normalized value: "2/2023" for week 2 of 2023
18219
- */
18220
- const weekAdapter = {
18221
- normalizeFunctionValue(value) {
18222
- const [week, year] = value.split("/");
18223
- return `${Number(week)}/${Number(year)}`;
18224
- },
18225
- getFormat() {
18226
- return undefined;
18227
- },
18228
- formatValue(normalizedValue) {
18229
- const [week, year] = normalizedValue.split("/");
18230
- return _t("W%(week)s %(year)s", { week, year });
18231
- },
18232
- toCellValue(normalizedValue) {
18233
- return this.formatValue(normalizedValue);
18234
- },
18235
- };
18236
- /**
18237
- * normalizes iso week number
18238
- */
18239
- const isoWeekNumberAdapter = {
18240
- normalizeFunctionValue(value) {
18241
- const isoWeek = toNumber(value, DEFAULT_LOCALE);
18242
- if (isoWeek < 0 || isoWeek > 53) {
18243
- throw new EvaluationError(_t("%s is not a valid week (it should be a number between 0 and 53)", isoWeek));
18244
- }
18245
- return isoWeek;
18246
- },
18247
- getFormat() {
18248
- return "0";
18249
- },
18250
- formatValue(normalizedValue, locale) {
18251
- locale = locale ?? DEFAULT_LOCALE;
18252
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18253
- return formatValue(value, { locale, format: this.getFormat(locale) });
18254
- },
18255
- toCellValue(normalizedValue) {
18256
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18257
- },
18258
- };
18259
- /**
18260
- * normalized month value is a string formatted as "MM/yyyy" (luxon format)
18261
- * e.g. "01/2020" for January 2020
18262
- */
18263
- const monthAdapter = {
18264
- normalizeFunctionValue(value) {
18265
- const date = toNumber(value, DEFAULT_LOCALE);
18266
- return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
18267
- },
18268
- getFormat() {
18269
- return "mmmm yyyy";
18270
- },
18271
- formatValue(normalizedValue, locale) {
18272
- locale = locale ?? DEFAULT_LOCALE;
18273
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18274
- return formatValue(value, { locale, format: this.getFormat(locale) });
18275
- },
18276
- toCellValue(normalizedValue) {
18277
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18278
- },
18279
- };
18280
- /**
18281
- * normalizes month number
18282
- */
18283
- const monthNumberAdapter = {
18284
- normalizeFunctionValue(value) {
18285
- const month = toNumber(value, DEFAULT_LOCALE);
18286
- if (month < 1 || month > 12) {
18287
- throw new EvaluationError(_t("%s is not a valid month (it should be a number between 1 and 12)", month));
18288
- }
18289
- return month;
18290
- },
18291
- getFormat() {
18292
- return "0";
18293
- },
18294
- formatValue(normalizedValue, locale) {
18295
- locale = locale ?? DEFAULT_LOCALE;
18296
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18297
- return formatValue(value, { locale, format: this.getFormat(locale) });
18298
- },
18299
- toCellValue(normalizedValue) {
18300
- return MONTHS[toNumber(normalizedValue, DEFAULT_LOCALE) - 1].toString();
18301
- },
18302
- };
18303
- /**
18304
- * normalized quarter value is "quarter/year"
18305
- * e.g. "1/2020" for Q1 2020
18306
- */
18307
- const quarterAdapter = {
18308
- normalizeFunctionValue(value) {
18309
- const [quarter, year] = value.split("/");
18310
- return `${quarter}/${year}`;
18311
- },
18312
- getFormat() {
18313
- return undefined;
18314
- },
18315
- formatValue(normalizedValue) {
18316
- const [quarter, year] = normalizedValue.split("/");
18317
- return _t("Q%(quarter)s %(year)s", { quarter, year });
18318
- },
18319
- toCellValue(normalizedValue) {
18320
- return this.formatValue(normalizedValue);
18321
- },
18322
- };
18323
- /**
18324
- * normalizes quarter number
18325
- */
18326
- const quarterNumberAdapter = {
18327
- normalizeFunctionValue(value) {
18328
- const quarter = toNumber(value, DEFAULT_LOCALE);
18329
- if (quarter < 1 || quarter > 4) {
18330
- throw new EvaluationError(_t("%s is not a valid quarter (it should be a number between 1 and 4)", quarter));
18331
- }
18332
- return quarter;
18333
- },
18334
- getFormat() {
18335
- return "0";
18336
- },
18337
- formatValue(normalizedValue, locale) {
18338
- locale = locale ?? DEFAULT_LOCALE;
18339
- const value = toNumber(normalizedValue, DEFAULT_LOCALE);
18340
- return formatValue(value, { locale, format: this.getFormat(locale) });
18341
- },
18342
- toCellValue(normalizedValue) {
18343
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18344
- },
18345
- };
18346
- const yearAdapter = {
18347
- normalizeFunctionValue(value) {
18348
- return toNumber(value, DEFAULT_LOCALE);
18349
- },
18350
- getFormat() {
18351
- return "0";
18352
- },
18353
- formatValue(normalizedValue, locale) {
18354
- locale = locale ?? DEFAULT_LOCALE;
18355
- return formatValue(normalizedValue, { locale, format: "0" });
18356
- },
18357
- toCellValue(normalizedValue) {
18358
- return toNumber(normalizedValue, DEFAULT_LOCALE);
18359
- },
18360
- };
18361
- pivotTimeAdapterRegistry
18362
- .add("day", dayAdapter)
18363
- .add("week", weekAdapter)
18364
- .add("month", monthAdapter)
18365
- .add("quarter", quarterAdapter)
18366
- .add("year", yearAdapter)
18367
- .add("day_of_month", dayOfMonthAdapter)
18368
- .add("iso_week_number", isoWeekNumberAdapter)
18369
- .add("month_number", monthNumberAdapter)
18370
- .add("quarter_number", quarterNumberAdapter)
18371
- .add("year_number", yearAdapter);
18372
-
18373
- const AGGREGATOR_NAMES = {
18374
- count: _t("Count"),
18375
- count_distinct: _t("Count Distinct"),
18376
- bool_and: _t("Boolean And"),
18377
- bool_or: _t("Boolean Or"),
18378
- max: _t("Maximum"),
18379
- min: _t("Minimum"),
18380
- avg: _t("Average"),
18381
- sum: _t("Sum"),
18382
- };
18383
- const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
18384
- const AGGREGATORS_BY_FIELD_TYPE = {
18385
- integer: NUMBER_CHAR_AGGREGATORS,
18386
- char: NUMBER_CHAR_AGGREGATORS,
18387
- boolean: ["count_distinct", "count", "bool_and", "bool_or"],
18388
- };
18389
- const AGGREGATORS = {};
18390
- for (const type in AGGREGATORS_BY_FIELD_TYPE) {
18391
- AGGREGATORS[type] = {};
18392
- for (const aggregator of AGGREGATORS_BY_FIELD_TYPE[type]) {
18393
- AGGREGATORS[type][aggregator] = AGGREGATOR_NAMES[aggregator];
18394
- }
18395
- }
18396
- const AGGREGATORS_FN = {
18397
- count: {
18398
- fn: (args) => countAny([args]),
18399
- format: () => "0",
18400
- },
18401
- count_distinct: {
18402
- fn: (args) => countUnique([args]),
18403
- format: () => "0",
18404
- },
18405
- bool_and: {
18406
- fn: (args) => boolAnd([args]).result,
18407
- format: () => undefined,
18408
- },
18409
- bool_or: {
18410
- fn: (args) => boolOr([args]).result,
18411
- format: () => undefined,
18412
- },
18413
- max: {
18414
- fn: (args, locale) => max([args], locale),
18415
- format: inferFormat,
18416
- },
18417
- min: {
18418
- fn: (args, locale) => min([args], locale),
18419
- format: inferFormat,
18420
- },
18421
- avg: {
18422
- fn: (args, locale) => average([args], locale),
18423
- format: inferFormat,
18424
- },
18425
- sum: {
18426
- fn: (args, locale) => sum([args], locale),
18427
- format: inferFormat,
18428
- },
18429
- };
18430
- /**
18431
- * Build a pivot formula expression
18432
- */
18433
- function makePivotFormula(formula, args) {
18434
- return `=${formula}(${args
18435
- .map((arg) => {
18436
- const stringIsNumber = typeof arg == "string" && !isNaN(Number(arg)) && Number(arg).toString() === arg;
18437
- const convertToNumber = typeof arg == "number" || stringIsNumber;
18438
- return convertToNumber ? `${arg}` : `"${arg.toString().replace(/"/g, '\\"')}"`;
18439
- })
18440
- .join(",")})`;
18441
- }
18442
- /**
18443
- * Given an object of form {"1": {...}, "2": {...}, ...} get the maximum ID used
18444
- * in this object
18445
- * If the object has no keys, return 0
18446
- *
18447
- */
18448
- function getMaxObjectId(o) {
18449
- const keys = Object.keys(o);
18450
- if (!keys.length) {
18451
- return 0;
18452
- }
18453
- const nums = keys.map((id) => parseInt(id, 10));
18454
- const max = Math.max(...nums);
18455
- return max;
18456
- }
18457
- const ALL_PERIODS = {
18458
- year: _t("Year"),
18459
- quarter: _t("Quarter"),
18460
- month: _t("Month"),
18461
- week: _t("Week"),
18462
- day: _t("Day"),
18463
- year_number: _t("Year"),
18464
- quarter_number: _t("Quarter"),
18465
- month_number: _t("Month"),
18466
- iso_week_number: _t("Week"),
18467
- day_of_month: _t("Day of Month"),
18468
- };
18469
- const DATE_FIELDS = ["date", "datetime"];
18470
- /**
18471
- * Parse a dimension string into a pivot dimension definition.
18472
- * e.g "create_date:month" => { name: "create_date", granularity: "month" }
18473
- */
18474
- function parseDimension(dimension) {
18475
- const [name, granularity] = dimension.split(":");
18476
- if (granularity) {
18477
- return { name, granularity };
18478
- }
18479
- return { name };
18480
- }
18481
- function isDateField(field) {
18482
- return DATE_FIELDS.includes(field.type);
18483
- }
18484
- function toPivotDomain(domainStr) {
18485
- if (domainStr.length % 2 !== 0) {
18486
- throw new Error("Invalid domain: odd number of elements");
18487
- }
18488
- const domain = [];
18489
- for (let i = 0; i < domainStr.length - 1; i += 2) {
18490
- domain.push({ field: domainStr[i], value: domainStr[i + 1] });
18491
- }
18492
- return domain;
18493
- }
18494
- function flatPivotDomain(domain) {
18495
- return domain.flatMap((arg) => [arg.field, arg.value]);
18496
- }
18497
- /**
18498
- * Parses the value defining a pivot group in a PIVOT formula
18499
- * e.g. given the following formula PIVOT.VALUE("1", "stage_id", "42", "status", "won"),
18500
- * the two group values are "42" and "won".
18501
- */
18502
- function toNormalizedPivotValue(dimension, groupValue) {
18503
- if (groupValue === null || groupValue === "null") {
18504
- return null;
18505
- }
18506
- const groupValueString = typeof groupValue === "boolean"
18507
- ? toString(groupValue).toLocaleLowerCase()
18508
- : toString(groupValue);
18509
- if (!pivotNormalizationValueRegistry.contains(dimension.type)) {
18510
- throw new EvaluationError(_t("Field %(field)s is not supported because of its type (%(type)s)", {
18511
- field: dimension.displayName,
18512
- type: dimension.type,
18513
- }));
18514
- }
18515
- // represents a field which is not set (=False server side)
18516
- if (groupValueString === "false") {
18517
- return false;
18518
- }
18519
- const normalizer = pivotNormalizationValueRegistry.get(dimension.type);
18520
- return normalizer(groupValueString, dimension.granularity);
18521
- }
18522
- function normalizeDateTime(value, granularity) {
18523
- if (!granularity) {
18524
- throw "";
18525
- }
18526
- return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
18527
- }
18528
- const pivotNormalizationValueRegistry = new Registry();
18529
- pivotNormalizationValueRegistry
18530
- .add("date", normalizeDateTime)
18531
- .add("datetime", normalizeDateTime)
18532
- .add("integer", (value) => toNumber(value, DEFAULT_LOCALE))
18533
- .add("boolean", (value) => toBoolean(value))
18534
- .add("char", (value) => toString(value));
18535
-
18536
18622
  /**
18537
18623
  * Get the pivot ID from the formula pivot ID.
18538
18624
  */
@@ -19109,14 +19195,10 @@ const PIVOT = {
19109
19195
  result[col].push({ value: "" });
19110
19196
  break;
19111
19197
  case "HEADER":
19112
- const domain = pivotCell.domain;
19113
- const lastNode = domain.at(-1);
19114
- if (lastNode?.field === "measure") {
19115
- result[col].push(pivot.getPivotMeasureValue(toString(lastNode.value), domain));
19116
- }
19117
- else {
19118
- result[col].push(pivot.getPivotHeaderValueAndFormat(domain));
19119
- }
19198
+ result[col].push(pivot.getPivotHeaderValueAndFormat(pivotCell.domain));
19199
+ break;
19200
+ case "MEASURE_HEADER":
19201
+ result[col].push(pivot.getPivotMeasureValue(pivotCell.measure, pivotCell.domain));
19120
19202
  break;
19121
19203
  case "VALUE":
19122
19204
  result[col].push(pivot.getPivotCellValueAndFormat(pivotCell.measure, pivotCell.domain));
@@ -20155,6 +20237,8 @@ const MODIFIER_KEYS = ["Shift", "Control", "Alt", "Meta"];
20155
20237
  * a child element.
20156
20238
  */
20157
20239
  function isChildEvent(parent, ev) {
20240
+ if (!parent)
20241
+ return false;
20158
20242
  return !!ev.target && parent.contains(ev.target);
20159
20243
  }
20160
20244
  function gridOverlayPosition() {
@@ -22694,7 +22778,8 @@ function truncateLabel(label) {
22694
22778
  /**
22695
22779
  * Get a default chart js configuration
22696
22780
  */
22697
- function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, truncateLabels }) {
22781
+ function getDefaultChartJsRuntime(chart, labels, fontColor, args) {
22782
+ const { format, locale, truncateLabels, horizontalChart } = args;
22698
22783
  const chartTitle = chart.title.text ? chart.title : { ...chart.title, content: "" };
22699
22784
  const options = {
22700
22785
  // https://www.chartjs.org/docs/latest/general/responsive.html
@@ -22738,8 +22823,11 @@ function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, tr
22738
22823
  callbacks: {
22739
22824
  label: function (tooltipItem) {
22740
22825
  const xLabel = tooltipItem.dataset?.label || tooltipItem.label;
22741
- // tooltipItem.parsed.y can be an object or a number for pie charts
22742
- const yLabel = tooltipItem.parsed.y ?? tooltipItem.parsed;
22826
+ // tooltipItem.parsed can be an object or a number for pie charts
22827
+ let yLabel = horizontalChart ? tooltipItem.parsed.x : tooltipItem.parsed.y;
22828
+ if (!yLabel) {
22829
+ yLabel = tooltipItem.parsed;
22830
+ }
22743
22831
  const toolTipFormat = !format && Math.abs(yLabel) >= 1000 ? "#,##" : format;
22744
22832
  const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
22745
22833
  return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
@@ -22913,6 +23001,7 @@ class BarChart extends AbstractChart {
22913
23001
  dataSetsHaveTitle;
22914
23002
  dataSetDesign;
22915
23003
  axesDesign;
23004
+ horizontal;
22916
23005
  constructor(definition, sheetId, getters) {
22917
23006
  super(definition, sheetId, getters);
22918
23007
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -22924,6 +23013,7 @@ class BarChart extends AbstractChart {
22924
23013
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
22925
23014
  this.dataSetDesign = definition.dataSets;
22926
23015
  this.axesDesign = definition.axesDesign;
23016
+ this.horizontal = definition.horizontal;
22927
23017
  }
22928
23018
  static transformDefinition(definition, executed) {
22929
23019
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -22995,6 +23085,7 @@ class BarChart extends AbstractChart {
22995
23085
  stacked: this.stacked,
22996
23086
  aggregated: this.aggregated,
22997
23087
  axesDesign: this.axesDesign,
23088
+ horizontal: this.horizontal,
22998
23089
  };
22999
23090
  }
23000
23091
  getDefinitionForExcel() {
@@ -23026,7 +23117,10 @@ class BarChart extends AbstractChart {
23026
23117
  }
23027
23118
  function getBarConfiguration(chart, labels, localeFormat) {
23028
23119
  const fontColor = chartFontColor(chart.background);
23029
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
23120
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, {
23121
+ ...localeFormat,
23122
+ horizontalChart: chart.horizontal,
23123
+ });
23030
23124
  const legend = {
23031
23125
  labels: { color: fontColor },
23032
23126
  };
@@ -23040,16 +23134,10 @@ function getBarConfiguration(chart, labels, localeFormat) {
23040
23134
  config.options.layout = {
23041
23135
  padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
23042
23136
  };
23043
- config.options.scales = {
23044
- x: {
23045
- ticks: {
23046
- padding: 5,
23047
- color: fontColor,
23048
- },
23049
- title: getChartAxisTitleRuntime(chart.axesDesign?.x),
23050
- },
23051
- };
23052
- const yAxis = {
23137
+ config.options.indexAxis = chart.horizontal ? "y" : "x";
23138
+ config.options.scales = {};
23139
+ const labelsAxis = { ticks: { padding: 5, color: fontColor } };
23140
+ const valuesAxis = {
23053
23141
  beginAtZero: true, // the origin of the y axis is always zero
23054
23142
  ticks: {
23055
23143
  color: fontColor,
@@ -23065,7 +23153,10 @@ function getBarConfiguration(chart, labels, localeFormat) {
23065
23153
  },
23066
23154
  },
23067
23155
  };
23156
+ const xAxis = chart.horizontal ? valuesAxis : labelsAxis;
23157
+ const yAxis = chart.horizontal ? labelsAxis : valuesAxis;
23068
23158
  const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
23159
+ config.options.scales.x = { ...xAxis, title: getChartAxisTitleRuntime(chart.axesDesign?.x) };
23069
23160
  if (useLeftAxis) {
23070
23161
  config.options.scales.y = {
23071
23162
  ...yAxis,
@@ -23132,7 +23223,7 @@ function createBarChartRuntime(chart, getters) {
23132
23223
  const label = definition.dataSets[index].label;
23133
23224
  dataset.label = label;
23134
23225
  }
23135
- if (definition.dataSets?.[index]?.yAxisId) {
23226
+ if (definition.dataSets?.[index]?.yAxisId && !chart.horizontal) {
23136
23227
  dataset["yAxisID"] = definition.dataSets[index].yAxisId;
23137
23228
  }
23138
23229
  }
@@ -24123,6 +24214,7 @@ class PieChart extends AbstractChart {
24123
24214
  type = "pie";
24124
24215
  aggregated;
24125
24216
  dataSetsHaveTitle;
24217
+ isDoughnut;
24126
24218
  constructor(definition, sheetId, getters) {
24127
24219
  super(definition, sheetId, getters);
24128
24220
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -24131,6 +24223,7 @@ class PieChart extends AbstractChart {
24131
24223
  this.legendPosition = definition.legendPosition;
24132
24224
  this.aggregated = definition.aggregated;
24133
24225
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24226
+ this.isDoughnut = definition.isDoughnut;
24134
24227
  }
24135
24228
  static transformDefinition(definition, executed) {
24136
24229
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -24148,6 +24241,7 @@ class PieChart extends AbstractChart {
24148
24241
  type: "pie",
24149
24242
  labelRange: context.auxiliaryRange || undefined,
24150
24243
  aggregated: context.aggregated ?? false,
24244
+ isDoughnut: false,
24151
24245
  };
24152
24246
  }
24153
24247
  getDefinition() {
@@ -24178,6 +24272,7 @@ class PieChart extends AbstractChart {
24178
24272
  : undefined,
24179
24273
  title: this.title,
24180
24274
  aggregated: this.aggregated,
24275
+ isDoughnut: this.isDoughnut,
24181
24276
  };
24182
24277
  }
24183
24278
  copyForSheetId(sheetId) {
@@ -24312,6 +24407,141 @@ function createPieChartRuntime(chart, getters) {
24312
24407
  };
24313
24408
  config.data.datasets.push(dataset);
24314
24409
  }
24410
+ if (chart.isDoughnut) {
24411
+ config.type = "doughnut";
24412
+ }
24413
+ return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
24414
+ }
24415
+
24416
+ class PyramidChart extends AbstractChart {
24417
+ dataSets;
24418
+ labelRange;
24419
+ background;
24420
+ legendPosition;
24421
+ aggregated;
24422
+ type = "pyramid";
24423
+ dataSetsHaveTitle;
24424
+ dataSetDesign;
24425
+ axesDesign;
24426
+ horizontal = true;
24427
+ stacked = true;
24428
+ constructor(definition, sheetId, getters) {
24429
+ super(definition, sheetId, getters);
24430
+ this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle).slice(0, 2);
24431
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
24432
+ this.background = definition.background;
24433
+ this.legendPosition = definition.legendPosition;
24434
+ this.aggregated = definition.aggregated;
24435
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24436
+ this.dataSetDesign = definition.dataSets;
24437
+ this.axesDesign = definition.axesDesign;
24438
+ }
24439
+ static transformDefinition(definition, executed) {
24440
+ return transformChartDefinitionWithDataSetsWithZone(definition, executed);
24441
+ }
24442
+ static validateChartDefinition(validator, definition) {
24443
+ return validator.checkValidations(definition, checkDataset, checkLabelRange);
24444
+ }
24445
+ static getDefinitionFromContextCreation(context) {
24446
+ return {
24447
+ background: context.background,
24448
+ dataSets: context.range ?? [],
24449
+ dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
24450
+ aggregated: context.aggregated ?? false,
24451
+ legendPosition: context.legendPosition ?? "top",
24452
+ title: context.title || { text: "" },
24453
+ type: "pyramid",
24454
+ labelRange: context.auxiliaryRange || undefined,
24455
+ axesDesign: context.axesDesign,
24456
+ horizontal: true,
24457
+ stacked: true,
24458
+ };
24459
+ }
24460
+ getContextCreation() {
24461
+ const range = [];
24462
+ for (const [i, dataSet] of this.dataSets.entries()) {
24463
+ range.push({
24464
+ ...this.dataSetDesign?.[i],
24465
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
24466
+ });
24467
+ }
24468
+ return {
24469
+ ...this,
24470
+ range,
24471
+ auxiliaryRange: this.labelRange
24472
+ ? this.getters.getRangeString(this.labelRange, this.sheetId)
24473
+ : undefined,
24474
+ };
24475
+ }
24476
+ copyForSheetId(sheetId) {
24477
+ const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
24478
+ const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
24479
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
24480
+ return new PyramidChart(definition, sheetId, this.getters);
24481
+ }
24482
+ copyInSheetId(sheetId) {
24483
+ const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
24484
+ return new PyramidChart(definition, sheetId, this.getters);
24485
+ }
24486
+ getDefinition() {
24487
+ return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
24488
+ }
24489
+ getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
24490
+ const ranges = [];
24491
+ for (const [i, dataSet] of dataSets.entries()) {
24492
+ ranges.push({
24493
+ ...this.dataSetDesign?.[i],
24494
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
24495
+ });
24496
+ }
24497
+ return {
24498
+ type: "pyramid",
24499
+ dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
24500
+ background: this.background,
24501
+ dataSets: ranges,
24502
+ legendPosition: this.legendPosition,
24503
+ labelRange: labelRange
24504
+ ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
24505
+ : undefined,
24506
+ title: this.title,
24507
+ aggregated: this.aggregated,
24508
+ axesDesign: this.axesDesign,
24509
+ horizontal: true,
24510
+ stacked: true,
24511
+ };
24512
+ }
24513
+ getDefinitionForExcel() {
24514
+ return undefined;
24515
+ }
24516
+ updateRanges(applyChange) {
24517
+ const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
24518
+ if (!isStale) {
24519
+ return this;
24520
+ }
24521
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
24522
+ return new PyramidChart(definition, this.sheetId, this.getters);
24523
+ }
24524
+ }
24525
+ function createPyramidChartRuntime(chart, getters) {
24526
+ const barDef = { ...chart.getDefinition(), type: "bar" };
24527
+ const barChart = new BarChart(barDef, chart.sheetId, getters);
24528
+ const barRuntime = createBarChartRuntime(barChart, getters);
24529
+ const config = barRuntime.chartJsConfig;
24530
+ let datasets = config.data?.datasets;
24531
+ if (datasets && datasets[0]) {
24532
+ datasets[0].data = datasets[0].data.map((value) => (value > 0 ? value : 0));
24533
+ }
24534
+ if (datasets && datasets[1]) {
24535
+ datasets[1].data = datasets[1].data.map((value) => (value > 0 ? -value : 0));
24536
+ }
24537
+ const scales = config.options.scales;
24538
+ const scalesXCallback = scales.x.ticks.callback;
24539
+ scales.x.ticks.callback = (value) => scalesXCallback(Math.abs(value));
24540
+ const tooltipLabelCallback = config.options.plugins.tooltip.callbacks.label;
24541
+ config.options.plugins.tooltip.callbacks.label = (item) => {
24542
+ const tooltipItem = { ...item, parsed: { y: item.parsed.y, x: Math.abs(item.parsed.x) } };
24543
+ return tooltipLabelCallback(tooltipItem);
24544
+ };
24315
24545
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
24316
24546
  }
24317
24547
 
@@ -24754,7 +24984,6 @@ chartRegistry.add("bar", {
24754
24984
  validateChartDefinition: BarChart.validateChartDefinition,
24755
24985
  transformDefinition: BarChart.transformDefinition,
24756
24986
  getChartDefinitionFromContextCreation: BarChart.getDefinitionFromContextCreation,
24757
- name: _t("Bar"),
24758
24987
  sequence: 10,
24759
24988
  });
24760
24989
  chartRegistry.add("combo", {
@@ -24764,7 +24993,6 @@ chartRegistry.add("combo", {
24764
24993
  validateChartDefinition: ComboChart.validateChartDefinition,
24765
24994
  transformDefinition: ComboChart.transformDefinition,
24766
24995
  getChartDefinitionFromContextCreation: ComboChart.getDefinitionFromContextCreation,
24767
- name: _t("Combo"),
24768
24996
  sequence: 15,
24769
24997
  });
24770
24998
  chartRegistry.add("line", {
@@ -24774,7 +25002,6 @@ chartRegistry.add("line", {
24774
25002
  validateChartDefinition: LineChart.validateChartDefinition,
24775
25003
  transformDefinition: LineChart.transformDefinition,
24776
25004
  getChartDefinitionFromContextCreation: LineChart.getDefinitionFromContextCreation,
24777
- name: _t("Line"),
24778
25005
  sequence: 20,
24779
25006
  });
24780
25007
  chartRegistry.add("pie", {
@@ -24784,7 +25011,6 @@ chartRegistry.add("pie", {
24784
25011
  validateChartDefinition: PieChart.validateChartDefinition,
24785
25012
  transformDefinition: PieChart.transformDefinition,
24786
25013
  getChartDefinitionFromContextCreation: PieChart.getDefinitionFromContextCreation,
24787
- name: _t("Pie"),
24788
25014
  sequence: 30,
24789
25015
  });
24790
25016
  chartRegistry.add("scorecard", {
@@ -24794,7 +25020,6 @@ chartRegistry.add("scorecard", {
24794
25020
  validateChartDefinition: ScorecardChart$1.validateChartDefinition,
24795
25021
  transformDefinition: ScorecardChart$1.transformDefinition,
24796
25022
  getChartDefinitionFromContextCreation: ScorecardChart$1.getDefinitionFromContextCreation,
24797
- name: _t("Scorecard"),
24798
25023
  sequence: 40,
24799
25024
  });
24800
25025
  chartRegistry.add("gauge", {
@@ -24804,7 +25029,6 @@ chartRegistry.add("gauge", {
24804
25029
  validateChartDefinition: GaugeChart.validateChartDefinition,
24805
25030
  transformDefinition: GaugeChart.transformDefinition,
24806
25031
  getChartDefinitionFromContextCreation: GaugeChart.getDefinitionFromContextCreation,
24807
- name: _t("Gauge"),
24808
25032
  sequence: 50,
24809
25033
  });
24810
25034
  chartRegistry.add("scatter", {
@@ -24814,7 +25038,6 @@ chartRegistry.add("scatter", {
24814
25038
  validateChartDefinition: ScatterChart.validateChartDefinition,
24815
25039
  transformDefinition: ScatterChart.transformDefinition,
24816
25040
  getChartDefinitionFromContextCreation: ScatterChart.getDefinitionFromContextCreation,
24817
- name: _t("Scatter"),
24818
25041
  sequence: 60,
24819
25042
  });
24820
25043
  chartRegistry.add("waterfall", {
@@ -24824,9 +25047,17 @@ chartRegistry.add("waterfall", {
24824
25047
  validateChartDefinition: WaterfallChart.validateChartDefinition,
24825
25048
  transformDefinition: WaterfallChart.transformDefinition,
24826
25049
  getChartDefinitionFromContextCreation: WaterfallChart.getDefinitionFromContextCreation,
24827
- name: _t("Waterfall"),
24828
25050
  sequence: 70,
24829
25051
  });
25052
+ chartRegistry.add("pyramid", {
25053
+ match: (type) => type === "pyramid",
25054
+ createChart: (definition, sheetId, getters) => new PyramidChart(definition, sheetId, getters),
25055
+ getChartRuntime: createPyramidChartRuntime,
25056
+ validateChartDefinition: PyramidChart.validateChartDefinition,
25057
+ transformDefinition: PyramidChart.transformDefinition,
25058
+ getChartDefinitionFromContextCreation: PyramidChart.getDefinitionFromContextCreation,
25059
+ sequence: 80,
25060
+ });
24830
25061
  const chartComponentRegistry = new Registry();
24831
25062
  chartComponentRegistry.add("line", ChartJsComponent);
24832
25063
  chartComponentRegistry.add("bar", ChartJsComponent);
@@ -24836,6 +25067,130 @@ chartComponentRegistry.add("gauge", GaugeChartComponent);
24836
25067
  chartComponentRegistry.add("scatter", ChartJsComponent);
24837
25068
  chartComponentRegistry.add("scorecard", ScorecardChart);
24838
25069
  chartComponentRegistry.add("waterfall", ChartJsComponent);
25070
+ chartComponentRegistry.add("pyramid", ChartJsComponent);
25071
+ const chartCategories = {
25072
+ line: _t("Line"),
25073
+ column: _t("Column"),
25074
+ bar: _t("Bar"),
25075
+ pie: _t("Pie"),
25076
+ misc: _t("Miscellaneous"),
25077
+ };
25078
+ const chartSubtypeRegistry = new Registry();
25079
+ chartSubtypeRegistry
25080
+ .add("line", {
25081
+ matcher: (definition) => definition.type === "line" && !definition.stacked,
25082
+ displayName: _t("Line"),
25083
+ chartType: "line",
25084
+ chartSubtype: "line",
25085
+ subtypeDefinition: { stacked: false },
25086
+ category: "line",
25087
+ preview: "o-spreadsheet-ChartPreview.LINE_CHART",
25088
+ })
25089
+ .add("stacked_line", {
25090
+ matcher: (definition) => definition.type === "line" && definition.stacked,
25091
+ displayName: _t("Stacked Line"),
25092
+ chartType: "line",
25093
+ chartSubtype: "stacked_line",
25094
+ subtypeDefinition: { stacked: true },
25095
+ category: "line",
25096
+ preview: "o-spreadsheet-ChartPreview.STACKED_AREA_CHART",
25097
+ })
25098
+ .add("scatter", {
25099
+ displayName: _t("Scatter"),
25100
+ chartType: "scatter",
25101
+ chartSubtype: "scatter",
25102
+ category: "misc",
25103
+ preview: "o-spreadsheet-ChartPreview.SCATTER_CHART",
25104
+ })
25105
+ .add("column", {
25106
+ matcher: (definition) => definition.type === "bar" && !definition.stacked && !definition.horizontal,
25107
+ displayName: _t("Column"),
25108
+ chartType: "bar",
25109
+ chartSubtype: "column",
25110
+ subtypeDefinition: { stacked: false, horizontal: false },
25111
+ category: "column",
25112
+ preview: "o-spreadsheet-ChartPreview.COLUMN_CHART",
25113
+ })
25114
+ .add("stacked_column", {
25115
+ matcher: (definition) => definition.type === "bar" && definition.stacked && !definition.horizontal,
25116
+ displayName: _t("Stacked Column"),
25117
+ chartType: "bar",
25118
+ chartSubtype: "stacked_column",
25119
+ subtypeDefinition: { stacked: true, horizontal: false },
25120
+ category: "column",
25121
+ preview: "o-spreadsheet-ChartPreview.STACKED_COLUMN_CHART",
25122
+ })
25123
+ .add("bar", {
25124
+ matcher: (definition) => definition.type === "bar" && !definition.stacked && !!definition.horizontal,
25125
+ displayName: _t("Bar"),
25126
+ chartType: "bar",
25127
+ chartSubtype: "bar",
25128
+ subtypeDefinition: { horizontal: true, stacked: false },
25129
+ category: "bar",
25130
+ preview: "o-spreadsheet-ChartPreview.BAR_CHART",
25131
+ })
25132
+ .add("stacked_bar", {
25133
+ matcher: (definition) => definition.type === "bar" && definition.stacked && !!definition.horizontal,
25134
+ displayName: _t("Stacked Bar"),
25135
+ chartType: "bar",
25136
+ chartSubtype: "stacked_bar",
25137
+ subtypeDefinition: { horizontal: true, stacked: true },
25138
+ category: "bar",
25139
+ preview: "o-spreadsheet-ChartPreview.STACKED_BAR_CHART",
25140
+ })
25141
+ .add("combo", {
25142
+ displayName: _t("Combo"),
25143
+ chartSubtype: "combo",
25144
+ chartType: "combo",
25145
+ category: "line",
25146
+ preview: "o-spreadsheet-ChartPreview.COMBO_CHART",
25147
+ })
25148
+ .add("pie", {
25149
+ matcher: (definition) => definition.type === "pie" && !definition.isDoughnut,
25150
+ displayName: _t("Pie"),
25151
+ chartSubtype: "pie",
25152
+ chartType: "pie",
25153
+ subtypeDefinition: { isDoughnut: false },
25154
+ category: "pie",
25155
+ preview: "o-spreadsheet-ChartPreview.PIE_CHART",
25156
+ })
25157
+ .add("doughnut", {
25158
+ matcher: (definition) => definition.type === "pie" && !!definition.isDoughnut,
25159
+ displayName: _t("Doughnut"),
25160
+ chartSubtype: "doughnut",
25161
+ chartType: "pie",
25162
+ subtypeDefinition: { isDoughnut: true },
25163
+ category: "pie",
25164
+ preview: "o-spreadsheet-ChartPreview.DOUGHNUT_CHART",
25165
+ })
25166
+ .add("gauge", {
25167
+ displayName: _t("Gauge"),
25168
+ chartSubtype: "gauge",
25169
+ chartType: "gauge",
25170
+ category: "misc",
25171
+ preview: "o-spreadsheet-ChartPreview.GAUGE_CHART",
25172
+ })
25173
+ .add("scorecard", {
25174
+ displayName: _t("Scorecard"),
25175
+ chartSubtype: "scorecard",
25176
+ chartType: "scorecard",
25177
+ category: "misc",
25178
+ preview: "o-spreadsheet-ChartPreview.SCORECARD_CHART",
25179
+ })
25180
+ .add("waterfall", {
25181
+ displayName: _t("Waterfall"),
25182
+ chartSubtype: "waterfall",
25183
+ chartType: "waterfall",
25184
+ category: "misc",
25185
+ preview: "o-spreadsheet-ChartPreview.WATERFALL_CHART",
25186
+ })
25187
+ .add("pyramid", {
25188
+ displayName: _t("Population Pyramid"),
25189
+ chartSubtype: "pyramid",
25190
+ chartType: "pyramid",
25191
+ category: "misc",
25192
+ preview: "o-spreadsheet-ChartPreview.POPULATION_PYRAMID_CHART",
25193
+ });
24839
25194
 
24840
25195
  /**
24841
25196
  * Registry intended to support usual currencies. It is mainly used to create
@@ -26692,20 +27047,6 @@ function transformDefinition(definition, executed) {
26692
27047
  }
26693
27048
  return transformation.transformDefinition(definition, executed);
26694
27049
  }
26695
- /**
26696
- * Get an empty definition based on the given context and the given type
26697
- */
26698
- function getChartDefinitionFromContextCreation(context, type) {
26699
- const chartClass = chartRegistry.get(type);
26700
- return chartClass.getChartDefinitionFromContextCreation(context);
26701
- }
26702
- function getChartTypes() {
26703
- const result = {};
26704
- for (const key of chartRegistry.getKeys()) {
26705
- result[key] = chartRegistry.get(key).name;
26706
- }
26707
- return result;
26708
- }
26709
27050
  /**
26710
27051
  * Return a "smart" chart definition in the given zone. The definition is "smart" because it will
26711
27052
  * use the best type of chart to display the data of the zone.
@@ -28165,6 +28506,40 @@ const pivotProperties = {
28165
28506
  },
28166
28507
  icon: "o-spreadsheet-Icon.PIVOT",
28167
28508
  };
28509
+ const FIX_FORMULAS = {
28510
+ name: _t("Convert to individual formulas"),
28511
+ execute(env) {
28512
+ const position = env.model.getters.getActivePosition();
28513
+ const cell = env.model.getters.getCorrespondingFormulaCell(position);
28514
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
28515
+ if (!cell || !pivotId) {
28516
+ return;
28517
+ }
28518
+ const { sheetId, col, row } = env.model.getters.getCellPosition(cell.id);
28519
+ const pivot = env.model.getters.getPivot(pivotId);
28520
+ pivot.init();
28521
+ if (!pivot.isValid()) {
28522
+ return;
28523
+ }
28524
+ env.model.dispatch("INSERT_PIVOT", {
28525
+ sheetId,
28526
+ col,
28527
+ row,
28528
+ pivotId,
28529
+ table: pivot.getTableStructure().export(),
28530
+ });
28531
+ },
28532
+ isVisible: (env) => {
28533
+ const position = env.model.getters.getActivePosition();
28534
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
28535
+ if (!pivotId) {
28536
+ return false;
28537
+ }
28538
+ const pivot = env.model.getters.getPivot(pivotId);
28539
+ return pivot.isValid() && env.model.getters.isSpillPivotFormula(position);
28540
+ },
28541
+ icon: "o-spreadsheet-Icon.PIVOT",
28542
+ };
28168
28543
 
28169
28544
  //------------------------------------------------------------------------------
28170
28545
  // Context Menu Registry
@@ -28263,6 +28638,10 @@ cellMenuRegistry
28263
28638
  name: INSERT_LINK_NAME,
28264
28639
  sequence: 150,
28265
28640
  separator: true,
28641
+ })
28642
+ .add("pivot_fix_formulas", {
28643
+ ...FIX_FORMULAS,
28644
+ sequence: 155,
28266
28645
  })
28267
28646
  .add("pivot_properties", {
28268
28647
  ...pivotProperties,
@@ -30485,6 +30864,7 @@ class GenericChartConfigPanel extends owl.Component {
30485
30864
  });
30486
30865
  dataSeriesRanges = [];
30487
30866
  labelRange;
30867
+ chartTerms = ChartTerms;
30488
30868
  setup() {
30489
30869
  this.dataSeriesRanges = this.props.definition.dataSets;
30490
30870
  this.labelRange = this.props.definition.labelRange;
@@ -30509,7 +30889,7 @@ class GenericChartConfigPanel extends owl.Component {
30509
30889
  return [
30510
30890
  {
30511
30891
  name: "aggregated",
30512
- label: _t("Aggregate"),
30892
+ label: this.chartTerms.AggregatedChart,
30513
30893
  value: this.props.definition.aggregated ?? false,
30514
30894
  onChange: this.onUpdateAggregated.bind(this),
30515
30895
  },
@@ -30585,9 +30965,6 @@ class GenericChartConfigPanel extends owl.Component {
30585
30965
 
30586
30966
  class BarConfigPanel extends GenericChartConfigPanel {
30587
30967
  static template = "o-spreadsheet-BarConfigPanel";
30588
- get stackedLabel() {
30589
- return _t("Stacked barchart");
30590
- }
30591
30968
  onUpdateStacked(stacked) {
30592
30969
  this.props.updateChart(this.props.figureId, {
30593
30970
  stacked,
@@ -31577,6 +31954,9 @@ class ChartWithAxisDesignPanel extends owl.Component {
31577
31954
  return "left";
31578
31955
  return dataSets[this.state.index].yAxisId === "y1" ? "right" : "left";
31579
31956
  }
31957
+ get canHaveTwoVerticalAxis() {
31958
+ return "horizontal" in this.props.definition ? !this.props.definition.horizontal : true;
31959
+ }
31580
31960
  updateDataSeriesLabel(ev) {
31581
31961
  const label = ev.target.value;
31582
31962
  const dataSets = this.props.definition.dataSets;
@@ -31741,19 +32121,13 @@ class LineConfigPanel extends GenericChartConfigPanel {
31741
32121
  }
31742
32122
  return false;
31743
32123
  }
31744
- get stackedLabel() {
31745
- return _t("Stacked linechart");
31746
- }
31747
- get cumulativeLabel() {
31748
- return _t("Cumulative data");
31749
- }
31750
32124
  getLabelRangeOptions() {
31751
32125
  const options = super.getLabelRangeOptions();
31752
32126
  if (this.canTreatLabelsAsText) {
31753
32127
  options.push({
31754
32128
  name: "labelsAsText",
31755
32129
  value: this.props.definition.labelsAsText,
31756
- label: _t("Treat labels as text"),
32130
+ label: this.chartTerms.TreatLabelsAsText,
31757
32131
  onChange: this.onUpdateLabelsAsText.bind(this),
31758
32132
  });
31759
32133
  }
@@ -31820,7 +32194,7 @@ class ScatterConfigPanel extends GenericChartConfigPanel {
31820
32194
  options.push({
31821
32195
  name: "labelsAsText",
31822
32196
  value: this.props.definition.labelsAsText,
31823
- label: _t("Treat labels as text"),
32197
+ label: this.chartTerms.TreatLabelsAsText,
31824
32198
  onChange: this.onUpdateLabelsAsText.bind(this),
31825
32199
  });
31826
32200
  }
@@ -32027,8 +32401,106 @@ chartSidePanelComponentRegistry
32027
32401
  .add("waterfall", {
32028
32402
  configuration: GenericChartConfigPanel,
32029
32403
  design: WaterfallChartDesignPanel,
32404
+ })
32405
+ .add("pyramid", {
32406
+ configuration: GenericChartConfigPanel,
32407
+ design: ChartWithAxisDesignPanel,
32030
32408
  });
32031
32409
 
32410
+ css /* scss */ `
32411
+ .o-section .o-type-selector {
32412
+ height: 30px;
32413
+ padding-left: 30px;
32414
+ }
32415
+ .o-type-selector-preview {
32416
+ left: 5px;
32417
+ top: 3px;
32418
+ .o-chart-preview {
32419
+ width: 24px;
32420
+ height: 24px;
32421
+ }
32422
+ }
32423
+
32424
+ .o-popover .o-chart-select-popover {
32425
+ box-sizing: border-box;
32426
+ background: #fff;
32427
+ .o-chart-type-item {
32428
+ cursor: pointer;
32429
+ padding: 3px 6px;
32430
+ margin: 1px 2px;
32431
+ &.selected,
32432
+ &:hover {
32433
+ background: #f5f5f5;
32434
+ border: 1px solid #ccc;
32435
+ padding: 2px 5px;
32436
+ }
32437
+ .o-chart-preview {
32438
+ width: 48px;
32439
+ height: 48px;
32440
+ }
32441
+ }
32442
+ }
32443
+ `;
32444
+ class ChartTypePicker extends owl.Component {
32445
+ static template = "o-spreadsheet-ChartTypePicker";
32446
+ static components = { Section, Popover };
32447
+ static props = { figureId: String, chartPanelStore: Object };
32448
+ categories = chartCategories;
32449
+ chartTypeByCategories = {};
32450
+ popoverRef = owl.useRef("popoverRef");
32451
+ selectRef = owl.useRef("selectRef");
32452
+ state = owl.useState({ popoverProps: undefined, popoverStyle: "" });
32453
+ setup() {
32454
+ owl.useExternalListener(window, "pointerdown", this.onExternalClick, { capture: true });
32455
+ for (const subtypeProperties of chartSubtypeRegistry.getAll()) {
32456
+ if (this.chartTypeByCategories[subtypeProperties.category]) {
32457
+ this.chartTypeByCategories[subtypeProperties.category].push(subtypeProperties);
32458
+ }
32459
+ else {
32460
+ this.chartTypeByCategories[subtypeProperties.category] = [subtypeProperties];
32461
+ }
32462
+ }
32463
+ }
32464
+ onExternalClick(ev) {
32465
+ if (isChildEvent(this.popoverRef.el?.parentElement, ev) ||
32466
+ isChildEvent(this.selectRef.el, ev)) {
32467
+ return;
32468
+ }
32469
+ this.closePopover();
32470
+ }
32471
+ onTypeChange(type) {
32472
+ this.props.chartPanelStore.changeChartType(this.props.figureId, type);
32473
+ this.closePopover();
32474
+ }
32475
+ getChartDefinition(figureId) {
32476
+ return this.env.model.getters.getChartDefinition(figureId);
32477
+ }
32478
+ getSelectedChartSubtypeProperties() {
32479
+ const definition = this.getChartDefinition(this.props.figureId);
32480
+ const matchedChart = chartSubtypeRegistry
32481
+ .getAll()
32482
+ .find((c) => c.matcher?.(definition) || false);
32483
+ return matchedChart || chartSubtypeRegistry.get(definition.type);
32484
+ }
32485
+ onPointerDown(ev) {
32486
+ if (this.state.popoverProps) {
32487
+ this.closePopover();
32488
+ return;
32489
+ }
32490
+ const target = ev.currentTarget;
32491
+ const { bottom, right, width } = target.getBoundingClientRect();
32492
+ this.state.popoverProps = {
32493
+ anchorRect: { x: right, y: bottom, width: 0, height: 0 },
32494
+ positioning: "TopRight",
32495
+ verticalOffset: 0,
32496
+ };
32497
+ this.state.popoverStyle = cssPropertiesToCss({ width: `${width}px` });
32498
+ }
32499
+ closePopover() {
32500
+ this.state.popoverProps = undefined;
32501
+ }
32502
+ }
32503
+
32032
32504
  class MainChartPanelStore extends SpreadsheetStore {
32033
32505
  mutators = ["activatePanel", "changeChartType"];
32034
32506
  panel = "configuration";
@@ -32036,7 +32508,7 @@ class MainChartPanelStore extends SpreadsheetStore {
32036
32508
  activatePanel(panel) {
32037
32509
  this.panel = panel;
32038
32510
  }
32039
- changeChartType(figureId, type) {
32511
+ changeChartType(figureId, newDisplayType) {
32040
32512
  this.creationContext = {
32041
32513
  ...this.creationContext,
32042
32514
  ...this.getters.getContextCreationChart(figureId),
@@ -32045,13 +32517,25 @@ class MainChartPanelStore extends SpreadsheetStore {
32045
32517
  if (!sheetId) {
32046
32518
  return;
32047
32519
  }
32048
- const definition = getChartDefinitionFromContextCreation(this.creationContext, type);
32520
+ const definition = this.getChartDefinitionFromContextCreation(figureId, newDisplayType);
32049
32521
  this.model.dispatch("UPDATE_CHART", {
32050
32522
  definition,
32051
32523
  id: figureId,
32052
32524
  sheetId,
32053
32525
  });
32054
32526
  }
32527
+ getChartDefinitionFromContextCreation(figureId, newDisplayType) {
32528
+ const newChartInfo = chartSubtypeRegistry.get(newDisplayType);
32529
+ const ChartClass = chartRegistry.get(newChartInfo.chartType);
32530
+ const contextCreation = {
32531
+ ...this.creationContext,
32532
+ ...this.getters.getContextCreationChart(figureId),
32533
+ };
32534
+ return {
32535
+ ...ChartClass.getChartDefinitionFromContextCreation(contextCreation),
32536
+ ...newChartInfo.subtypeDefinition,
32537
+ };
32538
+ }
32055
32539
  }
32056
32540
 
32057
32541
  css /* scss */ `
@@ -32080,7 +32564,7 @@ css /* scss */ `
32080
32564
  `;
32081
32565
  class ChartPanel extends owl.Component {
32082
32566
  static template = "o-spreadsheet-ChartPanel";
32083
- static components = { Section };
32567
+ static components = { Section, ChartTypePicker };
32084
32568
  static props = { onCloseSidePanel: Function, figureId: String };
32085
32569
  store;
32086
32570
  get figureId() {
@@ -32140,9 +32624,6 @@ class ChartPanel extends owl.Component {
32140
32624
  getChartDefinition(figureId) {
32141
32625
  return this.env.model.getters.getChartDefinition(figureId);
32142
32626
  }
32143
- get chartTypes() {
32144
- return getChartTypes();
32145
- }
32146
32627
  }
32147
32628
 
32148
32629
  css /* scss */ `
@@ -35216,9 +35697,17 @@ class SpreadsheetPivotTable {
35216
35697
  }
35217
35698
  getPivotCell(col, row, includeTotal = true) {
35218
35699
  const colHeadersHeight = this.columns.length;
35219
- if (row <= colHeadersHeight - 1) {
35700
+ if (col > 0 && row === colHeadersHeight - 1) {
35220
35701
  const domain = this.getColHeaderDomain(col, row);
35221
- return domain ? { type: "HEADER", domain } : { type: "EMPTY" };
35702
+ if (!domain) {
35703
+ return EMPTY_PIVOT_CELL;
35704
+ }
35705
+ const measure = domain.at(-1)?.value.toString() || "";
35706
+ return { type: "MEASURE_HEADER", domain: domain.slice(0, -1), measure };
35707
+ }
35708
+ else if (row <= colHeadersHeight - 1) {
35709
+ const domain = this.getColHeaderDomain(col, row);
35710
+ return domain ? { type: "HEADER", domain } : EMPTY_PIVOT_CELL;
35222
35711
  }
35223
35712
  else if (col === 0) {
35224
35713
  const rowIndex = row - colHeadersHeight;
@@ -35228,7 +35717,7 @@ class SpreadsheetPivotTable {
35228
35717
  else {
35229
35718
  const rowIndex = row - colHeadersHeight;
35230
35719
  if (!includeTotal && this.isTotalRow(rowIndex)) {
35231
- return { type: "EMPTY" };
35720
+ return EMPTY_PIVOT_CELL;
35232
35721
  }
35233
35722
  const domain = [...this.getRowDomain(rowIndex), ...this.getColDomain(col)];
35234
35723
  const measure = this.getColMeasure(col);
@@ -35282,6 +35771,7 @@ class SpreadsheetPivotTable {
35282
35771
  };
35283
35772
  }
35284
35773
  }
35774
+ const EMPTY_PIVOT_CELL = { type: "EMPTY" };
35285
35775
 
35286
35776
  /**
35287
35777
  * This function converts a list of data entry into a spreadsheet pivot table.
@@ -35479,6 +35969,9 @@ function createDate(dimension, value, locale) {
35479
35969
  if (!(granularity in MAP_VALUE_DIMENSION_DATE)) {
35480
35970
  throw new Error(`Unknown date granularity: ${granularity}`);
35481
35971
  }
35972
+ if (value === null) {
35973
+ return null;
35974
+ }
35482
35975
  if (!MAP_VALUE_DIMENSION_DATE[granularity].set.has(value)) {
35483
35976
  MAP_VALUE_DIMENSION_DATE[granularity].set.add(value);
35484
35977
  const date = toJsDate(value, locale);
@@ -35709,14 +36202,17 @@ class SpreadsheetPivot {
35709
36202
  if (dimension.type === "date") {
35710
36203
  const adapter = pivotTimeAdapter(dimension.granularity);
35711
36204
  return {
35712
- value: adapter.toCellValue(toNormalizedPivotValue(dimension, lastNode.value)),
36205
+ value: lastNode.value !== "null"
36206
+ ? adapter.toCellValue(toNormalizedPivotValue(dimension, lastNode.value))
36207
+ : _t("(Undefined)"),
35713
36208
  format: adapter.getFormat(this.getters.getLocale()),
35714
36209
  };
35715
36210
  }
35716
36211
  if (!finalCell) {
35717
36212
  return { value: "" };
35718
36213
  }
35719
- if (finalCell.value === null) {
36214
+ // Value can be null but stringified (e.g. an empty date, as for now every date is stringified)
36215
+ if (finalCell.value === null || finalCell.value === `${null}`) {
35720
36216
  return { value: _t("(Undefined)") };
35721
36217
  }
35722
36218
  return {
@@ -35738,10 +36234,15 @@ class SpreadsheetPivot {
35738
36234
  if (!operator) {
35739
36235
  throw new Error(`Aggregator ${aggregator} does not exist`);
35740
36236
  }
35741
- return {
35742
- value: values.length ? operator.fn([values], this.getters.getLocale()) : "",
35743
- format: operator.format(values[0]),
35744
- };
36237
+ try {
36238
+ return {
36239
+ value: values.length ? operator.fn([values], this.getters.getLocale()) : "",
36240
+ format: operator.format(values[0]),
36241
+ };
36242
+ }
36243
+ catch (e) {
36244
+ return handleError(e, aggregator.toUpperCase());
36245
+ }
35745
36246
  }
35746
36247
  getPossibleFieldValues(dimension) {
35747
36248
  const values = [];
@@ -51108,12 +51609,12 @@ class PivotCorePlugin extends CorePlugin {
51108
51609
  for (let col = 0; col < pivotCells.length; col++) {
51109
51610
  for (let row = 0; row < pivotCells[col].length; row++) {
51110
51611
  const pivotCell = pivotCells[col][row];
51111
- const cellPosition = {
51612
+ this.dispatch("UPDATE_CELL", {
51112
51613
  sheetId: position.sheetId,
51113
51614
  col: position.col + col,
51114
51615
  row: position.row + row,
51115
- };
51116
- this.addPivotFormula(cellPosition, formulaId, pivotCell);
51616
+ content: makePivotFormulaFromPivotCell(formulaId, pivotCell),
51617
+ });
51117
51618
  }
51118
51619
  }
51119
51620
  }
@@ -51143,21 +51644,6 @@ class PivotCorePlugin extends CorePlugin {
51143
51644
  });
51144
51645
  }
51145
51646
  }
51146
- addPivotFormula(position, formulaId, pivotCell) {
51147
- let content = undefined;
51148
- switch (pivotCell.type) {
51149
- case "HEADER":
51150
- content = makePivotFormula("PIVOT.HEADER", [formulaId, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
51151
- break;
51152
- case "VALUE":
51153
- content = makePivotFormula("PIVOT.VALUE", [formulaId, pivotCell.measure, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
51154
- break;
51155
- }
51156
- this.dispatch("UPDATE_CELL", {
51157
- ...position,
51158
- content,
51159
- });
51160
- }
51161
51647
  getPivotCore(pivotId) {
51162
51648
  const pivot = this.pivots[pivotId];
51163
51649
  if (!pivot) {
@@ -52822,7 +53308,6 @@ class Evaluator {
52822
53308
  if (!this.blockedArrayFormulas.has(position)) {
52823
53309
  this.invalidateSpreading(position);
52824
53310
  }
52825
- this.spreadingRelations.removeNode(position);
52826
53311
  const cell = this.getters.getCell(position);
52827
53312
  if (cell === undefined) {
52828
53313
  return EMPTY_CELL;
@@ -52864,6 +53349,7 @@ class Evaluator {
52864
53349
  this.assertSheetHasEnoughSpaceToSpreadFormulaResult(formulaPosition, formulaReturn);
52865
53350
  const nbColumns = formulaReturn.length;
52866
53351
  const nbRows = formulaReturn[0].length;
53352
+ this.spreadingRelations.removeNode(formulaPosition);
52867
53353
  forEachSpreadPositionInMatrix(nbColumns, nbRows, this.updateSpreadRelation(formulaPosition));
52868
53354
  this.assertNoMergedCellsInSpreadZone(formulaPosition, formulaReturn);
52869
53355
  forEachSpreadPositionInMatrix(nbColumns, nbRows, this.checkCollision(formulaPosition));
@@ -54391,9 +54877,10 @@ class PivotUIPlugin extends UIPlugin {
54391
54877
  "getPivot",
54392
54878
  "getFirstPivotFunction",
54393
54879
  "getPivotIdFromPosition",
54394
- "getPivotDomainArgsFromPosition",
54880
+ "getPivotCellFromPosition",
54395
54881
  "isPivotUnused",
54396
54882
  "areDomainArgsFieldsValid",
54883
+ "isSpillPivotFormula",
54397
54884
  ];
54398
54885
  pivots = {};
54399
54886
  unusedPivots;
@@ -54472,6 +54959,14 @@ class PivotUIPlugin extends UIPlugin {
54472
54959
  }
54473
54960
  return undefined;
54474
54961
  }
54962
+ isSpillPivotFormula(position) {
54963
+ const cell = this.getters.getCorrespondingFormulaCell(position);
54964
+ if (cell && cell.isFormula) {
54965
+ const pivotFunction = this.getFirstPivotFunction(cell.compiledFormula.tokens);
54966
+ return pivotFunction?.functionName === "PIVOT";
54967
+ }
54968
+ return false;
54969
+ }
54475
54970
  getFirstPivotFunction(tokens) {
54476
54971
  const pivotFunction = getFirstPivotFunction(tokens);
54477
54972
  if (!pivotFunction) {
@@ -54505,29 +55000,29 @@ class PivotUIPlugin extends UIPlugin {
54505
55000
  * If the cell is the result of PIVOT, the result is the domain of the cell
54506
55001
  * as if it was the individual pivot formula
54507
55002
  */
54508
- getPivotDomainArgsFromPosition(position) {
55003
+ getPivotCellFromPosition(position) {
54509
55004
  const cell = this.getters.getCorrespondingFormulaCell(position);
54510
55005
  if (!cell || !cell.isFormula || getNumberOfPivotFunctions(cell.compiledFormula.tokens) === 0) {
54511
- return undefined;
55006
+ return EMPTY_PIVOT_CELL;
54512
55007
  }
54513
55008
  const mainPosition = this.getters.getCellPosition(cell.id);
54514
55009
  const result = this.getters.getFirstPivotFunction(cell.compiledFormula.tokens);
54515
55010
  if (!result) {
54516
- return undefined;
55011
+ return EMPTY_PIVOT_CELL;
54517
55012
  }
54518
55013
  const { functionName, args } = result;
54519
55014
  if (functionName === "PIVOT") {
54520
55015
  const formulaId = args[0];
54521
55016
  if (!formulaId) {
54522
- return undefined;
55017
+ return EMPTY_PIVOT_CELL;
54523
55018
  }
54524
55019
  const pivotId = this.getters.getPivotId(formulaId.toString());
54525
55020
  if (!pivotId) {
54526
- return undefined;
55021
+ return EMPTY_PIVOT_CELL;
54527
55022
  }
54528
55023
  const pivot = this.getPivot(pivotId);
54529
55024
  if (!pivot.isValid()) {
54530
- return undefined;
55025
+ return EMPTY_PIVOT_CELL;
54531
55026
  }
54532
55027
  const includeTotal = args[2] === false ? false : undefined;
54533
55028
  const includeColumnHeaders = args[3] === false ? false : undefined;
@@ -54536,22 +55031,28 @@ class PivotUIPlugin extends UIPlugin {
54536
55031
  .getPivotCells(includeTotal, includeColumnHeaders);
54537
55032
  const pivotCol = position.col - mainPosition.col;
54538
55033
  const pivotRow = position.row - mainPosition.row;
54539
- const pivotCell = pivotCells[pivotCol][pivotRow];
54540
- if (pivotCell.type === "EMPTY") {
54541
- return undefined;
54542
- }
54543
- let domain = pivotCell.domain;
54544
- if (domain.at(-1)?.field === "measure") {
54545
- domain = domain.slice(0, -1);
54546
- }
54547
- return { domainArgs: domain, isHeader: pivotCell.type === "HEADER" };
55034
+ return pivotCells[pivotCol][pivotRow];
54548
55035
  }
54549
- let domain = toPivotDomain(args.slice(functionName === "PIVOT.VALUE" ? 2 : 1).map((x) => `${x}`));
54550
- if (domain.at(-1)?.field === "measure") {
54551
- domain = domain.slice(0, -1);
55036
+ if (functionName === "PIVOT.HEADER" && args.at(-2) === "measure") {
55037
+ const domain = toPivotDomain(args.slice(1, -2).map((x) => `${x}`));
55038
+ return {
55039
+ type: "MEASURE_HEADER",
55040
+ domain,
55041
+ measure: args.at(-1)?.toString() || "",
55042
+ };
55043
+ }
55044
+ else if (functionName === "PIVOT.HEADER") {
55045
+ return {
55046
+ type: "HEADER",
55047
+ domain: toPivotDomain(args.slice(1).map((x) => `${x}`)),
55048
+ };
54552
55049
  }
54553
- const isHeader = functionName === "PIVOT.HEADER";
54554
- return { domainArgs: domain, isHeader };
55050
+ const [measure, ...domainArgs] = args.slice(1);
55051
+ return {
55052
+ type: "VALUE",
55053
+ domain: toPivotDomain(domainArgs.map((x) => `${x}`)),
55054
+ measure: measure?.toString() || "",
55055
+ };
54555
55056
  }
54556
55057
  getPivot(pivotId) {
54557
55058
  return this.pivots[pivotId];
@@ -63834,6 +64335,9 @@ class SelectionStreamProcessorImpl {
63834
64335
  getBackToDefault() {
63835
64336
  this.stream.getBackToDefault();
63836
64337
  }
64338
+ getAnchor() {
64339
+ return this.anchor;
64340
+ }
63837
64341
  modifyAnchor(anchor, mode, options) {
63838
64342
  const sheetId = this.getters.getActiveSheetId();
63839
64343
  anchor = {
@@ -66797,6 +67301,7 @@ const registries = {
66797
67301
  chartSidePanelComponentRegistry,
66798
67302
  chartComponentRegistry,
66799
67303
  chartRegistry,
67304
+ chartSubtypeRegistry,
66800
67305
  topbarMenuRegistry,
66801
67306
  topbarComponentRegistry,
66802
67307
  clickableCellRegistry,
@@ -66905,6 +67410,7 @@ const components = {
66905
67410
  GaugeChartDesignPanel,
66906
67411
  ScorecardChartConfigPanel,
66907
67412
  ScorecardChartDesignPanel,
67413
+ ChartTypePicker,
66908
67414
  FigureComponent,
66909
67415
  Menu,
66910
67416
  Popover,
@@ -66954,6 +67460,7 @@ const constants = {
66954
67460
  DEFAULT_LOCALE,
66955
67461
  HIGHLIGHT_COLOR,
66956
67462
  PIVOT_TABLE_CONFIG,
67463
+ ChartTerms,
66957
67464
  };
66958
67465
 
66959
67466
  exports.AbstractCellClipboardHandler = AbstractCellClipboardHandler;
@@ -67002,6 +67509,6 @@ exports.tokenColors = tokenColors;
67002
67509
  exports.tokenize = tokenize;
67003
67510
 
67004
67511
 
67005
- __info__.version = "17.4.0-alpha.5";
67006
- __info__.date = "2024-06-14T10:01:40.605Z";
67007
- __info__.hash = "9ceed96";
67512
+ __info__.version = "17.4.0-alpha.6";
67513
+ __info__.date = "2024-06-19T13:46:27.157Z";
67514
+ __info__.hash = "a4f22e4";