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