@odoo/o-spreadsheet 17.3.0-alpha.2 → 17.3.0-alpha.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -3,9 +3,9 @@
3
3
  /**
4
4
  * This file is generated by o-spreadsheet build tools. Do not edit it.
5
5
  * @see https://github.com/odoo/o-spreadsheet
6
- * @version 17.3.0-alpha.2
7
- * @date 2024-04-05T14:01:07.060Z
8
- * @hash 8c5a229
6
+ * @version 17.3.0-alpha.3
7
+ * @date 2024-04-10T12:28:23.658Z
8
+ * @hash 80b5056
9
9
  */
10
10
 
11
11
  (function (exports, owl) {
@@ -189,7 +189,7 @@
189
189
  const DEFAULT_GAUGE_MIDDLE_COLOR = "#f1c232";
190
190
  const DEFAULT_GAUGE_UPPER_COLOR = "#6aa84f";
191
191
  const DEFAULT_SCORECARD_BASELINE_MODE = "difference";
192
- const DEFAULT_SCORECARD_BASELINE_COLOR_UP = "#6aa84f";
192
+ const DEFAULT_SCORECARD_BASELINE_COLOR_UP = "#6AA84F";
193
193
  const DEFAULT_SCORECARD_BASELINE_COLOR_DOWN = "#E06666";
194
194
  const LINE_FILL_TRANSPARENCY = 0.4;
195
195
  // session
@@ -561,7 +561,7 @@
561
561
  if (typeof o1 !== typeof o2)
562
562
  return false;
563
563
  if (typeof o1 !== "object")
564
- return o1 === o2;
564
+ return false;
565
565
  // Objects can have different keys if the values are undefined
566
566
  for (const key in o2) {
567
567
  if (!(key in o1) && o2[key] !== undefined) {
@@ -1885,6 +1885,11 @@
1885
1885
  "REMOVE_CONDITIONAL_FORMAT",
1886
1886
  "CHANGE_CONDITIONAL_FORMAT_PRIORITY",
1887
1887
  ]);
1888
+ const invalidateBordersCommands = new Set([
1889
+ "AUTOFILL_CELL",
1890
+ "SET_BORDER",
1891
+ "SET_ZONE_BORDERS",
1892
+ ]);
1888
1893
  const readonlyAllowedCommands = new Set([
1889
1894
  "START",
1890
1895
  "ACTIVATE_SHEET",
@@ -2182,6 +2187,7 @@
2182
2187
  BadExpression: "#BAD_EXPR",
2183
2188
  CircularDependency: "#CYCLE",
2184
2189
  UnknownFunction: "#NAME?",
2190
+ DivisionByZero: "#DIV/0!",
2185
2191
  GenericError: "#ERROR",
2186
2192
  };
2187
2193
  const errorTypes = new Set(Object.values(CellErrorType));
@@ -2220,9 +2226,9 @@
2220
2226
 
2221
2227
  // HELPERS
2222
2228
  const SORT_TYPES_ORDER = ["number", "string", "boolean", "undefined"];
2223
- function assert(condition, message) {
2229
+ function assert(condition, message, value) {
2224
2230
  if (!condition()) {
2225
- throw new EvaluationError(message);
2231
+ throw new EvaluationError(message, value);
2226
2232
  }
2227
2233
  }
2228
2234
  function inferFormat(data) {
@@ -2300,6 +2306,9 @@
2300
2306
  function assertNumberGreaterThanOrEqualToOne(value) {
2301
2307
  assert(() => value >= 1, _t("The function [[FUNCTION_NAME]] expects a number value to be greater than or equal to 1, but receives %s.", value.toString()));
2302
2308
  }
2309
+ function assertNotZero(value) {
2310
+ assert(() => value !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."), CellErrorType.DivisionByZero);
2311
+ }
2303
2312
  function toString(data) {
2304
2313
  const value = toValue(data);
2305
2314
  switch (typeof value) {
@@ -3065,6 +3074,9 @@
3065
3074
  return formattedValue;
3066
3075
  }
3067
3076
  function applyInternalNumberFormat(value, format, locale) {
3077
+ if (value === Infinity) {
3078
+ return "∞" + (format.isPercent ? "%" : "");
3079
+ }
3068
3080
  if (format.isPercent) {
3069
3081
  value = value * 100;
3070
3082
  }
@@ -3408,6 +3420,46 @@
3408
3420
  });
3409
3421
  return convertInternalFormatToFormat(roundedFormat);
3410
3422
  }
3423
+ function humanizeNumber({ value, format }, locale) {
3424
+ const numberFormat = formatLargeNumber({
3425
+ value,
3426
+ format,
3427
+ }, undefined, locale);
3428
+ return formatValue(value, { format: numberFormat, locale });
3429
+ }
3430
+ function formatLargeNumber(arg, unit, locale) {
3431
+ let value = 0;
3432
+ try {
3433
+ value = Math.abs(toNumber(arg?.value, locale));
3434
+ }
3435
+ catch (e) {
3436
+ return "";
3437
+ }
3438
+ const format = arg?.format;
3439
+ if (unit !== undefined) {
3440
+ const postFix = unit?.value;
3441
+ switch (postFix) {
3442
+ case "k":
3443
+ return createLargeNumberFormat(format, 1e3, "k");
3444
+ case "m":
3445
+ return createLargeNumberFormat(format, 1e6, "m");
3446
+ case "b":
3447
+ return createLargeNumberFormat(format, 1e9, "b");
3448
+ default:
3449
+ throw new EvaluationError(_t("The formatting unit should be 'k', 'm' or 'b'."));
3450
+ }
3451
+ }
3452
+ if (value < 1e5) {
3453
+ return createLargeNumberFormat(format, 0, "");
3454
+ }
3455
+ else if (value < 1e8) {
3456
+ return createLargeNumberFormat(format, 1e3, "k");
3457
+ }
3458
+ else if (value < 1e11) {
3459
+ return createLargeNumberFormat(format, 1e6, "m");
3460
+ }
3461
+ return createLargeNumberFormat(format, 1e9, "b");
3462
+ }
3411
3463
  function createLargeNumberFormat(format, magnitude, postFix, locale) {
3412
3464
  const internalFormat = parseFormat(format || "#,##0");
3413
3465
  const largeNumberFormat = [];
@@ -4600,14 +4652,19 @@
4600
4652
  const textWidthCache = {};
4601
4653
  function computeTextWidth(context, text, style, fontUnit = "pt") {
4602
4654
  const font = computeTextFont(style, fontUnit);
4655
+ context.save();
4656
+ context.font = font;
4657
+ const width = computeCachedTextWidth(context, text);
4658
+ context.restore();
4659
+ return width;
4660
+ }
4661
+ function computeCachedTextWidth(context, text) {
4662
+ const font = context.font;
4603
4663
  if (!textWidthCache[font]) {
4604
4664
  textWidthCache[font] = {};
4605
4665
  }
4606
4666
  if (textWidthCache[font][text] === undefined) {
4607
- context.save();
4608
- context.font = font;
4609
4667
  const textWidth = context.measureText(text).width;
4610
- context.restore();
4611
4668
  textWidthCache[font][text] = textWidth;
4612
4669
  }
4613
4670
  return textWidthCache[font][text];
@@ -4752,6 +4809,42 @@
4752
4809
  function getContextFontSize(font) {
4753
4810
  return Number(font.match(pxRegex)?.[1]);
4754
4811
  }
4812
+ // Inspired from https://stackoverflow.com/a/10511598
4813
+ function clipTextWithEllipsis(ctx, text, maxWidth) {
4814
+ let width = computeCachedTextWidth(ctx, text);
4815
+ if (width <= maxWidth) {
4816
+ return text;
4817
+ }
4818
+ const ellipsis = "…";
4819
+ const ellipsisWidth = computeCachedTextWidth(ctx, text);
4820
+ if (width <= ellipsisWidth) {
4821
+ return text;
4822
+ }
4823
+ let len = text.length;
4824
+ while (width >= maxWidth - ellipsisWidth && len-- > 0) {
4825
+ text = text.substring(0, len);
4826
+ width = computeCachedTextWidth(ctx, text);
4827
+ }
4828
+ return text + ellipsis;
4829
+ }
4830
+ function splitTextInTwoLines(text) {
4831
+ let spaces = "";
4832
+ while (text[0] === " ") {
4833
+ spaces += " ";
4834
+ text = text.slice(1);
4835
+ }
4836
+ const length = text.length;
4837
+ const middle = Math.floor(length / 2);
4838
+ const leftSpace = text.substring(0, middle).lastIndexOf(" ");
4839
+ const rightSpace = text.substring(middle).indexOf(" ") + middle;
4840
+ if (leftSpace === -1 && rightSpace === middle - 1) {
4841
+ return [spaces + text, ""];
4842
+ }
4843
+ if (leftSpace > length - rightSpace || rightSpace === middle - 1) {
4844
+ return [spaces + text.slice(0, leftSpace), spaces + text.slice(leftSpace + 1)];
4845
+ }
4846
+ return [spaces + text.slice(0, rightSpace), spaces + text.slice(rightSpace + 1)];
4847
+ }
4755
4848
  function drawDecoratedText(context, text, position, underline = false, strikethrough = false, strokeWidth = getContextFontSize(context.font) / 10 //This value is defined to get a good looking stroke
4756
4849
  ) {
4757
4850
  context.fillText(text, position.x, position.y);
@@ -7395,6 +7488,7 @@
7395
7488
  };
7396
7489
  const ChartTerms = {
7397
7490
  Series: _t("Series"),
7491
+ BackgroundColor: _t("Background color"),
7398
7492
  Errors: {
7399
7493
  Unexpected: _t("The chart definition is invalid for an unknown reason"),
7400
7494
  // BASIC CHART ERRORS (LINE | BAR | PIE)
@@ -9430,32 +9524,63 @@ stores.inject(MyMetaStore, storeInstance);
9430
9524
  }
9431
9525
  return true;
9432
9526
  }
9433
- // ---------------------------------------------------------------------------
9434
- // Scorecard
9435
- // ---------------------------------------------------------------------------
9436
- function getBaselineText(baseline, keyValue, baselineMode, locale) {
9527
+ function getChartPositionAtCenterOfViewport(getters, chartSize) {
9528
+ const { x, y } = getters.getMainViewportCoordinates();
9529
+ const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
9530
+ const { width, height } = getters.getVisibleRect(getters.getActiveMainViewport());
9531
+ const position = {
9532
+ x: x + scrollX + Math.max(0, (width - chartSize.width) / 2),
9533
+ y: y + scrollY + Math.max(0, (height - chartSize.height) / 2),
9534
+ }; // Position at the center of the scrollable viewport
9535
+ return position;
9536
+ }
9537
+
9538
+ function getBaselineText(baseline, keyValue, baselineMode, humanize, locale) {
9437
9539
  if (!baseline) {
9438
9540
  return "";
9439
9541
  }
9440
9542
  else if (baselineMode === "text" ||
9441
9543
  keyValue?.type !== CellValueType.number ||
9442
9544
  baseline.type !== CellValueType.number) {
9545
+ if (humanize) {
9546
+ return humanizeNumber(baseline, locale);
9547
+ }
9443
9548
  return baseline.formattedValue;
9444
9549
  }
9550
+ let { value, format } = baseline;
9551
+ if (baselineMode === "progress") {
9552
+ value = keyValue.value / value;
9553
+ format = "0.0%";
9554
+ }
9445
9555
  else {
9446
- let diff = keyValue.value - baseline.value;
9447
- if (baselineMode === "percentage" && diff !== 0) {
9448
- diff = (diff / baseline.value) * 100;
9556
+ value = Math.abs(keyValue.value - value);
9557
+ if (baselineMode === "percentage" && value !== 0) {
9558
+ value = value / baseline.value;
9449
9559
  }
9450
- if (baselineMode !== "percentage" && baseline.format) {
9451
- return formatValue(diff, { format: baseline.format, locale });
9560
+ if (baselineMode === "percentage") {
9561
+ format = "0.0%";
9452
9562
  }
9453
- const baselineStr = Math.abs(parseFloat(diff.toFixed(2))).toLocaleString();
9454
- return baselineMode === "percentage" ? baselineStr + "%" : baselineStr;
9563
+ if (!format) {
9564
+ value = Math.round(value * 100) / 100;
9565
+ }
9566
+ }
9567
+ if (humanize) {
9568
+ return humanizeNumber({ value, format }, locale);
9455
9569
  }
9570
+ return formatValue(value, { format, locale });
9571
+ }
9572
+ function getKeyValueText(keyValueCell, humanize, locale) {
9573
+ if (!keyValueCell) {
9574
+ return "";
9575
+ }
9576
+ if (humanize) {
9577
+ return humanizeNumber(keyValueCell, locale);
9578
+ }
9579
+ return keyValueCell.formattedValue ?? String(keyValueCell.value ?? "");
9456
9580
  }
9457
9581
  function getBaselineColor(baseline, baselineMode, keyValue, colorUp, colorDown) {
9458
9582
  if (baselineMode === "text" ||
9583
+ baselineMode === "progress" ||
9459
9584
  baseline?.type !== CellValueType.number ||
9460
9585
  keyValue?.type !== CellValueType.number) {
9461
9586
  return undefined;
@@ -9484,17 +9609,6 @@ stores.inject(MyMetaStore, storeInstance);
9484
9609
  }
9485
9610
  return "neutral";
9486
9611
  }
9487
- function getChartPositionAtCenterOfViewport(getters, chartSize) {
9488
- const { x, y } = getters.getMainViewportCoordinates();
9489
- const { scrollX, scrollY } = getters.getActiveSheetScrollInfo();
9490
- const { width, height } = getters.getVisibleRect(getters.getActiveMainViewport());
9491
- const position = {
9492
- x: x + scrollX + Math.max(0, (width - chartSize.width) / 2),
9493
- y: y + scrollY + Math.max(0, (height - chartSize.height) / 2),
9494
- }; // Position at the center of the scrollable viewport
9495
- return position;
9496
- }
9497
-
9498
9612
  function checkKeyValue(definition) {
9499
9613
  return definition.keyValue && !rangeReference.test(definition.keyValue)
9500
9614
  ? "InvalidScorecardKeyValue" /* CommandResult.InvalidScorecardKeyValue */
@@ -9512,10 +9626,12 @@ stores.inject(MyMetaStore, storeInstance);
9512
9626
  baseline;
9513
9627
  baselineMode;
9514
9628
  baselineDescr;
9629
+ progressBar = false;
9515
9630
  background;
9516
9631
  baselineColorUp;
9517
9632
  baselineColorDown;
9518
9633
  fontColor;
9634
+ humanize;
9519
9635
  type = "scorecard";
9520
9636
  constructor(definition, sheetId, getters) {
9521
9637
  super(definition, sheetId, getters);
@@ -9526,6 +9642,7 @@ stores.inject(MyMetaStore, storeInstance);
9526
9642
  this.background = definition.background;
9527
9643
  this.baselineColorUp = definition.baselineColorUp;
9528
9644
  this.baselineColorDown = definition.baselineColorDown;
9645
+ this.humanize = definition.humanize ?? false;
9529
9646
  }
9530
9647
  static validateChartDefinition(validator, definition) {
9531
9648
  return validator.checkValidations(definition, checkKeyValue, checkBaseline);
@@ -9595,6 +9712,7 @@ stores.inject(MyMetaStore, storeInstance);
9595
9712
  keyValue: keyValue
9596
9713
  ? this.getters.getRangeString(keyValue, targetSheetId || this.sheetId)
9597
9714
  : undefined,
9715
+ humanize: this.humanize,
9598
9716
  };
9599
9717
  }
9600
9718
  getDefinitionForExcel() {
@@ -9620,7 +9738,7 @@ stores.inject(MyMetaStore, storeInstance);
9620
9738
  if (structure.title) {
9621
9739
  ctx.font = structure.title.style.font;
9622
9740
  ctx.fillStyle = structure.title.style.color;
9623
- ctx.fillText(structure.title.text, structure.title.position.x, structure.title.position.y);
9741
+ ctx.fillText(clipTextWithEllipsis(ctx, structure.title.text, canvas.width - structure.title.position.x), structure.title.position.x, structure.title.position.y);
9624
9742
  }
9625
9743
  if (structure.baseline) {
9626
9744
  ctx.font = structure.baseline.style.font;
@@ -9647,20 +9765,41 @@ stores.inject(MyMetaStore, storeInstance);
9647
9765
  ctx.restore();
9648
9766
  }
9649
9767
  if (structure.baselineDescr) {
9650
- ctx.font = structure.baselineDescr.style.font;
9651
- ctx.fillStyle = structure.baselineDescr.style.color;
9652
- ctx.fillText(structure.baselineDescr.text, structure.baselineDescr.position.x, structure.baselineDescr.position.y);
9768
+ const descr = structure.baselineDescr[0];
9769
+ ctx.font = descr.style.font;
9770
+ ctx.fillStyle = descr.style.color;
9771
+ for (const description of structure.baselineDescr) {
9772
+ ctx.fillText(clipTextWithEllipsis(ctx, description.text, canvas.width - description.position.x), description.position.x, description.position.y);
9773
+ }
9653
9774
  }
9654
9775
  if (structure.key) {
9655
9776
  ctx.font = structure.key.style.font;
9656
9777
  ctx.fillStyle = structure.key.style.color;
9657
9778
  drawDecoratedText(ctx, structure.key.text, structure.key.position, structure.key.style.underline, structure.key.style.strikethrough);
9658
9779
  }
9780
+ if (structure.progressBar) {
9781
+ ctx.fillStyle = structure.progressBar.style.backgroundColor;
9782
+ ctx.beginPath();
9783
+ ctx.roundRect(structure.progressBar.position.x, structure.progressBar.position.y, structure.progressBar.dimension.width, structure.progressBar.dimension.height, structure.progressBar.dimension.height / 2);
9784
+ ctx.fill();
9785
+ ctx.fillStyle = structure.progressBar.style.color;
9786
+ ctx.beginPath();
9787
+ if (structure.progressBar.value > 0) {
9788
+ ctx.roundRect(structure.progressBar.position.x, structure.progressBar.position.y, structure.progressBar.dimension.width *
9789
+ Math.max(0, Math.min(1.0, structure.progressBar.value)), structure.progressBar.dimension.height, structure.progressBar.dimension.height / 2);
9790
+ }
9791
+ else {
9792
+ const width = structure.progressBar.dimension.width *
9793
+ Math.max(0, Math.min(1.0, -structure.progressBar.value));
9794
+ ctx.roundRect(structure.progressBar.position.x + structure.progressBar.dimension.width - width, structure.progressBar.position.y, width, structure.progressBar.dimension.height, structure.progressBar.dimension.height / 2);
9795
+ }
9796
+ ctx.fill();
9797
+ }
9659
9798
  }
9660
9799
  function createScorecardChartRuntime(chart, getters) {
9661
- let keyValue = "";
9662
9800
  let formattedKeyValue = "";
9663
9801
  let keyValueCell;
9802
+ const locale = getters.getLocale();
9664
9803
  if (chart.keyValue) {
9665
9804
  const keyValuePosition = {
9666
9805
  sheetId: chart.keyValue.sheetId,
@@ -9668,31 +9807,33 @@ stores.inject(MyMetaStore, storeInstance);
9668
9807
  row: chart.keyValue.zone.top,
9669
9808
  };
9670
9809
  keyValueCell = getters.getEvaluatedCell(keyValuePosition);
9671
- keyValue = String(keyValueCell.value ?? "");
9672
- formattedKeyValue = keyValueCell.formattedValue;
9810
+ formattedKeyValue = getKeyValueText(keyValueCell, chart.humanize ?? false, locale);
9673
9811
  }
9674
9812
  let baselineCell;
9675
9813
  const baseline = chart.baseline;
9676
9814
  if (baseline) {
9677
9815
  const baselinePosition = {
9678
- sheetId: chart.baseline.sheetId,
9679
- col: chart.baseline.zone.left,
9680
- row: chart.baseline.zone.top,
9816
+ sheetId: baseline.sheetId,
9817
+ col: baseline.zone.left,
9818
+ row: baseline.zone.top,
9681
9819
  };
9682
9820
  baselineCell = getters.getEvaluatedCell(baselinePosition);
9683
9821
  }
9684
9822
  const { background, fontColor } = getters.getStyleOfSingleCellChart(chart.background, chart.keyValue);
9685
- const locale = getters.getLocale();
9823
+ const baselineDisplay = getBaselineText(baselineCell, keyValueCell, chart.baselineMode, chart.humanize ?? false, locale);
9824
+ const baselineValue = chart.baselineMode === "progress" && isNumber(baselineDisplay, locale)
9825
+ ? toNumber(baselineDisplay, locale)
9826
+ : 0;
9686
9827
  return {
9687
9828
  title: _t(chart.title),
9688
- keyValue: formattedKeyValue || keyValue,
9689
- baselineDisplay: getBaselineText(baselineCell, keyValueCell, chart.baselineMode, locale),
9829
+ keyValue: formattedKeyValue,
9830
+ baselineDisplay,
9690
9831
  baselineArrow: getBaselineArrowDirection(baselineCell, keyValueCell, chart.baselineMode),
9691
9832
  baselineColor: getBaselineColor(baselineCell, chart.baselineMode, keyValueCell, chart.baselineColorUp, chart.baselineColorDown),
9692
- baselineDescr: chart.baselineDescr ? _t(chart.baselineDescr) : "",
9833
+ baselineDescr: chart.baselineMode !== "progress" && chart.baselineDescr ? _t(chart.baselineDescr) : "",
9693
9834
  fontColor,
9694
9835
  background,
9695
- baselineStyle: chart.baselineMode !== "percentage" && baseline
9836
+ baselineStyle: chart.baselineMode !== "percentage" && chart.baselineMode !== "progress" && baseline
9696
9837
  ? getters.getCellStyle({
9697
9838
  sheetId: baseline.sheetId,
9698
9839
  col: baseline.zone.left,
@@ -9706,17 +9847,21 @@ stores.inject(MyMetaStore, storeInstance);
9706
9847
  row: chart.keyValue.zone.top,
9707
9848
  })
9708
9849
  : undefined,
9850
+ progressBar: chart.baselineMode === "progress"
9851
+ ? {
9852
+ value: baselineValue,
9853
+ color: baselineValue > 0 ? chart.baselineColorUp : chart.baselineColorDown,
9854
+ }
9855
+ : undefined,
9709
9856
  };
9710
9857
  }
9711
9858
 
9712
9859
  /* Sizes of boxes containing the texts, in percentage of the Chart size */
9713
9860
  const TITLE_FONT_SIZE = 18;
9714
- const BASELINE_BOX_HEIGHT_RATIO = 0.35;
9715
- const KEY_BOX_HEIGHT_RATIO = 0.65;
9716
- /** Baseline description should have a smaller font than the baseline */
9717
- const BASELINE_DESCR_FONT_RATIO = 0.9;
9718
- /* Padding at the border of the chart, in percentage of the chart width */
9719
- const CHART_PADDING_RATIO = 0.02;
9861
+ const KEY_BOX_HEIGHT_RATIO = 0.8;
9862
+ /* Padding at the border of the chart */
9863
+ const CHART_PADDING = 10;
9864
+ const BOTTOM_PADDING_RATIO = 0.05;
9720
9865
  /**
9721
9866
  * Line height (in em)
9722
9867
  * Having a line heigh =1em (=font size) don't work, the font will overflow.
@@ -9756,31 +9901,37 @@ stores.inject(MyMetaStore, storeInstance);
9756
9901
  },
9757
9902
  };
9758
9903
  const style = this.getTextStyles();
9759
- const { height: titleHeight } = this.getTextDimensions(this.title, style.title.font);
9904
+ let titleHeight = 0;
9760
9905
  if (this.title) {
9906
+ ({ height: titleHeight } = this.getFullTextDimensions(this.title, style.title.font));
9761
9907
  structure.title = {
9762
9908
  text: this.title,
9763
9909
  style: style.title,
9764
9910
  position: {
9765
- x: this.chartPadding,
9766
- y: this.chartPadding + titleHeight,
9911
+ x: CHART_PADDING,
9912
+ y: CHART_PADDING / 2 + titleHeight,
9767
9913
  },
9768
9914
  };
9769
9915
  }
9770
9916
  const baselineArrowSize = style.baselineArrow?.size ?? 0;
9771
- const { height: baselineHeight, width: baselineWidth } = this.getTextDimensions(this.baseline, style.baselineValue.font);
9772
- const { width: baselineDescrWidth } = this.getTextDimensions(this.baselineDescr, style.baselineDescr.font);
9917
+ let { height: baselineHeight, width: baselineWidth } = this.getTextDimensions(this.baseline, style.baselineValue.font);
9918
+ if (!this.baseline) {
9919
+ baselineHeight = this.getTextDimensions(this.baselineDescr, style.baselineDescr.font).height;
9920
+ }
9921
+ const baselineDescrWidth = style.baselineDescr.isSplit
9922
+ ? Math.max(...splitTextInTwoLines(this.baselineDescr).map((line) => this.getTextDimensions(line, style.baselineDescr.font).width))
9923
+ : this.getTextDimensions(this.baselineDescr, style.baselineDescr.font).width;
9773
9924
  structure.baseline = {
9774
9925
  text: this.baseline,
9775
9926
  style: style.baselineValue,
9776
9927
  position: {
9777
9928
  x: (this.width - baselineWidth - baselineDescrWidth + baselineArrowSize) / 2,
9778
9929
  y: this.keyValue
9779
- ? this.height - 2 * this.chartPadding
9780
- : this.height - (this.height - titleHeight - baselineHeight) / 2 - this.chartPadding,
9930
+ ? this.height * (1 - BOTTOM_PADDING_RATIO * (this.runtime.progressBar ? 1 : 2))
9931
+ : this.height - (this.height - titleHeight - baselineHeight) / 2 - CHART_PADDING,
9781
9932
  },
9782
9933
  };
9783
- if (style.baselineArrow) {
9934
+ if (style.baselineArrow && !this.runtime.progressBar) {
9784
9935
  structure.baselineArrow = {
9785
9936
  direction: this.baselineArrow,
9786
9937
  style: style.baselineArrow,
@@ -9791,23 +9942,68 @@ stores.inject(MyMetaStore, storeInstance);
9791
9942
  };
9792
9943
  }
9793
9944
  if (this.baselineDescr) {
9794
- structure.baselineDescr = {
9795
- text: this.baselineDescr,
9796
- style: style.baselineDescr,
9945
+ const position = {
9946
+ x: structure.baseline.position.x + baselineWidth,
9947
+ y: structure.baseline.position.y,
9948
+ };
9949
+ if (style.baselineDescr.isSplit) {
9950
+ const description = splitTextInTwoLines(this.baselineDescr);
9951
+ const measure = this.context.measureText(description[1]);
9952
+ const deltaY = measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent;
9953
+ structure.baselineDescr = [
9954
+ {
9955
+ text: description[0],
9956
+ style: style.baselineDescr,
9957
+ position: {
9958
+ x: position.x,
9959
+ y: position.y - deltaY,
9960
+ },
9961
+ },
9962
+ {
9963
+ text: description[1],
9964
+ style: style.baselineDescr,
9965
+ position,
9966
+ },
9967
+ ];
9968
+ }
9969
+ else {
9970
+ structure.baselineDescr = [
9971
+ {
9972
+ text: this.baselineDescr,
9973
+ style: style.baselineDescr,
9974
+ position,
9975
+ },
9976
+ ];
9977
+ }
9978
+ }
9979
+ let progressBarHeight = 0;
9980
+ if (this.runtime.progressBar) {
9981
+ progressBarHeight = this.height * 0.05;
9982
+ structure.progressBar = {
9797
9983
  position: {
9798
- x: structure.baseline.position.x + baselineWidth,
9799
- y: structure.baseline.position.y,
9984
+ x: 2 * CHART_PADDING,
9985
+ y: this.height * (1 - 2 * BOTTOM_PADDING_RATIO) - baselineHeight - progressBarHeight,
9986
+ },
9987
+ dimension: {
9988
+ height: progressBarHeight,
9989
+ width: this.width - 4 * CHART_PADDING,
9990
+ },
9991
+ value: this.runtime.progressBar.value,
9992
+ style: {
9993
+ color: this.runtime.progressBar.color,
9994
+ backgroundColor: this.secondaryFontColor,
9800
9995
  },
9801
9996
  };
9802
9997
  }
9803
- const { height: keyHeight, width: keyWidth } = this.getTextDimensions(this.keyValue, style.keyValue.font);
9998
+ const { width: keyWidth, height: keyHeight } = this.getFullTextDimensions(this.keyValue, style.keyValue.font);
9804
9999
  if (this.keyValue) {
9805
10000
  structure.key = {
9806
10001
  text: this.keyValue,
9807
10002
  style: style.keyValue,
9808
10003
  position: {
9809
10004
  x: (this.width - keyWidth) / 2,
9810
- y: (this.height - baselineHeight + titleHeight + keyHeight) / 2 - this.chartPadding,
10005
+ y: this.height * (0.5 - BOTTOM_PADDING_RATIO * 2) +
10006
+ (titleHeight + keyHeight / (this.baseline || this.baselineDescr ? 2 : 1.2)) / 2,
9811
10007
  },
9812
10008
  };
9813
10009
  }
@@ -9834,9 +10030,6 @@ stores.inject(MyMetaStore, storeInstance);
9834
10030
  get secondaryFontColor() {
9835
10031
  return relativeLuminance(this.backgroundColor) > 0.3 ? "#525252" : "#C8C8C8";
9836
10032
  }
9837
- get chartPadding() {
9838
- return this.width * CHART_PADDING_RATIO;
9839
- }
9840
10033
  getTextDimensions(text, font) {
9841
10034
  this.context.font = font;
9842
10035
  const measure = this.context.measureText(text);
@@ -9845,16 +10038,44 @@ stores.inject(MyMetaStore, storeInstance);
9845
10038
  height: measure.actualBoundingBoxAscent + measure.actualBoundingBoxDescent,
9846
10039
  };
9847
10040
  }
10041
+ getFullTextDimensions(text, font) {
10042
+ this.context.font = font;
10043
+ const measure = this.context.measureText(text);
10044
+ return {
10045
+ width: measure.width,
10046
+ height: measure.fontBoundingBoxAscent + measure.fontBoundingBoxDescent,
10047
+ };
10048
+ }
9848
10049
  getTextStyles() {
9849
10050
  // If the widest text overflows horizontally, scale it down, and apply the same scaling factors to all the other fonts.
9850
- const maxLineWidth = this.width * (1 - 2 * CHART_PADDING_RATIO);
9851
- const widestElement = this.getWidestElement();
9852
- const baseFontSize = widestElement.getElementMaxFontSize(this.getDrawableHeight(), this);
9853
- const fontSizeMatchingWidth = getFontSizeMatchingWidth(maxLineWidth, baseFontSize, (fontSize) => widestElement.getElementWidth(fontSize, this.context, this));
9854
- let scalingFactor = fontSizeMatchingWidth / baseFontSize;
10051
+ const maxLineWidth = this.width - 2 * CHART_PADDING;
10052
+ const drawableHeight = this.getDrawableHeight();
9855
10053
  // Fonts sizes in px
9856
- const keyFontSize = new KeyValueElement(this.runtime.keyValueStyle).getElementMaxFontSize(this.getDrawableHeight(), this) * scalingFactor;
9857
- const baselineFontSize = new BaselineElement(this.runtime.baselineStyle).getElementMaxFontSize(this.getDrawableHeight(), this) * scalingFactor;
10054
+ const keyValueElement = new KeyValueElement(this.runtime.keyValueStyle);
10055
+ const heightFont = keyValueElement.getElementMaxFontSize(drawableHeight, this);
10056
+ const widthFont = getFontSizeMatchingWidth(maxLineWidth, 600, (fontSize) => keyValueElement.getElementWidth(fontSize, this.context, this));
10057
+ const keyFontSize = Math.min(heightFont, widthFont);
10058
+ let baselineValueFontSize = Math.floor(keyFontSize * 0.5);
10059
+ this.context.font = getDefaultContextFont(baselineValueFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic);
10060
+ const baselineText = this.baselineArrow !== "neutral" ? "A " + this.baseline : this.baseline;
10061
+ const baselineValueWidth = computeCachedTextWidth(this.context, baselineText);
10062
+ const remainingWidth = maxLineWidth - baselineValueWidth;
10063
+ let baselineDescrFontSize = getFontSizeMatchingWidth(remainingWidth, baselineValueFontSize, (fontSize) => computeTextWidth(this.context, this.baselineDescr, { fontSize }));
10064
+ let isBaselineSplit = false;
10065
+ if (baselineDescrFontSize < baselineValueFontSize / 2.5) {
10066
+ isBaselineSplit = true;
10067
+ baselineDescrFontSize = Math.floor(baselineValueFontSize / 2.5);
10068
+ for (const line of splitTextInTwoLines(this.baselineDescr)) {
10069
+ const lineWidth = getFontSizeMatchingWidth(remainingWidth, baselineValueFontSize, (fontSize) => {
10070
+ this.context.font = getDefaultContextFont(fontSize);
10071
+ return this.context.measureText(line).width;
10072
+ });
10073
+ baselineDescrFontSize = Math.min(baselineDescrFontSize, lineWidth);
10074
+ }
10075
+ }
10076
+ if (this.runtime.progressBar) {
10077
+ baselineValueFontSize /= 1.5;
10078
+ }
9858
10079
  return {
9859
10080
  title: {
9860
10081
  font: getDefaultContextFont(TITLE_FONT_SIZE),
@@ -9867,7 +10088,7 @@ stores.inject(MyMetaStore, storeInstance);
9867
10088
  underline: this.runtime.keyValueStyle?.underline,
9868
10089
  },
9869
10090
  baselineValue: {
9870
- font: getDefaultContextFont(baselineFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic),
10091
+ font: getDefaultContextFont(baselineValueFontSize, this.runtime.baselineStyle?.bold, this.runtime.baselineStyle?.italic),
9871
10092
  strikethrough: this.runtime.baselineStyle?.strikethrough,
9872
10093
  underline: this.runtime.baselineStyle?.underline,
9873
10094
  color: this.runtime.baselineStyle?.textColor ||
@@ -9875,33 +10096,25 @@ stores.inject(MyMetaStore, storeInstance);
9875
10096
  this.secondaryFontColor,
9876
10097
  },
9877
10098
  baselineDescr: {
9878
- font: getDefaultContextFont(baselineFontSize * BASELINE_DESCR_FONT_RATIO),
10099
+ font: getDefaultContextFont(baselineDescrFontSize),
10100
+ isSplit: isBaselineSplit,
9879
10101
  color: this.secondaryFontColor,
9880
10102
  },
9881
- baselineArrow: this.baselineArrow === "neutral"
10103
+ baselineArrow: this.baselineArrow === "neutral" || this.runtime.progressBar
9882
10104
  ? undefined
9883
10105
  : {
9884
- size: this.keyValue ? 0.8 * baselineFontSize : 0,
10106
+ size: this.keyValue ? 0.8 * baselineValueFontSize : 0,
9885
10107
  color: this.runtime.baselineColor || this.secondaryFontColor,
9886
10108
  },
9887
10109
  };
9888
10110
  }
9889
10111
  /** Get the height of the chart minus all the vertical paddings */
9890
10112
  getDrawableHeight() {
9891
- const verticalPadding = 2 * this.chartPadding;
9892
- let availableHeight = this.height - verticalPadding;
10113
+ const verticalPadding = CHART_PADDING + this.height * BOTTOM_PADDING_RATIO;
10114
+ let availableHeight = this.height - 2 * verticalPadding;
9893
10115
  availableHeight -= this.title ? TITLE_FONT_SIZE * LINE_HEIGHT : 0;
9894
10116
  return availableHeight;
9895
10117
  }
9896
- /** Return the element with he widest text in the chart */
9897
- getWidestElement() {
9898
- const baseline = new BaselineElement(this.runtime.baselineStyle);
9899
- const keyValue = new KeyValueElement(this.runtime.keyValueStyle);
9900
- return baseline.getElementWidth(BASELINE_BOX_HEIGHT_RATIO, this.context, this) >
9901
- keyValue.getElementWidth(KEY_BOX_HEIGHT_RATIO, this.context, this)
9902
- ? baseline
9903
- : keyValue;
9904
- }
9905
10118
  }
9906
10119
  class ScorecardScalableElement {
9907
10120
  style;
@@ -9910,29 +10123,7 @@ stores.inject(MyMetaStore, storeInstance);
9910
10123
  }
9911
10124
  measureTextWidth(ctx, text, fontSize) {
9912
10125
  ctx.font = getDefaultContextFont(fontSize, this.style.bold, this.style.italic);
9913
- return ctx.measureText(text).width;
9914
- }
9915
- }
9916
- class BaselineElement extends ScorecardScalableElement {
9917
- getElementWidth(fontSize, ctx, chart) {
9918
- if (!chart.runtime) {
9919
- return 0;
9920
- }
9921
- const baselineStr = chart.baseline;
9922
- // Put mock text to simulate the width of the up/down arrow
9923
- const largeText = chart.baselineArrow !== "neutral" ? "A " + baselineStr : baselineStr;
9924
- let textWidth = this.measureTextWidth(ctx, largeText, fontSize);
9925
- // Baseline descr font size should be smaller than baseline font size
9926
- textWidth += this.measureTextWidth(ctx, chart.baselineDescr, fontSize * BASELINE_DESCR_FONT_RATIO);
9927
- return textWidth;
9928
- }
9929
- getElementMaxFontSize(availableHeight, chart) {
9930
- if (!chart.runtime) {
9931
- return 0;
9932
- }
9933
- const haveBaseline = chart.baseline !== "" || chart.baselineDescr;
9934
- const maxHeight = haveBaseline ? BASELINE_BOX_HEIGHT_RATIO * availableHeight : 0;
9935
- return maxHeight / LINE_HEIGHT;
10126
+ return computeCachedTextWidth(ctx, text);
9936
10127
  }
9937
10128
  }
9938
10129
  class KeyValueElement extends ScorecardScalableElement {
@@ -10862,33 +11053,6 @@ stores.inject(MyMetaStore, storeInstance);
10862
11053
  // -----------------------------------------------------------------------------
10863
11054
  // FORMAT.LARGE.NUMBER
10864
11055
  // -----------------------------------------------------------------------------
10865
- function formatLargeNumber(arg, unit, locale) {
10866
- const value = Math.abs(toNumber(arg?.value, locale));
10867
- const format = arg?.format;
10868
- if (unit !== undefined) {
10869
- const postFix = unit?.value;
10870
- switch (postFix) {
10871
- case "k":
10872
- return createLargeNumberFormat(format, 1e3, "k");
10873
- case "m":
10874
- return createLargeNumberFormat(format, 1e6, "m");
10875
- case "b":
10876
- return createLargeNumberFormat(format, 1e9, "b");
10877
- default:
10878
- throw new EvaluationError(_t("The formatting unit should be 'k', 'm' or 'b'."));
10879
- }
10880
- }
10881
- if (value < 1e5) {
10882
- return createLargeNumberFormat(format, 0, "");
10883
- }
10884
- else if (value < 1e8) {
10885
- return createLargeNumberFormat(format, 1e3, "k");
10886
- }
10887
- else if (value < 1e11) {
10888
- return createLargeNumberFormat(format, 1e6, "m");
10889
- }
10890
- return createLargeNumberFormat(format, 1e9, "b");
10891
- }
10892
11056
  const FORMAT_LARGE_NUMBER = {
10893
11057
  description: _t("Apply a large number format"),
10894
11058
  args: [
@@ -11050,7 +11214,7 @@ stores.inject(MyMetaStore, storeInstance);
11050
11214
  compute: function (x, y) {
11051
11215
  const _x = toNumber(x, this.locale);
11052
11216
  const _y = toNumber(y, this.locale);
11053
- assert(() => _x !== 0 || _y !== 0, _t("Function [[FUNCTION_NAME]] caused a divide by zero error."));
11217
+ assert(() => _x !== 0 || _y !== 0, _t("Function [[FUNCTION_NAME]] caused a divide by zero error."), CellErrorType.DivisionByZero);
11054
11218
  return Math.atan2(_y, _x);
11055
11219
  },
11056
11220
  isExported: true,
@@ -11180,7 +11344,7 @@ stores.inject(MyMetaStore, storeInstance);
11180
11344
  returns: ["NUMBER"],
11181
11345
  compute: function (angle) {
11182
11346
  const _angle = toNumber(angle, this.locale);
11183
- assert(() => _angle !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
11347
+ assertNotZero(_angle);
11184
11348
  return 1 / Math.tan(_angle);
11185
11349
  },
11186
11350
  isExported: true,
@@ -11194,7 +11358,7 @@ stores.inject(MyMetaStore, storeInstance);
11194
11358
  returns: ["NUMBER"],
11195
11359
  compute: function (value) {
11196
11360
  const _value = toNumber(value, this.locale);
11197
- assert(() => _value !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
11361
+ assertNotZero(_value);
11198
11362
  return 1 / Math.tanh(_value);
11199
11363
  },
11200
11364
  isExported: true,
@@ -11322,7 +11486,7 @@ stores.inject(MyMetaStore, storeInstance);
11322
11486
  returns: ["NUMBER"],
11323
11487
  compute: function (angle) {
11324
11488
  const _angle = toNumber(angle, this.locale);
11325
- assert(() => _angle !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
11489
+ assertNotZero(_angle);
11326
11490
  return 1 / Math.sin(_angle);
11327
11491
  },
11328
11492
  isExported: true,
@@ -11336,7 +11500,7 @@ stores.inject(MyMetaStore, storeInstance);
11336
11500
  returns: ["NUMBER"],
11337
11501
  compute: function (value) {
11338
11502
  const _value = toNumber(value, this.locale);
11339
- assert(() => _value !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
11503
+ assertNotZero(_value);
11340
11504
  return 1 / Math.sinh(_value);
11341
11505
  },
11342
11506
  isExported: true,
@@ -11535,7 +11699,7 @@ stores.inject(MyMetaStore, storeInstance);
11535
11699
  // MOD
11536
11700
  // -----------------------------------------------------------------------------
11537
11701
  function mod(dividend, divisor) {
11538
- assert(() => divisor !== 0, _t("The divisor must be different from 0."));
11702
+ assert(() => divisor !== 0, _t("The divisor must be different from 0."), CellErrorType.DivisionByZero);
11539
11703
  const modulus = dividend % divisor;
11540
11704
  // -42 % 10 = -2 but we want 8, so need the code below
11541
11705
  if ((modulus > 0 && divisor < 0) || (modulus < 0 && divisor > 0)) {
@@ -12104,7 +12268,7 @@ stores.inject(MyMetaStore, storeInstance);
12104
12268
  count += 1;
12105
12269
  return acc + a;
12106
12270
  }, 0, locale);
12107
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12271
+ assertNotZero(count);
12108
12272
  return sum / count;
12109
12273
  }
12110
12274
  function countNumbers(values, locale) {
@@ -12171,7 +12335,7 @@ stores.inject(MyMetaStore, storeInstance);
12171
12335
  function covariance(dataY, dataX, isSample) {
12172
12336
  const { flatDataX, flatDataY } = filterAndFlatData(dataY, dataX);
12173
12337
  const count = flatDataY.length;
12174
- assert(() => count !== 0 && (!isSample || count !== 1), _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12338
+ assert(() => count !== 0 && (!isSample || count !== 1), _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."), CellErrorType.DivisionByZero);
12175
12339
  let sumY = 0;
12176
12340
  let sumX = 0;
12177
12341
  for (let i = 0; i < count; i++) {
@@ -12194,7 +12358,7 @@ stores.inject(MyMetaStore, storeInstance);
12194
12358
  count += 1;
12195
12359
  return acc + a;
12196
12360
  }, 0, locale);
12197
- assert(() => count !== 0 && (!isSample || count !== 1), _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12361
+ assert(() => count !== 0 && (!isSample || count !== 1), _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."), CellErrorType.DivisionByZero);
12198
12362
  const average = sum / count;
12199
12363
  return (reduceFunction(args, (acc, a) => acc + Math.pow(a - average, 2), 0, locale) /
12200
12364
  (count - (isSample ? 1 : 0)));
@@ -12391,7 +12555,7 @@ stores.inject(MyMetaStore, storeInstance);
12391
12555
  count += 1;
12392
12556
  return acc + a;
12393
12557
  }, 0, this.locale);
12394
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12558
+ assertNotZero(count);
12395
12559
  const average = sum / count;
12396
12560
  return reduceNumbers(values, (acc, a) => acc + Math.abs(average - a), 0, this.locale) / count;
12397
12561
  },
@@ -12463,7 +12627,7 @@ stores.inject(MyMetaStore, storeInstance);
12463
12627
  }
12464
12628
  }
12465
12629
  }
12466
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12630
+ assertNotZero(count);
12467
12631
  return { value: sum / count, format: inferFormat(args[0]) };
12468
12632
  },
12469
12633
  };
@@ -12483,7 +12647,7 @@ stores.inject(MyMetaStore, storeInstance);
12483
12647
  count += 1;
12484
12648
  return acc + a;
12485
12649
  }, 0, this.locale);
12486
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12650
+ assertNotZero(count);
12487
12651
  return {
12488
12652
  value: sum / count,
12489
12653
  format: inferFormat(args[0]),
@@ -12513,7 +12677,7 @@ stores.inject(MyMetaStore, storeInstance);
12513
12677
  sum += value;
12514
12678
  }
12515
12679
  }, this.locale);
12516
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12680
+ assertNotZero(count);
12517
12681
  return sum / count;
12518
12682
  },
12519
12683
  isExported: true,
@@ -12542,7 +12706,7 @@ stores.inject(MyMetaStore, storeInstance);
12542
12706
  sum += value;
12543
12707
  }
12544
12708
  }, this.locale);
12545
- assert(() => count !== 0, _t("Evaluation of function [[FUNCTION_NAME]] caused a divide by zero error."));
12709
+ assertNotZero(count);
12546
12710
  return sum / count;
12547
12711
  },
12548
12712
  isExported: true,
@@ -17967,7 +18131,7 @@ stores.inject(MyMetaStore, storeInstance);
17967
18131
  returns: ["NUMBER"],
17968
18132
  compute: function (dividend, divisor) {
17969
18133
  const _divisor = toNumber(divisor, this.locale);
17970
- assert(() => _divisor !== 0, _t("The divisor must be different from zero."));
18134
+ assert(() => _divisor !== 0, _t("The divisor must be different from zero."), CellErrorType.DivisionByZero);
17971
18135
  return {
17972
18136
  value: toNumber(dividend, this.locale) / _divisor,
17973
18137
  format: dividend?.format || divisor?.format,
@@ -21012,7 +21176,21 @@ stores.inject(MyMetaStore, storeInstance);
21012
21176
  return new ScatterChart(definition, this.sheetId, this.getters);
21013
21177
  }
21014
21178
  getDefinitionForExcel() {
21015
- return undefined; // TODO
21179
+ // Excel does not support aggregating labels
21180
+ if (this.aggregated) {
21181
+ return undefined;
21182
+ }
21183
+ const dataSets = this.dataSets
21184
+ .map((ds) => toExcelDataset(this.getters, ds))
21185
+ .filter((ds) => ds.range !== "");
21186
+ const labelRange = toExcelLabelRange(this.getters, this.labelRange, shouldRemoveFirstLabel(this.labelRange, this.dataSets[0], this.dataSetsHaveTitle));
21187
+ return {
21188
+ ...this.getDefinition(),
21189
+ backgroundColor: toXlsxHexColor(this.background || BACKGROUND_CHART_COLOR),
21190
+ fontColor: toXlsxHexColor(chartFontColor(this.background)),
21191
+ dataSets,
21192
+ labelRange,
21193
+ };
21016
21194
  }
21017
21195
  copyForSheetId(sheetId) {
21018
21196
  const dataSets = copyDataSetsWithNewSheetId(this.sheetId, sheetId, this.dataSets);
@@ -27440,13 +27618,30 @@ stores.inject(MyMetaStore, storeInstance);
27440
27618
  }
27441
27619
  }
27442
27620
 
27443
- class ChartColor extends owl.Component {
27444
- static template = "o-spreadsheet.ChartColor";
27445
- static components = { ColorPickerWidget, Section };
27621
+ const TRANSPARENT_BACKGROUND_SVG = /*xml*/ `
27622
+ <svg xmlns="http://www.w3.org/2000/svg" width="10" height="10">
27623
+ <path fill="#d9d9d9" d="M5 5h5v5H5zH0V0h5"/>
27624
+ </svg>
27625
+ `;
27626
+ css /* scss */ `
27627
+ .o-round-color-picker-button {
27628
+ width: 15px;
27629
+ height: 15px;
27630
+ cursor: pointer;
27631
+ border: 1px solid #aaa;
27632
+ background-position: 1px 1px;
27633
+ background-image: url("data:image/svg+xml,${encodeURIComponent(TRANSPARENT_BACKGROUND_SVG)}");
27634
+ }
27635
+ `;
27636
+ class RoundColorPicker extends owl.Component {
27637
+ static template = "o-spreadsheet.RoundColorPicker";
27638
+ static components = { ColorPickerWidget, Section, ColorPicker };
27446
27639
  static props = {
27447
27640
  currentColor: { type: String, optional: true },
27641
+ title: { type: String, optional: true },
27448
27642
  onColorPicked: Function,
27449
27643
  };
27644
+ colorPickerButtonRef = owl.useRef("colorPickerButton");
27450
27645
  state;
27451
27646
  setup() {
27452
27647
  this.state = owl.useState({ pickerOpened: false });
@@ -27458,6 +27653,19 @@ stores.inject(MyMetaStore, storeInstance);
27458
27653
  togglePicker() {
27459
27654
  this.state.pickerOpened = !this.state.pickerOpened;
27460
27655
  }
27656
+ onColorPicked(color) {
27657
+ this.props.onColorPicked(color);
27658
+ this.state.pickerOpened = false;
27659
+ }
27660
+ get colorPickerAnchorRect() {
27661
+ const button = this.colorPickerButtonRef.el;
27662
+ return getBoundingRectAsPOJO(button);
27663
+ }
27664
+ get buttonStyle() {
27665
+ return cssPropertiesToCss({
27666
+ background: this.props.currentColor,
27667
+ });
27668
+ }
27461
27669
  }
27462
27670
 
27463
27671
  class ChartTitle extends owl.Component {
@@ -27471,7 +27679,7 @@ stores.inject(MyMetaStore, storeInstance);
27471
27679
 
27472
27680
  class LineBarPieDesignPanel extends owl.Component {
27473
27681
  static template = "o-spreadsheet-LineBarPieDesignPanel";
27474
- static components = { ChartColor, ChartTitle, Section };
27682
+ static components = { RoundColorPicker, ChartTitle, Section };
27475
27683
  static props = {
27476
27684
  figureId: String,
27477
27685
  definition: Object,
@@ -27494,6 +27702,9 @@ stores.inject(MyMetaStore, storeInstance);
27494
27702
  [attr]: ev.target.value,
27495
27703
  });
27496
27704
  }
27705
+ get backgroundColorTitle() {
27706
+ return ChartTerms.BackgroundColor;
27707
+ }
27497
27708
  }
27498
27709
 
27499
27710
  class BarChartDesignPanel extends LineBarPieDesignPanel {
@@ -27563,6 +27774,10 @@ stores.inject(MyMetaStore, storeInstance);
27563
27774
  line-height: 18px;
27564
27775
  width: 100%;
27565
27776
  }
27777
+ td {
27778
+ box-sizing: border-box;
27779
+ height: 30px;
27780
+ }
27566
27781
  th.o-gauge-color-set-colorPicker {
27567
27782
  width: 8%;
27568
27783
  }
@@ -27585,7 +27800,12 @@ stores.inject(MyMetaStore, storeInstance);
27585
27800
  `;
27586
27801
  class GaugeChartDesignPanel extends owl.Component {
27587
27802
  static template = "o-spreadsheet-GaugeChartDesignPanel";
27588
- static components = { ColorPickerWidget, ChartErrorSection, ChartColor, ChartTitle, Section };
27803
+ static components = {
27804
+ ChartErrorSection,
27805
+ RoundColorPicker,
27806
+ ChartTitle,
27807
+ Section,
27808
+ };
27589
27809
  static props = {
27590
27810
  figureId: String,
27591
27811
  definition: Object,
@@ -27597,9 +27817,6 @@ stores.inject(MyMetaStore, storeInstance);
27597
27817
  sectionRuleDispatchResult: undefined,
27598
27818
  sectionRule: deepCopy(this.props.definition.sectionRule),
27599
27819
  });
27600
- setup() {
27601
- owl.useExternalListener(window, "click", this.closeMenus);
27602
- }
27603
27820
  get title() {
27604
27821
  return _t(this.props.definition.title);
27605
27822
  }
@@ -27640,27 +27857,22 @@ stores.inject(MyMetaStore, storeInstance);
27640
27857
  const sectionRule = deepCopy(this.state.sectionRule);
27641
27858
  sectionRule.colors[target] = color;
27642
27859
  this.updateSectionRule(sectionRule);
27643
- this.closeMenus();
27644
- }
27645
- toggleMenu(menu) {
27646
- const isSelected = this.state.openedMenu === menu;
27647
- this.closeMenus();
27648
- if (!isSelected) {
27649
- this.state.openedMenu = menu;
27650
- }
27651
27860
  }
27652
27861
  updateSectionRule(sectionRule) {
27653
27862
  this.state.sectionRuleDispatchResult = this.props.updateChart(this.props.figureId, {
27654
27863
  sectionRule,
27655
27864
  });
27865
+ if (this.state.sectionRuleDispatchResult.isSuccessful) {
27866
+ this.state.sectionRule = deepCopy(sectionRule);
27867
+ }
27656
27868
  }
27657
27869
  canUpdateSectionRule(sectionRule) {
27658
27870
  this.state.sectionRuleDispatchResult = this.props.canUpdateChart(this.props.figureId, {
27659
27871
  sectionRule,
27660
27872
  });
27661
27873
  }
27662
- closeMenus() {
27663
- this.state.openedMenu = undefined;
27874
+ get backgroundColorTitle() {
27875
+ return ChartTerms.BackgroundColor;
27664
27876
  }
27665
27877
  }
27666
27878
 
@@ -27808,39 +28020,36 @@ stores.inject(MyMetaStore, storeInstance);
27808
28020
 
27809
28021
  class ScorecardChartDesignPanel extends owl.Component {
27810
28022
  static template = "o-spreadsheet-ScorecardChartDesignPanel";
27811
- static components = { ColorPickerWidget, ChartColor, ChartTitle, Section };
28023
+ static components = { RoundColorPicker, ChartTitle, Section, Checkbox };
27812
28024
  static props = {
27813
28025
  figureId: String,
27814
28026
  definition: Object,
27815
28027
  updateChart: Function,
27816
28028
  canUpdateChart: Function,
27817
28029
  };
27818
- state = owl.useState({
27819
- openedColorPicker: undefined,
27820
- });
27821
- setup() {
27822
- owl.useExternalListener(window, "click", this.closeMenus);
27823
- }
27824
28030
  get title() {
27825
28031
  return _t(this.props.definition.title);
27826
28032
  }
28033
+ get colorsSectionTitle() {
28034
+ return this.props.definition.baselineMode === "progress"
28035
+ ? _t("Progress bar colors")
28036
+ : _t("Baseline colors");
28037
+ }
28038
+ get humanizeNumbersLabel() {
28039
+ return _t("Humanize numbers");
28040
+ }
27827
28041
  updateTitle(title) {
27828
28042
  this.props.updateChart(this.props.figureId, { title });
27829
28043
  }
28044
+ updateHumanizeNumbers(humanize) {
28045
+ this.props.updateChart(this.props.figureId, { humanize });
28046
+ }
27830
28047
  translate(term) {
27831
28048
  return _t(term);
27832
28049
  }
27833
28050
  updateBaselineDescr(ev) {
27834
28051
  this.props.updateChart(this.props.figureId, { baselineDescr: ev.target.value });
27835
28052
  }
27836
- toggleColorPicker(colorPickerId) {
27837
- if (this.state.openedColorPicker === colorPickerId) {
27838
- this.state.openedColorPicker = undefined;
27839
- }
27840
- else {
27841
- this.state.openedColorPicker = colorPickerId;
27842
- }
27843
- }
27844
28053
  setColor(color, colorPickerId) {
27845
28054
  switch (colorPickerId) {
27846
28055
  case "backgroundColor":
@@ -27853,10 +28062,9 @@ stores.inject(MyMetaStore, storeInstance);
27853
28062
  this.props.updateChart(this.props.figureId, { baselineColorUp: color });
27854
28063
  break;
27855
28064
  }
27856
- this.closeMenus();
27857
28065
  }
27858
- closeMenus() {
27859
- this.state.openedColorPicker = undefined;
28066
+ get backgroundColorTitle() {
28067
+ return ChartTerms.BackgroundColor;
27860
28068
  }
27861
28069
  }
27862
28070
 
@@ -28805,6 +29013,7 @@ stores.inject(MyMetaStore, storeInstance);
28805
29013
  ColorPickerWidget,
28806
29014
  ConditionalFormatPreviewList,
28807
29015
  Section,
29016
+ RoundColorPicker,
28808
29017
  };
28809
29018
  icons = ICONS;
28810
29019
  cellIsOperators = CellIsOperators;
@@ -32309,6 +32518,7 @@ stores.inject(MyMetaStore, storeInstance);
32309
32518
  onComposerCellFocused: { type: Function, optional: true },
32310
32519
  onComposerContentFocused: Function,
32311
32520
  isDefaultFocus: { type: Boolean, optional: true },
32521
+ onInputContextMenu: { type: Function, optional: true },
32312
32522
  };
32313
32523
  static components = { TextValueProvider, FunctionDescriptionProvider };
32314
32524
  static defaultProps = {
@@ -32359,6 +32569,9 @@ stores.inject(MyMetaStore, storeInstance);
32359
32569
  assistantStyle.right = `0px`;
32360
32570
  }
32361
32571
  }
32572
+ else if (this.props.delimitation) {
32573
+ assistantStyle["max-height"] = `${this.props.delimitation.height}px`;
32574
+ }
32362
32575
  return cssPropertiesToCss(assistantStyle);
32363
32576
  }
32364
32577
  // we can't allow input events to be triggered while we remove and add back the content of the composer in processContent
@@ -32645,6 +32858,11 @@ stores.inject(MyMetaStore, storeInstance);
32645
32858
  }
32646
32859
  }
32647
32860
  }
32861
+ onContextMenu(ev) {
32862
+ if (this.composerStore.editionMode === "inactive") {
32863
+ this.props.onInputContextMenu?.(ev);
32864
+ }
32865
+ }
32648
32866
  // ---------------------------------------------------------------------------
32649
32867
  // Private
32650
32868
  // ---------------------------------------------------------------------------
@@ -32866,6 +33084,7 @@ stores.inject(MyMetaStore, storeInstance);
32866
33084
  static template = "o-spreadsheet-GridComposer";
32867
33085
  static props = {
32868
33086
  gridDims: Object,
33087
+ onInputContextMenu: Function,
32869
33088
  };
32870
33089
  static components = { Composer };
32871
33090
  rect = this.defaultRect;
@@ -32913,6 +33132,7 @@ stores.inject(MyMetaStore, storeInstance);
32913
33132
  isDefaultFocus: true,
32914
33133
  onComposerContentFocused: () => this.composerFocusStore.focusGridComposerContent(),
32915
33134
  onComposerCellFocused: (content) => this.composerFocusStore.focusGridComposerCell(content),
33135
+ onInputContextMenu: this.props.onInputContextMenu,
32916
33136
  };
32917
33137
  }
32918
33138
  get containerStyle() {
@@ -33011,7 +33231,6 @@ stores.inject(MyMetaStore, storeInstance);
33011
33231
  cellPosition: Object,
33012
33232
  horizontalAlign: { type: String, optional: true },
33013
33233
  verticalAlign: { type: String, optional: true },
33014
- offset: { type: Object, optional: true },
33015
33234
  slots: Object,
33016
33235
  };
33017
33236
  get iconStyle() {
@@ -33022,8 +33241,8 @@ stores.inject(MyMetaStore, storeInstance);
33022
33241
  const x = this.getIconHorizontalPosition(rect, cellPosition);
33023
33242
  const y = this.getIconVerticalPosition(rect, cellPosition);
33024
33243
  return cssPropertiesToCss({
33025
- top: `${y + (this.props.offset?.y || 0)}px`,
33026
- left: `${x + (this.props.offset?.x || 0)}px`,
33244
+ top: `${y}px`,
33245
+ left: `${x}px`,
33027
33246
  });
33028
33247
  }
33029
33248
  getIconVerticalPosition(rect, cellPosition) {
@@ -33063,83 +33282,6 @@ stores.inject(MyMetaStore, storeInstance);
33063
33282
  }
33064
33283
  }
33065
33284
 
33066
- css /* scss */ `
33067
- .o-filter-icon {
33068
- color: ${FILTERS_COLOR};
33069
- display: flex;
33070
- align-items: center;
33071
- justify-content: center;
33072
- width: ${GRID_ICON_EDGE_LENGTH}px;
33073
- height: ${GRID_ICON_EDGE_LENGTH}px;
33074
-
33075
- &:hover {
33076
- background: ${FILTERS_COLOR};
33077
- color: #fff;
33078
- }
33079
-
33080
- &.o-high-contrast {
33081
- color: #defade;
33082
- }
33083
- &.o-high-contrast:hover {
33084
- color: ${FILTERS_COLOR};
33085
- background: #fff;
33086
- }
33087
- }
33088
- .o-filter-icon:hover {
33089
- background: ${FILTERS_COLOR};
33090
- color: #fff;
33091
- }
33092
- `;
33093
- class FilterIcon extends owl.Component {
33094
- static template = "o-spreadsheet-FilterIcon";
33095
- static props = {
33096
- cellPosition: Object,
33097
- };
33098
- cellPopovers;
33099
- setup() {
33100
- this.cellPopovers = useStore(CellPopoverStore);
33101
- }
33102
- onClick() {
33103
- const position = this.props.cellPosition;
33104
- const activePopover = this.cellPopovers.persistentCellPopover;
33105
- const { col, row } = position;
33106
- if (activePopover.isOpen &&
33107
- activePopover.col === col &&
33108
- activePopover.row === row &&
33109
- activePopover.type === "FilterMenu") {
33110
- this.cellPopovers.close();
33111
- return;
33112
- }
33113
- this.cellPopovers.open({ col, row }, "FilterMenu");
33114
- }
33115
- get isFilterActive() {
33116
- return this.env.model.getters.isFilterActive(this.props.cellPosition);
33117
- }
33118
- get iconClass() {
33119
- const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.cellPosition);
33120
- const luminance = relativeLuminance(cellStyle.fillColor || "#fff");
33121
- return luminance < 0.45 ? "o-high-contrast" : "";
33122
- }
33123
- }
33124
-
33125
- class FilterIconsOverlay extends owl.Component {
33126
- static template = "o-spreadsheet-FilterIconsOverlay";
33127
- static props = {
33128
- gridPosition: { type: Object, optional: true },
33129
- };
33130
- static components = {
33131
- GridCellIcon,
33132
- FilterIcon,
33133
- };
33134
- static defaultProps = {
33135
- gridPosition: { x: 0, y: 0 },
33136
- };
33137
- getFilterHeadersPositions() {
33138
- const sheetId = this.env.model.getters.getActiveSheetId();
33139
- return this.env.model.getters.getFilterHeaders(sheetId);
33140
- }
33141
- }
33142
-
33143
33285
  const CHECKBOX_WIDTH = 15;
33144
33286
  const MARGIN = (GRID_ICON_EDGE_LENGTH - CHECKBOX_WIDTH) / 2;
33145
33287
  css /* scss */ `
@@ -33807,6 +33949,80 @@ stores.inject(MyMetaStore, storeInstance);
33807
33949
  }
33808
33950
  }
33809
33951
 
33952
+ css /* scss */ `
33953
+ .o-filter-icon {
33954
+ color: ${FILTERS_COLOR};
33955
+ display: flex;
33956
+ align-items: center;
33957
+ justify-content: center;
33958
+ width: ${GRID_ICON_EDGE_LENGTH}px;
33959
+ height: ${GRID_ICON_EDGE_LENGTH}px;
33960
+
33961
+ &:hover {
33962
+ background: ${FILTERS_COLOR};
33963
+ color: #fff;
33964
+ }
33965
+
33966
+ &.o-high-contrast {
33967
+ color: #defade;
33968
+ }
33969
+ &.o-high-contrast:hover {
33970
+ color: ${FILTERS_COLOR};
33971
+ background: #fff;
33972
+ }
33973
+ }
33974
+ .o-filter-icon:hover {
33975
+ background: ${FILTERS_COLOR};
33976
+ color: #fff;
33977
+ }
33978
+ `;
33979
+ class FilterIcon extends owl.Component {
33980
+ static template = "o-spreadsheet-FilterIcon";
33981
+ static props = {
33982
+ cellPosition: Object,
33983
+ };
33984
+ cellPopovers;
33985
+ setup() {
33986
+ this.cellPopovers = useStore(CellPopoverStore);
33987
+ }
33988
+ onClick() {
33989
+ const position = this.props.cellPosition;
33990
+ const activePopover = this.cellPopovers.persistentCellPopover;
33991
+ const { col, row } = position;
33992
+ if (activePopover.isOpen &&
33993
+ activePopover.col === col &&
33994
+ activePopover.row === row &&
33995
+ activePopover.type === "FilterMenu") {
33996
+ this.cellPopovers.close();
33997
+ return;
33998
+ }
33999
+ this.cellPopovers.open({ col, row }, "FilterMenu");
34000
+ }
34001
+ get isFilterActive() {
34002
+ return this.env.model.getters.isFilterActive(this.props.cellPosition);
34003
+ }
34004
+ get iconClass() {
34005
+ const cellStyle = this.env.model.getters.getCellComputedStyle(this.props.cellPosition);
34006
+ const luminance = relativeLuminance(cellStyle.fillColor || "#fff");
34007
+ return luminance < 0.45 ? "o-high-contrast" : "";
34008
+ }
34009
+ }
34010
+
34011
+ class FilterIconsOverlay extends owl.Component {
34012
+ static template = "o-spreadsheet-FilterIconsOverlay";
34013
+ static props = {
34014
+ onMouseDown: Function,
34015
+ };
34016
+ static components = {
34017
+ GridCellIcon,
34018
+ FilterIcon,
34019
+ };
34020
+ getFilterHeadersPositions() {
34021
+ const sheetId = this.env.model.getters.getActiveSheetId();
34022
+ return this.env.model.getters.getFilterHeaders(sheetId);
34023
+ }
34024
+ }
34025
+
33810
34026
  css /* scss */ `
33811
34027
  .o-grid-add-rows {
33812
34028
  input {
@@ -34020,7 +34236,12 @@ stores.inject(MyMetaStore, storeInstance);
34020
34236
  onGridMoved: Function,
34021
34237
  gridOverlayDimensions: String,
34022
34238
  };
34023
- static components = { FiguresContainer, DataValidationOverlay, GridAddRowsFooter };
34239
+ static components = {
34240
+ FiguresContainer,
34241
+ DataValidationOverlay,
34242
+ GridAddRowsFooter,
34243
+ FilterIconsOverlay,
34244
+ };
34024
34245
  static defaultProps = {
34025
34246
  onCellHovered: () => { },
34026
34247
  onCellDoubleClicked: () => { },
@@ -34065,7 +34286,7 @@ stores.inject(MyMetaStore, storeInstance);
34065
34286
  get isPaintingFormat() {
34066
34287
  return this.env.model.getters.isPaintingFormat();
34067
34288
  }
34068
- onMouseDown(ev) {
34289
+ onMouseDown(ev, modifiers) {
34069
34290
  if (ev.button > 0) {
34070
34291
  // not main button, probably a context menu
34071
34292
  return;
@@ -34074,6 +34295,7 @@ stores.inject(MyMetaStore, storeInstance);
34074
34295
  this.props.onCellClicked(col, row, {
34075
34296
  expandZone: ev.shiftKey,
34076
34297
  addZone: isCtrlKey(ev),
34298
+ closePopover: modifiers?.closePopover ?? true,
34077
34299
  });
34078
34300
  }
34079
34301
  onDoubleClick(ev) {
@@ -35839,7 +36061,6 @@ stores.inject(MyMetaStore, storeInstance);
35839
36061
  Popover,
35840
36062
  VerticalScrollBar,
35841
36063
  HorizontalScrollBar,
35842
- FilterIconsOverlay,
35843
36064
  };
35844
36065
  HEADER_HEIGHT = HEADER_HEIGHT;
35845
36066
  HEADER_WIDTH = HEADER_WIDTH;
@@ -36140,17 +36361,17 @@ stores.inject(MyMetaStore, storeInstance);
36140
36361
  // ---------------------------------------------------------------------------
36141
36362
  // Zone selection with mouse
36142
36363
  // ---------------------------------------------------------------------------
36143
- onCellClicked(col, row, { addZone, expandZone }) {
36144
- if (this.cellPopovers.isOpen) {
36364
+ onCellClicked(col, row, modifiers) {
36365
+ if (modifiers.closePopover && this.cellPopovers.isOpen) {
36145
36366
  this.cellPopovers.close();
36146
36367
  }
36147
36368
  if (this.composerStore.editionMode === "editing") {
36148
36369
  this.composerStore.stopEdition();
36149
36370
  }
36150
- if (expandZone) {
36371
+ if (modifiers.expandZone) {
36151
36372
  this.env.model.selection.setAnchorCorner(col, row);
36152
36373
  }
36153
- else if (addZone) {
36374
+ else if (modifiers.addZone) {
36154
36375
  this.env.model.selection.addCellToSelection(col, row);
36155
36376
  }
36156
36377
  else {
@@ -36872,7 +37093,7 @@ stores.inject(MyMetaStore, storeInstance);
36872
37093
  line3DChart: undefined,
36873
37094
  stockChart: undefined,
36874
37095
  radarChart: undefined,
36875
- scatterChart: undefined,
37096
+ scatterChart: "scatter",
36876
37097
  pieChart: "pie",
36877
37098
  pie3DChart: undefined,
36878
37099
  doughnutChart: "pie",
@@ -37636,29 +37857,12 @@ stores.inject(MyMetaStore, storeInstance);
37636
37857
  return width;
37637
37858
  return Math.round((width / WIDTH_FACTOR) * 100) / 100;
37638
37859
  }
37639
- function convertBorderDescr(descr) {
37640
- if (!descr) {
37641
- return undefined;
37642
- }
37643
- return {
37644
- style: descr.style,
37645
- color: { rgb: descr.color },
37646
- };
37647
- }
37648
37860
  function extractStyle(cell, data) {
37649
37861
  let style = {};
37650
37862
  if (cell.style) {
37651
37863
  style = data.styles[cell.style];
37652
37864
  }
37653
37865
  const format = extractFormat(cell, data);
37654
- const exportedBorder = {};
37655
- if (cell.border) {
37656
- const border = data.borders[cell.border];
37657
- exportedBorder.left = convertBorderDescr(border.left);
37658
- exportedBorder.right = convertBorderDescr(border.right);
37659
- exportedBorder.bottom = convertBorderDescr(border.bottom);
37660
- exportedBorder.top = convertBorderDescr(border.top);
37661
- }
37662
37866
  const styles = {
37663
37867
  font: {
37664
37868
  size: style?.fontSize || DEFAULT_FONT_SIZE,
@@ -37672,7 +37876,7 @@ stores.inject(MyMetaStore, storeInstance);
37672
37876
  }
37673
37877
  : { reservedAttribute: "none" },
37674
37878
  numFmt: format ? { format: format, id: 0 /* id not used for export */ } : undefined,
37675
- border: exportedBorder || {},
37879
+ border: cell.border || 0,
37676
37880
  alignment: {
37677
37881
  horizontal: style.align,
37678
37882
  vertical: style.verticalAlign
@@ -37694,15 +37898,12 @@ stores.inject(MyMetaStore, storeInstance);
37694
37898
  return undefined;
37695
37899
  }
37696
37900
  function normalizeStyle(construct, styles) {
37697
- const { id: fontId } = pushElement(styles["font"], construct.fonts);
37698
- const { id: fillId } = pushElement(styles["fill"], construct.fills);
37699
- const { id: borderId } = pushElement(styles["border"], construct.borders);
37700
37901
  // Normalize this
37701
37902
  const numFmtId = convertFormat(styles["numFmt"], construct.numFmts);
37702
37903
  const style = {
37703
- fontId,
37704
- fillId,
37705
- borderId,
37904
+ fontId: pushElement(styles.font, construct.fonts),
37905
+ fillId: pushElement(styles.fill, construct.fills),
37906
+ borderId: styles.border,
37706
37907
  numFmtId,
37707
37908
  alignment: {
37708
37909
  vertical: styles.alignment.vertical,
@@ -37710,8 +37911,7 @@ stores.inject(MyMetaStore, storeInstance);
37710
37911
  wrapText: styles.alignment.wrapText,
37711
37912
  },
37712
37913
  };
37713
- const { id } = pushElement(style, construct.styles);
37714
- return id;
37914
+ return pushElement(style, construct.styles);
37715
37915
  }
37716
37916
  function convertFormat(format, numFmtStructure) {
37717
37917
  if (!format) {
@@ -37719,8 +37919,7 @@ stores.inject(MyMetaStore, storeInstance);
37719
37919
  }
37720
37920
  let formatId = XLSX_FORMAT_MAP[format.format];
37721
37921
  if (!formatId) {
37722
- const { id } = pushElement(format, numFmtStructure);
37723
- formatId = id + FIRST_NUMFMT_ID;
37922
+ formatId = pushElement(format, numFmtStructure) + FIRST_NUMFMT_ID;
37724
37923
  }
37725
37924
  return formatId;
37726
37925
  }
@@ -37745,20 +37944,15 @@ stores.inject(MyMetaStore, storeInstance);
37745
37944
  return id;
37746
37945
  }
37747
37946
  function pushElement(property, propertyList) {
37748
- for (let [key, value] of Object.entries(propertyList)) {
37749
- if (JSON.stringify(value) === JSON.stringify(property)) {
37750
- return { id: parseInt(key, 10), list: propertyList };
37947
+ let len = propertyList.length;
37948
+ const operator = typeof property === "object" ? deepEquals : (a, b) => a === b;
37949
+ for (let i = 0; i < len; i++) {
37950
+ if (operator(property, propertyList[i])) {
37951
+ return i;
37751
37952
  }
37752
37953
  }
37753
- let elemId = propertyList.findIndex((elem) => JSON.stringify(elem) === JSON.stringify(property));
37754
- if (elemId === -1) {
37755
- propertyList.push(property);
37756
- elemId = propertyList.length - 1;
37757
- }
37758
- return {
37759
- id: elemId,
37760
- list: propertyList,
37761
- };
37954
+ propertyList[propertyList.length] = property;
37955
+ return propertyList.length - 1;
37762
37956
  }
37763
37957
  const chartIds = [];
37764
37958
  /**
@@ -38553,7 +38747,25 @@ stores.inject(MyMetaStore, storeInstance);
38553
38747
  }
38554
38748
  return document;
38555
38749
  }
38556
- function getDefaultXLSXStructure() {
38750
+ function convertBorderDescr(descr) {
38751
+ if (!descr) {
38752
+ return undefined;
38753
+ }
38754
+ return {
38755
+ style: descr.style,
38756
+ color: { rgb: descr.color },
38757
+ };
38758
+ }
38759
+ function getDefaultXLSXStructure(data) {
38760
+ const xlsxBorders = Object.values(data.borders).map((border) => {
38761
+ return {
38762
+ left: convertBorderDescr(border.left),
38763
+ right: convertBorderDescr(border.right),
38764
+ bottom: convertBorderDescr(border.bottom),
38765
+ top: convertBorderDescr(border.top),
38766
+ };
38767
+ });
38768
+ const borders = [{}, ...xlsxBorders];
38557
38769
  return {
38558
38770
  relsFiles: [],
38559
38771
  sharedStrings: [],
@@ -38576,7 +38788,7 @@ stores.inject(MyMetaStore, storeInstance);
38576
38788
  },
38577
38789
  ],
38578
38790
  fills: [{ reservedAttribute: "none" }, { reservedAttribute: "gray125" }],
38579
- borders: [{}],
38791
+ borders,
38580
38792
  numFmts: [],
38581
38793
  dxfs: [],
38582
38794
  };
@@ -39112,8 +39324,8 @@ stores.inject(MyMetaStore, storeInstance);
39112
39324
  return {
39113
39325
  title: chartTitle,
39114
39326
  type: CHART_TYPE_CONVERSION_MAP[chartType],
39115
- dataSets: this.extractChartDatasets(this.querySelector(rootChartElement, `c:${chartType}`)),
39116
- labelRange: this.extractChildTextContent(rootChartElement, "c:ser c:cat c:f"),
39327
+ dataSets: this.extractChartDatasets(this.querySelector(rootChartElement, `c:${chartType}`), chartType),
39328
+ labelRange: this.extractChildTextContent(rootChartElement, `c:ser ${chartType === "scatterChart" ? "c:numRef" : "c:cat"} c:f`),
39117
39329
  backgroundColor: this.extractChildAttr(rootChartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
39118
39330
  default: "ffffff",
39119
39331
  }).asString(),
@@ -39142,8 +39354,8 @@ stores.inject(MyMetaStore, storeInstance);
39142
39354
  title: chartTitle,
39143
39355
  type: "combo",
39144
39356
  dataSets: [
39145
- ...this.extractChartDatasets(this.querySelector(chartElement, `c:barChart`)),
39146
- ...this.extractChartDatasets(this.querySelector(chartElement, `c:lineChart`)),
39357
+ ...this.extractChartDatasets(this.querySelector(chartElement, `c:barChart`), "comboChart"),
39358
+ ...this.extractChartDatasets(this.querySelector(chartElement, `c:lineChart`), "comboChart"),
39147
39359
  ],
39148
39360
  labelRange: this.extractChildTextContent(chartElement, "c:ser c:cat c:f"),
39149
39361
  backgroundColor: this.extractChildAttr(chartElement, "c:chartSpace > c:spPr a:srgbClr", "val", {
@@ -39161,7 +39373,10 @@ stores.inject(MyMetaStore, storeInstance);
39161
39373
  fontColor: "000000",
39162
39374
  };
39163
39375
  }
39164
- extractChartDatasets(chartElement) {
39376
+ extractChartDatasets(chartElement, chartType) {
39377
+ if (chartType === "scatterChart") {
39378
+ return this.extractScatterChartDatasets(chartElement);
39379
+ }
39165
39380
  return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
39166
39381
  return {
39167
39382
  label: this.extractChildTextContent(chartDataElement, "c:tx c:f"),
@@ -39169,6 +39384,14 @@ stores.inject(MyMetaStore, storeInstance);
39169
39384
  };
39170
39385
  });
39171
39386
  }
39387
+ extractScatterChartDatasets(chartElement) {
39388
+ return this.mapOnElements({ parent: chartElement, query: "c:ser" }, (chartDataElement) => {
39389
+ return {
39390
+ label: this.extractChildTextContent(chartDataElement, "c:xVal c:f", { required: false }),
39391
+ range: this.extractChildTextContent(chartDataElement, "c:yVal c:f", { required: true }),
39392
+ };
39393
+ });
39394
+ }
39172
39395
  /**
39173
39396
  * The chart type in the XML isn't explicitly defined, but there is an XML element that define the
39174
39397
  * chart, and this element tag name tells us which type of chart it is. We just need to find this XML element.
@@ -40562,12 +40785,14 @@ stores.inject(MyMetaStore, storeInstance);
40562
40785
  static getters = [];
40563
40786
  history;
40564
40787
  dispatch;
40565
- constructor(stateObserver, dispatch) {
40788
+ canDispatch;
40789
+ constructor(stateObserver, dispatch, canDispatch) {
40566
40790
  this.history = Object.assign(Object.create(stateObserver), {
40567
40791
  update: stateObserver.addChange.bind(stateObserver, this),
40568
40792
  selectCell: () => { },
40569
40793
  });
40570
40794
  this.dispatch = dispatch;
40795
+ this.canDispatch = canDispatch;
40571
40796
  }
40572
40797
  /**
40573
40798
  * Export for excel should be available for all plugins, even for the UI.
@@ -40646,8 +40871,8 @@ stores.inject(MyMetaStore, storeInstance);
40646
40871
  class CorePlugin extends BasePlugin {
40647
40872
  getters;
40648
40873
  uuidGenerator;
40649
- constructor({ getters, stateObserver, range, dispatch, uuidGenerator }) {
40650
- super(stateObserver, dispatch);
40874
+ constructor({ getters, stateObserver, range, dispatch, canDispatch, uuidGenerator, }) {
40875
+ super(stateObserver, dispatch, canDispatch);
40651
40876
  range.addRangeProvider(this.adaptRanges.bind(this));
40652
40877
  this.getters = getters;
40653
40878
  this.uuidGenerator = uuidGenerator;
@@ -46144,8 +46369,8 @@ stores.inject(MyMetaStore, storeInstance);
46144
46369
  getters;
46145
46370
  ui;
46146
46371
  selection;
46147
- constructor({ getters, stateObserver, dispatch, uiActions, selection }) {
46148
- super(stateObserver, dispatch);
46372
+ constructor({ getters, stateObserver, dispatch, canDispatch, uiActions, selection, }) {
46373
+ super(stateObserver, dispatch, canDispatch);
46149
46374
  this.getters = getters;
46150
46375
  this.ui = uiActions;
46151
46376
  this.selection = selection;
@@ -47425,7 +47650,9 @@ stores.inject(MyMetaStore, storeInstance);
47425
47650
  this.blockedArrayFormulas = this.createEmptyPositionSet();
47426
47651
  this.spreadingRelations = new SpreadingRelation(this.createEmptyPositionSet.bind(this));
47427
47652
  this.formulaDependencies = lazy(() => {
47428
- const dependencies = [...this.getAllCells()].flatMap((position) => this.getDirectDependencies(position).map((range) => ({
47653
+ const dependencies = [...this.getAllCells()].flatMap((position) => this.getDirectDependencies(position)
47654
+ .filter((range) => !range.invalidSheetName && !range.invalidXc)
47655
+ .map((range) => ({
47429
47656
  data: position,
47430
47657
  boundingBox: {
47431
47658
  zone: range.zone,
@@ -51509,6 +51736,7 @@ stores.inject(MyMetaStore, storeInstance);
51509
51736
  "CREATE_TABLE",
51510
51737
  "UPDATE_TABLE",
51511
51738
  "UPDATE_FILTER",
51739
+ "REMOVE_TABLE",
51512
51740
  ];
51513
51741
  const invalidateTableStyleCommandsSet = new Set(invalidateTableStyleCommands);
51514
51742
  function doesCommandInvalidatesTableStyle(cmd) {
@@ -51536,6 +51764,10 @@ stores.inject(MyMetaStore, storeInstance);
51536
51764
  this.styles = {};
51537
51765
  return;
51538
51766
  }
51767
+ if (invalidateBordersCommands.has(cmd.type)) {
51768
+ this.borders = {};
51769
+ return;
51770
+ }
51539
51771
  }
51540
51772
  getCellComputedBorder(position) {
51541
51773
  const { sheetId, row, col } = position;
@@ -55476,7 +55708,6 @@ stores.inject(MyMetaStore, storeInstance);
55476
55708
  Popover,
55477
55709
  VerticalScrollBar,
55478
55710
  HorizontalScrollBar,
55479
- FilterIconsOverlay,
55480
55711
  };
55481
55712
  cellPopovers;
55482
55713
  onMouseWheel;
@@ -56363,6 +56594,13 @@ stores.inject(MyMetaStore, storeInstance);
56363
56594
  "border-color": SELECTION_BORDER_COLOR,
56364
56595
  });
56365
56596
  }
56597
+ get delimitation() {
56598
+ const { width, height } = this.env.model.getters.getSheetViewDimensionWithHeaders();
56599
+ return {
56600
+ width,
56601
+ height,
56602
+ };
56603
+ }
56366
56604
  onFocus(selection) {
56367
56605
  this.composerFocusStore.focusTopBarComposer(selection);
56368
56606
  }
@@ -58646,6 +58884,9 @@ stores.inject(MyMetaStore, storeInstance);
58646
58884
  case "line":
58647
58885
  plot = addLineChart(chart.data);
58648
58886
  break;
58887
+ case "scatter":
58888
+ plot = addScatterChart(chart.data);
58889
+ break;
58649
58890
  case "pie":
58650
58891
  plot = addDoughnutChart(chart.data, chartSheetIndex, data, { holeSize: 0 });
58651
58892
  break;
@@ -58923,6 +59164,52 @@ stores.inject(MyMetaStore, storeInstance);
58923
59164
  ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
58924
59165
  `;
58925
59166
  }
59167
+ function addScatterChart(chart) {
59168
+ const colors = new ChartColors();
59169
+ const dataSetsNodes = [];
59170
+ for (const [dsIndex, dataset] of Object.entries(chart.dataSets)) {
59171
+ dataSetsNodes.push(escapeXml /*xml*/ `
59172
+ <c:ser>
59173
+ <c:idx val="${dsIndex}"/>
59174
+ <c:order val="${dsIndex}"/>
59175
+ <c:smooth val="0"/>
59176
+ <c:spPr>
59177
+ <a:ln w="19050" cap="rnd">
59178
+ <a:noFill/>
59179
+ <a:round/>
59180
+ </a:ln>
59181
+ <a:effectLst/>
59182
+ </c:spPr>
59183
+ <c:marker>
59184
+ <c:symbol val="circle" />
59185
+ <c:size val="5"/>
59186
+ ${shapeProperty({ backgroundColor: toXlsxHexColor(colors.next()) })}
59187
+ </c:marker>
59188
+ ${chart.labelRange
59189
+ ? escapeXml /*xml*/ `<c:xVal> <!-- x-coordinate values -->
59190
+ ${numberRef(chart.labelRange)}
59191
+ </c:xVal>`
59192
+ : ""}
59193
+ <c:yVal> <!-- y-coordinate values -->
59194
+ ${numberRef(dataset.range)}
59195
+ </c:yVal>
59196
+ </c:ser>
59197
+ `);
59198
+ }
59199
+ const axisPos = chart.verticalAxisPosition === "left" ? "l" : "r";
59200
+ return escapeXml /*xml*/ `
59201
+ <c:scatterChart>
59202
+ <!-- each data marker in the series does not have a different color -->
59203
+ <c:varyColors val="0"/>
59204
+ <c:scatterStyle val="lineMarker"/>
59205
+ ${joinXmlNodes(dataSetsNodes)}
59206
+ <c:axId val="${catAxId}" />
59207
+ <c:axId val="${valAxId}" />
59208
+ </c:scatterChart>
59209
+ ${addAx("b", "c:valAx", catAxId, valAxId, { fontColor: chart.fontColor })}
59210
+ ${addAx(axisPos, "c:valAx", valAxId, catAxId, { fontColor: chart.fontColor })}
59211
+ `;
59212
+ }
58926
59213
  function addDoughnutChart(chart, chartSheetIndex, data, { holeSize } = { holeSize: 50 }) {
58927
59214
  const colors = new ChartColors();
58928
59215
  const maxLength = largeMax(chart.dataSets.map((ds) => getRangeSize(ds.range, chartSheetIndex, data)));
@@ -59063,8 +59350,7 @@ stores.inject(MyMetaStore, storeInstance);
59063
59350
  attrs.push(["t", "b"]);
59064
59351
  }
59065
59352
  else if (forceString || !isNumber(value, DEFAULT_LOCALE)) {
59066
- const { id } = pushElement(content, sharedStrings);
59067
- value = id.toString();
59353
+ value = pushElement(content, sharedStrings);
59068
59354
  attrs.push(["t", "s"]);
59069
59355
  }
59070
59356
  return { attrs, node: escapeXml /*xml*/ `<v>${value}</v>` };
@@ -59189,8 +59475,7 @@ stores.inject(MyMetaStore, storeInstance);
59189
59475
  if (rule.style.fillColor) {
59190
59476
  dxf.fill = { fgColor: { rgb: rule.style.fillColor } };
59191
59477
  }
59192
- const { id } = pushElement(dxf, dxfs);
59193
- ruleAttributes.push(["dxfId", id]);
59478
+ ruleAttributes.push(["dxfId", pushElement(dxf, dxfs)]);
59194
59479
  return escapeXml /*xml*/ `
59195
59480
  <conditionalFormatting sqref="${cf.ranges.join(" ")}">
59196
59481
  <cfRule ${formatAttributes(ruleAttributes)}>
@@ -60020,8 +60305,9 @@ stores.inject(MyMetaStore, storeInstance);
60020
60305
  */
60021
60306
  function getXLSX(data) {
60022
60307
  data = fixLengthySheetNames(data);
60308
+ data = purgeSingleRowTables(data);
60023
60309
  const files = [];
60024
- const construct = getDefaultXLSXStructure();
60310
+ const construct = getDefaultXLSXStructure(data);
60025
60311
  files.push(createWorkbook(data, construct));
60026
60312
  files.push(...createWorksheets(data, construct));
60027
60313
  files.push(createStylesSheet(construct));
@@ -60301,6 +60587,16 @@ stores.inject(MyMetaStore, storeInstance);
60301
60587
  }
60302
60588
  return JSON.parse(stringifiedData);
60303
60589
  }
60590
+ /** Excel files do not support tables with a single row the defined range
60591
+ * Since those tables are not really useful (no filtering/limited styling)
60592
+ * This function filters out all tables with a single row.
60593
+ */
60594
+ function purgeSingleRowTables(data) {
60595
+ for (const sheet of data.sheets) {
60596
+ sheet.tables = sheet.tables.filter((table) => zoneToDimension(toZone(table.range)).numberOfRows > 1);
60597
+ }
60598
+ return data;
60599
+ }
60304
60600
 
60305
60601
  var Status;
60306
60602
  (function (Status) {
@@ -60554,6 +60850,7 @@ stores.inject(MyMetaStore, storeInstance);
60554
60850
  stateObserver: this.state,
60555
60851
  range: this.range,
60556
60852
  dispatch: this.dispatchFromCorePlugin,
60853
+ canDispatch: this.canDispatch,
60557
60854
  uuidGenerator: this.uuidGenerator,
60558
60855
  custom: this.config.custom,
60559
60856
  external: this.config.external,
@@ -60564,6 +60861,7 @@ stores.inject(MyMetaStore, storeInstance);
60564
60861
  getters: this.getters,
60565
60862
  stateObserver: this.state,
60566
60863
  dispatch: this.dispatch,
60864
+ canDispatch: this.canDispatch,
60567
60865
  selection: this.selection,
60568
60866
  moveClient: this.session.move.bind(this.session),
60569
60867
  custom: this.config.custom,
@@ -60887,7 +61185,7 @@ stores.inject(MyMetaStore, storeInstance);
60887
61185
  const components = {
60888
61186
  Checkbox,
60889
61187
  Section,
60890
- ChartColor,
61188
+ RoundColorPicker,
60891
61189
  ChartDataSeries,
60892
61190
  ChartErrorSection,
60893
61191
  ChartLabelRange,
@@ -60990,9 +61288,9 @@ stores.inject(MyMetaStore, storeInstance);
60990
61288
  exports.tokenize = tokenize;
60991
61289
 
60992
61290
 
60993
- __info__.version = "17.3.0-alpha.2";
60994
- __info__.date = "2024-04-05T14:01:07.060Z";
60995
- __info__.hash = "8c5a229";
61291
+ __info__.version = "17.3.0-alpha.3";
61292
+ __info__.date = "2024-04-10T12:28:23.658Z";
61293
+ __info__.hash = "80b5056";
60996
61294
 
60997
61295
 
60998
61296
  })(this.o_spreadsheet = this.o_spreadsheet || {}, owl);