@odoo/o-spreadsheet 17.4.0-alpha.5 → 17.4.0-alpha.7

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.
@@ -1,11 +1,10 @@
1
1
 
2
- // @odoo-module ignore
3
2
  /**
4
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
5
4
  * @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
5
+ * @version 17.4.0-alpha.7
6
+ * @date 2024-06-21T08:42:22.370Z
7
+ * @hash a99db9a
9
8
  */
10
9
 
11
10
  (function (exports, owl) {
@@ -6201,7 +6200,7 @@
6201
6200
  };
6202
6201
  }
6203
6202
  isPasteAllowed(sheetId, target, content, clipboardOptions) {
6204
- if (!("cells" in content)) {
6203
+ if (!content.cells) {
6205
6204
  return "Success" /* CommandResult.Success */;
6206
6205
  }
6207
6206
  if (clipboardOptions?.isCutOperation && clipboardOptions?.pasteOption !== undefined) {
@@ -6215,6 +6214,17 @@
6215
6214
  return "WrongPasteSelection" /* CommandResult.WrongPasteSelection */;
6216
6215
  }
6217
6216
  }
6217
+ const clipboardHeight = content.cells.length;
6218
+ const clipboardWidth = content.cells[0].length;
6219
+ for (const zone of getPasteZones(target, content.cells)) {
6220
+ if (this.getters.doesIntersectMerge(sheetId, zone)) {
6221
+ if (target.length > 1 ||
6222
+ !this.getters.isSingleCellOrMerge(sheetId, target[0]) ||
6223
+ clipboardHeight * clipboardWidth !== 1) {
6224
+ return "WillRemoveExistingMerge" /* CommandResult.WillRemoveExistingMerge */;
6225
+ }
6226
+ }
6227
+ }
6218
6228
  return "Success" /* CommandResult.Success */;
6219
6229
  }
6220
6230
  /**
@@ -6454,13 +6464,22 @@
6454
6464
  }
6455
6465
  const { rowsIndexes, columnsIndexes } = data;
6456
6466
  const sheetId = data.sheetId;
6457
- const cellPositions = rowsIndexes.map((row) => columnsIndexes.map((col) => ({ col, row, sheetId })));
6458
- return {
6459
- cellPositions,
6460
- };
6467
+ const cfRules = [];
6468
+ for (const row of rowsIndexes) {
6469
+ const cfRuleInRow = [];
6470
+ for (const col of columnsIndexes) {
6471
+ const cfRules = Array.from(this.getters.getRulesByCell(sheetId, col, row));
6472
+ cfRuleInRow.push({
6473
+ position: { col, row, sheetId },
6474
+ rules: cfRules,
6475
+ });
6476
+ }
6477
+ cfRules.push(cfRuleInRow);
6478
+ }
6479
+ return { cfRules };
6461
6480
  }
6462
6481
  paste(target, clippedContent, options) {
6463
- if (!clippedContent?.cellPositions ||
6482
+ if (!clippedContent?.cfRules ||
6464
6483
  options?.pasteOption === "asValue" ||
6465
6484
  !("zones" in target) ||
6466
6485
  !target.zones.length) {
@@ -6469,7 +6488,7 @@
6469
6488
  const zones = target.zones;
6470
6489
  const sheetId = target.sheetId;
6471
6490
  if (!options?.isCutOperation) {
6472
- this.pasteFromCopy(sheetId, zones, clippedContent.cellPositions);
6491
+ this.pasteFromCopy(sheetId, zones, clippedContent.cfRules, options);
6473
6492
  }
6474
6493
  else {
6475
6494
  this.pasteFromCut(sheetId, zones, clippedContent);
@@ -6477,12 +6496,12 @@
6477
6496
  }
6478
6497
  pasteFromCut(sheetId, target, content) {
6479
6498
  const selection = target[0];
6480
- this.pasteZone(sheetId, selection.left, selection.top, content.cellPositions, {
6499
+ this.pasteZone(sheetId, selection.left, selection.top, content.cfRules, {
6481
6500
  isCutOperation: true,
6482
6501
  });
6483
6502
  }
6484
- pasteZone(sheetId, col, row, positions, clipboardOptions) {
6485
- for (const [r, rowCells] of positions.entries()) {
6503
+ pasteZone(sheetId, col, row, cfRules, clipboardOptions) {
6504
+ for (const [r, rowCells] of cfRules.entries()) {
6486
6505
  for (const [c, origin] of rowCells.entries()) {
6487
6506
  const position = { col: col + c, row: row + r, sheetId };
6488
6507
  this.pasteCf(origin, position, clipboardOptions?.isCutOperation);
@@ -6490,23 +6509,21 @@
6490
6509
  }
6491
6510
  }
6492
6511
  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
- }
6512
+ if (origin?.rules && origin.rules.length > 0) {
6513
+ const zone = positionToZone(target);
6514
+ for (const rule of origin.rules) {
6515
+ const toRemoveZones = [];
6516
+ if (isCutOperation) {
6517
+ //remove from current rule
6518
+ toRemoveZones.push(positionToZone(origin.position));
6519
+ }
6520
+ if (origin.position.sheetId === target.sheetId) {
6521
+ this.adaptCFRules(origin.position.sheetId, rule, [zone], toRemoveZones);
6522
+ }
6523
+ else {
6524
+ this.adaptCFRules(origin.position.sheetId, rule, [], toRemoveZones);
6525
+ const cfToCopyTo = this.getCFToCopyTo(target.sheetId, rule);
6526
+ this.adaptCFRules(target.sheetId, cfToCopyTo, [zone], []);
6510
6527
  }
6511
6528
  }
6512
6529
  }
@@ -6549,13 +6566,20 @@
6549
6566
  }
6550
6567
  const { rowsIndexes, columnsIndexes } = data;
6551
6568
  const sheetId = data.sheetId;
6552
- const cellPositions = rowsIndexes.map((row) => columnsIndexes.map((col) => ({ col, row, sheetId })));
6553
- return {
6554
- cellPositions,
6555
- };
6569
+ const dvRules = [];
6570
+ for (const row of rowsIndexes) {
6571
+ const dvRuleInRow = [];
6572
+ for (const col of columnsIndexes) {
6573
+ const position = { sheetId, col, row };
6574
+ const rule = this.getters.getValidationRuleForCell(position);
6575
+ dvRuleInRow.push({ position, rule });
6576
+ }
6577
+ dvRules.push(dvRuleInRow);
6578
+ }
6579
+ return { dvRules };
6556
6580
  }
6557
6581
  paste(target, clippedContent, options) {
6558
- if (!clippedContent?.cellPositions) {
6582
+ if (!clippedContent?.dvRules) {
6559
6583
  return;
6560
6584
  }
6561
6585
  if (options?.pasteOption) {
@@ -6567,7 +6591,7 @@
6567
6591
  const zones = target.zones;
6568
6592
  const sheetId = target.sheetId;
6569
6593
  if (!options?.isCutOperation) {
6570
- this.pasteFromCopy(sheetId, zones, clippedContent.cellPositions);
6594
+ this.pasteFromCopy(sheetId, zones, clippedContent.dvRules);
6571
6595
  }
6572
6596
  else {
6573
6597
  this.pasteFromCut(sheetId, zones, clippedContent);
@@ -6575,12 +6599,12 @@
6575
6599
  }
6576
6600
  pasteFromCut(sheetId, target, content) {
6577
6601
  const selection = target[0];
6578
- this.pasteZone(sheetId, selection.left, selection.top, content.cellPositions, {
6602
+ this.pasteZone(sheetId, selection.left, selection.top, content.dvRules, {
6579
6603
  isCutOperation: true,
6580
6604
  });
6581
6605
  }
6582
- pasteZone(sheetId, col, row, positions, clipboardOptions) {
6583
- for (const [r, rowCells] of positions.entries()) {
6606
+ pasteZone(sheetId, col, row, dvRules, clipboardOptions) {
6607
+ for (const [r, rowCells] of dvRules.entries()) {
6584
6608
  for (const [c, origin] of rowCells.entries()) {
6585
6609
  const position = { col: col + c, row: row + r, sheetId };
6586
6610
  this.pasteDataValidation(origin, position, clipboardOptions?.isCutOperation);
@@ -6588,41 +6612,43 @@
6588
6612
  }
6589
6613
  }
6590
6614
  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);
6615
+ if (origin) {
6616
+ const zone = positionToZone(target);
6617
+ const rule = origin.rule;
6618
+ if (!rule) {
6619
+ const targetRule = this.getters.getValidationRuleForCell(target);
6620
+ if (targetRule) {
6621
+ // Remove the data validation rule on the target cell
6622
+ this.adaptDataValidationRule(target.sheetId, targetRule, [], [zone]);
6609
6623
  }
6610
- else {
6611
- this.adaptDataValidationRule(origin.sheetId, rule, [], toRemoveZone);
6612
- const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule);
6613
- this.adaptDataValidationRule(target.sheetId, copyToRule, [zone], []);
6624
+ return;
6625
+ }
6626
+ const toRemoveZone = [];
6627
+ if (isCutOperation) {
6628
+ toRemoveZone.push(positionToZone(origin.position));
6629
+ }
6630
+ if (origin.position.sheetId === target.sheetId) {
6631
+ const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule, false);
6632
+ this.adaptDataValidationRule(origin.position.sheetId, copyToRule, [zone], toRemoveZone);
6633
+ }
6634
+ else {
6635
+ const originRule = this.getters.getValidationRuleForCell(origin.position);
6636
+ if (originRule) {
6637
+ this.adaptDataValidationRule(origin.position.sheetId, originRule, [], toRemoveZone);
6614
6638
  }
6639
+ const copyToRule = this.getDataValidationRuleToCopyTo(target.sheetId, rule);
6640
+ this.adaptDataValidationRule(target.sheetId, copyToRule, [zone], []);
6615
6641
  }
6616
6642
  }
6617
6643
  }
6618
- getDataValidationRuleToCopyTo(targetSheetId, originRule) {
6644
+ getDataValidationRuleToCopyTo(targetSheetId, originRule, newId = true) {
6619
6645
  const ruleInTargetSheet = this.getters
6620
6646
  .getDataValidationRules(targetSheetId)
6621
6647
  .find((rule) => deepEquals(originRule.criterion, rule.criterion) &&
6622
6648
  originRule.isBlocking === rule.isBlocking);
6623
6649
  return ruleInTargetSheet
6624
6650
  ? ruleInTargetSheet
6625
- : { ...originRule, id: this.uuidGenerator.uuidv4(), ranges: [] };
6651
+ : { ...originRule, id: newId ? this.uuidGenerator.uuidv4() : originRule.id, ranges: [] };
6626
6652
  }
6627
6653
  /**
6628
6654
  * Add or remove XCs to a given data validation rule.
@@ -6714,69 +6740,63 @@
6714
6740
  }
6715
6741
 
6716
6742
  class MergeClipboardHandler extends AbstractCellClipboardHandler {
6717
- isPasteAllowed(sheetId, target, content) {
6718
- if (!("cells" in content)) {
6719
- return "Success" /* CommandResult.Success */;
6743
+ copy(data) {
6744
+ if (!data.zones.length) {
6745
+ return;
6720
6746
  }
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
- }
6747
+ const sheetId = this.getters.getActiveSheetId();
6748
+ const { rowsIndexes, columnsIndexes } = data;
6749
+ const merges = [];
6750
+ for (const row of rowsIndexes) {
6751
+ const mergesInRow = [];
6752
+ for (const col of columnsIndexes) {
6753
+ const position = { col, row, sheetId };
6754
+ mergesInRow.push(this.getters.getMerge(position));
6730
6755
  }
6756
+ merges.push(mergesInRow);
6731
6757
  }
6732
- return "Success" /* CommandResult.Success */;
6758
+ return { merges };
6733
6759
  }
6734
6760
  /**
6735
6761
  * Paste the clipboard content in the given target
6736
6762
  */
6737
6763
  paste(target, content, options) {
6738
- if (options?.isCutOperation || !("zones" in target) || !target.zones.length) {
6764
+ if (!content.merges ||
6765
+ options?.isCutOperation ||
6766
+ !("zones" in target) ||
6767
+ !target.zones.length) {
6739
6768
  return;
6740
6769
  }
6741
- this.pasteFromCopy(target.sheetId, target.zones, content.cells, options);
6770
+ this.pasteFromCopy(target.sheetId, target.zones, content.merges, options);
6742
6771
  }
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
- }
6772
+ pasteZone(sheetId, col, row, merges) {
6773
+ for (const [r, rowMerges] of merges.entries()) {
6774
+ for (const [c, originMerge] of rowMerges.entries()) {
6749
6775
  const position = { col: col + c, row: row + r, sheetId };
6750
- this.pasteMergeIfExist(origin.position, position);
6776
+ this.pasteMerge(originMerge, position);
6751
6777
  }
6752
6778
  }
6753
6779
  }
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
- });
6780
+ pasteMerge(originMerge, target) {
6781
+ if (!originMerge) {
6782
+ return;
6783
+ }
6784
+ if (this.getters.isInMerge(target)) {
6785
+ return;
6779
6786
  }
6787
+ const { sheetId, col, row } = target;
6788
+ this.dispatch("ADD_MERGE", {
6789
+ sheetId,
6790
+ force: true,
6791
+ target: [
6792
+ {
6793
+ left: col,
6794
+ top: row,
6795
+ right: col + originMerge.right - originMerge.left,
6796
+ bottom: row + originMerge.bottom - originMerge.top,
6797
+ },
6798
+ ],
6799
+ });
6780
6800
  }
6781
6801
  }
6782
6802
 
@@ -7756,6 +7776,486 @@
7756
7776
  };
7757
7777
  }
7758
7778
 
7779
+ function boolAnd(args) {
7780
+ let foundBoolean = false;
7781
+ let acc = true;
7782
+ conditionalVisitBoolean(args, (arg) => {
7783
+ foundBoolean = true;
7784
+ acc = acc && arg;
7785
+ return acc;
7786
+ });
7787
+ return {
7788
+ foundBoolean,
7789
+ result: acc,
7790
+ };
7791
+ }
7792
+ function boolOr(args) {
7793
+ let foundBoolean = false;
7794
+ let acc = false;
7795
+ conditionalVisitBoolean(args, (arg) => {
7796
+ foundBoolean = true;
7797
+ acc = acc || arg;
7798
+ return !acc;
7799
+ });
7800
+ return {
7801
+ foundBoolean,
7802
+ result: acc,
7803
+ };
7804
+ }
7805
+
7806
+ function sum(values, locale) {
7807
+ return reduceNumbers(values, (acc, a) => acc + a, 0, locale);
7808
+ }
7809
+ function countUnique(args) {
7810
+ return reduceAny(args, (acc, a) => (isDataNonEmpty(a) ? acc.add(a?.value) : acc), new Set()).size;
7811
+ }
7812
+
7813
+ function assertSameNumberOfElements(...args) {
7814
+ const dims = args[0].length;
7815
+ 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())));
7816
+ }
7817
+ function average(values, locale) {
7818
+ let count = 0;
7819
+ const sum = reduceNumbers(values, (acc, a) => {
7820
+ count += 1;
7821
+ return acc + a;
7822
+ }, 0, locale);
7823
+ assertNotZero(count);
7824
+ return sum / count;
7825
+ }
7826
+ function countNumbers(values, locale) {
7827
+ let count = 0;
7828
+ for (let n of values) {
7829
+ if (isMatrix(n)) {
7830
+ for (let i of n) {
7831
+ for (let j of i) {
7832
+ if (typeof j.value === "number") {
7833
+ count += 1;
7834
+ }
7835
+ }
7836
+ }
7837
+ }
7838
+ else {
7839
+ const value = n?.value;
7840
+ if (!isEvaluationError(value) &&
7841
+ (typeof value !== "string" || isNumber(value, locale) || parseDateTime(value, locale))) {
7842
+ count += 1;
7843
+ }
7844
+ }
7845
+ }
7846
+ return count;
7847
+ }
7848
+ function countAny(values) {
7849
+ return reduceAny(values, (acc, a) => (a !== undefined && a.value !== null ? acc + 1 : acc), 0);
7850
+ }
7851
+ function max(values, locale) {
7852
+ const result = reduceNumbers(values, (acc, a) => (acc < a ? a : acc), -Infinity, locale);
7853
+ return result === -Infinity ? 0 : result;
7854
+ }
7855
+ function min(values, locale) {
7856
+ const result = reduceNumbers(values, (acc, a) => (a < acc ? a : acc), Infinity, locale);
7857
+ return result === Infinity ? 0 : result;
7858
+ }
7859
+
7860
+ const pivotTimeAdapterRegistry = new Registry();
7861
+ function pivotTimeAdapter(granularity) {
7862
+ return pivotTimeAdapterRegistry.get(granularity);
7863
+ }
7864
+ /**
7865
+ * The Time Adapter: Managing Time Periods for Pivot Functions
7866
+ *
7867
+ * Overview:
7868
+ * A time adapter is responsible for managing time periods associated with pivot functions.
7869
+ * Each type of period (day, week, month, quarter, etc.) has its own dedicated adapter.
7870
+ * The adapter's primary role is to normalize period values between spreadsheet functions,
7871
+ * and the pivot.
7872
+ * By normalizing the period value, it can be stored consistently in the pivot.
7873
+ *
7874
+ * Normalization Process:
7875
+ * When working with functions in the spreadsheet, the time adapter normalizes
7876
+ * the provided period to facilitate accurate lookup of values in the pivot.
7877
+ * For instance, if the spreadsheet function represents a day period as a number generated
7878
+ * by the DATE function (DATE(2023, 12, 25)), the time adapter will normalize it accordingly.
7879
+ *
7880
+ */
7881
+ /**
7882
+ * Normalized value: "12/25/2023"
7883
+ *
7884
+ * Note: Those two format are equivalent:
7885
+ * - "MM/dd/yyyy" (luxon format)
7886
+ * - "mm/dd/yyyy" (spreadsheet format)
7887
+ **/
7888
+ const dayAdapter = {
7889
+ normalizeFunctionValue(value) {
7890
+ const date = toNumber(value, DEFAULT_LOCALE);
7891
+ return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/dd/yyyy" });
7892
+ },
7893
+ getFormat(locale) {
7894
+ return (locale ?? DEFAULT_LOCALE).dateFormat;
7895
+ },
7896
+ formatValue(normalizedValue, locale) {
7897
+ locale = locale ?? DEFAULT_LOCALE;
7898
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7899
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7900
+ },
7901
+ toCellValue(normalizedValue) {
7902
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7903
+ },
7904
+ };
7905
+ /**
7906
+ * normalizes day of month number
7907
+ */
7908
+ const dayOfMonthAdapter = {
7909
+ normalizeFunctionValue(value) {
7910
+ const day = toNumber(value, DEFAULT_LOCALE);
7911
+ if (day < 1 || day > 31) {
7912
+ throw new EvaluationError(_t("%s is not a valid day of month (it should be a number between 1 and 31)", day));
7913
+ }
7914
+ return day;
7915
+ },
7916
+ getFormat() {
7917
+ return "0";
7918
+ },
7919
+ formatValue(normalizedValue, locale) {
7920
+ locale = locale ?? DEFAULT_LOCALE;
7921
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7922
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7923
+ },
7924
+ toCellValue(normalizedValue) {
7925
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7926
+ },
7927
+ };
7928
+ /**
7929
+ * Normalized value: "2/2023" for week 2 of 2023
7930
+ */
7931
+ const weekAdapter = {
7932
+ normalizeFunctionValue(value) {
7933
+ const [week, year] = value.split("/");
7934
+ return `${Number(week)}/${Number(year)}`;
7935
+ },
7936
+ getFormat() {
7937
+ return undefined;
7938
+ },
7939
+ formatValue(normalizedValue) {
7940
+ const [week, year] = normalizedValue.split("/");
7941
+ return _t("W%(week)s %(year)s", { week, year });
7942
+ },
7943
+ toCellValue(normalizedValue) {
7944
+ return this.formatValue(normalizedValue);
7945
+ },
7946
+ };
7947
+ /**
7948
+ * normalizes iso week number
7949
+ */
7950
+ const isoWeekNumberAdapter = {
7951
+ normalizeFunctionValue(value) {
7952
+ const isoWeek = toNumber(value, DEFAULT_LOCALE);
7953
+ if (isoWeek < 0 || isoWeek > 53) {
7954
+ throw new EvaluationError(_t("%s is not a valid week (it should be a number between 0 and 53)", isoWeek));
7955
+ }
7956
+ return isoWeek;
7957
+ },
7958
+ getFormat() {
7959
+ return "0";
7960
+ },
7961
+ formatValue(normalizedValue, locale) {
7962
+ locale = locale ?? DEFAULT_LOCALE;
7963
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7964
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7965
+ },
7966
+ toCellValue(normalizedValue) {
7967
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7968
+ },
7969
+ };
7970
+ /**
7971
+ * normalized month value is a string formatted as "MM/yyyy" (luxon format)
7972
+ * e.g. "01/2020" for January 2020
7973
+ */
7974
+ const monthAdapter = {
7975
+ normalizeFunctionValue(value) {
7976
+ const date = toNumber(value, DEFAULT_LOCALE);
7977
+ return formatValue(date, { locale: DEFAULT_LOCALE, format: "mm/yyyy" });
7978
+ },
7979
+ getFormat() {
7980
+ return "mmmm yyyy";
7981
+ },
7982
+ formatValue(normalizedValue, locale) {
7983
+ locale = locale ?? DEFAULT_LOCALE;
7984
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
7985
+ return formatValue(value, { locale, format: this.getFormat(locale) });
7986
+ },
7987
+ toCellValue(normalizedValue) {
7988
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
7989
+ },
7990
+ };
7991
+ /**
7992
+ * normalizes month number
7993
+ */
7994
+ const monthNumberAdapter = {
7995
+ normalizeFunctionValue(value) {
7996
+ const month = toNumber(value, DEFAULT_LOCALE);
7997
+ if (month < 1 || month > 12) {
7998
+ throw new EvaluationError(_t("%s is not a valid month (it should be a number between 1 and 12)", month));
7999
+ }
8000
+ return month;
8001
+ },
8002
+ getFormat() {
8003
+ return "0";
8004
+ },
8005
+ formatValue(normalizedValue, locale) {
8006
+ locale = locale ?? DEFAULT_LOCALE;
8007
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
8008
+ return formatValue(value, { locale, format: this.getFormat(locale) });
8009
+ },
8010
+ toCellValue(normalizedValue) {
8011
+ return MONTHS[toNumber(normalizedValue, DEFAULT_LOCALE) - 1].toString();
8012
+ },
8013
+ };
8014
+ /**
8015
+ * normalized quarter value is "quarter/year"
8016
+ * e.g. "1/2020" for Q1 2020
8017
+ */
8018
+ const quarterAdapter = {
8019
+ normalizeFunctionValue(value) {
8020
+ const [quarter, year] = value.split("/");
8021
+ return `${quarter}/${year}`;
8022
+ },
8023
+ getFormat() {
8024
+ return undefined;
8025
+ },
8026
+ formatValue(normalizedValue) {
8027
+ const [quarter, year] = normalizedValue.split("/");
8028
+ return _t("Q%(quarter)s %(year)s", { quarter, year });
8029
+ },
8030
+ toCellValue(normalizedValue) {
8031
+ return this.formatValue(normalizedValue);
8032
+ },
8033
+ };
8034
+ /**
8035
+ * normalizes quarter number
8036
+ */
8037
+ const quarterNumberAdapter = {
8038
+ normalizeFunctionValue(value) {
8039
+ const quarter = toNumber(value, DEFAULT_LOCALE);
8040
+ if (quarter < 1 || quarter > 4) {
8041
+ throw new EvaluationError(_t("%s is not a valid quarter (it should be a number between 1 and 4)", quarter));
8042
+ }
8043
+ return quarter;
8044
+ },
8045
+ getFormat() {
8046
+ return "0";
8047
+ },
8048
+ formatValue(normalizedValue, locale) {
8049
+ locale = locale ?? DEFAULT_LOCALE;
8050
+ const value = toNumber(normalizedValue, DEFAULT_LOCALE);
8051
+ return formatValue(value, { locale, format: this.getFormat(locale) });
8052
+ },
8053
+ toCellValue(normalizedValue) {
8054
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
8055
+ },
8056
+ };
8057
+ const yearAdapter = {
8058
+ normalizeFunctionValue(value) {
8059
+ return toNumber(value, DEFAULT_LOCALE);
8060
+ },
8061
+ getFormat() {
8062
+ return "0";
8063
+ },
8064
+ formatValue(normalizedValue, locale) {
8065
+ locale = locale ?? DEFAULT_LOCALE;
8066
+ return formatValue(normalizedValue, { locale, format: "0" });
8067
+ },
8068
+ toCellValue(normalizedValue) {
8069
+ return toNumber(normalizedValue, DEFAULT_LOCALE);
8070
+ },
8071
+ };
8072
+ pivotTimeAdapterRegistry
8073
+ .add("day", dayAdapter)
8074
+ .add("week", weekAdapter)
8075
+ .add("month", monthAdapter)
8076
+ .add("quarter", quarterAdapter)
8077
+ .add("year", yearAdapter)
8078
+ .add("day_of_month", dayOfMonthAdapter)
8079
+ .add("iso_week_number", isoWeekNumberAdapter)
8080
+ .add("month_number", monthNumberAdapter)
8081
+ .add("quarter_number", quarterNumberAdapter)
8082
+ .add("year_number", yearAdapter);
8083
+
8084
+ const AGGREGATOR_NAMES = {
8085
+ count: _t("Count"),
8086
+ count_distinct: _t("Count Distinct"),
8087
+ bool_and: _t("Boolean And"),
8088
+ bool_or: _t("Boolean Or"),
8089
+ max: _t("Maximum"),
8090
+ min: _t("Minimum"),
8091
+ avg: _t("Average"),
8092
+ sum: _t("Sum"),
8093
+ };
8094
+ const NUMBER_CHAR_AGGREGATORS = ["max", "min", "avg", "sum", "count_distinct", "count"];
8095
+ const AGGREGATORS_BY_FIELD_TYPE = {
8096
+ integer: NUMBER_CHAR_AGGREGATORS,
8097
+ char: NUMBER_CHAR_AGGREGATORS,
8098
+ boolean: ["count_distinct", "count", "bool_and", "bool_or"],
8099
+ };
8100
+ const AGGREGATORS = {};
8101
+ for (const type in AGGREGATORS_BY_FIELD_TYPE) {
8102
+ AGGREGATORS[type] = {};
8103
+ for (const aggregator of AGGREGATORS_BY_FIELD_TYPE[type]) {
8104
+ AGGREGATORS[type][aggregator] = AGGREGATOR_NAMES[aggregator];
8105
+ }
8106
+ }
8107
+ const AGGREGATORS_FN = {
8108
+ count: {
8109
+ fn: (args) => countAny([args]),
8110
+ format: () => "0",
8111
+ },
8112
+ count_distinct: {
8113
+ fn: (args) => countUnique([args]),
8114
+ format: () => "0",
8115
+ },
8116
+ bool_and: {
8117
+ fn: (args) => boolAnd([args]).result,
8118
+ format: () => undefined,
8119
+ },
8120
+ bool_or: {
8121
+ fn: (args) => boolOr([args]).result,
8122
+ format: () => undefined,
8123
+ },
8124
+ max: {
8125
+ fn: (args, locale) => max([args], locale),
8126
+ format: inferFormat,
8127
+ },
8128
+ min: {
8129
+ fn: (args, locale) => min([args], locale),
8130
+ format: inferFormat,
8131
+ },
8132
+ avg: {
8133
+ fn: (args, locale) => average([args], locale),
8134
+ format: inferFormat,
8135
+ },
8136
+ sum: {
8137
+ fn: (args, locale) => sum([args], locale),
8138
+ format: inferFormat,
8139
+ },
8140
+ };
8141
+ function makePivotFormulaFromPivotCell(pivotFormulaId, pivotCell) {
8142
+ switch (pivotCell.type) {
8143
+ case "HEADER":
8144
+ return makePivotFormula("PIVOT.HEADER", [pivotFormulaId, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
8145
+ case "MEASURE_HEADER":
8146
+ return makePivotFormula("PIVOT.HEADER", [pivotFormulaId, ...flatPivotDomain(pivotCell.domain), "measure", pivotCell.measure].filter(isDefined));
8147
+ case "VALUE":
8148
+ return makePivotFormula("PIVOT.VALUE", [pivotFormulaId, pivotCell.measure, ...flatPivotDomain(pivotCell.domain)].filter(isDefined));
8149
+ case "EMPTY":
8150
+ return "";
8151
+ }
8152
+ }
8153
+ /**
8154
+ * Build a pivot formula expression
8155
+ */
8156
+ function makePivotFormula(formula, args) {
8157
+ return `=${formula}(${args
8158
+ .map((arg) => {
8159
+ const stringIsNumber = typeof arg == "string" && !isNaN(Number(arg)) && Number(arg).toString() === arg;
8160
+ const convertToNumber = typeof arg == "number" || stringIsNumber;
8161
+ return convertToNumber ? `${arg}` : `"${arg.toString().replace(/"/g, '\\"')}"`;
8162
+ })
8163
+ .join(",")})`;
8164
+ }
8165
+ /**
8166
+ * Given an object of form {"1": {...}, "2": {...}, ...} get the maximum ID used
8167
+ * in this object
8168
+ * If the object has no keys, return 0
8169
+ *
8170
+ */
8171
+ function getMaxObjectId(o) {
8172
+ const keys = Object.keys(o);
8173
+ if (!keys.length) {
8174
+ return 0;
8175
+ }
8176
+ const nums = keys.map((id) => parseInt(id, 10));
8177
+ const max = Math.max(...nums);
8178
+ return max;
8179
+ }
8180
+ const ALL_PERIODS = {
8181
+ year: _t("Year"),
8182
+ quarter: _t("Quarter"),
8183
+ month: _t("Month"),
8184
+ week: _t("Week"),
8185
+ day: _t("Day"),
8186
+ year_number: _t("Year"),
8187
+ quarter_number: _t("Quarter"),
8188
+ month_number: _t("Month"),
8189
+ iso_week_number: _t("Week"),
8190
+ day_of_month: _t("Day of Month"),
8191
+ };
8192
+ const DATE_FIELDS = ["date", "datetime"];
8193
+ /**
8194
+ * Parse a dimension string into a pivot dimension definition.
8195
+ * e.g "create_date:month" => { name: "create_date", granularity: "month" }
8196
+ */
8197
+ function parseDimension(dimension) {
8198
+ const [name, granularity] = dimension.split(":");
8199
+ if (granularity) {
8200
+ return { name, granularity };
8201
+ }
8202
+ return { name };
8203
+ }
8204
+ function isDateField(field) {
8205
+ return DATE_FIELDS.includes(field.type);
8206
+ }
8207
+ function toPivotDomain(domainStr) {
8208
+ if (domainStr.length % 2 !== 0) {
8209
+ throw new Error("Invalid domain: odd number of elements");
8210
+ }
8211
+ const domain = [];
8212
+ for (let i = 0; i < domainStr.length - 1; i += 2) {
8213
+ domain.push({ field: domainStr[i], value: domainStr[i + 1] });
8214
+ }
8215
+ return domain;
8216
+ }
8217
+ function flatPivotDomain(domain) {
8218
+ return domain.flatMap((arg) => [arg.field, arg.value]);
8219
+ }
8220
+ /**
8221
+ * Parses the value defining a pivot group in a PIVOT formula
8222
+ * e.g. given the following formula PIVOT.VALUE("1", "stage_id", "42", "status", "won"),
8223
+ * the two group values are "42" and "won".
8224
+ */
8225
+ function toNormalizedPivotValue(dimension, groupValue) {
8226
+ if (groupValue === null || groupValue === "null") {
8227
+ return null;
8228
+ }
8229
+ const groupValueString = typeof groupValue === "boolean"
8230
+ ? toString(groupValue).toLocaleLowerCase()
8231
+ : toString(groupValue);
8232
+ if (!pivotNormalizationValueRegistry.contains(dimension.type)) {
8233
+ throw new EvaluationError(_t("Field %(field)s is not supported because of its type (%(type)s)", {
8234
+ field: dimension.displayName,
8235
+ type: dimension.type,
8236
+ }));
8237
+ }
8238
+ // represents a field which is not set (=False server side)
8239
+ if (groupValueString === "false") {
8240
+ return false;
8241
+ }
8242
+ const normalizer = pivotNormalizationValueRegistry.get(dimension.type);
8243
+ return normalizer(groupValueString, dimension.granularity);
8244
+ }
8245
+ function normalizeDateTime(value, granularity) {
8246
+ if (!granularity) {
8247
+ throw "";
8248
+ }
8249
+ return pivotTimeAdapter(granularity).normalizeFunctionValue(value);
8250
+ }
8251
+ const pivotNormalizationValueRegistry = new Registry();
8252
+ pivotNormalizationValueRegistry
8253
+ .add("date", normalizeDateTime)
8254
+ .add("datetime", normalizeDateTime)
8255
+ .add("integer", (value) => toNumber(value, DEFAULT_LOCALE))
8256
+ .add("boolean", (value) => toBoolean(value))
8257
+ .add("char", (value) => toString(value));
8258
+
7759
8259
  /**
7760
8260
  * Change the reference types inside the given token, if the token represent a range or a cell
7761
8261
  *
@@ -7954,6 +8454,11 @@
7954
8454
  const ChartTerms = {
7955
8455
  Series: _t("Series"),
7956
8456
  BackgroundColor: _t("Background color"),
8457
+ StackedBarChart: _t("Stacked bar chart"),
8458
+ StackedLineChart: _t("Stacked line chart"),
8459
+ CumulativeData: _t("Cumulative data"),
8460
+ TreatLabelsAsText: _t("Treat labels as text"),
8461
+ AggregatedChart: _t("Aggregate"),
7957
8462
  Errors: {
7958
8463
  Unexpected: _t("The chart definition is invalid for an unknown reason"),
7959
8464
  // BASIC CHART ERRORS (LINE | BAR | PIE)
@@ -8886,6 +9391,7 @@ stores.inject(MyMetaStore, storeInstance);
8886
9391
  }
8887
9392
  const color = highlight.color || HIGHLIGHT_COLOR;
8888
9393
  const { ctx } = renderingContext;
9394
+ ctx.save();
8889
9395
  if (!highlight.noBorder) {
8890
9396
  if (highlight.dashed) {
8891
9397
  ctx.setLineDash([5, 3]);
@@ -8905,6 +9411,7 @@ stores.inject(MyMetaStore, storeInstance);
8905
9411
  ctx.fillStyle = setColorAlpha(toHex(color), highlight.fillAlpha ?? 0.12);
8906
9412
  ctx.fillRect(x, y, width, height);
8907
9413
  }
9414
+ ctx.restore();
8908
9415
  }
8909
9416
 
8910
9417
  class HighlightStore extends SpreadsheetStore {
@@ -9435,8 +9942,20 @@ stores.inject(MyMetaStore, storeInstance);
9435
9942
  replaceSelectedRange(zone) {
9436
9943
  const ref = this.getZoneReference(zone);
9437
9944
  const currentToken = this.tokenAtCursor;
9438
- const start = currentToken?.type === "REFERENCE" ? currentToken.start : this.selectionStart;
9439
- this.replaceText(ref, start, this.selectionEnd);
9945
+ let replaceStart = this.selectionStart;
9946
+ if (currentToken?.type === "REFERENCE") {
9947
+ replaceStart = currentToken.start;
9948
+ }
9949
+ else if (currentToken?.type === "RIGHT_PAREN") {
9950
+ // match left parenthesis
9951
+ const leftParenthesisIndex = this.currentTokens.findIndex((token) => token.type === "LEFT_PAREN" && token.parenIndex === currentToken.parenIndex);
9952
+ const functionToken = this.currentTokens[leftParenthesisIndex - 1];
9953
+ if (functionToken === undefined) {
9954
+ return;
9955
+ }
9956
+ replaceStart = functionToken.start;
9957
+ }
9958
+ this.replaceText(ref, replaceStart, this.selectionEnd);
9440
9959
  }
9441
9960
  /**
9442
9961
  * Replace the reference of the old zone by the new one.
@@ -9456,10 +9975,8 @@ stores.inject(MyMetaStore, storeInstance);
9456
9975
  const refRange = this.getters.getRangeFromSheetXC(activeSheetId, xc);
9457
9976
  return isEqual(this.getters.expandZone(activeSheetId, refRange.zone), oldZone);
9458
9977
  });
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
9978
  if (!previousRefToken) {
9462
- throw new Error("Previous range not found");
9979
+ return;
9463
9980
  }
9464
9981
  const previousRange = this.getters.getRangeFromSheetXC(activeSheetId, previousRefToken.value);
9465
9982
  this.selectionStart = previousRefToken.start;
@@ -9471,6 +9988,17 @@ stores.inject(MyMetaStore, storeInstance);
9471
9988
  getZoneReference(zone) {
9472
9989
  const inputSheetId = this.currentEditedCell.sheetId;
9473
9990
  const sheetId = this.getters.getActiveSheetId();
9991
+ if (zone.top === zone.bottom && zone.left === zone.right) {
9992
+ const position = { sheetId, col: zone.left, row: zone.top };
9993
+ const pivotId = this.getters.getPivotIdFromPosition(position);
9994
+ const pivotCell = this.getters.getPivotCellFromPosition(position);
9995
+ const cell = this.getters.getCell(position);
9996
+ if (pivotId && pivotCell.type !== "EMPTY" && !cell?.isFormula) {
9997
+ const formulaPivotId = this.getters.getPivotFormulaId(pivotId);
9998
+ const formula = makePivotFormulaFromPivotCell(formulaPivotId, pivotCell);
9999
+ return formula.slice(1); // strip leading =
10000
+ }
10001
+ }
9474
10002
  const range = this.getters.getRangeFromZone(sheetId, zone);
9475
10003
  return this.getters.getSelectionRangeString(range, inputSheetId);
9476
10004
  }
@@ -9573,19 +10101,35 @@ stores.inject(MyMetaStore, storeInstance);
9573
10101
  const colorIndex = this.colorIndexByRange[rangeString];
9574
10102
  return colors$1[colorIndex % colors$1.length];
9575
10103
  };
9576
- return this.getReferencedRanges().map((range) => {
10104
+ const highlights = [];
10105
+ for (const range of this.getReferencedRanges()) {
9577
10106
  const rangeString = this.getters.getRangeString(range, editionSheetId);
9578
10107
  const { numberOfRows, numberOfCols } = zoneToDimension(range.zone);
9579
10108
  const zone = numberOfRows * numberOfCols === 1
9580
10109
  ? this.getters.expandZone(range.sheetId, range.zone)
9581
10110
  : range.zone;
9582
- return {
10111
+ highlights.push({
9583
10112
  zone,
9584
10113
  color: rangeColor(rangeString),
9585
10114
  sheetId: range.sheetId,
9586
10115
  interactive: true,
9587
- };
9588
- });
10116
+ });
10117
+ }
10118
+ const activeSheetId = this.getters.getActiveSheetId();
10119
+ const selectionZone = this.model.selection.getAnchor().zone;
10120
+ const isSelectionHightlighted = highlights.find((highlight) => highlight.sheetId === activeSheetId && isEqual(highlight.zone, selectionZone));
10121
+ if (this.editionMode === "selecting" && !isSelectionHightlighted) {
10122
+ highlights.push({
10123
+ zone: selectionZone,
10124
+ color: "#445566",
10125
+ sheetId: activeSheetId,
10126
+ dashed: true,
10127
+ interactive: false,
10128
+ noFill: true,
10129
+ thinLine: true,
10130
+ });
10131
+ }
10132
+ return highlights;
9589
10133
  }
9590
10134
  /**
9591
10135
  * Return ranges currently referenced in the composer
@@ -9844,8 +10388,14 @@ stores.inject(MyMetaStore, storeInstance);
9844
10388
  owl.useEffect(() => {
9845
10389
  const runtime = this.chartRuntime;
9846
10390
  if (!deepEquals(runtime, this.currentRuntime, "ignoreFunctions")) {
10391
+ if (runtime.chartJsConfig.type !== this.currentRuntime.chartJsConfig.type) {
10392
+ this.chart?.destroy();
10393
+ this.createChart(deepCopy(runtime.chartJsConfig));
10394
+ }
10395
+ else {
10396
+ this.updateChartJs(deepCopy(runtime));
10397
+ }
9847
10398
  this.currentRuntime = runtime;
9848
- this.updateChartJs(deepCopy(runtime));
9849
10399
  }
9850
10400
  });
9851
10401
  }
@@ -10213,6 +10763,9 @@ stores.inject(MyMetaStore, storeInstance);
10213
10763
  }
10214
10764
  function getDefinedAxis(definition) {
10215
10765
  let useLeftAxis = false, useRightAxis = false;
10766
+ if ("horizontal" in definition && definition.horizontal) {
10767
+ return { useLeftAxis: true, useRightAxis: false };
10768
+ }
10216
10769
  for (const design of definition.dataSets || []) {
10217
10770
  if (design.yAxisId === "y1") {
10218
10771
  useRightAxis = true;
@@ -11780,13 +12333,6 @@ stores.inject(MyMetaStore, storeInstance);
11780
12333
  FORMAT_LARGE_NUMBER: FORMAT_LARGE_NUMBER
11781
12334
  });
11782
12335
 
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
12336
  const DEFAULT_FACTOR = 1;
11791
12337
  const DEFAULT_MODE = 0;
11792
12338
  const DEFAULT_PLACES = 0;
@@ -12900,53 +13446,6 @@ stores.inject(MyMetaStore, storeInstance);
12900
13446
  TRUNC: TRUNC
12901
13447
  });
12902
13448
 
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
13449
  function filterAndFlatData(dataY, dataX) {
12951
13450
  const _flatDataY = [];
12952
13451
  const _flatDataX = [];
@@ -13502,7 +14001,7 @@ stores.inject(MyMetaStore, storeInstance);
13502
14001
  // LINEST
13503
14002
  // -----------------------------------------------------------------------------
13504
14003
  const LINEST = {
13505
- description: _t("Compute the intercept of the linear regression."),
14004
+ description: _t("Given partial data about a linear trend, calculates various parameters about the ideal linear trend using the least-squares method."),
13506
14005
  args: [
13507
14006
  arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
13508
14007
  arg("data_x (range<number>, default={1;2;3;...})", _t("The range representing the array or matrix of independent data.")),
@@ -13518,7 +14017,7 @@ stores.inject(MyMetaStore, storeInstance);
13518
14017
  // LOGEST
13519
14018
  // -----------------------------------------------------------------------------
13520
14019
  const LOGEST = {
13521
- description: _t("Compute the intercept of the linear regression."),
14020
+ description: _t("Given partial data about an exponential growth curve, calculates various parameters about the best fit ideal exponential growth curve."),
13522
14021
  args: [
13523
14022
  arg("data_y (range<number>)", _t("The range representing the array or matrix of dependent data.")),
13524
14023
  arg("data_x (range<number>, optional, default={1;2;3;...})", _t("The range representing the array or matrix of independent data.")),
@@ -17921,33 +18420,6 @@ stores.inject(MyMetaStore, storeInstance);
17921
18420
  NA: NA
17922
18421
  });
17923
18422
 
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
18423
  // -----------------------------------------------------------------------------
17952
18424
  // AND
17953
18425
  // -----------------------------------------------------------------------------
@@ -18145,393 +18617,6 @@ stores.inject(MyMetaStore, storeInstance);
18145
18617
  XOR: XOR
18146
18618
  });
18147
18619
 
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
18620
  /**
18536
18621
  * Get the pivot ID from the formula pivot ID.
18537
18622
  */
@@ -19108,14 +19193,10 @@ stores.inject(MyMetaStore, storeInstance);
19108
19193
  result[col].push({ value: "" });
19109
19194
  break;
19110
19195
  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
- }
19196
+ result[col].push(pivot.getPivotHeaderValueAndFormat(pivotCell.domain));
19197
+ break;
19198
+ case "MEASURE_HEADER":
19199
+ result[col].push(pivot.getPivotMeasureValue(pivotCell.measure, pivotCell.domain));
19119
19200
  break;
19120
19201
  case "VALUE":
19121
19202
  result[col].push(pivot.getPivotCellValueAndFormat(pivotCell.measure, pivotCell.domain));
@@ -20154,6 +20235,8 @@ stores.inject(MyMetaStore, storeInstance);
20154
20235
  * a child element.
20155
20236
  */
20156
20237
  function isChildEvent(parent, ev) {
20238
+ if (!parent)
20239
+ return false;
20157
20240
  return !!ev.target && parent.contains(ev.target);
20158
20241
  }
20159
20242
  function gridOverlayPosition() {
@@ -22693,7 +22776,8 @@ stores.inject(MyMetaStore, storeInstance);
22693
22776
  /**
22694
22777
  * Get a default chart js configuration
22695
22778
  */
22696
- function getDefaultChartJsRuntime(chart, labels, fontColor, { format, locale, truncateLabels }) {
22779
+ function getDefaultChartJsRuntime(chart, labels, fontColor, args) {
22780
+ const { format, locale, truncateLabels, horizontalChart } = args;
22697
22781
  const chartTitle = chart.title.text ? chart.title : { ...chart.title, content: "" };
22698
22782
  const options = {
22699
22783
  // https://www.chartjs.org/docs/latest/general/responsive.html
@@ -22737,8 +22821,11 @@ stores.inject(MyMetaStore, storeInstance);
22737
22821
  callbacks: {
22738
22822
  label: function (tooltipItem) {
22739
22823
  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;
22824
+ // tooltipItem.parsed can be an object or a number for pie charts
22825
+ let yLabel = horizontalChart ? tooltipItem.parsed.x : tooltipItem.parsed.y;
22826
+ if (!yLabel) {
22827
+ yLabel = tooltipItem.parsed;
22828
+ }
22742
22829
  const toolTipFormat = !format && Math.abs(yLabel) >= 1000 ? "#,##" : format;
22743
22830
  const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
22744
22831
  return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
@@ -22912,6 +22999,7 @@ stores.inject(MyMetaStore, storeInstance);
22912
22999
  dataSetsHaveTitle;
22913
23000
  dataSetDesign;
22914
23001
  axesDesign;
23002
+ horizontal;
22915
23003
  constructor(definition, sheetId, getters) {
22916
23004
  super(definition, sheetId, getters);
22917
23005
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -22923,6 +23011,7 @@ stores.inject(MyMetaStore, storeInstance);
22923
23011
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
22924
23012
  this.dataSetDesign = definition.dataSets;
22925
23013
  this.axesDesign = definition.axesDesign;
23014
+ this.horizontal = definition.horizontal;
22926
23015
  }
22927
23016
  static transformDefinition(definition, executed) {
22928
23017
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -22994,6 +23083,7 @@ stores.inject(MyMetaStore, storeInstance);
22994
23083
  stacked: this.stacked,
22995
23084
  aggregated: this.aggregated,
22996
23085
  axesDesign: this.axesDesign,
23086
+ horizontal: this.horizontal,
22997
23087
  };
22998
23088
  }
22999
23089
  getDefinitionForExcel() {
@@ -23025,7 +23115,10 @@ stores.inject(MyMetaStore, storeInstance);
23025
23115
  }
23026
23116
  function getBarConfiguration(chart, labels, localeFormat) {
23027
23117
  const fontColor = chartFontColor(chart.background);
23028
- const config = getDefaultChartJsRuntime(chart, labels, fontColor, localeFormat);
23118
+ const config = getDefaultChartJsRuntime(chart, labels, fontColor, {
23119
+ ...localeFormat,
23120
+ horizontalChart: chart.horizontal,
23121
+ });
23029
23122
  const legend = {
23030
23123
  labels: { color: fontColor },
23031
23124
  };
@@ -23039,16 +23132,10 @@ stores.inject(MyMetaStore, storeInstance);
23039
23132
  config.options.layout = {
23040
23133
  padding: { left: 20, right: 20, top: chart.title ? 10 : 25, bottom: 10 },
23041
23134
  };
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 = {
23135
+ config.options.indexAxis = chart.horizontal ? "y" : "x";
23136
+ config.options.scales = {};
23137
+ const labelsAxis = { ticks: { padding: 5, color: fontColor } };
23138
+ const valuesAxis = {
23052
23139
  beginAtZero: true, // the origin of the y axis is always zero
23053
23140
  ticks: {
23054
23141
  color: fontColor,
@@ -23064,7 +23151,10 @@ stores.inject(MyMetaStore, storeInstance);
23064
23151
  },
23065
23152
  },
23066
23153
  };
23154
+ const xAxis = chart.horizontal ? valuesAxis : labelsAxis;
23155
+ const yAxis = chart.horizontal ? labelsAxis : valuesAxis;
23067
23156
  const { useLeftAxis, useRightAxis } = getDefinedAxis(chart.getDefinition());
23157
+ config.options.scales.x = { ...xAxis, title: getChartAxisTitleRuntime(chart.axesDesign?.x) };
23068
23158
  if (useLeftAxis) {
23069
23159
  config.options.scales.y = {
23070
23160
  ...yAxis,
@@ -23131,7 +23221,7 @@ stores.inject(MyMetaStore, storeInstance);
23131
23221
  const label = definition.dataSets[index].label;
23132
23222
  dataset.label = label;
23133
23223
  }
23134
- if (definition.dataSets?.[index]?.yAxisId) {
23224
+ if (definition.dataSets?.[index]?.yAxisId && !chart.horizontal) {
23135
23225
  dataset["yAxisID"] = definition.dataSets[index].yAxisId;
23136
23226
  }
23137
23227
  }
@@ -24122,6 +24212,7 @@ stores.inject(MyMetaStore, storeInstance);
24122
24212
  type = "pie";
24123
24213
  aggregated;
24124
24214
  dataSetsHaveTitle;
24215
+ isDoughnut;
24125
24216
  constructor(definition, sheetId, getters) {
24126
24217
  super(definition, sheetId, getters);
24127
24218
  this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
@@ -24130,6 +24221,7 @@ stores.inject(MyMetaStore, storeInstance);
24130
24221
  this.legendPosition = definition.legendPosition;
24131
24222
  this.aggregated = definition.aggregated;
24132
24223
  this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24224
+ this.isDoughnut = definition.isDoughnut;
24133
24225
  }
24134
24226
  static transformDefinition(definition, executed) {
24135
24227
  return transformChartDefinitionWithDataSetsWithZone(definition, executed);
@@ -24147,6 +24239,7 @@ stores.inject(MyMetaStore, storeInstance);
24147
24239
  type: "pie",
24148
24240
  labelRange: context.auxiliaryRange || undefined,
24149
24241
  aggregated: context.aggregated ?? false,
24242
+ isDoughnut: false,
24150
24243
  };
24151
24244
  }
24152
24245
  getDefinition() {
@@ -24177,6 +24270,7 @@ stores.inject(MyMetaStore, storeInstance);
24177
24270
  : undefined,
24178
24271
  title: this.title,
24179
24272
  aggregated: this.aggregated,
24273
+ isDoughnut: this.isDoughnut,
24180
24274
  };
24181
24275
  }
24182
24276
  copyForSheetId(sheetId) {
@@ -24311,6 +24405,141 @@ stores.inject(MyMetaStore, storeInstance);
24311
24405
  };
24312
24406
  config.data.datasets.push(dataset);
24313
24407
  }
24408
+ if (chart.isDoughnut) {
24409
+ config.type = "doughnut";
24410
+ }
24411
+ return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
24412
+ }
24413
+
24414
+ class PyramidChart extends AbstractChart {
24415
+ dataSets;
24416
+ labelRange;
24417
+ background;
24418
+ legendPosition;
24419
+ aggregated;
24420
+ type = "pyramid";
24421
+ dataSetsHaveTitle;
24422
+ dataSetDesign;
24423
+ axesDesign;
24424
+ horizontal = true;
24425
+ stacked = true;
24426
+ constructor(definition, sheetId, getters) {
24427
+ super(definition, sheetId, getters);
24428
+ this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle).slice(0, 2);
24429
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
24430
+ this.background = definition.background;
24431
+ this.legendPosition = definition.legendPosition;
24432
+ this.aggregated = definition.aggregated;
24433
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
24434
+ this.dataSetDesign = definition.dataSets;
24435
+ this.axesDesign = definition.axesDesign;
24436
+ }
24437
+ static transformDefinition(definition, executed) {
24438
+ return transformChartDefinitionWithDataSetsWithZone(definition, executed);
24439
+ }
24440
+ static validateChartDefinition(validator, definition) {
24441
+ return validator.checkValidations(definition, checkDataset, checkLabelRange);
24442
+ }
24443
+ static getDefinitionFromContextCreation(context) {
24444
+ return {
24445
+ background: context.background,
24446
+ dataSets: context.range ?? [],
24447
+ dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
24448
+ aggregated: context.aggregated ?? false,
24449
+ legendPosition: context.legendPosition ?? "top",
24450
+ title: context.title || { text: "" },
24451
+ type: "pyramid",
24452
+ labelRange: context.auxiliaryRange || undefined,
24453
+ axesDesign: context.axesDesign,
24454
+ horizontal: true,
24455
+ stacked: true,
24456
+ };
24457
+ }
24458
+ getContextCreation() {
24459
+ const range = [];
24460
+ for (const [i, dataSet] of this.dataSets.entries()) {
24461
+ range.push({
24462
+ ...this.dataSetDesign?.[i],
24463
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
24464
+ });
24465
+ }
24466
+ return {
24467
+ ...this,
24468
+ range,
24469
+ auxiliaryRange: this.labelRange
24470
+ ? this.getters.getRangeString(this.labelRange, this.sheetId)
24471
+ : undefined,
24472
+ };
24473
+ }
24474
+ copyForSheetId(sheetId) {
24475
+ const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
24476
+ const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
24477
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
24478
+ return new PyramidChart(definition, sheetId, this.getters);
24479
+ }
24480
+ copyInSheetId(sheetId) {
24481
+ const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
24482
+ return new PyramidChart(definition, sheetId, this.getters);
24483
+ }
24484
+ getDefinition() {
24485
+ return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
24486
+ }
24487
+ getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
24488
+ const ranges = [];
24489
+ for (const [i, dataSet] of dataSets.entries()) {
24490
+ ranges.push({
24491
+ ...this.dataSetDesign?.[i],
24492
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
24493
+ });
24494
+ }
24495
+ return {
24496
+ type: "pyramid",
24497
+ dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
24498
+ background: this.background,
24499
+ dataSets: ranges,
24500
+ legendPosition: this.legendPosition,
24501
+ labelRange: labelRange
24502
+ ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
24503
+ : undefined,
24504
+ title: this.title,
24505
+ aggregated: this.aggregated,
24506
+ axesDesign: this.axesDesign,
24507
+ horizontal: true,
24508
+ stacked: true,
24509
+ };
24510
+ }
24511
+ getDefinitionForExcel() {
24512
+ return undefined;
24513
+ }
24514
+ updateRanges(applyChange) {
24515
+ const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
24516
+ if (!isStale) {
24517
+ return this;
24518
+ }
24519
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
24520
+ return new PyramidChart(definition, this.sheetId, this.getters);
24521
+ }
24522
+ }
24523
+ function createPyramidChartRuntime(chart, getters) {
24524
+ const barDef = { ...chart.getDefinition(), type: "bar" };
24525
+ const barChart = new BarChart(barDef, chart.sheetId, getters);
24526
+ const barRuntime = createBarChartRuntime(barChart, getters);
24527
+ const config = barRuntime.chartJsConfig;
24528
+ let datasets = config.data?.datasets;
24529
+ if (datasets && datasets[0]) {
24530
+ datasets[0].data = datasets[0].data.map((value) => (value > 0 ? value : 0));
24531
+ }
24532
+ if (datasets && datasets[1]) {
24533
+ datasets[1].data = datasets[1].data.map((value) => (value > 0 ? -value : 0));
24534
+ }
24535
+ const scales = config.options.scales;
24536
+ const scalesXCallback = scales.x.ticks.callback;
24537
+ scales.x.ticks.callback = (value) => scalesXCallback(Math.abs(value));
24538
+ const tooltipLabelCallback = config.options.plugins.tooltip.callbacks.label;
24539
+ config.options.plugins.tooltip.callbacks.label = (item) => {
24540
+ const tooltipItem = { ...item, parsed: { y: item.parsed.y, x: Math.abs(item.parsed.x) } };
24541
+ return tooltipLabelCallback(tooltipItem);
24542
+ };
24314
24543
  return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
24315
24544
  }
24316
24545
 
@@ -24753,7 +24982,6 @@ stores.inject(MyMetaStore, storeInstance);
24753
24982
  validateChartDefinition: BarChart.validateChartDefinition,
24754
24983
  transformDefinition: BarChart.transformDefinition,
24755
24984
  getChartDefinitionFromContextCreation: BarChart.getDefinitionFromContextCreation,
24756
- name: _t("Bar"),
24757
24985
  sequence: 10,
24758
24986
  });
24759
24987
  chartRegistry.add("combo", {
@@ -24763,7 +24991,6 @@ stores.inject(MyMetaStore, storeInstance);
24763
24991
  validateChartDefinition: ComboChart.validateChartDefinition,
24764
24992
  transformDefinition: ComboChart.transformDefinition,
24765
24993
  getChartDefinitionFromContextCreation: ComboChart.getDefinitionFromContextCreation,
24766
- name: _t("Combo"),
24767
24994
  sequence: 15,
24768
24995
  });
24769
24996
  chartRegistry.add("line", {
@@ -24773,7 +25000,6 @@ stores.inject(MyMetaStore, storeInstance);
24773
25000
  validateChartDefinition: LineChart.validateChartDefinition,
24774
25001
  transformDefinition: LineChart.transformDefinition,
24775
25002
  getChartDefinitionFromContextCreation: LineChart.getDefinitionFromContextCreation,
24776
- name: _t("Line"),
24777
25003
  sequence: 20,
24778
25004
  });
24779
25005
  chartRegistry.add("pie", {
@@ -24783,7 +25009,6 @@ stores.inject(MyMetaStore, storeInstance);
24783
25009
  validateChartDefinition: PieChart.validateChartDefinition,
24784
25010
  transformDefinition: PieChart.transformDefinition,
24785
25011
  getChartDefinitionFromContextCreation: PieChart.getDefinitionFromContextCreation,
24786
- name: _t("Pie"),
24787
25012
  sequence: 30,
24788
25013
  });
24789
25014
  chartRegistry.add("scorecard", {
@@ -24793,7 +25018,6 @@ stores.inject(MyMetaStore, storeInstance);
24793
25018
  validateChartDefinition: ScorecardChart$1.validateChartDefinition,
24794
25019
  transformDefinition: ScorecardChart$1.transformDefinition,
24795
25020
  getChartDefinitionFromContextCreation: ScorecardChart$1.getDefinitionFromContextCreation,
24796
- name: _t("Scorecard"),
24797
25021
  sequence: 40,
24798
25022
  });
24799
25023
  chartRegistry.add("gauge", {
@@ -24803,7 +25027,6 @@ stores.inject(MyMetaStore, storeInstance);
24803
25027
  validateChartDefinition: GaugeChart.validateChartDefinition,
24804
25028
  transformDefinition: GaugeChart.transformDefinition,
24805
25029
  getChartDefinitionFromContextCreation: GaugeChart.getDefinitionFromContextCreation,
24806
- name: _t("Gauge"),
24807
25030
  sequence: 50,
24808
25031
  });
24809
25032
  chartRegistry.add("scatter", {
@@ -24813,7 +25036,6 @@ stores.inject(MyMetaStore, storeInstance);
24813
25036
  validateChartDefinition: ScatterChart.validateChartDefinition,
24814
25037
  transformDefinition: ScatterChart.transformDefinition,
24815
25038
  getChartDefinitionFromContextCreation: ScatterChart.getDefinitionFromContextCreation,
24816
- name: _t("Scatter"),
24817
25039
  sequence: 60,
24818
25040
  });
24819
25041
  chartRegistry.add("waterfall", {
@@ -24823,9 +25045,17 @@ stores.inject(MyMetaStore, storeInstance);
24823
25045
  validateChartDefinition: WaterfallChart.validateChartDefinition,
24824
25046
  transformDefinition: WaterfallChart.transformDefinition,
24825
25047
  getChartDefinitionFromContextCreation: WaterfallChart.getDefinitionFromContextCreation,
24826
- name: _t("Waterfall"),
24827
25048
  sequence: 70,
24828
25049
  });
25050
+ chartRegistry.add("pyramid", {
25051
+ match: (type) => type === "pyramid",
25052
+ createChart: (definition, sheetId, getters) => new PyramidChart(definition, sheetId, getters),
25053
+ getChartRuntime: createPyramidChartRuntime,
25054
+ validateChartDefinition: PyramidChart.validateChartDefinition,
25055
+ transformDefinition: PyramidChart.transformDefinition,
25056
+ getChartDefinitionFromContextCreation: PyramidChart.getDefinitionFromContextCreation,
25057
+ sequence: 80,
25058
+ });
24829
25059
  const chartComponentRegistry = new Registry();
24830
25060
  chartComponentRegistry.add("line", ChartJsComponent);
24831
25061
  chartComponentRegistry.add("bar", ChartJsComponent);
@@ -24835,6 +25065,130 @@ stores.inject(MyMetaStore, storeInstance);
24835
25065
  chartComponentRegistry.add("scatter", ChartJsComponent);
24836
25066
  chartComponentRegistry.add("scorecard", ScorecardChart);
24837
25067
  chartComponentRegistry.add("waterfall", ChartJsComponent);
25068
+ chartComponentRegistry.add("pyramid", ChartJsComponent);
25069
+ const chartCategories = {
25070
+ line: _t("Line"),
25071
+ column: _t("Column"),
25072
+ bar: _t("Bar"),
25073
+ pie: _t("Pie"),
25074
+ misc: _t("Miscellaneous"),
25075
+ };
25076
+ const chartSubtypeRegistry = new Registry();
25077
+ chartSubtypeRegistry
25078
+ .add("line", {
25079
+ matcher: (definition) => definition.type === "line" && !definition.stacked,
25080
+ displayName: _t("Line"),
25081
+ chartType: "line",
25082
+ chartSubtype: "line",
25083
+ subtypeDefinition: { stacked: false },
25084
+ category: "line",
25085
+ preview: "o-spreadsheet-ChartPreview.LINE_CHART",
25086
+ })
25087
+ .add("stacked_line", {
25088
+ matcher: (definition) => definition.type === "line" && definition.stacked,
25089
+ displayName: _t("Stacked Line"),
25090
+ chartType: "line",
25091
+ chartSubtype: "stacked_line",
25092
+ subtypeDefinition: { stacked: true },
25093
+ category: "line",
25094
+ preview: "o-spreadsheet-ChartPreview.STACKED_AREA_CHART",
25095
+ })
25096
+ .add("scatter", {
25097
+ displayName: _t("Scatter"),
25098
+ chartType: "scatter",
25099
+ chartSubtype: "scatter",
25100
+ category: "misc",
25101
+ preview: "o-spreadsheet-ChartPreview.SCATTER_CHART",
25102
+ })
25103
+ .add("column", {
25104
+ matcher: (definition) => definition.type === "bar" && !definition.stacked && !definition.horizontal,
25105
+ displayName: _t("Column"),
25106
+ chartType: "bar",
25107
+ chartSubtype: "column",
25108
+ subtypeDefinition: { stacked: false, horizontal: false },
25109
+ category: "column",
25110
+ preview: "o-spreadsheet-ChartPreview.COLUMN_CHART",
25111
+ })
25112
+ .add("stacked_column", {
25113
+ matcher: (definition) => definition.type === "bar" && definition.stacked && !definition.horizontal,
25114
+ displayName: _t("Stacked Column"),
25115
+ chartType: "bar",
25116
+ chartSubtype: "stacked_column",
25117
+ subtypeDefinition: { stacked: true, horizontal: false },
25118
+ category: "column",
25119
+ preview: "o-spreadsheet-ChartPreview.STACKED_COLUMN_CHART",
25120
+ })
25121
+ .add("bar", {
25122
+ matcher: (definition) => definition.type === "bar" && !definition.stacked && !!definition.horizontal,
25123
+ displayName: _t("Bar"),
25124
+ chartType: "bar",
25125
+ chartSubtype: "bar",
25126
+ subtypeDefinition: { horizontal: true, stacked: false },
25127
+ category: "bar",
25128
+ preview: "o-spreadsheet-ChartPreview.BAR_CHART",
25129
+ })
25130
+ .add("stacked_bar", {
25131
+ matcher: (definition) => definition.type === "bar" && definition.stacked && !!definition.horizontal,
25132
+ displayName: _t("Stacked Bar"),
25133
+ chartType: "bar",
25134
+ chartSubtype: "stacked_bar",
25135
+ subtypeDefinition: { horizontal: true, stacked: true },
25136
+ category: "bar",
25137
+ preview: "o-spreadsheet-ChartPreview.STACKED_BAR_CHART",
25138
+ })
25139
+ .add("combo", {
25140
+ displayName: _t("Combo"),
25141
+ chartSubtype: "combo",
25142
+ chartType: "combo",
25143
+ category: "line",
25144
+ preview: "o-spreadsheet-ChartPreview.COMBO_CHART",
25145
+ })
25146
+ .add("pie", {
25147
+ matcher: (definition) => definition.type === "pie" && !definition.isDoughnut,
25148
+ displayName: _t("Pie"),
25149
+ chartSubtype: "pie",
25150
+ chartType: "pie",
25151
+ subtypeDefinition: { isDoughnut: false },
25152
+ category: "pie",
25153
+ preview: "o-spreadsheet-ChartPreview.PIE_CHART",
25154
+ })
25155
+ .add("doughnut", {
25156
+ matcher: (definition) => definition.type === "pie" && !!definition.isDoughnut,
25157
+ displayName: _t("Doughnut"),
25158
+ chartSubtype: "doughnut",
25159
+ chartType: "pie",
25160
+ subtypeDefinition: { isDoughnut: true },
25161
+ category: "pie",
25162
+ preview: "o-spreadsheet-ChartPreview.DOUGHNUT_CHART",
25163
+ })
25164
+ .add("gauge", {
25165
+ displayName: _t("Gauge"),
25166
+ chartSubtype: "gauge",
25167
+ chartType: "gauge",
25168
+ category: "misc",
25169
+ preview: "o-spreadsheet-ChartPreview.GAUGE_CHART",
25170
+ })
25171
+ .add("scorecard", {
25172
+ displayName: _t("Scorecard"),
25173
+ chartSubtype: "scorecard",
25174
+ chartType: "scorecard",
25175
+ category: "misc",
25176
+ preview: "o-spreadsheet-ChartPreview.SCORECARD_CHART",
25177
+ })
25178
+ .add("waterfall", {
25179
+ displayName: _t("Waterfall"),
25180
+ chartSubtype: "waterfall",
25181
+ chartType: "waterfall",
25182
+ category: "misc",
25183
+ preview: "o-spreadsheet-ChartPreview.WATERFALL_CHART",
25184
+ })
25185
+ .add("pyramid", {
25186
+ displayName: _t("Population Pyramid"),
25187
+ chartSubtype: "pyramid",
25188
+ chartType: "pyramid",
25189
+ category: "misc",
25190
+ preview: "o-spreadsheet-ChartPreview.POPULATION_PYRAMID_CHART",
25191
+ });
24838
25192
 
24839
25193
  /**
24840
25194
  * Registry intended to support usual currencies. It is mainly used to create
@@ -26691,20 +27045,6 @@ stores.inject(MyMetaStore, storeInstance);
26691
27045
  }
26692
27046
  return transformation.transformDefinition(definition, executed);
26693
27047
  }
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
27048
  /**
26709
27049
  * Return a "smart" chart definition in the given zone. The definition is "smart" because it will
26710
27050
  * use the best type of chart to display the data of the zone.
@@ -28164,6 +28504,40 @@ stores.inject(MyMetaStore, storeInstance);
28164
28504
  },
28165
28505
  icon: "o-spreadsheet-Icon.PIVOT",
28166
28506
  };
28507
+ const FIX_FORMULAS = {
28508
+ name: _t("Convert to individual formulas"),
28509
+ execute(env) {
28510
+ const position = env.model.getters.getActivePosition();
28511
+ const cell = env.model.getters.getCorrespondingFormulaCell(position);
28512
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
28513
+ if (!cell || !pivotId) {
28514
+ return;
28515
+ }
28516
+ const { sheetId, col, row } = env.model.getters.getCellPosition(cell.id);
28517
+ const pivot = env.model.getters.getPivot(pivotId);
28518
+ pivot.init();
28519
+ if (!pivot.isValid()) {
28520
+ return;
28521
+ }
28522
+ env.model.dispatch("INSERT_PIVOT", {
28523
+ sheetId,
28524
+ col,
28525
+ row,
28526
+ pivotId,
28527
+ table: pivot.getTableStructure().export(),
28528
+ });
28529
+ },
28530
+ isVisible: (env) => {
28531
+ const position = env.model.getters.getActivePosition();
28532
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
28533
+ if (!pivotId) {
28534
+ return false;
28535
+ }
28536
+ const pivot = env.model.getters.getPivot(pivotId);
28537
+ return pivot.isValid() && env.model.getters.isSpillPivotFormula(position);
28538
+ },
28539
+ icon: "o-spreadsheet-Icon.PIVOT",
28540
+ };
28167
28541
 
28168
28542
  //------------------------------------------------------------------------------
28169
28543
  // Context Menu Registry
@@ -28262,6 +28636,10 @@ stores.inject(MyMetaStore, storeInstance);
28262
28636
  name: INSERT_LINK_NAME,
28263
28637
  sequence: 150,
28264
28638
  separator: true,
28639
+ })
28640
+ .add("pivot_fix_formulas", {
28641
+ ...FIX_FORMULAS,
28642
+ sequence: 155,
28265
28643
  })
28266
28644
  .add("pivot_properties", {
28267
28645
  ...pivotProperties,
@@ -30484,6 +30862,7 @@ stores.inject(MyMetaStore, storeInstance);
30484
30862
  });
30485
30863
  dataSeriesRanges = [];
30486
30864
  labelRange;
30865
+ chartTerms = ChartTerms;
30487
30866
  setup() {
30488
30867
  this.dataSeriesRanges = this.props.definition.dataSets;
30489
30868
  this.labelRange = this.props.definition.labelRange;
@@ -30508,7 +30887,7 @@ stores.inject(MyMetaStore, storeInstance);
30508
30887
  return [
30509
30888
  {
30510
30889
  name: "aggregated",
30511
- label: _t("Aggregate"),
30890
+ label: this.chartTerms.AggregatedChart,
30512
30891
  value: this.props.definition.aggregated ?? false,
30513
30892
  onChange: this.onUpdateAggregated.bind(this),
30514
30893
  },
@@ -30584,9 +30963,6 @@ stores.inject(MyMetaStore, storeInstance);
30584
30963
 
30585
30964
  class BarConfigPanel extends GenericChartConfigPanel {
30586
30965
  static template = "o-spreadsheet-BarConfigPanel";
30587
- get stackedLabel() {
30588
- return _t("Stacked barchart");
30589
- }
30590
30966
  onUpdateStacked(stacked) {
30591
30967
  this.props.updateChart(this.props.figureId, {
30592
30968
  stacked,
@@ -31576,6 +31952,9 @@ stores.inject(MyMetaStore, storeInstance);
31576
31952
  return "left";
31577
31953
  return dataSets[this.state.index].yAxisId === "y1" ? "right" : "left";
31578
31954
  }
31955
+ get canHaveTwoVerticalAxis() {
31956
+ return "horizontal" in this.props.definition ? !this.props.definition.horizontal : true;
31957
+ }
31579
31958
  updateDataSeriesLabel(ev) {
31580
31959
  const label = ev.target.value;
31581
31960
  const dataSets = this.props.definition.dataSets;
@@ -31740,19 +32119,13 @@ stores.inject(MyMetaStore, storeInstance);
31740
32119
  }
31741
32120
  return false;
31742
32121
  }
31743
- get stackedLabel() {
31744
- return _t("Stacked linechart");
31745
- }
31746
- get cumulativeLabel() {
31747
- return _t("Cumulative data");
31748
- }
31749
32122
  getLabelRangeOptions() {
31750
32123
  const options = super.getLabelRangeOptions();
31751
32124
  if (this.canTreatLabelsAsText) {
31752
32125
  options.push({
31753
32126
  name: "labelsAsText",
31754
32127
  value: this.props.definition.labelsAsText,
31755
- label: _t("Treat labels as text"),
32128
+ label: this.chartTerms.TreatLabelsAsText,
31756
32129
  onChange: this.onUpdateLabelsAsText.bind(this),
31757
32130
  });
31758
32131
  }
@@ -31819,7 +32192,7 @@ stores.inject(MyMetaStore, storeInstance);
31819
32192
  options.push({
31820
32193
  name: "labelsAsText",
31821
32194
  value: this.props.definition.labelsAsText,
31822
- label: _t("Treat labels as text"),
32195
+ label: this.chartTerms.TreatLabelsAsText,
31823
32196
  onChange: this.onUpdateLabelsAsText.bind(this),
31824
32197
  });
31825
32198
  }
@@ -32026,8 +32399,106 @@ stores.inject(MyMetaStore, storeInstance);
32026
32399
  .add("waterfall", {
32027
32400
  configuration: GenericChartConfigPanel,
32028
32401
  design: WaterfallChartDesignPanel,
32402
+ })
32403
+ .add("pyramid", {
32404
+ configuration: GenericChartConfigPanel,
32405
+ design: ChartWithAxisDesignPanel,
32029
32406
  });
32030
32407
 
32408
+ css /* scss */ `
32409
+ .o-section .o-type-selector {
32410
+ height: 30px;
32411
+ padding-left: 30px;
32412
+ }
32413
+ .o-type-selector-preview {
32414
+ left: 5px;
32415
+ top: 3px;
32416
+ .o-chart-preview {
32417
+ width: 24px;
32418
+ height: 24px;
32419
+ }
32420
+ }
32421
+
32422
+ .o-popover .o-chart-select-popover {
32423
+ box-sizing: border-box;
32424
+ background: #fff;
32425
+ .o-chart-type-item {
32426
+ cursor: pointer;
32427
+ padding: 3px 6px;
32428
+ margin: 1px 2px;
32429
+ &.selected,
32430
+ &:hover {
32431
+ background: #f5f5f5;
32432
+ border: 1px solid #ccc;
32433
+ padding: 2px 5px;
32434
+ }
32435
+ .o-chart-preview {
32436
+ width: 48px;
32437
+ height: 48px;
32438
+ }
32439
+ }
32440
+ }
32441
+ `;
32442
+ class ChartTypePicker extends owl.Component {
32443
+ static template = "o-spreadsheet-ChartTypePicker";
32444
+ static components = { Section, Popover };
32445
+ static props = { figureId: String, chartPanelStore: Object };
32446
+ categories = chartCategories;
32447
+ chartTypeByCategories = {};
32448
+ popoverRef = owl.useRef("popoverRef");
32449
+ selectRef = owl.useRef("selectRef");
32450
+ state = owl.useState({ popoverProps: undefined, popoverStyle: "" });
32451
+ setup() {
32452
+ owl.useExternalListener(window, "pointerdown", this.onExternalClick, { capture: true });
32453
+ for (const subtypeProperties of chartSubtypeRegistry.getAll()) {
32454
+ if (this.chartTypeByCategories[subtypeProperties.category]) {
32455
+ this.chartTypeByCategories[subtypeProperties.category].push(subtypeProperties);
32456
+ }
32457
+ else {
32458
+ this.chartTypeByCategories[subtypeProperties.category] = [subtypeProperties];
32459
+ }
32460
+ }
32461
+ }
32462
+ onExternalClick(ev) {
32463
+ if (isChildEvent(this.popoverRef.el?.parentElement, ev) ||
32464
+ isChildEvent(this.selectRef.el, ev)) {
32465
+ return;
32466
+ }
32467
+ this.closePopover();
32468
+ }
32469
+ onTypeChange(type) {
32470
+ this.props.chartPanelStore.changeChartType(this.props.figureId, type);
32471
+ this.closePopover();
32472
+ }
32473
+ getChartDefinition(figureId) {
32474
+ return this.env.model.getters.getChartDefinition(figureId);
32475
+ }
32476
+ getSelectedChartSubtypeProperties() {
32477
+ const definition = this.getChartDefinition(this.props.figureId);
32478
+ const matchedChart = chartSubtypeRegistry
32479
+ .getAll()
32480
+ .find((c) => c.matcher?.(definition) || false);
32481
+ return matchedChart || chartSubtypeRegistry.get(definition.type);
32482
+ }
32483
+ onPointerDown(ev) {
32484
+ if (this.state.popoverProps) {
32485
+ this.closePopover();
32486
+ return;
32487
+ }
32488
+ const target = ev.currentTarget;
32489
+ const { bottom, right, width } = target.getBoundingClientRect();
32490
+ this.state.popoverProps = {
32491
+ anchorRect: { x: right, y: bottom, width: 0, height: 0 },
32492
+ positioning: "TopRight",
32493
+ verticalOffset: 0,
32494
+ };
32495
+ this.state.popoverStyle = cssPropertiesToCss({ width: `${width}px` });
32496
+ }
32497
+ closePopover() {
32498
+ this.state.popoverProps = undefined;
32499
+ }
32500
+ }
32501
+
32031
32502
  class MainChartPanelStore extends SpreadsheetStore {
32032
32503
  mutators = ["activatePanel", "changeChartType"];
32033
32504
  panel = "configuration";
@@ -32035,7 +32506,7 @@ stores.inject(MyMetaStore, storeInstance);
32035
32506
  activatePanel(panel) {
32036
32507
  this.panel = panel;
32037
32508
  }
32038
- changeChartType(figureId, type) {
32509
+ changeChartType(figureId, newDisplayType) {
32039
32510
  this.creationContext = {
32040
32511
  ...this.creationContext,
32041
32512
  ...this.getters.getContextCreationChart(figureId),
@@ -32044,13 +32515,25 @@ stores.inject(MyMetaStore, storeInstance);
32044
32515
  if (!sheetId) {
32045
32516
  return;
32046
32517
  }
32047
- const definition = getChartDefinitionFromContextCreation(this.creationContext, type);
32518
+ const definition = this.getChartDefinitionFromContextCreation(figureId, newDisplayType);
32048
32519
  this.model.dispatch("UPDATE_CHART", {
32049
32520
  definition,
32050
32521
  id: figureId,
32051
32522
  sheetId,
32052
32523
  });
32053
32524
  }
32525
+ getChartDefinitionFromContextCreation(figureId, newDisplayType) {
32526
+ const newChartInfo = chartSubtypeRegistry.get(newDisplayType);
32527
+ const ChartClass = chartRegistry.get(newChartInfo.chartType);
32528
+ const contextCreation = {
32529
+ ...this.creationContext,
32530
+ ...this.getters.getContextCreationChart(figureId),
32531
+ };
32532
+ return {
32533
+ ...ChartClass.getChartDefinitionFromContextCreation(contextCreation),
32534
+ ...newChartInfo.subtypeDefinition,
32535
+ };
32536
+ }
32054
32537
  }
32055
32538
 
32056
32539
  css /* scss */ `
@@ -32079,7 +32562,7 @@ stores.inject(MyMetaStore, storeInstance);
32079
32562
  `;
32080
32563
  class ChartPanel extends owl.Component {
32081
32564
  static template = "o-spreadsheet-ChartPanel";
32082
- static components = { Section };
32565
+ static components = { Section, ChartTypePicker };
32083
32566
  static props = { onCloseSidePanel: Function, figureId: String };
32084
32567
  store;
32085
32568
  get figureId() {
@@ -32139,9 +32622,6 @@ stores.inject(MyMetaStore, storeInstance);
32139
32622
  getChartDefinition(figureId) {
32140
32623
  return this.env.model.getters.getChartDefinition(figureId);
32141
32624
  }
32142
- get chartTypes() {
32143
- return getChartTypes();
32144
- }
32145
32625
  }
32146
32626
 
32147
32627
  css /* scss */ `
@@ -34146,6 +34626,10 @@ stores.inject(MyMetaStore, storeInstance);
34146
34626
  case "ACTIVATE_SHEET":
34147
34627
  this.isSearchDirty = true;
34148
34628
  break;
34629
+ case "REPLACE_SEARCH":
34630
+ for (const match of cmd.matches) {
34631
+ this.replaceMatch(match, cmd.searchString, cmd.replaceWith, cmd.searchOptions);
34632
+ }
34149
34633
  }
34150
34634
  }
34151
34635
  finalize() {
@@ -34319,6 +34803,21 @@ stores.inject(MyMetaStore, storeInstance);
34319
34803
  searchOptions: this.searchOptions,
34320
34804
  });
34321
34805
  }
34806
+ replaceMatch(selectedMatch, searchString, replaceWith, searchOptions) {
34807
+ const cell = this.getters.getCell(selectedMatch);
34808
+ if (!cell?.content) {
34809
+ return;
34810
+ }
34811
+ if (cell?.isFormula && !searchOptions.searchFormulas) {
34812
+ return;
34813
+ }
34814
+ const searchRegex = getSearchRegex(searchString, searchOptions);
34815
+ const replaceRegex = new RegExp(searchRegex.source, searchRegex.flags + "g");
34816
+ const toReplace = this.getters.getCellText(selectedMatch, searchOptions.searchFormulas);
34817
+ const content = toReplace.replace(replaceRegex, replaceWith);
34818
+ const canonicalContent = canonicalizeNumberContent(content, this.getters.getLocale());
34819
+ this.model.dispatch("UPDATE_CELL", { ...selectedMatch, content: canonicalContent });
34820
+ }
34322
34821
  getSearchableString(position) {
34323
34822
  return this.getters.getCellText(position, this.searchOptions.searchFormulas);
34324
34823
  }
@@ -35070,7 +35569,7 @@ stores.inject(MyMetaStore, storeInstance);
35070
35569
  function createPivotDimension(fields, dimension) {
35071
35570
  const field = fields[dimension.name];
35072
35571
  const type = field?.type ?? "integer";
35073
- const granularity = field && isDateField(field) ? dimension.granularity ?? "month_number" : undefined;
35572
+ const granularity = field && isDateField(field) ? dimension.granularity : undefined;
35074
35573
  return {
35075
35574
  /**
35076
35575
  * Get the display name of the dimension
@@ -35215,9 +35714,17 @@ stores.inject(MyMetaStore, storeInstance);
35215
35714
  }
35216
35715
  getPivotCell(col, row, includeTotal = true) {
35217
35716
  const colHeadersHeight = this.columns.length;
35218
- if (row <= colHeadersHeight - 1) {
35717
+ if (col > 0 && row === colHeadersHeight - 1) {
35718
+ const domain = this.getColHeaderDomain(col, row);
35719
+ if (!domain) {
35720
+ return EMPTY_PIVOT_CELL;
35721
+ }
35722
+ const measure = domain.at(-1)?.value.toString() || "";
35723
+ return { type: "MEASURE_HEADER", domain: domain.slice(0, -1), measure };
35724
+ }
35725
+ else if (row <= colHeadersHeight - 1) {
35219
35726
  const domain = this.getColHeaderDomain(col, row);
35220
- return domain ? { type: "HEADER", domain } : { type: "EMPTY" };
35727
+ return domain ? { type: "HEADER", domain } : EMPTY_PIVOT_CELL;
35221
35728
  }
35222
35729
  else if (col === 0) {
35223
35730
  const rowIndex = row - colHeadersHeight;
@@ -35227,7 +35734,7 @@ stores.inject(MyMetaStore, storeInstance);
35227
35734
  else {
35228
35735
  const rowIndex = row - colHeadersHeight;
35229
35736
  if (!includeTotal && this.isTotalRow(rowIndex)) {
35230
- return { type: "EMPTY" };
35737
+ return EMPTY_PIVOT_CELL;
35231
35738
  }
35232
35739
  const domain = [...this.getRowDomain(rowIndex), ...this.getColDomain(col)];
35233
35740
  const measure = this.getColMeasure(col);
@@ -35281,6 +35788,7 @@ stores.inject(MyMetaStore, storeInstance);
35281
35788
  };
35282
35789
  }
35283
35790
  }
35791
+ const EMPTY_PIVOT_CELL = { type: "EMPTY" };
35284
35792
 
35285
35793
  /**
35286
35794
  * This function converts a list of data entry into a spreadsheet pivot table.
@@ -35474,10 +35982,13 @@ stores.inject(MyMetaStore, storeInstance);
35474
35982
  }
35475
35983
 
35476
35984
  function createDate(dimension, value, locale) {
35477
- const granularity = dimension.granularity || "month_number";
35478
- if (!(granularity in MAP_VALUE_DIMENSION_DATE)) {
35985
+ const granularity = dimension.granularity;
35986
+ if (!granularity || !(granularity in MAP_VALUE_DIMENSION_DATE)) {
35479
35987
  throw new Error(`Unknown date granularity: ${granularity}`);
35480
35988
  }
35989
+ if (value === null) {
35990
+ return null;
35991
+ }
35481
35992
  if (!MAP_VALUE_DIMENSION_DATE[granularity].set.has(value)) {
35482
35993
  MAP_VALUE_DIMENSION_DATE[granularity].set.add(value);
35483
35994
  const date = toJsDate(value, locale);
@@ -35708,14 +36219,17 @@ stores.inject(MyMetaStore, storeInstance);
35708
36219
  if (dimension.type === "date") {
35709
36220
  const adapter = pivotTimeAdapter(dimension.granularity);
35710
36221
  return {
35711
- value: adapter.toCellValue(toNormalizedPivotValue(dimension, lastNode.value)),
36222
+ value: lastNode.value !== "null"
36223
+ ? adapter.toCellValue(toNormalizedPivotValue(dimension, lastNode.value))
36224
+ : _t("(Undefined)"),
35712
36225
  format: adapter.getFormat(this.getters.getLocale()),
35713
36226
  };
35714
36227
  }
35715
36228
  if (!finalCell) {
35716
36229
  return { value: "" };
35717
36230
  }
35718
- if (finalCell.value === null) {
36231
+ // Value can be null but stringified (e.g. an empty date, as for now every date is stringified)
36232
+ if (finalCell.value === null || finalCell.value === `${null}`) {
35719
36233
  return { value: _t("(Undefined)") };
35720
36234
  }
35721
36235
  return {
@@ -35737,10 +36251,15 @@ stores.inject(MyMetaStore, storeInstance);
35737
36251
  if (!operator) {
35738
36252
  throw new Error(`Aggregator ${aggregator} does not exist`);
35739
36253
  }
35740
- return {
35741
- value: values.length ? operator.fn([values], this.getters.getLocale()) : "",
35742
- format: operator.format(values[0]),
35743
- };
36254
+ try {
36255
+ return {
36256
+ value: values.length ? operator.fn([values], this.getters.getLocale()) : "",
36257
+ format: operator.format(values[0]),
36258
+ };
36259
+ }
36260
+ catch (e) {
36261
+ return handleError(e, aggregator.toUpperCase());
36262
+ }
35744
36263
  }
35745
36264
  getPossibleFieldValues(dimension) {
35746
36265
  const values = [];
@@ -51107,12 +51626,12 @@ stores.inject(MyMetaStore, storeInstance);
51107
51626
  for (let col = 0; col < pivotCells.length; col++) {
51108
51627
  for (let row = 0; row < pivotCells[col].length; row++) {
51109
51628
  const pivotCell = pivotCells[col][row];
51110
- const cellPosition = {
51629
+ this.dispatch("UPDATE_CELL", {
51111
51630
  sheetId: position.sheetId,
51112
51631
  col: position.col + col,
51113
51632
  row: position.row + row,
51114
- };
51115
- this.addPivotFormula(cellPosition, formulaId, pivotCell);
51633
+ content: makePivotFormulaFromPivotCell(formulaId, pivotCell),
51634
+ });
51116
51635
  }
51117
51636
  }
51118
51637
  }
@@ -51142,21 +51661,6 @@ stores.inject(MyMetaStore, storeInstance);
51142
51661
  });
51143
51662
  }
51144
51663
  }
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
51664
  getPivotCore(pivotId) {
51161
51665
  const pivot = this.pivots[pivotId];
51162
51666
  if (!pivot) {
@@ -52821,7 +53325,6 @@ stores.inject(MyMetaStore, storeInstance);
52821
53325
  if (!this.blockedArrayFormulas.has(position)) {
52822
53326
  this.invalidateSpreading(position);
52823
53327
  }
52824
- this.spreadingRelations.removeNode(position);
52825
53328
  const cell = this.getters.getCell(position);
52826
53329
  if (cell === undefined) {
52827
53330
  return EMPTY_CELL;
@@ -52863,6 +53366,7 @@ stores.inject(MyMetaStore, storeInstance);
52863
53366
  this.assertSheetHasEnoughSpaceToSpreadFormulaResult(formulaPosition, formulaReturn);
52864
53367
  const nbColumns = formulaReturn.length;
52865
53368
  const nbRows = formulaReturn[0].length;
53369
+ this.spreadingRelations.removeNode(formulaPosition);
52866
53370
  forEachSpreadPositionInMatrix(nbColumns, nbRows, this.updateSpreadRelation(formulaPosition));
52867
53371
  this.assertNoMergedCellsInSpreadZone(formulaPosition, formulaReturn);
52868
53372
  forEachSpreadPositionInMatrix(nbColumns, nbRows, this.checkCollision(formulaPosition));
@@ -54390,9 +54894,10 @@ stores.inject(MyMetaStore, storeInstance);
54390
54894
  "getPivot",
54391
54895
  "getFirstPivotFunction",
54392
54896
  "getPivotIdFromPosition",
54393
- "getPivotDomainArgsFromPosition",
54897
+ "getPivotCellFromPosition",
54394
54898
  "isPivotUnused",
54395
54899
  "areDomainArgsFieldsValid",
54900
+ "isSpillPivotFormula",
54396
54901
  ];
54397
54902
  pivots = {};
54398
54903
  unusedPivots;
@@ -54471,6 +54976,14 @@ stores.inject(MyMetaStore, storeInstance);
54471
54976
  }
54472
54977
  return undefined;
54473
54978
  }
54979
+ isSpillPivotFormula(position) {
54980
+ const cell = this.getters.getCorrespondingFormulaCell(position);
54981
+ if (cell && cell.isFormula) {
54982
+ const pivotFunction = this.getFirstPivotFunction(cell.compiledFormula.tokens);
54983
+ return pivotFunction?.functionName === "PIVOT";
54984
+ }
54985
+ return false;
54986
+ }
54474
54987
  getFirstPivotFunction(tokens) {
54475
54988
  const pivotFunction = getFirstPivotFunction(tokens);
54476
54989
  if (!pivotFunction) {
@@ -54504,29 +55017,29 @@ stores.inject(MyMetaStore, storeInstance);
54504
55017
  * If the cell is the result of PIVOT, the result is the domain of the cell
54505
55018
  * as if it was the individual pivot formula
54506
55019
  */
54507
- getPivotDomainArgsFromPosition(position) {
55020
+ getPivotCellFromPosition(position) {
54508
55021
  const cell = this.getters.getCorrespondingFormulaCell(position);
54509
55022
  if (!cell || !cell.isFormula || getNumberOfPivotFunctions(cell.compiledFormula.tokens) === 0) {
54510
- return undefined;
55023
+ return EMPTY_PIVOT_CELL;
54511
55024
  }
54512
55025
  const mainPosition = this.getters.getCellPosition(cell.id);
54513
55026
  const result = this.getters.getFirstPivotFunction(cell.compiledFormula.tokens);
54514
55027
  if (!result) {
54515
- return undefined;
55028
+ return EMPTY_PIVOT_CELL;
54516
55029
  }
54517
55030
  const { functionName, args } = result;
54518
55031
  if (functionName === "PIVOT") {
54519
55032
  const formulaId = args[0];
54520
55033
  if (!formulaId) {
54521
- return undefined;
55034
+ return EMPTY_PIVOT_CELL;
54522
55035
  }
54523
55036
  const pivotId = this.getters.getPivotId(formulaId.toString());
54524
55037
  if (!pivotId) {
54525
- return undefined;
55038
+ return EMPTY_PIVOT_CELL;
54526
55039
  }
54527
55040
  const pivot = this.getPivot(pivotId);
54528
55041
  if (!pivot.isValid()) {
54529
- return undefined;
55042
+ return EMPTY_PIVOT_CELL;
54530
55043
  }
54531
55044
  const includeTotal = args[2] === false ? false : undefined;
54532
55045
  const includeColumnHeaders = args[3] === false ? false : undefined;
@@ -54535,22 +55048,28 @@ stores.inject(MyMetaStore, storeInstance);
54535
55048
  .getPivotCells(includeTotal, includeColumnHeaders);
54536
55049
  const pivotCol = position.col - mainPosition.col;
54537
55050
  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" };
55051
+ return pivotCells[pivotCol][pivotRow];
55052
+ }
55053
+ if (functionName === "PIVOT.HEADER" && args.at(-2) === "measure") {
55054
+ const domain = toPivotDomain(args.slice(1, -2).map((x) => `${x}`));
55055
+ return {
55056
+ type: "MEASURE_HEADER",
55057
+ domain,
55058
+ measure: args.at(-1)?.toString() || "",
55059
+ };
54547
55060
  }
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);
55061
+ else if (functionName === "PIVOT.HEADER") {
55062
+ return {
55063
+ type: "HEADER",
55064
+ domain: toPivotDomain(args.slice(1).map((x) => `${x}`)),
55065
+ };
54551
55066
  }
54552
- const isHeader = functionName === "PIVOT.HEADER";
54553
- return { domainArgs: domain, isHeader };
55067
+ const [measure, ...domainArgs] = args.slice(1);
55068
+ return {
55069
+ type: "VALUE",
55070
+ domain: toPivotDomain(domainArgs.map((x) => `${x}`)),
55071
+ measure: measure?.toString() || "",
55072
+ };
54554
55073
  }
54555
55074
  getPivot(pivotId) {
54556
55075
  return this.pivots[pivotId];
@@ -56421,43 +56940,6 @@ stores.inject(MyMetaStore, storeInstance);
56421
56940
  }
56422
56941
  }
56423
56942
 
56424
- /**
56425
- * Find and Replace Plugin
56426
- *
56427
- * This plugin is used in combination with the find_and_replace sidePanel
56428
- * It is used to 'highlight' cells that match an input string according to
56429
- * the given searchOptions. The second part of this plugin makes it possible
56430
- * (again with the find_and_replace sidePanel), to replace the values that match
56431
- * the search with a new value.
56432
- */
56433
- class FindAndReplacePlugin extends UIPlugin {
56434
- static getters = [];
56435
- handle(cmd) {
56436
- switch (cmd.type) {
56437
- case "REPLACE_SEARCH":
56438
- for (const match of cmd.matches) {
56439
- this.replaceMatch(match, cmd.searchString, cmd.replaceWith, cmd.searchOptions);
56440
- }
56441
- break;
56442
- }
56443
- }
56444
- replaceMatch(selectedMatch, searchString, replaceWith, searchOptions) {
56445
- const cell = this.getters.getCell(selectedMatch);
56446
- if (!cell?.content) {
56447
- return;
56448
- }
56449
- if (cell?.isFormula && !searchOptions.searchFormulas) {
56450
- return;
56451
- }
56452
- const searchRegex = getSearchRegex(searchString, searchOptions);
56453
- const replaceRegex = new RegExp(searchRegex.source, searchRegex.flags + "g");
56454
- const toReplace = this.getters.getCellText(selectedMatch, searchOptions.searchFormulas);
56455
- const content = toReplace.replace(replaceRegex, replaceWith);
56456
- const canonicalContent = canonicalizeNumberContent(content, this.getters.getLocale());
56457
- this.dispatch("UPDATE_CELL", { ...selectedMatch, content: canonicalContent });
56458
- }
56459
- }
56460
-
56461
56943
  class FormatPlugin extends UIPlugin {
56462
56944
  // ---------------------------------------------------------------------------
56463
56945
  // Command Handling
@@ -60272,7 +60754,6 @@ stores.inject(MyMetaStore, storeInstance);
60272
60754
  .add("ui_sheet", SheetUIPlugin)
60273
60755
  .add("ui_options", UIOptionsPlugin)
60274
60756
  .add("autofill", AutofillPlugin)
60275
- .add("find_and_replace", FindAndReplacePlugin)
60276
60757
  .add("sort", SortPlugin)
60277
60758
  .add("automatic_sum", AutomaticSumPlugin)
60278
60759
  .add("format", FormatPlugin)
@@ -63833,6 +64314,9 @@ stores.inject(MyMetaStore, storeInstance);
63833
64314
  getBackToDefault() {
63834
64315
  this.stream.getBackToDefault();
63835
64316
  }
64317
+ getAnchor() {
64318
+ return this.anchor;
64319
+ }
63836
64320
  modifyAnchor(anchor, mode, options) {
63837
64321
  const sheetId = this.getters.getActiveSheetId();
63838
64322
  anchor = {
@@ -66796,6 +67280,7 @@ stores.inject(MyMetaStore, storeInstance);
66796
67280
  chartSidePanelComponentRegistry,
66797
67281
  chartComponentRegistry,
66798
67282
  chartRegistry,
67283
+ chartSubtypeRegistry,
66799
67284
  topbarMenuRegistry,
66800
67285
  topbarComponentRegistry,
66801
67286
  clickableCellRegistry,
@@ -66904,6 +67389,7 @@ stores.inject(MyMetaStore, storeInstance);
66904
67389
  GaugeChartDesignPanel,
66905
67390
  ScorecardChartConfigPanel,
66906
67391
  ScorecardChartDesignPanel,
67392
+ ChartTypePicker,
66907
67393
  FigureComponent,
66908
67394
  Menu,
66909
67395
  Popover,
@@ -66953,6 +67439,7 @@ stores.inject(MyMetaStore, storeInstance);
66953
67439
  DEFAULT_LOCALE,
66954
67440
  HIGHLIGHT_COLOR,
66955
67441
  PIVOT_TABLE_CONFIG,
67442
+ ChartTerms,
66956
67443
  };
66957
67444
 
66958
67445
  exports.AbstractCellClipboardHandler = AbstractCellClipboardHandler;
@@ -67001,9 +67488,9 @@ stores.inject(MyMetaStore, storeInstance);
67001
67488
  exports.tokenize = tokenize;
67002
67489
 
67003
67490
 
67004
- __info__.version = "17.4.0-alpha.5";
67005
- __info__.date = "2024-06-14T10:01:40.605Z";
67006
- __info__.hash = "9ceed96";
67491
+ __info__.version = "17.4.0-alpha.7";
67492
+ __info__.date = "2024-06-21T08:42:22.370Z";
67493
+ __info__.hash = "a99db9a";
67007
67494
 
67008
67495
 
67009
67496
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);