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