@odoo/o-spreadsheet 18.1.0-alpha.7 → 18.1.0-alpha.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -2,9 +2,9 @@
2
2
  /**
3
3
  * This file is generated by o-spreadsheet build tools. Do not edit it.
4
4
  * @see https://github.com/odoo/o-spreadsheet
5
- * @version 18.1.0-alpha.7
6
- * @date 2024-12-05T10:40:26.512Z
7
- * @hash 7b1c39b
5
+ * @version 18.1.0-alpha.8
6
+ * @date 2024-12-19T07:49:54.421Z
7
+ * @hash 87a1567
8
8
  */
9
9
 
10
10
  (function (exports, owl) {
@@ -266,7 +266,6 @@
266
266
  const MIN_COL_WIDTH = 5;
267
267
  const HEADER_HEIGHT = 26;
268
268
  const HEADER_WIDTH = 48;
269
- const TOPBAR_HEIGHT = 63;
270
269
  const TOPBAR_TOOLBAR_HEIGHT = 34;
271
270
  const BOTTOMBAR_HEIGHT = 36;
272
271
  const DEFAULT_CELL_WIDTH = 96;
@@ -790,6 +789,7 @@
790
789
  * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions/Character_Classes
791
790
  */
792
791
  const whiteSpaceSpecialCharacters = [
792
+ " ",
793
793
  "\t",
794
794
  "\f",
795
795
  "\v",
@@ -804,17 +804,15 @@
804
804
  String.fromCharCode(parseInt("3000", 16)),
805
805
  String.fromCharCode(parseInt("feff", 16)),
806
806
  ];
807
- const whiteSpaceRegexp = new RegExp(whiteSpaceSpecialCharacters.join("|") + "|(\r\n|\r|\n)", "g");
807
+ const whiteSpaceRegexp = new RegExp(whiteSpaceSpecialCharacters.join("|"), "g");
808
+ const newLineRegexp = /(\r\n|\r)/g;
808
809
  /**
809
- * Replace all the special spaces in a string (non-breaking, tabs, ...) by normal spaces, and all the
810
- * different newlines types by \n.
810
+ * Replace all different newlines characters by \n
811
811
  */
812
- function replaceSpecialSpaces(text) {
812
+ function replaceNewLines(text) {
813
813
  if (!text)
814
814
  return "";
815
- if (!whiteSpaceRegexp.test(text))
816
- return text;
817
- return text.replace(whiteSpaceRegexp, (match, newLine) => (newLine ? NEWLINE : " "));
815
+ return text.replace(newLineRegexp, NEWLINE);
818
816
  }
819
817
  /**
820
818
  * Determine if the numbers are consecutive.
@@ -980,7 +978,7 @@
980
978
 
981
979
  const RBA_REGEX = /rgba?\(|\s+|\)/gi;
982
980
  const HEX_MATCH = /^#([A-F\d]{2}){3,4}$/;
983
- const colors$1 = [
981
+ const colors = [
984
982
  "#eb6d00",
985
983
  "#0074d9",
986
984
  "#ad8e00",
@@ -1003,6 +1001,12 @@
1003
1001
  function colorNumberString(color) {
1004
1002
  return toHex(color.toString(16).padStart(6, "0"));
1005
1003
  }
1004
+ function colorToNumber(color) {
1005
+ if (typeof color === "number") {
1006
+ return color;
1007
+ }
1008
+ return Number.parseInt(toHex(color).slice(1), 16);
1009
+ }
1006
1010
  /**
1007
1011
  * Converts any CSS color value to a standardized hex6 value.
1008
1012
  * Accepts: hex3, hex6, hex8, rgb[1] and rgba[1].
@@ -1353,9 +1357,9 @@
1353
1357
  "#056BD9", // Blue #3
1354
1358
  "#155193", // Blue #4
1355
1359
  "#A76DBC", // Violet #1
1356
- "#7F4295", // Violet #1
1357
- "#6D2387", // Violet #1
1358
- "#4F1565", // Violet #1
1360
+ "#7F4295", // Violet #2
1361
+ "#6D2387", // Violet #3
1362
+ "#4F1565", // Violet #4
1359
1363
  "#EA6175", // Red #1
1360
1364
  "#CE4257", // Red #2
1361
1365
  "#982738", // Red #3
@@ -1381,6 +1385,81 @@
1381
1385
  "#C08A16", // Yellow #3
1382
1386
  "#936A12", // Yellow #4
1383
1387
  ];
1388
+ // Same as above but with alternating colors
1389
+ const ALTERNATING_COLORS_MD = [
1390
+ "#4EA7F2", // Blue #1
1391
+ "#43C5B1", // Teal #1
1392
+ "#EA6175", // Red #1
1393
+ "#F4A261", // Orange #1
1394
+ "#8481DD", // Purple #1
1395
+ "#FFD86D", // Yellow #1
1396
+ "#3188E6", // Blue #2
1397
+ "#00A78D", // Teal #2
1398
+ "#CE4257", // Red #2
1399
+ "#F48935", // Orange #2
1400
+ "#5752D1", // Purple #2
1401
+ "#FFBC2C", // Yellow #2
1402
+ ];
1403
+ const ALTERNATING_COLORS_LG = [
1404
+ "#4EA7F2", // Blue #1
1405
+ "#A76DBC", // Violet #1
1406
+ "#EA6175", // Red #1
1407
+ "#43C5B1", // Teal #1
1408
+ "#F4A261", // Orange #1
1409
+ "#8481DD", // Purple #1
1410
+ "#A4A8B6", // Gray #1
1411
+ "#FFD86D", // Yellow #1
1412
+ "#3188E6", // Blue #2
1413
+ "#7F4295", // Violet #2
1414
+ "#CE4257", // Red #2
1415
+ "#00A78D", // Teal #2
1416
+ "#F48935", // Orange #2
1417
+ "#5752D1", // Purple #2
1418
+ "#7E8290", // Gray #2
1419
+ "#FFBC2C", // Yellow #2
1420
+ "#056BD9", // Blue #3
1421
+ "#6D2387", // Violet #3
1422
+ "#982738", // Red #3
1423
+ "#0E8270", // Teal #3
1424
+ "#BE5D10", // Orange #3
1425
+ "#3A3580", // Purple #3
1426
+ "#545B70", // Gray #3
1427
+ "#C08A16", // Yellow #3
1428
+ ];
1429
+ const ALTERNATING_COLORS_XL = [
1430
+ "#4EA7F2", // Blue #1
1431
+ "#A76DBC", // Violet #1
1432
+ "#EA6175", // Red #1
1433
+ "#43C5B1", // Teal #1
1434
+ "#F4A261", // Orange #1
1435
+ "#8481DD", // Purple #1
1436
+ "#A4A8B6", // Grey #1
1437
+ "#FFD86D", // Yellow #1
1438
+ "#3188E6", // Blue #2
1439
+ "#7F4295", // Violet #2
1440
+ "#CE4257", // Red #2
1441
+ "#00A78D", // Teal #2
1442
+ "#F48935", // Orange #2
1443
+ "#5752D1", // Purple #2
1444
+ "#7E8290", // Grey #2
1445
+ "#FFBC2C", // Yellow #2
1446
+ "#056BD9", // Blue #3
1447
+ "#6D2387", // Violet #3
1448
+ "#982738", // Red #3
1449
+ "#0E8270", // Teal #3
1450
+ "#BE5D10", // Orange #3
1451
+ "#3A3580", // Purple #3
1452
+ "#545B70", // Grey #3
1453
+ "#C08A16", // Yellow #3
1454
+ "#155193", // Blue #4
1455
+ "#4F1565", // Violet #4
1456
+ "#791B29", // Red #4
1457
+ "#105F53", // Teal #4
1458
+ "#7D380D", // Orange #4
1459
+ "#26235F", // Purple #4
1460
+ "#3F4250", // Grey #4
1461
+ "#936A12", // Yellow #4
1462
+ ];
1384
1463
  function getNthColor(index, palette) {
1385
1464
  return palette[index % palette.length];
1386
1465
  }
@@ -1398,6 +1477,20 @@
1398
1477
  return COLORS_XL;
1399
1478
  }
1400
1479
  }
1480
+ function getAlternatingColorsPalette(quantity) {
1481
+ if (quantity <= 6) {
1482
+ return COLORS_SM;
1483
+ }
1484
+ else if (quantity <= 12) {
1485
+ return ALTERNATING_COLORS_MD;
1486
+ }
1487
+ else if (quantity <= 24) {
1488
+ return ALTERNATING_COLORS_LG;
1489
+ }
1490
+ else {
1491
+ return ALTERNATING_COLORS_XL;
1492
+ }
1493
+ }
1401
1494
  class ColorGenerator {
1402
1495
  preferredColors;
1403
1496
  currentColorIndex = 0;
@@ -1412,6 +1505,62 @@
1412
1505
  : getNthColor(this.currentColorIndex++, this.palette);
1413
1506
  }
1414
1507
  }
1508
+ class AlternatingColorGenerator extends ColorGenerator {
1509
+ constructor(paletteSize, preferredColors = []) {
1510
+ super(paletteSize, preferredColors);
1511
+ this.palette = getAlternatingColorsPalette(paletteSize).filter((c) => !preferredColors.includes(c));
1512
+ }
1513
+ }
1514
+ /**
1515
+ * Returns a function that maps a value to a color using a color scale defined by the given
1516
+ * color/threshold values pairs.
1517
+ */
1518
+ function getColorScale(colorScalePoints) {
1519
+ if (colorScalePoints.length < 2) {
1520
+ throw new Error("Color scale must have at least 2 points");
1521
+ }
1522
+ const sortedColorScalePoints = [...colorScalePoints.sort((a, b) => a.value - b.value)];
1523
+ const thresholds = [];
1524
+ for (let i = 1; i < sortedColorScalePoints.length; i++) {
1525
+ const minColor = colorToNumber(sortedColorScalePoints[i - 1].color);
1526
+ const maxColor = colorToNumber(sortedColorScalePoints[i].color);
1527
+ thresholds.push({
1528
+ min: sortedColorScalePoints[i - 1].value,
1529
+ max: sortedColorScalePoints[i].value,
1530
+ minColor,
1531
+ maxColor,
1532
+ colorDiff: computeColorDiffUnits(sortedColorScalePoints[i - 1].value, sortedColorScalePoints[i].value, minColor, maxColor),
1533
+ });
1534
+ }
1535
+ return (value) => {
1536
+ if (value < thresholds[0].min) {
1537
+ return colorNumberString(thresholds[0].minColor);
1538
+ }
1539
+ for (const threshold of thresholds) {
1540
+ if (value >= threshold.min && value <= threshold.max) {
1541
+ return colorNumberString(colorCell(value, threshold.min, threshold.minColor, threshold.colorDiff));
1542
+ }
1543
+ }
1544
+ return colorNumberString(thresholds[thresholds.length - 1].maxColor);
1545
+ };
1546
+ }
1547
+ function computeColorDiffUnits(minValue, maxValue, minColor, maxColor) {
1548
+ const deltaValue = maxValue - minValue;
1549
+ const deltaColorR = ((minColor >> 16) % 256) - ((maxColor >> 16) % 256);
1550
+ const deltaColorG = ((minColor >> 8) % 256) - ((maxColor >> 8) % 256);
1551
+ const deltaColorB = (minColor % 256) - (maxColor % 256);
1552
+ const colorDiffUnitR = deltaColorR / deltaValue;
1553
+ const colorDiffUnitG = deltaColorG / deltaValue;
1554
+ const colorDiffUnitB = deltaColorB / deltaValue;
1555
+ return [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB];
1556
+ }
1557
+ function colorCell(value, minValue, minColor, colorDiffUnit) {
1558
+ const [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB] = colorDiffUnit;
1559
+ const r = Math.round(((minColor >> 16) % 256) - colorDiffUnitR * (value - minValue));
1560
+ const g = Math.round(((minColor >> 8) % 256) - colorDiffUnitG * (value - minValue));
1561
+ const b = Math.round((minColor % 256) - colorDiffUnitB * (value - minValue));
1562
+ return (r << 16) | (g << 8) | b;
1563
+ }
1415
1564
 
1416
1565
  //------------------------------------------------------------------------------
1417
1566
  // Coordinate
@@ -3283,6 +3432,7 @@
3283
3432
  ]);
3284
3433
  const invalidateChartEvaluationCommands = new Set([
3285
3434
  "EVALUATE_CELLS",
3435
+ "EVALUATE_CHARTS",
3286
3436
  "UPDATE_CELL",
3287
3437
  "UNHIDE_COLUMNS_ROWS",
3288
3438
  "HIDE_COLUMNS_ROWS",
@@ -3319,6 +3469,7 @@
3319
3469
  "RESIZE_SHEETVIEW",
3320
3470
  "SET_VIEWPORT_OFFSET",
3321
3471
  "EVALUATE_CELLS",
3472
+ "EVALUATE_CHARTS",
3322
3473
  "SET_FORMULA_VISIBILITY",
3323
3474
  "UPDATE_FILTER",
3324
3475
  ]);
@@ -6523,7 +6674,7 @@
6523
6674
  const POSTFIX_UNARY_OPERATORS = ["%"];
6524
6675
  const OPERATORS = "+,-,*,/,:,=,<>,>=,>,<=,<,^,&".split(",").concat(POSTFIX_UNARY_OPERATORS);
6525
6676
  function tokenize(str, locale = DEFAULT_LOCALE) {
6526
- str = replaceSpecialSpaces(str);
6677
+ str = replaceNewLines(str);
6527
6678
  const chars = new TokenizingChars(str);
6528
6679
  const result = [];
6529
6680
  while (!chars.isOver()) {
@@ -6670,12 +6821,12 @@
6670
6821
  if (length) {
6671
6822
  return { type: "SPACE", value: NEWLINE.repeat(length) };
6672
6823
  }
6673
- while (chars.current === " ") {
6674
- length++;
6675
- chars.shift();
6824
+ let spaces = "";
6825
+ while (chars.current && chars.current.match(whiteSpaceRegexp)) {
6826
+ spaces += chars.shift();
6676
6827
  }
6677
- if (length) {
6678
- return { type: "SPACE", value: " ".repeat(length) };
6828
+ if (spaces) {
6829
+ return { type: "SPACE", value: spaces };
6679
6830
  }
6680
6831
  return null;
6681
6832
  }
@@ -7534,6 +7685,16 @@
7534
7685
  }
7535
7686
  return domainToString([...domain.slice(0, index), ...domain.slice(index + 1)]);
7536
7687
  }
7688
+ function sortPivotTree(tree, baseDomain, sortFn) {
7689
+ const sortedTree = [...tree];
7690
+ const domain = [...baseDomain];
7691
+ sortedTree.sort((node1, node2) => sortFn([...domain, node1], [...domain, node2]));
7692
+ for (const node of tree) {
7693
+ const children = sortPivotTree(node.children, [...domain, node], sortFn);
7694
+ node.children = children;
7695
+ }
7696
+ return sortedTree;
7697
+ }
7537
7698
 
7538
7699
  const pivotTimeAdapterRegistry = new Registry();
7539
7700
  function pivotTimeAdapter(granularity) {
@@ -8044,6 +8205,29 @@
8044
8205
  format: `${" ".repeat(indent)}${format}* `,
8045
8206
  };
8046
8207
  }
8208
+ function isSortedColumnValid(sortedColumn, pivot) {
8209
+ try {
8210
+ if (!pivot.getMeasure(sortedColumn.measure)) {
8211
+ return false;
8212
+ }
8213
+ const columns = pivot.definition.columns;
8214
+ for (let i = 0; i < sortedColumn.domain.length; i++) {
8215
+ if (columns[i].nameWithGranularity !== sortedColumn.domain[i].field) {
8216
+ return false;
8217
+ }
8218
+ const possibleValues = pivot
8219
+ .getPossibleFieldValues(columns[i])
8220
+ .map((v) => v.value);
8221
+ if (!possibleValues.includes(sortedColumn.domain[i].value)) {
8222
+ return false;
8223
+ }
8224
+ }
8225
+ return true;
8226
+ }
8227
+ catch (e) {
8228
+ return false;
8229
+ }
8230
+ }
8047
8231
 
8048
8232
  class CellClipboardHandler extends AbstractCellClipboardHandler {
8049
8233
  isCutAllowed(data) {
@@ -14652,7 +14836,7 @@ stores.inject(MyMetaStore, storeInstance);
14652
14836
  CellValueType.boolean,
14653
14837
  ];
14654
14838
  function cellsSortingCriterion(sortingOrder) {
14655
- const inverse = sortingOrder === "ascending" ? 1 : -1;
14839
+ const inverse = sortingOrder === "asc" ? 1 : -1;
14656
14840
  return (left, right) => {
14657
14841
  if (left.type === CellValueType.empty) {
14658
14842
  return right.type === CellValueType.empty ? 0 : 1;
@@ -14744,7 +14928,7 @@ stores.inject(MyMetaStore, storeInstance);
14744
14928
  const sortColumns = [];
14745
14929
  const nRows = matrix.length;
14746
14930
  for (let i = 0; i < criteria.length; i += 2) {
14747
- sortingOrders.push(toBoolean(toScalar(criteria[i + 1])?.value) ? "ascending" : "descending");
14931
+ sortingOrders.push(toBoolean(toScalar(criteria[i + 1])?.value) ? "asc" : "desc");
14748
14932
  const sortColumn = criteria[i];
14749
14933
  if (isMatrix(sortColumn) && (sortColumn.length > 1 || sortColumn[0].length > 1)) {
14750
14934
  assert(() => sortColumn.length === 1 && sortColumn[0].length === nRows, _t("Wrong size for %s. Expected a range of size 1x%s. Got %sx%s.", `sort_column${i + 1}`, nRows, sortColumn.length, sortColumn[0].length));
@@ -14761,12 +14945,12 @@ stores.inject(MyMetaStore, storeInstance);
14761
14945
  if (sortColumns.length === 0) {
14762
14946
  for (let i = 0; i < matrix[0].length; i++) {
14763
14947
  sortColumns.push(matrix.map((row) => row[i].value));
14764
- sortingOrders.push("ascending");
14948
+ sortingOrders.push("asc");
14765
14949
  }
14766
14950
  }
14767
14951
  const sortingCriteria = {
14768
- descending: cellsSortingCriterion("descending"),
14769
- ascending: cellsSortingCriterion("ascending"),
14952
+ desc: cellsSortingCriterion("desc"),
14953
+ asc: cellsSortingCriterion("asc"),
14770
14954
  };
14771
14955
  const indexes = range(0, matrix.length);
14772
14956
  indexes.sort((a, b) => {
@@ -20490,20 +20674,8 @@ stores.inject(MyMetaStore, storeInstance);
20490
20674
  replaceSelectedRange(zone) {
20491
20675
  const ref = this.getZoneReference(zone);
20492
20676
  const currentToken = this.tokenAtCursor;
20493
- let replaceStart = this.selectionStart;
20494
- if (currentToken?.type === "REFERENCE") {
20495
- replaceStart = currentToken.start;
20496
- }
20497
- else if (currentToken?.type === "RIGHT_PAREN") {
20498
- // match left parenthesis
20499
- const leftParenthesisIndex = this.currentTokens.findIndex((token) => token.type === "LEFT_PAREN" && token.parenthesesCode === currentToken.parenthesesCode);
20500
- const functionToken = this.currentTokens[leftParenthesisIndex - 1];
20501
- if (functionToken === undefined) {
20502
- return;
20503
- }
20504
- replaceStart = functionToken.start;
20505
- }
20506
- this.replaceText(ref, replaceStart, this.selectionEnd);
20677
+ const start = currentToken?.type === "REFERENCE" ? currentToken.start : this.selectionStart;
20678
+ this.replaceText(ref, start, this.selectionEnd);
20507
20679
  }
20508
20680
  /**
20509
20681
  * Replace the reference of the old zone by the new one.
@@ -20536,17 +20708,6 @@ stores.inject(MyMetaStore, storeInstance);
20536
20708
  getZoneReference(zone) {
20537
20709
  const inputSheetId = this.sheetId;
20538
20710
  const sheetId = this.getters.getActiveSheetId();
20539
- if (zone.top === zone.bottom && zone.left === zone.right) {
20540
- const position = { sheetId, col: zone.left, row: zone.top };
20541
- const pivotId = this.getters.getPivotIdFromPosition(position);
20542
- const pivotCell = this.getters.getPivotCellFromPosition(position);
20543
- const cell = this.getters.getCell(position);
20544
- if (pivotId && pivotCell.type !== "EMPTY" && !cell?.isFormula) {
20545
- const formulaPivotId = this.getters.getPivotFormulaId(pivotId);
20546
- const formula = createPivotFormula(formulaPivotId, pivotCell);
20547
- return localizeFormula(formula, this.getters.getLocale()).slice(1); // strip leading =
20548
- }
20549
- }
20550
20711
  const range = this.getters.getRangeFromZone(sheetId, zone);
20551
20712
  return this.getters.getSelectionRangeString(range, inputSheetId);
20552
20713
  }
@@ -20717,37 +20878,21 @@ stores.inject(MyMetaStore, storeInstance);
20717
20878
  const editionSheetId = this.sheetId;
20718
20879
  const rangeColor = (rangeString) => {
20719
20880
  const colorIndex = this.colorIndexByRange[rangeString];
20720
- return colors$1[colorIndex % colors$1.length];
20881
+ return colors[colorIndex % colors.length];
20721
20882
  };
20722
- const highlights = [];
20723
- for (const range of this.getReferencedRanges()) {
20883
+ return this.getReferencedRanges().map((range) => {
20724
20884
  const rangeString = this.getters.getRangeString(range, editionSheetId);
20725
20885
  const { numberOfRows, numberOfCols } = zoneToDimension(range.zone);
20726
20886
  const zone = numberOfRows * numberOfCols === 1
20727
20887
  ? this.getters.expandZone(range.sheetId, range.zone)
20728
20888
  : range.zone;
20729
- highlights.push({
20889
+ return {
20730
20890
  zone,
20731
20891
  color: rangeColor(rangeString),
20732
20892
  sheetId: range.sheetId,
20733
20893
  interactive: true,
20734
- });
20735
- }
20736
- const activeSheetId = this.getters.getActiveSheetId();
20737
- const selectionZone = this.model.selection.getAnchor().zone;
20738
- const isSelectionHightlighted = highlights.find((highlight) => highlight.sheetId === activeSheetId && isEqual(highlight.zone, selectionZone));
20739
- if (this.editionMode === "selecting" && !isSelectionHightlighted) {
20740
- highlights.push({
20741
- zone: selectionZone,
20742
- color: "#445566",
20743
- sheetId: activeSheetId,
20744
- dashed: true,
20745
- interactive: false,
20746
- noFill: true,
20747
- thinLine: true,
20748
- });
20749
- }
20750
- return highlights;
20894
+ };
20895
+ });
20751
20896
  }
20752
20897
  /**
20753
20898
  * Return ranges currently referenced in the composer
@@ -20975,6 +21120,7 @@ stores.inject(MyMetaStore, storeInstance);
20975
21120
  return error;
20976
21121
  },
20977
21122
  isBadExpression: true,
21123
+ normalizedFormula: tokens.map((t) => t.value).join(""),
20978
21124
  };
20979
21125
  }
20980
21126
  }
@@ -21102,6 +21248,7 @@ stores.inject(MyMetaStore, storeInstance);
21102
21248
  symbols,
21103
21249
  tokens,
21104
21250
  isBadExpression: false,
21251
+ normalizedFormula: cacheKey,
21105
21252
  };
21106
21253
  return compiledFormula;
21107
21254
  }
@@ -21366,6 +21513,7 @@ stores.inject(MyMetaStore, storeInstance);
21366
21513
  description: definition.name,
21367
21514
  htmlContent: [{ value: str, color: tokenColors.NUMBER }],
21368
21515
  fuzzySearchKey: str + definition.name,
21516
+ alwaysExpanded: true,
21369
21517
  };
21370
21518
  })
21371
21519
  .filter(isDefined);
@@ -27759,6 +27907,19 @@ stores.inject(MyMetaStore, storeInstance);
27759
27907
  ["GaugeLowerInflectionPointNaN" /* CommandResult.GaugeLowerInflectionPointNaN */]: _t("The lower inflection point value must be a number"),
27760
27908
  ["GaugeUpperInflectionPointNaN" /* CommandResult.GaugeUpperInflectionPointNaN */]: _t("The upper inflection point value must be a number"),
27761
27909
  },
27910
+ GeoChart: {
27911
+ ColorScales: {
27912
+ blues: _t("Blues"),
27913
+ cividis: _t("Cividis"),
27914
+ greens: _t("Greens"),
27915
+ greys: _t("Greys"),
27916
+ oranges: _t("Oranges"),
27917
+ purples: _t("Purples"),
27918
+ rainbow: _t("Rainbow"),
27919
+ reds: _t("Reds"),
27920
+ viridis: _t("Viridis"),
27921
+ },
27922
+ },
27762
27923
  };
27763
27924
  const CustomCurrencyTerms = {
27764
27925
  Custom: _t("Custom"),
@@ -28162,6 +28323,26 @@ stores.inject(MyMetaStore, storeInstance);
28162
28323
  locale: getters.getLocale(),
28163
28324
  };
28164
28325
  }
28326
+ function getGeoChartData(definition, dataSets, labelRange, getters) {
28327
+ const labelValues = getChartLabelValues(getters, dataSets, labelRange);
28328
+ let labels = labelValues.formattedValues;
28329
+ if (definition.dataSetsHaveTitle) {
28330
+ labels.shift();
28331
+ }
28332
+ let dataSetsValues = getChartDatasetValues(getters, dataSets);
28333
+ ({ labels, dataSetsValues } = aggregateDataForLabels(labels, dataSetsValues));
28334
+ const format = getChartDatasetFormat(getters, dataSets, "left") ||
28335
+ getChartDatasetFormat(getters, dataSets, "right");
28336
+ return {
28337
+ dataSetsValues,
28338
+ axisFormats: { y: format },
28339
+ labels,
28340
+ locale: getters.getLocale(),
28341
+ availableRegions: getters.getGeoChartAvailableRegions(),
28342
+ geoFeatureNameToId: getters.geoFeatureNameToId,
28343
+ getGeoJsonFeatures: getters.getGeoJsonFeatures,
28344
+ };
28345
+ }
28165
28346
  function getTrendDatasetForBarChart(config, data) {
28166
28347
  const filteredValues = [];
28167
28348
  const filteredLabels = [];
@@ -28714,6 +28895,43 @@ stores.inject(MyMetaStore, storeInstance);
28714
28895
  }
28715
28896
  return datasets;
28716
28897
  }
28898
+ function getGeoChartDatasets(definition, args) {
28899
+ const { availableRegions, dataSetsValues, labels } = args;
28900
+ const regionName = definition.region || availableRegions[0]?.id;
28901
+ const features = regionName ? args.getGeoJsonFeatures(regionName) : undefined;
28902
+ const dataset = {
28903
+ outline: features,
28904
+ showOutline: !!features,
28905
+ data: [],
28906
+ };
28907
+ if (features && regionName) {
28908
+ const labelsAndValues = {};
28909
+ if (dataSetsValues[0]) {
28910
+ for (let i = 0; i < dataSetsValues[0].data.length; i++) {
28911
+ if (!labels[i] || dataSetsValues[0].data[i] === undefined) {
28912
+ continue;
28913
+ }
28914
+ const featureId = args.geoFeatureNameToId(regionName, labels[i]);
28915
+ if (featureId) {
28916
+ labelsAndValues[featureId] = { value: dataSetsValues[0].data[i], label: labels[i] };
28917
+ }
28918
+ }
28919
+ }
28920
+ for (const feature of features) {
28921
+ if (!feature.id) {
28922
+ continue;
28923
+ }
28924
+ dataset.data.push({
28925
+ feature: {
28926
+ ...feature,
28927
+ properties: { name: labelsAndValues[feature.id]?.label },
28928
+ },
28929
+ value: labelsAndValues[feature.id]?.value,
28930
+ });
28931
+ }
28932
+ }
28933
+ return [dataset];
28934
+ }
28717
28935
  function getTrendingLineDataSet(dataset, config, data) {
28718
28936
  const defaultBorderColor = colorToRGBA(dataset.backgroundColor);
28719
28937
  defaultBorderColor.a = 1;
@@ -29091,6 +29309,44 @@ stores.inject(MyMetaStore, storeInstance);
29091
29309
  },
29092
29310
  };
29093
29311
  }
29312
+ function getGeoChartScales(definition, args) {
29313
+ const { locale, axisFormats, availableRegions } = args;
29314
+ const geoLegendPosition = legendPositionToGeoLegendPosition(definition.legendPosition);
29315
+ const region = definition.region
29316
+ ? availableRegions.find((r) => r.id === definition.region)
29317
+ : availableRegions[0];
29318
+ const format = axisFormats?.y || axisFormats?.y1;
29319
+ return {
29320
+ projection: {
29321
+ // projection: region?.defaultProjection,
29322
+ projection: getGeoChartProjection(region?.defaultProjection || "mercator"),
29323
+ axis: "x",
29324
+ },
29325
+ color: {
29326
+ axis: "x",
29327
+ display: definition.legendPosition !== "none",
29328
+ border: { color: GRAY_300 },
29329
+ grid: { color: GRAY_300 },
29330
+ ticks: {
29331
+ color: chartFontColor(definition.background),
29332
+ callback: formatTickValue({ locale, format }),
29333
+ },
29334
+ legend: {
29335
+ position: geoLegendPosition,
29336
+ align: geoLegendPosition.includes("right") ? "left" : "right",
29337
+ margin: getLegendMargin(definition),
29338
+ },
29339
+ interpolate: getRuntimeColorScale(definition),
29340
+ missing: definition.missingValueColor || "#ffffff",
29341
+ },
29342
+ };
29343
+ }
29344
+ function getGeoChartProjection(projection) {
29345
+ if (projection === "conicConformal") {
29346
+ return window.ChartGeo.geoConicConformal().rotate([100, 0]); // Centered on the US
29347
+ }
29348
+ return projection;
29349
+ }
29094
29350
  function getChartAxisTitleRuntime(design) {
29095
29351
  if (design?.title?.text) {
29096
29352
  const { text, color, align, italic, bold } = design.title;
@@ -29154,6 +29410,44 @@ stores.inject(MyMetaStore, storeInstance);
29154
29410
  };
29155
29411
  }
29156
29412
  }
29413
+ function getRuntimeColorScale(definition) {
29414
+ if (!definition.colorScale || typeof definition.colorScale === "string") {
29415
+ return definition.colorScale || "oranges";
29416
+ }
29417
+ const scaleColors = [{ value: 0, color: definition.colorScale.minColor }];
29418
+ if (definition.colorScale.midColor) {
29419
+ scaleColors.push({ value: 0.5, color: definition.colorScale.midColor });
29420
+ }
29421
+ scaleColors.push({ value: 1, color: definition.colorScale.maxColor });
29422
+ return getColorScale(scaleColors);
29423
+ }
29424
+ function getLegendMargin(definition) {
29425
+ switch (definition.legendPosition) {
29426
+ case "top":
29427
+ case "right":
29428
+ const hasTitle = !!definition.title.text;
29429
+ const topMargin = hasTitle ? CHART_PADDING_TOP + 30 : CHART_PADDING_TOP;
29430
+ return { top: topMargin, left: CHART_PADDING$1, right: CHART_PADDING$1 };
29431
+ case "bottom":
29432
+ case "left":
29433
+ case "none":
29434
+ return { left: CHART_PADDING$1, right: CHART_PADDING$1, bottom: CHART_PADDING_BOTTOM };
29435
+ }
29436
+ }
29437
+ function legendPositionToGeoLegendPosition(position) {
29438
+ switch (position) {
29439
+ case "top":
29440
+ return "top-left";
29441
+ case "right":
29442
+ return "top-right";
29443
+ case "bottom":
29444
+ return "bottom-right";
29445
+ case "left":
29446
+ return "bottom-left";
29447
+ case "none":
29448
+ return "bottom-left";
29449
+ }
29450
+ }
29157
29451
 
29158
29452
  function getChartShowValues(definition, args) {
29159
29453
  const { axisFormats, locale } = args;
@@ -29313,6 +29607,25 @@ stores.inject(MyMetaStore, storeInstance);
29313
29607
  },
29314
29608
  };
29315
29609
  }
29610
+ function getGeoChartTooltip(definition, args) {
29611
+ const { locale, axisFormats } = args;
29612
+ const format = axisFormats?.y || axisFormats?.y1;
29613
+ return {
29614
+ filter: function (tooltipItem) {
29615
+ return tooltipItem.raw.value !== undefined;
29616
+ },
29617
+ callbacks: {
29618
+ label: function (tooltipItem) {
29619
+ const rawItem = tooltipItem.raw;
29620
+ const xLabel = rawItem.feature.properties.name;
29621
+ const yLabel = rawItem.value;
29622
+ const toolTipFormat = !format && Math.abs(yLabel) >= 1000 ? "#,##" : format;
29623
+ const yLabelStr = formatValue(yLabel, { format: toolTipFormat, locale });
29624
+ return xLabel ? `${xLabel}: ${yLabelStr}` : yLabelStr;
29625
+ },
29626
+ },
29627
+ };
29628
+ }
29316
29629
  function calculatePercentage(dataset, dataIndex) {
29317
29630
  const numericData = dataset.filter((value) => typeof value === "number");
29318
29631
  const total = numericData.reduce((sum, value) => sum + value, 0);
@@ -29339,6 +29652,10 @@ stores.inject(MyMetaStore, storeInstance);
29339
29652
  getComboChartDatasets: getComboChartDatasets,
29340
29653
  getComboChartLegend: getComboChartLegend,
29341
29654
  getData: getData,
29655
+ getGeoChartData: getGeoChartData,
29656
+ getGeoChartDatasets: getGeoChartDatasets,
29657
+ getGeoChartScales: getGeoChartScales,
29658
+ getGeoChartTooltip: getGeoChartTooltip,
29342
29659
  getLineChartData: getLineChartData,
29343
29660
  getLineChartDatasets: getLineChartDatasets,
29344
29661
  getLineChartLegend: getLineChartLegend,
@@ -29912,6 +30229,133 @@ stores.inject(MyMetaStore, storeInstance);
29912
30229
  return clip(value, minValue, maxValue);
29913
30230
  }
29914
30231
 
30232
+ class GeoChart extends AbstractChart {
30233
+ dataSets;
30234
+ labelRange;
30235
+ background;
30236
+ legendPosition;
30237
+ type = "geo";
30238
+ dataSetsHaveTitle;
30239
+ dataSetDesign;
30240
+ colorScale;
30241
+ missingValueColor;
30242
+ region;
30243
+ constructor(definition, sheetId, getters) {
30244
+ super(definition, sheetId, getters);
30245
+ this.dataSets = createDataSets(getters, definition.dataSets, sheetId, definition.dataSetsHaveTitle);
30246
+ this.labelRange = createValidRange(getters, sheetId, definition.labelRange);
30247
+ this.background = definition.background;
30248
+ this.legendPosition = definition.legendPosition;
30249
+ this.dataSetsHaveTitle = definition.dataSetsHaveTitle;
30250
+ this.dataSetDesign = definition.dataSets;
30251
+ this.colorScale = definition.colorScale;
30252
+ this.missingValueColor = definition.missingValueColor;
30253
+ this.region = definition.region;
30254
+ }
30255
+ static transformDefinition(definition, executed) {
30256
+ return transformChartDefinitionWithDataSetsWithZone(definition, executed);
30257
+ }
30258
+ static validateChartDefinition(validator, definition) {
30259
+ return validator.checkValidations(definition, checkDataset, checkLabelRange);
30260
+ }
30261
+ static getDefinitionFromContextCreation(context) {
30262
+ return {
30263
+ background: context.background,
30264
+ dataSets: context.range ?? [],
30265
+ dataSetsHaveTitle: context.dataSetsHaveTitle ?? false,
30266
+ legendPosition: context.legendPosition ?? "top",
30267
+ title: context.title || { text: "" },
30268
+ type: "geo",
30269
+ labelRange: context.auxiliaryRange || undefined,
30270
+ aggregated: context.aggregated,
30271
+ };
30272
+ }
30273
+ getContextCreation() {
30274
+ const range = [];
30275
+ for (const [i, dataSet] of this.dataSets.entries()) {
30276
+ range.push({
30277
+ ...this.dataSetDesign?.[i],
30278
+ dataRange: this.getters.getRangeString(dataSet.dataRange, this.sheetId),
30279
+ });
30280
+ }
30281
+ return {
30282
+ ...this,
30283
+ range,
30284
+ auxiliaryRange: this.labelRange
30285
+ ? this.getters.getRangeString(this.labelRange, this.sheetId)
30286
+ : undefined,
30287
+ };
30288
+ }
30289
+ copyForSheetId(sheetId) {
30290
+ const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
30291
+ const labelRange = copyLabelRangeWithNewSheetId(this.sheetId, sheetId, this.labelRange);
30292
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange, sheetId);
30293
+ return new GeoChart(definition, sheetId, this.getters);
30294
+ }
30295
+ copyInSheetId(sheetId) {
30296
+ const definition = this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange, sheetId);
30297
+ return new GeoChart(definition, sheetId, this.getters);
30298
+ }
30299
+ getDefinition() {
30300
+ return this.getDefinitionWithSpecificDataSets(this.dataSets, this.labelRange);
30301
+ }
30302
+ getDefinitionWithSpecificDataSets(dataSets, labelRange, targetSheetId) {
30303
+ const ranges = [];
30304
+ for (const [i, dataSet] of dataSets.entries()) {
30305
+ ranges.push({
30306
+ ...this.dataSetDesign?.[i],
30307
+ dataRange: this.getters.getRangeString(dataSet.dataRange, targetSheetId || this.sheetId),
30308
+ });
30309
+ }
30310
+ return {
30311
+ type: "geo",
30312
+ dataSetsHaveTitle: dataSets.length ? Boolean(dataSets[0].labelCell) : false,
30313
+ background: this.background,
30314
+ dataSets: ranges,
30315
+ legendPosition: this.legendPosition,
30316
+ labelRange: labelRange
30317
+ ? this.getters.getRangeString(labelRange, targetSheetId || this.sheetId)
30318
+ : undefined,
30319
+ title: this.title,
30320
+ colorScale: this.colorScale,
30321
+ missingValueColor: this.missingValueColor,
30322
+ region: this.region,
30323
+ };
30324
+ }
30325
+ getDefinitionForExcel() {
30326
+ return undefined;
30327
+ }
30328
+ updateRanges(applyChange) {
30329
+ const { dataSets, labelRange, isStale } = updateChartRangesWithDataSets(this.getters, applyChange, this.dataSets, this.labelRange);
30330
+ if (!isStale) {
30331
+ return this;
30332
+ }
30333
+ const definition = this.getDefinitionWithSpecificDataSets(dataSets, labelRange);
30334
+ return new GeoChart(definition, this.sheetId, this.getters);
30335
+ }
30336
+ }
30337
+ function createGeoChartRuntime(chart, getters) {
30338
+ const definition = chart.getDefinition();
30339
+ const chartData = getGeoChartData(definition, chart.dataSets, chart.labelRange, getters);
30340
+ const config = {
30341
+ type: "choropleth",
30342
+ data: {
30343
+ datasets: getGeoChartDatasets(definition, chartData),
30344
+ },
30345
+ options: {
30346
+ ...CHART_COMMON_OPTIONS,
30347
+ layout: getChartLayout(),
30348
+ scales: getGeoChartScales(definition, chartData),
30349
+ plugins: {
30350
+ title: getChartTitle(definition),
30351
+ tooltip: getGeoChartTooltip(definition, chartData),
30352
+ legend: { display: false },
30353
+ },
30354
+ },
30355
+ };
30356
+ return { chartJsConfig: config, background: chart.background || BACKGROUND_CHART_COLOR };
30357
+ }
30358
+
29915
30359
  class LineChart extends AbstractChart {
29916
30360
  dataSets;
29917
30361
  labelRange;
@@ -30905,6 +31349,15 @@ stores.inject(MyMetaStore, storeInstance);
30905
31349
  getChartDefinitionFromContextCreation: RadarChart.getDefinitionFromContextCreation,
30906
31350
  sequence: 80,
30907
31351
  });
31352
+ chartRegistry.add("geo", {
31353
+ match: (type) => type === "geo",
31354
+ createChart: (definition, sheetId, getters) => new GeoChart(definition, sheetId, getters),
31355
+ getChartRuntime: createGeoChartRuntime,
31356
+ validateChartDefinition: GeoChart.validateChartDefinition,
31357
+ transformDefinition: GeoChart.transformDefinition,
31358
+ getChartDefinitionFromContextCreation: GeoChart.getDefinitionFromContextCreation,
31359
+ sequence: 90,
31360
+ });
30908
31361
  const chartComponentRegistry = new Registry();
30909
31362
  chartComponentRegistry.add("line", ChartJsComponent);
30910
31363
  chartComponentRegistry.add("bar", ChartJsComponent);
@@ -30916,6 +31369,7 @@ stores.inject(MyMetaStore, storeInstance);
30916
31369
  chartComponentRegistry.add("waterfall", ChartJsComponent);
30917
31370
  chartComponentRegistry.add("pyramid", ChartJsComponent);
30918
31371
  chartComponentRegistry.add("radar", ChartJsComponent);
31372
+ chartComponentRegistry.add("geo", ChartJsComponent);
30919
31373
  const chartCategories = {
30920
31374
  line: _t("Line"),
30921
31375
  column: _t("Column"),
@@ -31075,6 +31529,13 @@ stores.inject(MyMetaStore, storeInstance);
31075
31529
  subtypeDefinition: { fillArea: true },
31076
31530
  category: "misc",
31077
31531
  preview: "o-spreadsheet-ChartPreview.FILLED_RADAR_CHART",
31532
+ })
31533
+ .add("geo", {
31534
+ displayName: _t("Geo Chart"),
31535
+ chartSubtype: "geo",
31536
+ chartType: "geo",
31537
+ category: "misc",
31538
+ preview: "o-spreadsheet-ChartPreview.GEO_CHART",
31078
31539
  });
31079
31540
 
31080
31541
  /**
@@ -31925,18 +32386,66 @@ stores.inject(MyMetaStore, storeInstance);
31925
32386
  },
31926
32387
  };
31927
32388
 
31928
- css /*SCSS*/ `
31929
- .o-filter-menu-value {
31930
- padding: 4px;
31931
- line-height: 20px;
31932
- height: 28px;
31933
- .o-filter-menu-value-checked {
31934
- width: 20px;
32389
+ const CHECK_SVG = /*xml*/ `
32390
+ <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'>
32391
+ <path fill='none' stroke='#FFF' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/>
32392
+ </svg>
32393
+ `;
32394
+ const CHECKBOX_WIDTH = 14;
32395
+ css /* scss */ `
32396
+ label.o-checkbox {
32397
+ input {
32398
+ appearance: none;
32399
+ -webkit-appearance: none;
32400
+ -moz-appearance: none;
32401
+ border-radius: 0;
32402
+ width: ${CHECKBOX_WIDTH}px;
32403
+ height: ${CHECKBOX_WIDTH}px;
32404
+ vertical-align: top;
32405
+ box-sizing: border-box;
32406
+ outline: none;
32407
+ border: 1px solid ${GRAY_300};
32408
+ cursor: pointer;
32409
+
32410
+ &:hover {
32411
+ border-color: ${ACTION_COLOR};
32412
+ }
32413
+
32414
+ &:checked {
32415
+ background: url("data:image/svg+xml,${encodeURIComponent(CHECK_SVG)}");
32416
+ background-color: ${ACTION_COLOR};
32417
+ border-color: ${ACTION_COLOR};
32418
+ }
32419
+
32420
+ &:focus {
32421
+ outline: none;
32422
+ box-shadow: 0 0 0 0.25rem rgba(113, 75, 103, 0.25);
32423
+ border-color: ${ACTION_COLOR};
32424
+ }
31935
32425
  }
31936
32426
  }
31937
32427
  `;
32428
+ class Checkbox extends owl.Component {
32429
+ static template = "o-spreadsheet.Checkbox";
32430
+ static props = {
32431
+ label: { type: String, optional: true },
32432
+ value: { type: Boolean, optional: true },
32433
+ className: { type: String, optional: true },
32434
+ name: { type: String, optional: true },
32435
+ title: { type: String, optional: true },
32436
+ disabled: { type: Boolean, optional: true },
32437
+ onChange: Function,
32438
+ };
32439
+ static defaultProps = { value: false };
32440
+ onChange(ev) {
32441
+ const value = ev.target.checked;
32442
+ this.props.onChange(value);
32443
+ }
32444
+ }
32445
+
31938
32446
  class FilterMenuValueItem extends owl.Component {
31939
32447
  static template = "o-spreadsheet-FilterMenuValueItem";
32448
+ static components = { Checkbox };
31940
32449
  static props = {
31941
32450
  value: String,
31942
32451
  isChecked: Boolean,
@@ -31974,8 +32483,6 @@ stores.inject(MyMetaStore, storeInstance);
31974
32483
  .o-filter-menu-item {
31975
32484
  display: flex;
31976
32485
  box-sizing: border-box;
31977
- height: ${MENU_ITEM_HEIGHT}px;
31978
- padding: 4px 4px 4px 0px;
31979
32486
  cursor: pointer;
31980
32487
  user-select: none;
31981
32488
 
@@ -31984,14 +32491,6 @@ stores.inject(MyMetaStore, storeInstance);
31984
32491
  }
31985
32492
  }
31986
32493
 
31987
- input {
31988
- box-sizing: border-box;
31989
- margin-bottom: 5px;
31990
- border: 1px solid #949494;
31991
- height: 24px;
31992
- padding-right: 28px;
31993
- }
31994
-
31995
32494
  .o-search-icon {
31996
32495
  right: 5px;
31997
32496
  top: 3px;
@@ -32008,19 +32507,12 @@ stores.inject(MyMetaStore, storeInstance);
32008
32507
  display: flex;
32009
32508
  flex-direction: row;
32010
32509
  margin-bottom: 4px;
32011
-
32012
- .o-filter-menu-action-text {
32013
- cursor: pointer;
32014
- margin-right: 10px;
32015
- color: blue;
32016
- text-decoration: underline;
32017
- }
32018
32510
  }
32019
32511
 
32020
32512
  .o-filter-menu-list {
32021
32513
  flex: auto;
32022
32514
  overflow-y: auto;
32023
- border: 1px solid #949494;
32515
+ border: 1px solid ${GRAY_300};
32024
32516
 
32025
32517
  .o-filter-menu-no-values {
32026
32518
  color: #949494;
@@ -34212,6 +34704,21 @@ stores.inject(MyMetaStore, storeInstance);
34212
34704
  isReadonlyAllowed: true,
34213
34705
  icon: "o-spreadsheet-Icon.PIVOT",
34214
34706
  };
34707
+ const pivotSortingAsc = {
34708
+ name: _t("Ascending"),
34709
+ execute: (env) => sortPivot(env, "asc"),
34710
+ isActive: (env) => isPivotSortMenuItemActive(env, "asc"),
34711
+ };
34712
+ const pivotSortingDesc = {
34713
+ name: _t("Descending"),
34714
+ execute: (env) => sortPivot(env, "desc"),
34715
+ isActive: (env) => isPivotSortMenuItemActive(env, "desc"),
34716
+ };
34717
+ const noPivotSorting = {
34718
+ name: _t("No sorting"),
34719
+ execute: (env) => sortPivot(env, "none"),
34720
+ isActive: (env) => isPivotSortMenuItemActive(env, "none"),
34721
+ };
34215
34722
  const FIX_FORMULAS = {
34216
34723
  name: _t("Convert to individual formulas"),
34217
34724
  execute(env) {
@@ -34248,6 +34755,66 @@ stores.inject(MyMetaStore, storeInstance);
34248
34755
  },
34249
34756
  icon: "o-spreadsheet-Icon.PIVOT",
34250
34757
  };
34758
+ function canSortPivot(env) {
34759
+ const position = env.model.getters.getActivePosition();
34760
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
34761
+ if (!pivotId ||
34762
+ !env.model.getters.isExistingPivot(pivotId) ||
34763
+ !env.model.getters.isSpillPivotFormula(position)) {
34764
+ return false;
34765
+ }
34766
+ const pivot = env.model.getters.getPivot(pivotId);
34767
+ if (!pivot.isValid()) {
34768
+ return false;
34769
+ }
34770
+ const pivotCell = env.model.getters.getPivotCellFromPosition(position);
34771
+ return pivotCell.type === "VALUE" || pivotCell.type === "MEASURE_HEADER";
34772
+ }
34773
+ function sortPivot(env, order) {
34774
+ const position = env.model.getters.getActivePosition();
34775
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
34776
+ const pivotCell = env.model.getters.getPivotCellFromPosition(position);
34777
+ if (pivotCell.type === "EMPTY" || pivotCell.type === "HEADER" || !pivotId) {
34778
+ return;
34779
+ }
34780
+ if (order === "none") {
34781
+ env.model.dispatch("UPDATE_PIVOT", {
34782
+ pivotId: pivotId,
34783
+ pivot: {
34784
+ ...env.model.getters.getPivotCoreDefinition(pivotId),
34785
+ sortedColumn: undefined,
34786
+ },
34787
+ });
34788
+ return;
34789
+ }
34790
+ const pivot = env.model.getters.getPivot(pivotId);
34791
+ const colDomain = domainToColRowDomain(pivot, pivotCell.domain).colDomain;
34792
+ env.model.dispatch("UPDATE_PIVOT", {
34793
+ pivotId: pivotId,
34794
+ pivot: {
34795
+ ...env.model.getters.getPivotCoreDefinition(pivotId),
34796
+ sortedColumn: { domain: colDomain, order, measure: pivotCell.measure },
34797
+ },
34798
+ });
34799
+ }
34800
+ function isPivotSortMenuItemActive(env, order) {
34801
+ const position = env.model.getters.getActivePosition();
34802
+ const pivotId = env.model.getters.getPivotIdFromPosition(position);
34803
+ const pivotCell = env.model.getters.getPivotCellFromPosition(position);
34804
+ if (pivotCell.type === "EMPTY" || pivotCell.type === "HEADER" || !pivotId) {
34805
+ return false;
34806
+ }
34807
+ const pivot = env.model.getters.getPivot(pivotId);
34808
+ const colDomain = domainToColRowDomain(pivot, pivotCell.domain).colDomain;
34809
+ const sortedColumn = pivot.definition.sortedColumn;
34810
+ if (order === "none") {
34811
+ return !sortedColumn;
34812
+ }
34813
+ if (!sortedColumn || sortedColumn.order !== order) {
34814
+ return false;
34815
+ }
34816
+ return sortedColumn.measure === pivotCell.measure && deepEquals(sortedColumn.domain, colDomain);
34817
+ }
34251
34818
 
34252
34819
  //------------------------------------------------------------------------------
34253
34820
  // Context Menu Registry
@@ -34346,14 +34913,33 @@ stores.inject(MyMetaStore, storeInstance);
34346
34913
  name: INSERT_LINK_NAME,
34347
34914
  sequence: 150,
34348
34915
  separator: true,
34916
+ })
34917
+ .add("pivot_sorting", {
34918
+ name: _t("Sort pivot"),
34919
+ sequence: 155,
34920
+ icon: "o-spreadsheet-Icon.SORT_RANGE",
34921
+ isVisible: canSortPivot,
34349
34922
  })
34350
34923
  .add("pivot_fix_formulas", {
34351
34924
  ...FIX_FORMULAS,
34352
- sequence: 155,
34925
+ sequence: 160,
34353
34926
  })
34354
34927
  .add("pivot_properties", {
34355
34928
  ...pivotProperties,
34356
34929
  sequence: 170,
34930
+ separator: true,
34931
+ })
34932
+ .addChild("pivot_sorting_asc", ["pivot_sorting"], {
34933
+ ...pivotSortingAsc,
34934
+ sequence: 10,
34935
+ })
34936
+ .addChild("pivot_sorting_desc", ["pivot_sorting"], {
34937
+ ...pivotSortingDesc,
34938
+ sequence: 20,
34939
+ })
34940
+ .addChild("pivot_sorting_none", ["pivot_sorting"], {
34941
+ ...noPivotSorting,
34942
+ sequence: 30,
34357
34943
  });
34358
34944
 
34359
34945
  const sortRange = {
@@ -34366,7 +34952,7 @@ stores.inject(MyMetaStore, storeInstance);
34366
34952
  execute: (env) => {
34367
34953
  const { anchor, zones } = env.model.getters.getSelection();
34368
34954
  const sheetId = env.model.getters.getActiveSheetId();
34369
- interactiveSortSelection(env, sheetId, anchor.cell, zones[0], "ascending");
34955
+ interactiveSortSelection(env, sheetId, anchor.cell, zones[0], "asc");
34370
34956
  },
34371
34957
  icon: "o-spreadsheet-Icon.SORT_ASCENDING",
34372
34958
  };
@@ -34394,7 +34980,7 @@ stores.inject(MyMetaStore, storeInstance);
34394
34980
  execute: (env) => {
34395
34981
  const { anchor, zones } = env.model.getters.getSelection();
34396
34982
  const sheetId = env.model.getters.getActiveSheetId();
34397
- interactiveSortSelection(env, sheetId, anchor.cell, zones[0], "descending");
34983
+ interactiveSortSelection(env, sheetId, anchor.cell, zones[0], "desc");
34398
34984
  },
34399
34985
  icon: "o-spreadsheet-Icon.SORT_DESCENDING",
34400
34986
  };
@@ -34877,6 +35463,227 @@ stores.inject(MyMetaStore, storeInstance);
34877
35463
  }
34878
35464
  }
34879
35465
 
35466
+ class PositionMap {
35467
+ map = {};
35468
+ constructor(entries = []) {
35469
+ for (const [position, value] of entries) {
35470
+ this.set(position, value);
35471
+ }
35472
+ }
35473
+ set({ sheetId, col, row }, value) {
35474
+ const map = this.map;
35475
+ if (!map[sheetId]) {
35476
+ map[sheetId] = {};
35477
+ }
35478
+ if (!map[sheetId][col]) {
35479
+ map[sheetId][col] = {};
35480
+ }
35481
+ map[sheetId][col][row] = value;
35482
+ }
35483
+ get({ sheetId, col, row }) {
35484
+ return this.map[sheetId]?.[col]?.[row];
35485
+ }
35486
+ getSheet(sheetId) {
35487
+ return this.map[sheetId];
35488
+ }
35489
+ has({ sheetId, col, row }) {
35490
+ return this.map[sheetId]?.[col]?.[row] !== undefined;
35491
+ }
35492
+ delete({ sheetId, col, row }) {
35493
+ delete this.map[sheetId]?.[col]?.[row];
35494
+ }
35495
+ keys() {
35496
+ const map = this.map;
35497
+ const keys = [];
35498
+ for (const sheetId in map) {
35499
+ for (const col in map[sheetId]) {
35500
+ for (const row in map[sheetId][col]) {
35501
+ keys.push({ sheetId, col: parseInt(col), row: parseInt(row) });
35502
+ }
35503
+ }
35504
+ }
35505
+ return keys;
35506
+ }
35507
+ keysForSheet(sheetId) {
35508
+ const map = this.map[sheetId];
35509
+ if (!map) {
35510
+ return [];
35511
+ }
35512
+ const keys = [];
35513
+ for (const col in map) {
35514
+ for (const row in map[col]) {
35515
+ keys.push({ sheetId, col: parseInt(col), row: parseInt(row) });
35516
+ }
35517
+ }
35518
+ return keys;
35519
+ }
35520
+ *entries() {
35521
+ const map = this.map;
35522
+ for (const position of this.keys()) {
35523
+ const { sheetId, col, row } = position;
35524
+ yield [position, map[sheetId][col][row]];
35525
+ }
35526
+ }
35527
+ }
35528
+
35529
+ class FormulaFingerprintStore extends SpreadsheetStore {
35530
+ mutators = ["enable", "disable"];
35531
+ isInvalidated = false;
35532
+ fingerprintColors = {
35533
+ [DATA_FINGERPRINT]: "#D9D9D9",
35534
+ };
35535
+ isEnabled = false;
35536
+ colors = new PositionMap();
35537
+ handle(cmd) {
35538
+ if (isCoreCommand(cmd) && this.isEnabled) {
35539
+ this.isInvalidated = true;
35540
+ }
35541
+ switch (cmd.type) {
35542
+ case "UNDO":
35543
+ case "REDO":
35544
+ case "ACTIVATE_SHEET":
35545
+ if (this.isEnabled) {
35546
+ this.isInvalidated = true;
35547
+ }
35548
+ break;
35549
+ }
35550
+ }
35551
+ finalize() {
35552
+ if (this.isInvalidated) {
35553
+ this.isInvalidated = false;
35554
+ this.computeFingerprints();
35555
+ }
35556
+ }
35557
+ enable() {
35558
+ this.isEnabled = true;
35559
+ this.computeFingerprints();
35560
+ }
35561
+ disable() {
35562
+ this.isEnabled = false;
35563
+ this.colors = new PositionMap();
35564
+ }
35565
+ computeFingerprints() {
35566
+ this.colors = new PositionMap();
35567
+ const fingerprints = new PositionMap();
35568
+ const allFingerprints = new Set();
35569
+ const activeSheetId = this.getters.getActiveSheetId();
35570
+ const cells = this.getters.getCells(activeSheetId);
35571
+ for (const cellId in cells) {
35572
+ const fingerprint = this.computeFingerprint(cells[cellId]);
35573
+ if (!fingerprint) {
35574
+ continue;
35575
+ }
35576
+ allFingerprints.add(fingerprint);
35577
+ const position = this.getters.getCellPosition(cellId);
35578
+ fingerprints.set(position, fingerprint);
35579
+ }
35580
+ this.assignColors(allFingerprints);
35581
+ for (const [position, fingerprint] of fingerprints.entries()) {
35582
+ const color = this.fingerprintColors[fingerprint];
35583
+ this.colors.set(position, color);
35584
+ this.colorSpreadZone(position, color);
35585
+ }
35586
+ }
35587
+ colorSpreadZone(position, fingerprintColor) {
35588
+ const spreadZone = this.getters.getSpreadZone(position);
35589
+ if (!spreadZone) {
35590
+ return;
35591
+ }
35592
+ const sheetId = position.sheetId;
35593
+ for (let row = spreadZone.top; row <= spreadZone.bottom; row++) {
35594
+ for (let col = spreadZone.left; col <= spreadZone.right; col++) {
35595
+ const spreadPosition = { sheetId, col, row };
35596
+ this.colors.set(spreadPosition, fingerprintColor);
35597
+ }
35598
+ }
35599
+ }
35600
+ assignColors(fingerprints) {
35601
+ const colors = new AlternatingColorGenerator(fingerprints.size);
35602
+ Object.keys(this.fingerprintColors).forEach(() => colors.next());
35603
+ for (const fingerprint of fingerprints) {
35604
+ if (!this.fingerprintColors[fingerprint]) {
35605
+ this.fingerprintColors[fingerprint] = setColorAlpha(colors.next(), 0.8);
35606
+ }
35607
+ }
35608
+ }
35609
+ computeFingerprint(cell) {
35610
+ const position = this.getters.getCellPosition(cell.id);
35611
+ if (cell.isFormula) {
35612
+ return this.computeFormulaFingerprint(position, cell);
35613
+ }
35614
+ else {
35615
+ return this.getLiteralFingerprint(position);
35616
+ }
35617
+ }
35618
+ computeFormulaFingerprint(position, cell) {
35619
+ const dependencies = cell.compiledFormula.dependencies;
35620
+ const colCellOffset = position.col;
35621
+ const rowCellOffset = position.row;
35622
+ const positionSheetIndex = this.getters.getSheetIds().indexOf(position.sheetId);
35623
+ // As an optimization, we do not build each reference vector individually
35624
+ // to sum them up, but instead we directly add each component to the resulting
35625
+ // vector. This is equivalent to summing up all reference vectors.
35626
+ const fingerprintVector = {
35627
+ dx: 0,
35628
+ dy: 0,
35629
+ dSheet: 0,
35630
+ };
35631
+ for (const range of dependencies) {
35632
+ const zone = range.zone;
35633
+ const [left, right] = range.parts;
35634
+ const rangeSheetIndex = this.getters.getSheetIds().indexOf(range.sheetId);
35635
+ fingerprintVector.dSheet = rangeSheetIndex - positionSheetIndex;
35636
+ // in relative mode, we offset the col and row by the cell's position
35637
+ // in absolute mode, we offset the col and row relative to the sheet
35638
+ const isLeftUnbounded = range.isFullRow && !range.unboundedZone.hasHeader;
35639
+ const isTopUnbounded = range.isFullCol && !range.unboundedZone.hasHeader;
35640
+ const leftOffset = isLeftUnbounded || left?.colFixed ? 0 : colCellOffset;
35641
+ const topOffset = isTopUnbounded || left?.rowFixed ? 0 : rowCellOffset;
35642
+ const isRightFixed = (!right && left?.colFixed) || right?.colFixed;
35643
+ const isBottomFixed = (!right && left.rowFixed) || right?.rowFixed;
35644
+ const isRightUnbounded = range.unboundedZone.right === undefined;
35645
+ const isBottomUnbounded = range.unboundedZone.bottom === undefined;
35646
+ const rightOffset = isRightUnbounded || isRightFixed ? 0 : colCellOffset;
35647
+ const bottomOffset = isBottomUnbounded || isBottomFixed ? 0 : rowCellOffset;
35648
+ const referenceZone = reorderZone({
35649
+ top: zone.top - topOffset,
35650
+ left: zone.left - leftOffset,
35651
+ right: zone.right - rightOffset,
35652
+ bottom: zone.bottom - bottomOffset,
35653
+ });
35654
+ for (let dy = referenceZone.top; dy <= referenceZone.bottom; dy++) {
35655
+ for (let dx = referenceZone.left; dx <= referenceZone.right; dx++) {
35656
+ fingerprintVector.dx += dx;
35657
+ fingerprintVector.dy += dy;
35658
+ }
35659
+ }
35660
+ }
35661
+ // removes the index placeholders from the normalized formula
35662
+ // =|N0|+|N1|+|N0| -> =|N|+|N|+|N|
35663
+ const normalizedFormula = cell.compiledFormula.normalizedFormula.replace(/(|\w)(\d)(|)/g, "$1$3");
35664
+ return hash(fingerprintVector) + normalizedFormula;
35665
+ }
35666
+ getLiteralFingerprint(position) {
35667
+ const evaluatedCell = this.getters.getEvaluatedCell(position);
35668
+ switch (evaluatedCell.type) {
35669
+ case CellValueType.number:
35670
+ case CellValueType.boolean:
35671
+ return DATA_FINGERPRINT;
35672
+ case CellValueType.text:
35673
+ case CellValueType.empty:
35674
+ case CellValueType.error:
35675
+ return undefined;
35676
+ }
35677
+ }
35678
+ }
35679
+ function hash(vector) {
35680
+ return Object.entries(vector)
35681
+ .sort(([a], [b]) => a.localeCompare(b))
35682
+ .map(([_, value]) => value)
35683
+ .join(",");
35684
+ }
35685
+ const DATA_FINGERPRINT = "DATA_FINGERPRINT";
35686
+
34880
35687
  const hideCols = {
34881
35688
  name: HIDE_COLUMNS_NAME,
34882
35689
  execute: (env) => {
@@ -35048,6 +35855,19 @@ stores.inject(MyMetaStore, storeInstance);
35048
35855
  return env.model.getters.getGridLinesVisibility(sheetId);
35049
35856
  },
35050
35857
  };
35858
+ const irregularityMap = {
35859
+ name: _t("Irregularity map"),
35860
+ execute: (env) => {
35861
+ const fingerprintStore = env.getStore(FormulaFingerprintStore);
35862
+ if (fingerprintStore.isEnabled) {
35863
+ fingerprintStore.disable();
35864
+ }
35865
+ else {
35866
+ fingerprintStore.enable();
35867
+ }
35868
+ },
35869
+ icon: "o-spreadsheet-Icon.IRREGULARITY_MAP",
35870
+ };
35051
35871
  const viewFormulas = {
35052
35872
  name: _t("Formulas"),
35053
35873
  isActive: (env) => env.model.getters.shouldShowFormulas(),
@@ -35665,6 +36485,11 @@ stores.inject(MyMetaStore, storeInstance);
35665
36485
  .addChild("view_formulas", ["view", "show"], {
35666
36486
  ...viewFormulas,
35667
36487
  sequence: 10,
36488
+ })
36489
+ .addChild("view_irregularity_map", ["view"], {
36490
+ ...irregularityMap,
36491
+ sequence: 40,
36492
+ separator: true,
35668
36493
  })
35669
36494
  // ---------------------------------------------------------------------
35670
36495
  // INSERT MENU ITEMS
@@ -35967,62 +36792,6 @@ stores.inject(MyMetaStore, storeInstance);
35967
36792
  }
35968
36793
  const otRegistry = new OTRegistry();
35969
36794
 
35970
- const CHECK_SVG = /*xml*/ `
35971
- <svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 20 20'>
35972
- <path fill='none' stroke='#FFF' stroke-linecap='round' stroke-linejoin='round' stroke-width='3' d='m6 10 3 3 6-6'/>
35973
- </svg>
35974
- `;
35975
- const CHECKBOX_WIDTH = 14;
35976
- css /* scss */ `
35977
- label.o-checkbox {
35978
- input {
35979
- appearance: none;
35980
- -webkit-appearance: none;
35981
- -moz-appearance: none;
35982
- border-radius: 0;
35983
- width: ${CHECKBOX_WIDTH}px;
35984
- height: ${CHECKBOX_WIDTH}px;
35985
- vertical-align: top;
35986
- box-sizing: border-box;
35987
- outline: none;
35988
- border: 1px solid ${GRAY_300};
35989
-
35990
- &:hover {
35991
- border-color: ${ACTION_COLOR};
35992
- }
35993
-
35994
- &:checked {
35995
- background: url("data:image/svg+xml,${encodeURIComponent(CHECK_SVG)}");
35996
- background-color: ${ACTION_COLOR};
35997
- border-color: ${ACTION_COLOR};
35998
- }
35999
-
36000
- &:focus {
36001
- outline: none;
36002
- box-shadow: 0 0 0 0.25rem rgba(113, 75, 103, 0.25);
36003
- border-color: ${ACTION_COLOR};
36004
- }
36005
- }
36006
- }
36007
- `;
36008
- class Checkbox extends owl.Component {
36009
- static template = "o-spreadsheet.Checkbox";
36010
- static props = {
36011
- label: { type: String, optional: true },
36012
- value: { type: Boolean, optional: true },
36013
- className: { type: String, optional: true },
36014
- name: { type: String, optional: true },
36015
- title: { type: String, optional: true },
36016
- disabled: { type: Boolean, optional: true },
36017
- onChange: Function,
36018
- };
36019
- static defaultProps = { value: false };
36020
- onChange(ev) {
36021
- const value = ev.target.checked;
36022
- this.props.onChange(value);
36023
- }
36024
- }
36025
-
36026
36795
  class Section extends owl.Component {
36027
36796
  static template = "o_spreadsheet.Section";
36028
36797
  static props = {
@@ -38262,6 +39031,104 @@ stores.inject(MyMetaStore, storeInstance);
38262
39031
  }
38263
39032
  }
38264
39033
 
39034
+ class GeoChartRegionSelectSection extends owl.Component {
39035
+ static template = "o-spreadsheet-GeoChartRegionSelectSection";
39036
+ static components = { Section };
39037
+ static props = {
39038
+ figureId: String,
39039
+ definition: Object,
39040
+ updateChart: Function,
39041
+ };
39042
+ updateSelectedRegion(ev) {
39043
+ const value = ev.target.value;
39044
+ this.props.updateChart(this.props.figureId, { region: value });
39045
+ }
39046
+ get availableRegions() {
39047
+ return this.env.model.getters.getGeoChartAvailableRegions();
39048
+ }
39049
+ get selectedRegion() {
39050
+ return this.props.definition.region || this.availableRegions[0]?.id;
39051
+ }
39052
+ }
39053
+
39054
+ class GeoChartConfigPanel extends GenericChartConfigPanel {
39055
+ static template = "o-spreadsheet-GeoChartConfigPanel";
39056
+ static components = { ...GenericChartConfigPanel.components, GeoChartRegionSelectSection };
39057
+ get dataRanges() {
39058
+ return this.getDataSeriesRanges().slice(0, 1);
39059
+ }
39060
+ onDataSeriesConfirmed() {
39061
+ this.dataSeriesRanges = spreadRange(this.env.model.getters, this.dataSeriesRanges).slice(0, 1);
39062
+ this.state.datasetDispatchResult = this.props.updateChart(this.props.figureId, {
39063
+ dataSets: this.dataSeriesRanges,
39064
+ });
39065
+ }
39066
+ getLabelRangeOptions() {
39067
+ return [
39068
+ {
39069
+ name: "dataSetsHaveTitle",
39070
+ label: this.dataSetsHaveTitleLabel,
39071
+ value: this.props.definition.dataSetsHaveTitle,
39072
+ onChange: this.onUpdateDataSetsHaveTitle.bind(this),
39073
+ },
39074
+ ];
39075
+ }
39076
+ }
39077
+
39078
+ const DEFAULT_CUSTOM_COLOR_SCALE = {
39079
+ minColor: "#FFF5EB",
39080
+ midColor: "#FD8D3C",
39081
+ maxColor: "#7F2704",
39082
+ };
39083
+ class GeoChartDesignPanel extends ChartWithAxisDesignPanel {
39084
+ static template = "o-spreadsheet-GeoChartDesignPanel";
39085
+ static components = { ...ChartWithAxisDesignPanel.components, RoundColorPicker };
39086
+ colorScalesChoices = ChartTerms.GeoChart.ColorScales;
39087
+ updateColorScaleType(ev) {
39088
+ const value = ev.target.value;
39089
+ value === "custom"
39090
+ ? this.updateColorScale(DEFAULT_CUSTOM_COLOR_SCALE)
39091
+ : this.updateColorScale(value);
39092
+ }
39093
+ updateColorScale(colorScale) {
39094
+ this.props.updateChart(this.props.figureId, { colorScale });
39095
+ }
39096
+ updateMissingValueColor(color) {
39097
+ this.props.updateChart(this.props.figureId, { missingValueColor: color });
39098
+ }
39099
+ updateLegendPosition(ev) {
39100
+ const value = ev.target.value;
39101
+ this.props.updateChart(this.props.figureId, { legendPosition: value });
39102
+ }
39103
+ get selectedColorScale() {
39104
+ return typeof this.props.definition.colorScale === "object"
39105
+ ? "custom"
39106
+ : this.props.definition.colorScale || "oranges";
39107
+ }
39108
+ get selectedMissingValueColor() {
39109
+ return this.props.definition.missingValueColor || "#ffffff";
39110
+ }
39111
+ get customColorScale() {
39112
+ if (typeof this.props.definition.colorScale === "object") {
39113
+ return this.props.definition.colorScale;
39114
+ }
39115
+ return undefined;
39116
+ }
39117
+ getCustomColorScaleColor(color) {
39118
+ return this.customColorScale?.[color] ?? "";
39119
+ }
39120
+ setCustomColorScaleColor(colorType, color) {
39121
+ if (!color && colorType !== "midColor") {
39122
+ color = "#fff";
39123
+ }
39124
+ const customColorScale = this.customColorScale;
39125
+ if (!customColorScale) {
39126
+ return;
39127
+ }
39128
+ this.updateColorScale({ ...customColorScale, [colorType]: color });
39129
+ }
39130
+ }
39131
+
38265
39132
  class LineConfigPanel extends GenericChartConfigPanel {
38266
39133
  static template = "o-spreadsheet-LineConfigPanel";
38267
39134
  get canTreatLabelsAsText() {
@@ -38575,6 +39442,10 @@ stores.inject(MyMetaStore, storeInstance);
38575
39442
  .add("radar", {
38576
39443
  configuration: GenericChartConfigPanel,
38577
39444
  design: RadarChartDesignPanel,
39445
+ })
39446
+ .add("geo", {
39447
+ configuration: GeoChartConfigPanel,
39448
+ design: GeoChartDesignPanel,
38578
39449
  });
38579
39450
 
38580
39451
  css /* scss */ `
@@ -39938,7 +40809,7 @@ stores.inject(MyMetaStore, storeInstance);
39938
40809
  border-color: ${GRAY_300};
39939
40810
 
39940
40811
  &.active {
39941
- border-color: ${SELECTION_BORDER_COLOR};
40812
+ border-color: ${ACTION_COLOR};
39942
40813
  }
39943
40814
 
39944
40815
  &.o-invalid {
@@ -40896,7 +41767,7 @@ stores.inject(MyMetaStore, storeInstance);
40896
41767
  }
40897
41768
  const point = this.state.rules.colorScale[target];
40898
41769
  if (point) {
40899
- point.color = Number.parseInt(color.slice(1), 16);
41770
+ point.color = colorToNumber(color);
40900
41771
  }
40901
41772
  this.updateConditionalFormat({ rule: this.state.rules.colorScale });
40902
41773
  this.closeMenus();
@@ -41041,6 +41912,9 @@ stores.inject(MyMetaStore, storeInstance);
41041
41912
  return [this.state.rules.dataBar.rangeValues || ""];
41042
41913
  }
41043
41914
  updateDataBarColor(color) {
41915
+ if (!isColorValid(color)) {
41916
+ return;
41917
+ }
41044
41918
  this.state.rules.dataBar.color = Number.parseInt(color.slice(1), 16);
41045
41919
  this.updateConditionalFormat({ rule: this.state.rules.dataBar });
41046
41920
  }
@@ -43644,6 +44518,64 @@ stores.inject(MyMetaStore, storeInstance);
43644
44518
  }
43645
44519
  }
43646
44520
 
44521
+ css /* scss */ `
44522
+ .o-pivot-sort {
44523
+ .o-sort-card {
44524
+ width: fit-content;
44525
+ background-color: ${GRAY_100};
44526
+ border: 1px solid ${GRAY_300};
44527
+
44528
+ .o-sort-value {
44529
+ color: ${PRIMARY_BUTTON_BG};
44530
+ }
44531
+ }
44532
+ }
44533
+ `;
44534
+ class PivotSortSection extends owl.Component {
44535
+ static template = "o-spreadsheet-PivotSortSection";
44536
+ static components = {
44537
+ Section,
44538
+ };
44539
+ static props = {
44540
+ definition: Object,
44541
+ pivotId: String,
44542
+ };
44543
+ get hasValidSort() {
44544
+ const pivot = this.env.model.getters.getPivot(this.props.pivotId);
44545
+ return (!!this.props.definition.sortedColumn &&
44546
+ isSortedColumnValid(this.props.definition.sortedColumn, pivot));
44547
+ }
44548
+ get sortDescription() {
44549
+ const sortOrder = this.props.definition.sortedColumn?.order === "asc" ? _t("ascending") : _t("descending");
44550
+ return _t("Sorted on column (%(ascOrDesc)s):", {
44551
+ ascOrDesc: sortOrder,
44552
+ });
44553
+ }
44554
+ get sortValuesAndFields() {
44555
+ const sortedColumn = this.props.definition.sortedColumn;
44556
+ if (!sortedColumn) {
44557
+ return [];
44558
+ }
44559
+ const pivot = this.env.model.getters.getPivot(this.props.pivotId);
44560
+ const locale = this.env.model.getters.getLocale();
44561
+ const currentDomain = [];
44562
+ const sortValues = [];
44563
+ for (const domainItem of sortedColumn.domain) {
44564
+ currentDomain.push(domainItem);
44565
+ const { value, format } = pivot.getPivotHeaderValueAndFormat(currentDomain);
44566
+ const label = formatValue(value, { format, locale });
44567
+ const field = pivot.definition.getDimension(domainItem.field);
44568
+ sortValues.push({ field: getFieldDisplayName(field), value: label });
44569
+ }
44570
+ if (sortedColumn.domain.length === 0) {
44571
+ sortValues.push({ value: _t("Total") });
44572
+ }
44573
+ const measureLabel = pivot.getMeasure(sortedColumn.measure).displayName;
44574
+ sortValues.push({ value: measureLabel, field: _t("Measure") });
44575
+ return sortValues;
44576
+ }
44577
+ }
44578
+
43647
44579
  css /* scss */ `
43648
44580
  .add-calculated-measure {
43649
44581
  cursor: pointer;
@@ -43657,6 +44589,7 @@ stores.inject(MyMetaStore, storeInstance);
43657
44589
  PivotDimensionOrder,
43658
44590
  PivotDimensionGranularity,
43659
44591
  PivotMeasureEditor,
44592
+ PivotSortSection,
43660
44593
  };
43661
44594
  static props = {
43662
44595
  definition: Object,
@@ -43813,9 +44746,13 @@ stores.inject(MyMetaStore, storeInstance);
43813
44746
  }
43814
44747
  updateMeasure(measure, newMeasure) {
43815
44748
  const { measures } = this.props.definition;
43816
- this.props.onDimensionsUpdated({
44749
+ const update = {
43817
44750
  measures: measures.map((m) => (m.id === measure.id ? newMeasure : m)),
43818
- });
44751
+ };
44752
+ if (this.props.definition.sortedColumn?.measure === measure.id) {
44753
+ update.sortedColumn = { ...this.props.definition.sortedColumn, measure: newMeasure.id };
44754
+ }
44755
+ this.props.onDimensionsUpdated(update);
43819
44756
  }
43820
44757
  getMeasureId(fieldName, aggregator) {
43821
44758
  const baseId = fieldName + (aggregator ? `:${aggregator}` : "");
@@ -43971,10 +44908,12 @@ stores.inject(MyMetaStore, storeInstance);
43971
44908
  measures;
43972
44909
  columns;
43973
44910
  rows;
44911
+ sortedColumn;
43974
44912
  constructor(definition, fields) {
43975
44913
  this.measures = definition.measures.map((measure) => createMeasure(fields, measure));
43976
44914
  this.columns = definition.columns.map((dimension) => createPivotDimension(fields, dimension));
43977
44915
  this.rows = definition.rows.map((dimension) => createPivotDimension(fields, dimension));
44916
+ this.sortedColumn = definition.sortedColumn;
43978
44917
  }
43979
44918
  getDimension(nameWithGranularity) {
43980
44919
  const dimension = this.columns.find((d) => d.nameWithGranularity === nameWithGranularity) ||
@@ -44128,6 +45067,7 @@ stores.inject(MyMetaStore, storeInstance);
44128
45067
  pivotCells = {};
44129
45068
  rowTree;
44130
45069
  colTree;
45070
+ isSorted = false;
44131
45071
  constructor(columns, rows, measures, fieldsType) {
44132
45072
  this.columns = columns.map((row) => {
44133
45073
  // offset in the pivot table
@@ -44301,6 +45241,7 @@ stores.inject(MyMetaStore, storeInstance);
44301
45241
  value,
44302
45242
  field: row.fields[rowDepth],
44303
45243
  children: [],
45244
+ type: this.fieldsType[fieldName] || "char",
44304
45245
  width: 0, // not used
44305
45246
  };
44306
45247
  treesAtDepth[depth].push(node);
@@ -44323,6 +45264,7 @@ stores.inject(MyMetaStore, storeInstance);
44323
45264
  field: leaf.fields[depth],
44324
45265
  children: [],
44325
45266
  width: leaf.width,
45267
+ type: this.fieldsType[fieldName] || "char",
44326
45268
  };
44327
45269
  if (treesAtDepth[depth]?.at(-1)?.value !== value) {
44328
45270
  treesAtDepth[depth + 1] = [];
@@ -44341,6 +45283,35 @@ stores.inject(MyMetaStore, storeInstance);
44341
45283
  fieldsType: this.fieldsType,
44342
45284
  };
44343
45285
  }
45286
+ sort(measure, sortedColumn, getValue) {
45287
+ if (this.isSorted) {
45288
+ return;
45289
+ }
45290
+ const getSortValue = (measure, domain) => {
45291
+ const rawValue = getValue(measure, domain).value;
45292
+ return typeof rawValue === "number" ? rawValue : -Infinity;
45293
+ };
45294
+ const sortColDomain = sortedColumn.domain;
45295
+ const sortFn = (rowDomain1, rowDomain2) => {
45296
+ const value1 = getSortValue(measure, [...rowDomain1, ...sortColDomain]);
45297
+ const value2 = getSortValue(measure, [...rowDomain2, ...sortColDomain]);
45298
+ return sortedColumn.order === "asc" ? value1 - value2 : value2 - value1;
45299
+ };
45300
+ const sortedRowTree = sortPivotTree(this.rowTree(), [], sortFn);
45301
+ this.rowTree = lazy(sortedRowTree);
45302
+ this.rows = [...this.rowTreeToRows(sortedRowTree), this.rows[this.rows.length - 1]];
45303
+ this.isSorted = true;
45304
+ }
45305
+ rowTreeToRows(tree, parentRow) {
45306
+ return tree.flatMap((node) => {
45307
+ const row = {
45308
+ indent: parentRow ? parentRow.indent + 1 : 0,
45309
+ fields: [...(parentRow?.fields || []), node.field],
45310
+ values: [...(parentRow?.values || []), node.value],
45311
+ };
45312
+ return [row, ...this.rowTreeToRows(node.children, row)];
45313
+ });
45314
+ }
44344
45315
  }
44345
45316
  const EMPTY_PIVOT_CELL = { type: "EMPTY" };
44346
45317
 
@@ -44418,6 +45389,7 @@ stores.inject(MyMetaStore, storeInstance);
44418
45389
  value: groups[key]?.[0]?.[column.nameWithGranularity]?.value ?? null,
44419
45390
  field: colName,
44420
45391
  children: dataEntriesToColumnsTree(groups[key] || [], columns, index + 1),
45392
+ type: column.type,
44421
45393
  width: 0,
44422
45394
  };
44423
45395
  });
@@ -45283,6 +46255,7 @@ stores.inject(MyMetaStore, storeInstance);
45283
46255
  format: measure.format,
45284
46256
  display: measure.display,
45285
46257
  })),
46258
+ sortedColumn: this.shouldKeepSortedColumn(definition) ? definition.sortedColumn : undefined,
45286
46259
  };
45287
46260
  if (!this.draft && deepEquals(coreDefinition, cleanedDefinition)) {
45288
46261
  return;
@@ -45341,6 +46314,19 @@ stores.inject(MyMetaStore, storeInstance);
45341
46314
  }
45342
46315
  return granularitiesPerFields;
45343
46316
  }
46317
+ /**
46318
+ * Check if we want to keep the sorted column when updating the pivot definition. We should remove it if either
46319
+ * the measure is not in the new definition or the columns have changed.
46320
+ */
46321
+ shouldKeepSortedColumn(newDefinition) {
46322
+ const { sortedColumn } = newDefinition;
46323
+ if (!sortedColumn) {
46324
+ return true;
46325
+ }
46326
+ const oldDefinition = this.getters.getPivotCoreDefinition(this.pivotId);
46327
+ return (newDefinition.measures.find((measure) => measure.id === sortedColumn.measure) &&
46328
+ deepEquals(oldDefinition.columns, newDefinition.columns));
46329
+ }
45344
46330
  }
45345
46331
 
45346
46332
  class PivotSpreadsheetSidePanel extends owl.Component {
@@ -46442,6 +47428,7 @@ stores.inject(MyMetaStore, storeInstance);
46442
47428
  }
46443
47429
  }
46444
47430
  `;
47431
+ const DEFAULT_TABLE_STYLE_COLOR = "#3C78D8";
46445
47432
  class TableStyleEditorPanel extends owl.Component {
46446
47433
  static template = "o-spreadsheet-TableStyleEditorPanel";
46447
47434
  static components = { Section, RoundColorPicker, TableStylePreview };
@@ -46460,7 +47447,7 @@ stores.inject(MyMetaStore, storeInstance);
46460
47447
  : null;
46461
47448
  return {
46462
47449
  pickerOpened: false,
46463
- primaryColor: editedStyle?.primaryColor || "#3C78D8",
47450
+ primaryColor: editedStyle?.primaryColor || DEFAULT_TABLE_STYLE_COLOR,
46464
47451
  selectedTemplateName: editedStyle?.templateName || "lightColoredText",
46465
47452
  styleName: editedStyle?.displayName || this.env.model.getters.getNewCustomTableStyleName(),
46466
47453
  };
@@ -46469,7 +47456,7 @@ stores.inject(MyMetaStore, storeInstance);
46469
47456
  this.state.pickerOpened = !this.state.pickerOpened;
46470
47457
  }
46471
47458
  onColorPicked(color) {
46472
- this.state.primaryColor = color;
47459
+ this.state.primaryColor = isColorValid(color) ? color : DEFAULT_TABLE_STYLE_COLOR;
46473
47460
  this.state.pickerOpened = false;
46474
47461
  }
46475
47462
  onTemplatePicked(templateName) {
@@ -48085,6 +49072,7 @@ stores.inject(MyMetaStore, storeInstance);
48085
49072
  draggedFigure: undefined,
48086
49073
  horizontalSnap: undefined,
48087
49074
  verticalSnap: undefined,
49075
+ cancelDnd: undefined,
48088
49076
  });
48089
49077
  setup() {
48090
49078
  owl.onMounted(() => {
@@ -48097,12 +49085,28 @@ stores.inject(MyMetaStore, storeInstance);
48097
49085
  // new rendering
48098
49086
  this.render();
48099
49087
  });
49088
+ owl.onWillUpdateProps(() => {
49089
+ const sheetId = this.env.model.getters.getActiveSheetId();
49090
+ const draggedFigureId = this.dnd.draggedFigure?.id;
49091
+ if (draggedFigureId && !this.env.model.getters.getFigure(sheetId, draggedFigureId)) {
49092
+ if (this.dnd.cancelDnd) {
49093
+ this.dnd.cancelDnd();
49094
+ }
49095
+ this.dnd.draggedFigure = undefined;
49096
+ this.dnd.horizontalSnap = undefined;
49097
+ this.dnd.verticalSnap = undefined;
49098
+ this.dnd.cancelDnd = undefined;
49099
+ }
49100
+ });
48100
49101
  }
48101
49102
  getVisibleFigures() {
48102
49103
  const visibleFigures = this.env.model.getters.getVisibleFigures();
48103
49104
  if (this.dnd.draggedFigure &&
48104
49105
  !visibleFigures.some((figure) => figure.id === this.dnd.draggedFigure?.id)) {
48105
- visibleFigures.push(this.env.model.getters.getFigure(this.env.model.getters.getActiveSheetId(), this.dnd.draggedFigure?.id));
49106
+ const draggedFigure = this.env.model.getters.getFigure(this.env.model.getters.getActiveSheetId(), this.dnd.draggedFigure?.id);
49107
+ if (draggedFigure) {
49108
+ visibleFigures.push(draggedFigure);
49109
+ }
48106
49110
  }
48107
49111
  return visibleFigures;
48108
49112
  }
@@ -48221,7 +49225,7 @@ stores.inject(MyMetaStore, storeInstance);
48221
49225
  this.dnd.verticalSnap = undefined;
48222
49226
  this.env.model.dispatch("UPDATE_FIGURE", { sheetId, id: figure.id, x, y });
48223
49227
  };
48224
- startDnd(onMouseMove, onMouseUp);
49228
+ this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
48225
49229
  }
48226
49230
  /**
48227
49231
  * Initialize the resize of a figure with mouse movements
@@ -48269,7 +49273,7 @@ stores.inject(MyMetaStore, storeInstance);
48269
49273
  this.dnd.horizontalSnap = undefined;
48270
49274
  this.dnd.verticalSnap = undefined;
48271
49275
  };
48272
- startDnd(onMouseMove, onMouseUp);
49276
+ this.dnd.cancelDnd = startDnd(onMouseMove, onMouseUp);
48273
49277
  }
48274
49278
  getOtherFigures(figId) {
48275
49279
  return this.getVisibleFigures().filter((f) => f.id !== figId);
@@ -49376,9 +50380,11 @@ stores.inject(MyMetaStore, storeInstance);
49376
50380
  class GridRenderer {
49377
50381
  getters;
49378
50382
  renderer;
50383
+ fingerprints;
49379
50384
  constructor(get) {
49380
50385
  this.getters = get(ModelStore).getters;
49381
50386
  this.renderer = get(RendererStore);
50387
+ this.fingerprints = get(FormulaFingerprintStore);
49382
50388
  this.renderer.register(this);
49383
50389
  }
49384
50390
  get renderingLayers() {
@@ -49856,14 +50862,22 @@ stores.inject(MyMetaStore, storeInstance);
49856
50862
  const showFormula = this.getters.shouldShowFormulas();
49857
50863
  const { x, y, width, height } = this.getters.getVisibleRect(zone);
49858
50864
  const { verticalAlign } = this.getters.getCellStyle(position);
50865
+ let style = this.getters.getCellComputedStyle(position);
50866
+ if (this.fingerprints.isEnabled) {
50867
+ const fingerprintColor = this.fingerprints.colors.get(position);
50868
+ style = { ...style, fillColor: fingerprintColor };
50869
+ }
50870
+ const dataBarFill = this.fingerprints.isEnabled
50871
+ ? undefined
50872
+ : this.getters.getConditionalDataBar(position);
49859
50873
  const box = {
49860
50874
  x,
49861
50875
  y,
49862
50876
  width,
49863
50877
  height,
49864
50878
  border: this.getters.getCellComputedBorder(position) || undefined,
49865
- style: this.getters.getCellComputedStyle(position),
49866
- dataBarFill: this.getters.getConditionalDataBar(position),
50879
+ style,
50880
+ dataBarFill,
49867
50881
  verticalAlign,
49868
50882
  isError: (cell.type === CellValueType.error && !!cell.message) ||
49869
50883
  this.getters.isDataValidationInvalid(position),
@@ -49888,7 +50902,6 @@ stores.inject(MyMetaStore, storeInstance);
49888
50902
  box.hasIcon = this.getters.doesCellHaveGridIcon(position);
49889
50903
  const headerIconWidth = box.hasIcon ? GRID_ICON_EDGE_LENGTH + GRID_ICON_MARGIN : 0;
49890
50904
  /** Content */
49891
- const style = this.getters.getCellComputedStyle(position);
49892
50905
  const wrapping = style.wrapping || "overflow";
49893
50906
  const wrapText = wrapping === "wrap" && !showFormula;
49894
50907
  const maxWidth = width - 2 * MIN_CELL_TEXT_MARGIN;
@@ -51866,62 +52879,6 @@ stores.inject(MyMetaStore, storeInstance);
51866
52879
  }
51867
52880
  }
51868
52881
 
51869
- class PositionMap {
51870
- map = {};
51871
- constructor(entries = []) {
51872
- for (const [position, value] of entries) {
51873
- this.set(position, value);
51874
- }
51875
- }
51876
- set({ sheetId, col, row }, value) {
51877
- const map = this.map;
51878
- if (!map[sheetId]) {
51879
- map[sheetId] = {};
51880
- }
51881
- if (!map[sheetId][col]) {
51882
- map[sheetId][col] = {};
51883
- }
51884
- map[sheetId][col][row] = value;
51885
- }
51886
- get({ sheetId, col, row }) {
51887
- return this.map[sheetId]?.[col]?.[row];
51888
- }
51889
- getSheet(sheetId) {
51890
- return this.map[sheetId];
51891
- }
51892
- has({ sheetId, col, row }) {
51893
- return this.map[sheetId]?.[col]?.[row] !== undefined;
51894
- }
51895
- delete({ sheetId, col, row }) {
51896
- delete this.map[sheetId]?.[col]?.[row];
51897
- }
51898
- keys() {
51899
- const map = this.map;
51900
- const keys = [];
51901
- for (const sheetId in map) {
51902
- for (const col in map[sheetId]) {
51903
- for (const row in map[sheetId][col]) {
51904
- keys.push({ sheetId, col: parseInt(col), row: parseInt(row) });
51905
- }
51906
- }
51907
- }
51908
- return keys;
51909
- }
51910
- keysForSheet(sheetId) {
51911
- const map = this.map[sheetId];
51912
- if (!map) {
51913
- return [];
51914
- }
51915
- const keys = [];
51916
- for (const col in map) {
51917
- for (const row in map[col]) {
51918
- keys.push({ sheetId, col: parseInt(col), row: parseInt(row) });
51919
- }
51920
- }
51921
- return keys;
51922
- }
51923
- }
51924
-
51925
52882
  /**
51926
52883
  * Core Plugin
51927
52884
  *
@@ -52360,7 +53317,7 @@ stores.inject(MyMetaStore, storeInstance);
52360
53317
  const before = this.getters.getCell({ sheetId, col, row });
52361
53318
  const hasContent = "content" in after || "formula" in after;
52362
53319
  // Compute the new cell properties
52363
- const afterContent = hasContent ? replaceSpecialSpaces(after?.content) : before?.content || "";
53320
+ const afterContent = hasContent ? replaceNewLines(after?.content) : before?.content || "";
52364
53321
  let style;
52365
53322
  if (after.style !== undefined) {
52366
53323
  style = after.style || undefined;
@@ -55305,6 +56262,9 @@ stores.inject(MyMetaStore, storeInstance);
55305
56262
  };
55306
56263
  }
55307
56264
  getUnboundedZone(sheetId, zone) {
56265
+ if (zone.bottom === undefined || zone.right === undefined) {
56266
+ return zone;
56267
+ }
55308
56268
  const isFullRow = zone.left === 0 && zone.right === this.getNumberCols(sheetId) - 1;
55309
56269
  const isFullCol = zone.top === 0 && zone.bottom === this.getNumberRows(sheetId) - 1;
55310
56270
  return {
@@ -59588,66 +60548,25 @@ stores.inject(MyMetaStore, storeInstance);
59588
60548
  return;
59589
60549
  }
59590
60550
  const zone = this.getters.getRangeFromSheetXC(sheetId, range).zone;
59591
- const colorCellArgs = [];
60551
+ const colorThresholds = [{ value: minValue, color: rule.minimum.color }];
59592
60552
  if (rule.midpoint && midValue) {
59593
- colorCellArgs.push({
59594
- minValue,
59595
- minColor: rule.minimum.color,
59596
- colorDiffUnit: this.computeColorDiffUnits(minValue, midValue, rule.minimum.color, rule.midpoint.color),
59597
- });
59598
- colorCellArgs.push({
59599
- minValue: midValue,
59600
- minColor: rule.midpoint.color,
59601
- colorDiffUnit: this.computeColorDiffUnits(midValue, maxValue, rule.midpoint.color, rule.maximum.color),
59602
- });
59603
- }
59604
- else {
59605
- colorCellArgs.push({
59606
- minValue,
59607
- minColor: rule.minimum.color,
59608
- colorDiffUnit: this.computeColorDiffUnits(minValue, maxValue, rule.minimum.color, rule.maximum.color),
59609
- });
60553
+ colorThresholds.push({ value: midValue, color: rule.midpoint.color });
59610
60554
  }
60555
+ colorThresholds.push({ value: maxValue, color: rule.maximum.color });
60556
+ const colorScale = getColorScale(colorThresholds);
59611
60557
  for (let row = zone.top; row <= zone.bottom; row++) {
59612
60558
  for (let col = zone.left; col <= zone.right; col++) {
59613
60559
  const cell = this.getters.getEvaluatedCell({ sheetId, col, row });
59614
60560
  if (cell.type === CellValueType.number) {
59615
60561
  const value = clip(cell.value, minValue, maxValue);
59616
- let color;
59617
- if (colorCellArgs.length === 2 && midValue) {
59618
- color =
59619
- value <= midValue
59620
- ? this.colorCell(value, colorCellArgs[0].minValue, colorCellArgs[0].minColor, colorCellArgs[0].colorDiffUnit)
59621
- : this.colorCell(value, colorCellArgs[1].minValue, colorCellArgs[1].minColor, colorCellArgs[1].colorDiffUnit);
59622
- }
59623
- else {
59624
- color = this.colorCell(value, colorCellArgs[0].minValue, colorCellArgs[0].minColor, colorCellArgs[0].colorDiffUnit);
59625
- }
59626
60562
  if (!computedStyle[col])
59627
60563
  computedStyle[col] = [];
59628
60564
  computedStyle[col][row] = computedStyle[col]?.[row] || {};
59629
- computedStyle[col][row].fillColor = colorNumberString(color);
60565
+ computedStyle[col][row].fillColor = colorScale(value);
59630
60566
  }
59631
60567
  }
59632
60568
  }
59633
60569
  }
59634
- computeColorDiffUnits(minValue, maxValue, minColor, maxColor) {
59635
- const deltaValue = maxValue - minValue;
59636
- const deltaColorR = ((minColor >> 16) % 256) - ((maxColor >> 16) % 256);
59637
- const deltaColorG = ((minColor >> 8) % 256) - ((maxColor >> 8) % 256);
59638
- const deltaColorB = (minColor % 256) - (maxColor % 256);
59639
- const colorDiffUnitR = deltaColorR / deltaValue;
59640
- const colorDiffUnitG = deltaColorG / deltaValue;
59641
- const colorDiffUnitB = deltaColorB / deltaValue;
59642
- return [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB];
59643
- }
59644
- colorCell(value, minValue, minColor, colorDiffUnit) {
59645
- const [colorDiffUnitR, colorDiffUnitG, colorDiffUnitB] = colorDiffUnit;
59646
- const r = Math.round(((minColor >> 16) % 256) - colorDiffUnitR * (value - minValue));
59647
- const g = Math.round(((minColor >> 8) % 256) - colorDiffUnitG * (value - minValue));
59648
- const b = Math.round((minColor % 256) - colorDiffUnitB * (value - minValue));
59649
- return (r << 16) | (g << 8) | b;
59650
- }
59651
60570
  /**
59652
60571
  * Execute the predicate to know if a conditional formatting rule should be applied to a cell
59653
60572
  */
@@ -60273,7 +61192,7 @@ stores.inject(MyMetaStore, storeInstance);
60273
61192
  }
60274
61193
  getValuesToAggregate(measure, domain) {
60275
61194
  const { rowDomain, colDomain } = domainToColRowDomain(this, domain);
60276
- const table = this.getTableStructure();
61195
+ const table = super.getTableStructure();
60277
61196
  const values = [];
60278
61197
  if (colDomain.length === 0 &&
60279
61198
  rowDomain.length < this.definition.rows.length &&
@@ -60707,6 +61626,21 @@ stores.inject(MyMetaStore, storeInstance);
60707
61626
  }
60708
61627
  throw new Error(`Value ${result.value} is not a number`);
60709
61628
  }
61629
+ getTableStructure() {
61630
+ const table = super.getTableStructure();
61631
+ this.sortTableStructure(table);
61632
+ return table;
61633
+ }
61634
+ sortTableStructure(table) {
61635
+ if (!this.definition.sortedColumn || table.isSorted) {
61636
+ return;
61637
+ }
61638
+ const measure = this.definition.sortedColumn.measure;
61639
+ const isSortValid = isSortedColumnValid(this.definition.sortedColumn, this);
61640
+ if (isSortValid) {
61641
+ table.sort(measure, this.definition.sortedColumn, (measure, domain) => this._getPivotCellValueAndFormat(measure, domain));
61642
+ }
61643
+ }
60710
61644
  }
60711
61645
  return PivotPresentationLayer;
60712
61646
  }
@@ -62482,23 +63416,6 @@ stores.inject(MyMetaStore, storeInstance);
62482
63416
  }
62483
63417
  }
62484
63418
 
62485
- function randomChoice(arr) {
62486
- return arr[Math.floor(Math.random() * arr.length)];
62487
- }
62488
- const colors = [
62489
- "#ff851b",
62490
- "#0074d9",
62491
- "#7fdbff",
62492
- "#b10dc9",
62493
- "#39cccc",
62494
- "#f012be",
62495
- "#3d9970",
62496
- "#111111",
62497
- "#ff4136",
62498
- "#aaaaaa",
62499
- "#85144b",
62500
- "#001f3f",
62501
- ];
62502
63419
  class CollaborativePlugin extends UIPlugin {
62503
63420
  static getters = [
62504
63421
  "getClientsToDisplay",
@@ -62507,7 +63424,7 @@ stores.inject(MyMetaStore, storeInstance);
62507
63424
  "isFullySynchronized",
62508
63425
  ];
62509
63426
  static layers = ["Selection"];
62510
- availableColors = new Set(colors);
63427
+ availableColors = new AlternatingColorGenerator(12);
62511
63428
  colors = {};
62512
63429
  session;
62513
63430
  constructor(config) {
@@ -62518,14 +63435,6 @@ stores.inject(MyMetaStore, storeInstance);
62518
63435
  return (position.row < this.getters.getNumberRows(position.sheetId) &&
62519
63436
  position.col < this.getters.getNumberCols(position.sheetId));
62520
63437
  }
62521
- chooseNewColor() {
62522
- if (this.availableColors.size === 0) {
62523
- this.availableColors = new Set(colors);
62524
- }
62525
- const color = randomChoice([...this.availableColors.values()]);
62526
- this.availableColors.delete(color);
62527
- return color;
62528
- }
62529
63438
  getClient() {
62530
63439
  return this.session.getClient();
62531
63440
  }
@@ -62560,7 +63469,7 @@ stores.inject(MyMetaStore, storeInstance);
62560
63469
  this.isPositionValid(client.position)) {
62561
63470
  const position = client.position;
62562
63471
  if (!this.colors[client.id]) {
62563
- this.colors[client.id] = this.chooseNewColor();
63472
+ this.colors[client.id] = this.availableColors.next();
62564
63473
  }
62565
63474
  const color = this.colors[client.id];
62566
63475
  clients.push({ ...client, position, color });
@@ -62797,7 +63706,7 @@ stores.inject(MyMetaStore, storeInstance);
62797
63706
  for (let row = zone.top; row <= zone.bottom; row++) {
62798
63707
  const position = { sheetId, col, row };
62799
63708
  const pivotCell = this.getters.getPivotCellFromPosition(position);
62800
- if (pivotCell.type === "VALUE") {
63709
+ if (this.isSpilledPivotValueFormula(position, pivotCell)) {
62801
63710
  measurePositions.push(position);
62802
63711
  const pivotId = this.getters.getPivotIdFromPosition(position) || "";
62803
63712
  measuresByPivotId[pivotId] ??= new Set();
@@ -62834,6 +63743,10 @@ stores.inject(MyMetaStore, storeInstance);
62834
63743
  format,
62835
63744
  });
62836
63745
  }
63746
+ isSpilledPivotValueFormula(position, pivotCell) {
63747
+ const cell = this.getters.getCell(position);
63748
+ return pivotCell.type === "VALUE" && !cell?.isFormula;
63749
+ }
62837
63750
  /**
62838
63751
  * This function allows to adjust the quantity of decimal places after a decimal
62839
63752
  * point on cells containing number value. It does this by changing the cells
@@ -62884,6 +63797,69 @@ stores.inject(MyMetaStore, storeInstance);
62884
63797
  }
62885
63798
  }
62886
63799
 
63800
+ class GeoFeaturePlugin extends UIPlugin {
63801
+ static getters = [
63802
+ "getGeoJsonFeatures",
63803
+ "geoFeatureNameToId",
63804
+ "getGeoChartAvailableRegions",
63805
+ ];
63806
+ geoJsonService;
63807
+ geoJsonCache = {};
63808
+ constructor(config) {
63809
+ super(config);
63810
+ this.geoJsonService = config.external.geoJsonService;
63811
+ }
63812
+ getGeoChartAvailableRegions() {
63813
+ if (!this.geoJsonService) {
63814
+ console.error("No geoJsonService provided to the model");
63815
+ return [];
63816
+ }
63817
+ return this.geoJsonService.getAvailableRegions() || [];
63818
+ }
63819
+ getGeoJsonFeatures(region) {
63820
+ if (!this.geoJsonService) {
63821
+ console.error("No geoJsonService provided to the model");
63822
+ return;
63823
+ }
63824
+ const cachedGeoJson = this.geoJsonCache[region];
63825
+ if (cachedGeoJson instanceof Promise) {
63826
+ return undefined;
63827
+ }
63828
+ if (cachedGeoJson !== undefined) {
63829
+ return cachedGeoJson ?? undefined;
63830
+ }
63831
+ this.geoJsonCache[region] = new Promise(async (resolve) => {
63832
+ const json = await this.geoJsonService?.getTopoJson(region);
63833
+ this.geoJsonCache[region] = this.convertToGeoJson(json);
63834
+ this.dispatch("EVALUATE_CHARTS");
63835
+ resolve();
63836
+ });
63837
+ return undefined;
63838
+ }
63839
+ geoFeatureNameToId(region, featureName) {
63840
+ if (!this.geoJsonService) {
63841
+ console.error("No geoJsonService provided to the model");
63842
+ return;
63843
+ }
63844
+ return this.geoJsonService.geoFeatureNameToId(region, featureName);
63845
+ }
63846
+ convertToGeoJson(json) {
63847
+ if (!json) {
63848
+ return null;
63849
+ }
63850
+ // TopoJSON
63851
+ if (json.type === "Topology") {
63852
+ const features = window.ChartGeo.topojson.feature(json, Object.values(json.objects)[0]);
63853
+ return features.type === "FeatureCollection" ? features.features : [features];
63854
+ }
63855
+ // GeoJSON
63856
+ else if (json.type === "FeatureCollection") {
63857
+ return json.features;
63858
+ }
63859
+ throw new Error("Invalid TopoJSON");
63860
+ }
63861
+ }
63862
+
62887
63863
  class HeaderVisibilityUIPlugin extends UIPlugin {
62888
63864
  static getters = [
62889
63865
  "getNextVisibleCellPosition",
@@ -64421,10 +65397,13 @@ stores.inject(MyMetaStore, storeInstance);
64421
65397
  case "RESIZE_TABLE": {
64422
65398
  const table = this.getters.getCoreTableMatchingTopLeft(cmd.sheetId, cmd.zone);
64423
65399
  this.dispatch("UPDATE_TABLE", { ...cmd });
64424
- if (!table || !table.config.automaticAutofill)
65400
+ if (!table)
64425
65401
  return;
64426
- const oldTableZone = table.range.zone;
64427
65402
  const newTableZone = this.getters.getRangeFromRangeData(cmd.newTableRange).zone;
65403
+ this.selection.selectCell(newTableZone.right, newTableZone.bottom);
65404
+ if (!table.config.automaticAutofill)
65405
+ return;
65406
+ const oldTableZone = table.range.zone;
64428
65407
  if (newTableZone.bottom >= oldTableZone.bottom) {
64429
65408
  for (let col = newTableZone.left; col <= newTableZone.right; col++) {
64430
65409
  const autofillSource = { col, row: oldTableZone.bottom, sheetId: cmd.sheetId };
@@ -65630,15 +66609,23 @@ stores.inject(MyMetaStore, storeInstance);
65630
66609
  handler.paste({ zones: pasteTarget, sheetId }, data, { isCutOperation: true });
65631
66610
  const toRemove = isBasedBefore ? cmd.elements.map((el) => el + thickness) : cmd.elements;
65632
66611
  let currentIndex = cmd.base;
66612
+ const resizingGroups = {};
65633
66613
  for (const element of toRemove) {
65634
66614
  const size = this.getters.getHeaderSize(cmd.sheetId, cmd.dimension, element);
66615
+ const currentSize = this.getters.getHeaderSize(cmd.sheetId, cmd.dimension, currentIndex);
66616
+ if (size != currentSize) {
66617
+ resizingGroups[size] ??= [];
66618
+ resizingGroups[size].push(currentIndex);
66619
+ currentIndex += 1;
66620
+ }
66621
+ }
66622
+ for (const size in resizingGroups) {
65635
66623
  this.dispatch("RESIZE_COLUMNS_ROWS", {
65636
66624
  dimension: cmd.dimension,
65637
66625
  sheetId: cmd.sheetId,
65638
- size,
65639
- elements: [currentIndex],
66626
+ size: parseInt(size, 10),
66627
+ elements: resizingGroups[size],
65640
66628
  });
65641
- currentIndex += 1;
65642
66629
  }
65643
66630
  this.dispatch("REMOVE_COLUMNS_ROWS", {
65644
66631
  dimension: cmd.dimension,
@@ -66220,6 +67207,7 @@ stores.inject(MyMetaStore, storeInstance);
66220
67207
  break;
66221
67208
  case "DELETE_SHEET":
66222
67209
  this.cleanViewports();
67210
+ this.sheetsWithDirtyViewports.delete(cmd.sheetId);
66223
67211
  break;
66224
67212
  case "ACTIVATE_SHEET":
66225
67213
  this.sheetsWithDirtyViewports.add(cmd.sheetIdTo);
@@ -66870,7 +67858,8 @@ stores.inject(MyMetaStore, storeInstance);
66870
67858
  .add("data_cleanup", DataCleanupPlugin)
66871
67859
  .add("table_autofill", TableAutofillPlugin)
66872
67860
  .add("table_ui_resize", TableResizeUI)
66873
- .add("datavalidation_insert", DataValidationInsertionPlugin);
67861
+ .add("datavalidation_insert", DataValidationInsertionPlugin)
67862
+ .add("geo_features", GeoFeaturePlugin);
66874
67863
  // Plugins which have a state, but which should not be shared in collaborative
66875
67864
  const statefulUIPluginRegistry = new Registry()
66876
67865
  .add("selection", GridSelectionPlugin)
@@ -68614,8 +69603,8 @@ stores.inject(MyMetaStore, storeInstance);
68614
69603
  .o-topbar-composer {
68615
69604
  height: fit-content;
68616
69605
  margin-top: -1px;
69606
+ margin-bottom: -1px;
68617
69607
  border: 1px solid;
68618
- z-index: ${ComponentsImportance.TopBarComposer};
68619
69608
  font-family: ${DEFAULT_FONT};
68620
69609
 
68621
69610
  .o-composer:empty:not(:focus):not(.active)::before {
@@ -68673,6 +69662,7 @@ stores.inject(MyMetaStore, storeInstance);
68673
69662
  }
68674
69663
  return cssPropertiesToCss({
68675
69664
  "border-color": SELECTION_BORDER_COLOR,
69665
+ "z-index": String(ComponentsImportance.TopBarComposer),
68676
69666
  });
68677
69667
  }
68678
69668
  onFocus(selection) {
@@ -68781,6 +69771,15 @@ stores.inject(MyMetaStore, storeInstance);
68781
69771
  }
68782
69772
  }
68783
69773
 
69774
+ .irregularity-map {
69775
+ border-top: 1px solid ${SEPARATOR_COLOR};
69776
+ height: ${TOPBAR_TOOLBAR_HEIGHT}px;
69777
+
69778
+ .alert-info {
69779
+ border-left: 3px solid ${ALERT_INFO_BORDER};
69780
+ }
69781
+ }
69782
+
68784
69783
  .o-topbar-composer {
68785
69784
  flex-grow: 1;
68786
69785
  }
@@ -68874,8 +69873,10 @@ stores.inject(MyMetaStore, storeInstance);
68874
69873
  formatNumberMenuItemSpec = formatNumberMenuItemSpec;
68875
69874
  isntToolbarMenu = false;
68876
69875
  composerFocusStore;
69876
+ fingerprints;
68877
69877
  setup() {
68878
69878
  this.composerFocusStore = useStore(ComposerFocusStore);
69879
+ this.fingerprints = useStore(FormulaFingerprintStore);
68879
69880
  owl.useExternalListener(window, "click", this.onExternalClick);
68880
69881
  owl.onWillStart(() => this.updateCellState());
68881
69882
  owl.onWillUpdateProps(() => this.updateCellState());
@@ -69346,7 +70347,7 @@ stores.inject(MyMetaStore, storeInstance);
69346
70347
  properties["grid-template-rows"] = `auto`;
69347
70348
  }
69348
70349
  else {
69349
- properties["grid-template-rows"] = `${TOPBAR_HEIGHT}px auto ${BOTTOMBAR_HEIGHT + 1}px`;
70350
+ properties["grid-template-rows"] = `max-content auto ${BOTTOMBAR_HEIGHT + 1}px`;
69350
70351
  }
69351
70352
  properties["grid-template-columns"] = `auto ${this.sidePanel.panelSize}px`;
69352
70353
  return cssPropertiesToCss(properties);
@@ -70424,9 +71425,6 @@ stores.inject(MyMetaStore, storeInstance);
70424
71425
  getBackToDefault() {
70425
71426
  this.stream.getBackToDefault();
70426
71427
  }
70427
- getAnchor() {
70428
- return this.anchor;
70429
- }
70430
71428
  modifyAnchor(anchor, mode, options) {
70431
71429
  const sheetId = this.getters.getActiveSheetId();
70432
71430
  anchor = {
@@ -73358,6 +74356,7 @@ stores.inject(MyMetaStore, storeInstance);
73358
74356
  session: this.session,
73359
74357
  defaultCurrency: this.config.defaultCurrency,
73360
74358
  customColors: this.config.customColors || [],
74359
+ external: this.config.external,
73361
74360
  };
73362
74361
  }
73363
74362
  // ---------------------------------------------------------------------------
@@ -73589,7 +74588,6 @@ stores.inject(MyMetaStore, storeInstance);
73589
74588
  MIN_COL_WIDTH,
73590
74589
  HEADER_HEIGHT,
73591
74590
  HEADER_WIDTH,
73592
- TOPBAR_HEIGHT,
73593
74591
  BOTTOMBAR_HEIGHT,
73594
74592
  DEFAULT_CELL_WIDTH,
73595
74593
  DEFAULT_CELL_HEIGHT,
@@ -73830,9 +74828,9 @@ stores.inject(MyMetaStore, storeInstance);
73830
74828
  exports.tokenize = tokenize;
73831
74829
 
73832
74830
 
73833
- __info__.version = "18.1.0-alpha.7";
73834
- __info__.date = "2024-12-05T10:40:26.512Z";
73835
- __info__.hash = "7b1c39b";
74831
+ __info__.version = "18.1.0-alpha.8";
74832
+ __info__.date = "2024-12-19T07:49:54.421Z";
74833
+ __info__.hash = "87a1567";
73836
74834
 
73837
74835
 
73838
74836
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);